tui-calendar
Version:
TOAST UI Calendar
395 lines (328 loc) • 8.77 kB
JavaScript
/**
* @fileoverview Common collections.
* @author NHN FE Development Lab <dl_javascript@nhn.com>
*/
'use strict';
var util = require('tui-code-snippet');
var forEachProp = util.forEachOwnProperties,
forEachArr = util.forEachArray,
isFunc = util.isFunction,
isObj = util.isObject;
var aps = Array.prototype.slice;
/**
* Common collection.
*
* It need function for get model's unique id.
*
* if the function is not supplied then it use default function {@link Collection#getItemID}
* @constructor
* @param {function} [getItemIDFn] function for get model's id.
*/
function Collection(getItemIDFn) {
/**
* @type {object.<string, *>}
*/
this.items = {};
/**
* @type {number}
*/
this.length = 0;
if (isFunc(getItemIDFn)) {
/**
* @type {function}
*/
this.getItemID = getItemIDFn;
}
}
/**********
* static props
**********/
/**
* Combind supplied function filters and condition.
* @param {...function} filters - function filters
* @returns {function} combined filter
*/
Collection.and = function(filters) {
var cnt;
filters = aps.call(arguments);
cnt = filters.length;
return function(item) {
var i = 0;
for (; i < cnt; i += 1) {
if (!filters[i].call(null, item)) {
return false;
}
}
return true;
};
};
/**********
* prototype props
**********/
/**
* get model's unique id.
* @param {object} item model instance.
* @returns {number} model unique id.
*/
Collection.prototype.getItemID = function(item) {
return String(item._id);
};
/**
* add models.
* @param {...*} item models to add this collection.
*/
Collection.prototype.add = function(item) {
var self = this,
id,
ownItems;
if (arguments.length > 1) {
forEachArr(aps.call(arguments), function(o) {
self.add(o);
});
return;
}
id = this.getItemID(item);
ownItems = this.items;
if (!ownItems[id]) {
this.length += 1;
}
ownItems[id] = item;
};
/**
* remove models.
* @param {...(object|string|number)} id model instance or unique id to delete.
* @returns {array} deleted model list.
*/
Collection.prototype.remove = function(id) {
var self = this,
removed = [],
ownItems,
itemToRemove;
if (!this.length) {
return removed;
}
if (arguments.length > 1) {
removed = util.map(aps.call(arguments), function(_id) {
return self.remove(_id);
});
return removed;
}
ownItems = this.items;
if (isObj(id)) {
id = this.getItemID(id);
}
if (!ownItems[id]) {
return removed;
}
this.length -= 1;
itemToRemove = ownItems[id];
delete ownItems[id];
return itemToRemove;
};
/**
* remove all models in collection.
*/
Collection.prototype.clear = function() {
this.items = {};
this.length = 0;
};
/**
* check collection has specific model.
* @param {(object|string|number|function)} id model instance or id or filter function to check
* @returns {boolean} is has model?
*/
Collection.prototype.has = function(id) {
var isFilter,
has;
if (!this.length) {
return false;
}
isFilter = isFunc(id);
has = false;
if (isFilter) {
this.each(function(item) {
if (id(item) === true) {
has = true;
return false; // returning false can stop this loop
}
return true;
});
} else {
id = isObj(id) ? this.getItemID(id) : id;
has = util.isExisty(this.items[id]);
}
return has;
};
/**
* invoke callback when model exist in collection.
* @param {(string|number)} id model unique id.
* @param {function} fn the callback.
* @param {*} [context] callback context.
*/
Collection.prototype.doWhenHas = function(id, fn, context) {
var item = this.items[id];
if (!util.isExisty(item)) {
return;
}
fn.call(context || this, item);
};
/**
* Search model. and return new collection.
* @param {function} filter filter function.
* @returns {Collection} new collection with filtered models.
* @example
* collection.find(function(item) {
* return item.edited === true;
* });
*
* function filter1(item) {
* return item.edited === false;
* }
*
* function filter2(item) {
* return item.disabled === false;
* }
*
* collection.find(Collection.and(filter1, filter2));
*/
Collection.prototype.find = function(filter) {
var result = new Collection();
if (this.hasOwnProperty('getItemID')) {
result.getItemID = this.getItemID;
}
this.each(function(item) {
if (filter(item) === true) {
result.add(item);
}
});
return result;
};
/**
* Group element by specific key values.
*
* if key parameter is function then invoke it and use returned value.
* @param {(string|number|function|array)} key key property or getter function.
* if string[] supplied, create each collection before grouping.
* @param {function} [groupFunc] - function that return each group's key
* @returns {object.<string, Collection>} grouped object
* @example
*
* // pass `string`, `number`, `boolean` type value then group by property value.
* collection.groupBy('gender'); // group by 'gender' property value.
* collection.groupBy(50); // group by '50' property value.
*
* // pass `function` then group by return value. each invocation `function` is called with `(item)`.
* collection.groupBy(function(item) {
* if (item.score > 60) {
* return 'pass';
* }
* return 'fail';
* });
*
* // pass `array` with first arguments then create each collection before grouping.
* collection.groupBy(['go', 'ruby', 'javascript']);
* // result: { 'go': empty Collection, 'ruby': empty Collection, 'javascript': empty Collection }
*
* // can pass `function` with `array` then group each elements.
* collection.groupBy(['go', 'ruby', 'javascript'], function(item) {
* if (item.isFast) {
* return 'go';
* }
*
* return item.name;
* });
*/
Collection.prototype.groupBy = function(key, groupFunc) {
var result = {},
collection,
baseValue,
keyIsFunc = isFunc(key),
getItemIDFn = this.getItemID;
if (util.isArray(key)) {
util.forEachArray(key, function(k) {
result[String(k)] = new Collection(getItemIDFn);
});
if (!groupFunc) {
return result;
}
key = groupFunc;
keyIsFunc = true;
}
this.each(function(item) {
if (keyIsFunc) {
baseValue = key(item);
} else {
baseValue = item[key];
if (isFunc(baseValue)) {
baseValue = baseValue.apply(item);
}
}
collection = result[baseValue];
if (!collection) {
collection = result[baseValue] = new Collection(getItemIDFn);
}
collection.add(item);
});
return result;
};
/**
* Return single item in collection.
*
* Returned item is inserted in this collection firstly.
* @param {function} [filter] - function filter
* @returns {object} item.
*/
Collection.prototype.single = function(filter) {
var result,
useFilter = util.isFunction(filter);
this.each(function(item) {
if (!useFilter) {
result = item;
return false; // returning false can stop this loop
}
if (filter(item)) {
result = item;
return false; // returning false can stop this loop
}
return true;
}, this);
return result;
};
/**
* sort a basis of supplied compare function.
* @param {function} compareFunction compareFunction
* @returns {array} sorted array.
*/
Collection.prototype.sort = function(compareFunction) {
var arr = [];
this.each(function(item) {
arr.push(item);
});
if (isFunc(compareFunction)) {
arr = arr.sort(compareFunction);
}
return arr;
};
/**
* iterate each model element.
*
* when iteratee return false then break the loop.
* @param {function} iteratee iteratee(item, index, items)
* @param {*} [context] context
*/
Collection.prototype.each = function(iteratee, context) {
forEachProp(this.items, iteratee, context || this);
};
/**
* return new array with collection items.
* @returns {array} new array.
*/
Collection.prototype.toArray = function() {
if (!this.length) {
return [];
}
return util.map(this.items, function(item) {
return item;
});
};
module.exports = Collection;