/**
* @module meteoJS/base/collection
*/
import addEventFunctions from '../Events.js';
import Unique from './Unique.js';
/**
* Triggered on adding item to collection.
*
* @event module:meteoJS/base/collection#add:item
* @param {module:meteoJS/base/unique.Unique} item - Added item.
*/
/**
* Triggered on replacing item with already existing ID.
*
* @event module:meteoJS/base/collection#replace:item
* @param {module:meteoJS/base/unique.Unique} item - Added item.
* @param {module:meteoJS/base/unique.Unique} removedItem - Replaced and removed item.
*/
/**
* Triggered on removing item from collection.
*
* @event module:meteoJS/base/collection#remove:item
* @param {module:meteoJS/base/unique.Unique} item - Removed item.
*/
/**
* Options for constructor.
*
* @typedef {Object} module:meteoJS/base/collection~options
* @property {boolean} [fireReplace] - Fire {@link module:meteoJS/base/collection#replace:item|replace:item}.
* @property {boolean} [fireAddRemoveOnReplace] -
* Fire {@link module:meteoJS/base/collection#add:item|add:item} and
* {@link module:meteoJS/base/collection#remove:item|remove:item} on
* replacing an item.
* @property {boolean} [appendOnReplace] -
* Append item to the end, if item is replaced.
* @property {undefined|Function} [sortFunction] -
* Sort function to sort the collection list.
* @property {undefined|Function} [emptyObjectMaker] -
* Function that returns an empty
* {@link module:meteoJS/base/unqiue.Unique|Unique}-Object or
* an instance of a child class.
*/
/**
* Collection-class for {@link module:meteoJS/base/unqiue.Unique|Unique}-Objects
* or objects of child classes.
*
* @implements {Iterator}
* @fires module:meteoJS/base/collection#add:item
* @fires module:meteoJS/base/collection#remove:item
* @fires module:meteoJS/base/collection#replace:item
*/
export class Collection {
/**
* @param {module:meteoJS/base/collection~options} options - Options.
*/
constructor({ fireReplace=true,
fireAddRemoveOnReplace=false,
appendOnReplace=true,
sortFunction,
emptyObjectMaker
} = {}) {
/** @type Object */
this.options = {
fireReplace,
fireAddRemoveOnReplace,
appendOnReplace,
sortFunction,
emptyObjectMaker
};
/**
* List of the IDs of the items.
* @type mixed[]
* @private
*/
this._itemIds = [];
/**
* Items, ID as key of the object.
* @type Object.<mixed,module:meteoJS/base/unique.Unique>
* @private
*/
this._items = {};
}
/**
* Count of the items in this collection.
*
* @type integer
* @readonly
*/
get count() {
return this._itemIds.length;
}
[Symbol.iterator]() {
let i = 0;
return {
next: () => {
return (i < this._itemIds.length)
? { value: this._items[this._itemIds[i++]] }
: { done: true };
}
};
}
/**
* Items (ordered list).
*
* @type module:meteoJS/base/unique.Unique[]
* @readonly
*/
get items() {
return this._itemIds.map(id => this._items[id]);
}
/**
* List of IDs (ordered list).
*
* @type mixed[]
* @readonly
*/
get itemIds() {
return this._itemIds;
}
/**
* Sort function for the items.
*
* @type undefined|Function
*/
get sortFunction() {
return this.options.sortFunction;
}
set sortFunction(sortFunction) {
this.options.sortFunction = sortFunction;
this._sort();
}
/**
* Returns item by ID, Unique-Object with undefined id, if ID doesn't exist.
*
* @param {mixed} id - ID.
* @returns {module:meteoJS/base/unique.Unique} Item.
*/
getItemById(id) {
return (id in this._items) ? this._items[id] :
(this.options.emptyObjectMaker === undefined)
? new Unique()
: this.options.emptyObjectMaker.call(this);
}
/**
* Is item appended to the collection.
*
* @param {module:meteoJS/base/unique.Unique} item - Item.
* @returns {boolean} If appended.
*/
contains(item) {
let result = this.containsId(item.id);
if (result)
result = item === this.getItemById(item.id);
return result;
}
/**
* Exists an ID in this collection.
*
* @param {mixed} id - ID.
* @returns {boolean} If exists.
*/
containsId(id) {
return (id in this._items);
}
/**
* Append an item to the collection.
*
* @param {...module:meteoJS/base/unique.Unique} items - New items.
* @returns {module:meteoJS/base/collection.Collection} This.
* @fires module:meteoJS/base/collection#add:item
* @fires module:meteoJS/base/collection#remove:item
* @fires module:meteoJS/base/collection#replace:item
*/
append(...items) {
const addItem = [];
const removeItem = [];
const replaceItem = [];
items.forEach(item => {
let id = item.id;
if (this.containsId(id)) {
let itemInCollection = this.getItemById(id);
if (this.options.appendOnReplace) {
this._itemIds.splice(this._itemIds.indexOf(id), 1);
this._itemIds.push(id);
}
if (itemInCollection !== item) {
this._items[id] = item;
if (this.options.fireReplace)
replaceItem.push([item, itemInCollection]);
if (this.options.fireAddRemoveOnReplace) {
removeItem.push(itemInCollection);
addItem.push(item);
}
}
}
else {
this._itemIds.push(id);
this._items[id] = item;
addItem.push(item);
}
});
this._sort();
addItem.forEach(item => this.trigger('add:item', item));
removeItem.forEach(item => this.trigger('remove:item', item));
replaceItem.forEach(([item, itemInCollection]) => this.trigger('replace:item', item, itemInCollection));
return this;
}
/**
* Removes an item from the collection.
*
* @param {...module:meteoJS/base/unique.Unique} items - Items to remove.
* @returns {module:meteoJS/base/collection.Collection} This.
* @fires module:meteoJS/base/collection#remove:item
*/
remove(...items) {
items.forEach(item => {
let i = this._itemIds.indexOf(item.id);
if (i > -1) {
let realItem = this._items[item.id];
delete this._items[item.id];
this._itemIds.splice(i, 1);
this.trigger('remove:item', realItem);
}
});
return this;
}
/**
* Removes an item by ID from the collection.
*
* @param {mixed} id - ID of the item to delete.
* @returns {module:meteoJS/base/collection.Collection} This.
* @fires module:meteoJS/base/collection#remove:item
*/
removeById(...ids) {
ids.forEach(id => {
let i = this._itemIds.indexOf(id);
if (i > -1) {
let item = this._items[id];
delete this._items[id];
this._itemIds.splice(i, 1);
this.trigger('remove:item', item);
}
});
return this;
}
/**
* Sorts Collection-List.
*
* @private
*/
_sort() {
if (this.options.sortFunction === undefined)
return;
this._itemIds.sort((a,b) => {
return this.options.sortFunction(this._items[a], this._items[b]);
});
}
}
addEventFunctions(Collection.prototype);
export default Collection;