/**
* @class Collection
* @description Provides the sortable, iterable storage of entities that are
* gettable, settable, sortable, removable, etcera(ble) by name
* @author Chris Peters
*/
export default class Collection {
constructor() {
/**
* @member {Array} The sorted list
* @private
*/
this._items = [];
}
/**
* Returns the item { name, item } object
*
* @param {String} name
* @return {Object}
* @private
*/
_getRawItem(name) {
let item;
this._rawEach(function(iterItem, i, iterName) {
if (name === iterName) {
item = iterItem;
return false;
}
});
return item;
}
/**
* Iterates the collection's sorted items. The raw item, index, name, and the
* list being iterated are supplied to the provided function
*
* @param {Function} fn
* @private
*/
_rawEach(fn) {
for(var i = 0, len = this._items.length; i < len; i += 1) {
if (fn(this._items[i], i, this._items[i].name, this._items) === false) {
break;
}
}
}
/**
* Add an item with optional name
*
* @param {Any} item The item to add
* @param {String} [name] The optional name of the item
* @return {Collection}
*/
addItem(item, name) {
name = name || '';
this._items.push({
item, name
});
return this;
}
/**
* Add multiple items
*
* @param {...Object} items Can be the object itself or an object containing the entity and it's name
* eg: <code>{ item: Entity, name: 'entityName' }</code>
* @return {Collection}
*/
addItems(...items) {
for (let item of items) {
if (typeof item.item === 'object' && typeof item.name === 'string') {
// if item has item/name structure
this.addItem(item.item, item.name);
} else {
// for convenience allow user to add just item
this.addItem(item);
}
}
return this;
}
/**
* Iterates the collection's sorted items. The item, index, and name are supplied
* to the provided function
*
* @param {Function} fn The function to execute on the iterable
* @param {Object} [scope] The scope with which to execute the function
*/
each(fn, scope) {
fn = scope ? fn.bind(scope) : fn;
for (var i = 0, len = this._items.length; i < len; i++) {
let item = this._items[i];
if (fn(item.item, i, item.name) === false) {
break;
}
}
}
/**
* iterates items and return the ones that meet criteria
*
* @param {Function} fn Truth predicate
* @param {Object} [scope] The scope with which to execute the function
* @return {Array}
*/
filter(fn, scope) {
let filteredItems = [];
this.each((item, i, name)=> {
let predicate = fn(item, i, name);
if (predicate) {
filteredItems.push(item);
}
}, scope);
return filteredItems;
}
/**
* Returns a list of just the items
*
* @return {Array}
*/
getItemArray() {
return this._items.map((item)=> {
return item.item;
});
}
/**
* Returns an existing item by name, or undefined if the name is not found
*
* @param {String} name The name of the item
* @return {Any}
*/
getItem(name) {
let item;
this.each((iterItem, i, iterName)=> {
if (name === iterName) {
item = iterItem;
return false;
}
});
return item;
}
/**
* Returns an existing item by index
*
* @param {Integer} index
* @return {Any}
*/
getItemAt(index) {
return this._items[index].item;
}
/**
* Returns the count of items in collection
*
* @return {Integer}
*/
getItemCount() {
return this._items.length;
}
/**
* Returns an item's current index
*
* @param {String} name
* @return {Integer}
*/
getItemIndex(name) {
let index;
this.each((iterItem, i, iterName)=> {
if (name === iterName) {
index = i;
return false;
}
});
return index;
}
/**
* Removes all items from collection
*/
removeAllItems() {
this._items = [];
}
/**
* Removes an object by name
*
* @method SW.Collection.prototype.removeItem
* @param {String} name
* @return {Boolean} Returns true if item removed, false if not
*/
removeItem(name) {
var removed = false;
this._rawEach((iterItem, i, iterName, items)=> {
if (name === iterName) {
iterItem = null;
items.splice(i, 1);
removed = true;
// break out of loop
return false;
}
});
return removed;
}
/**
* Assigns a new value to an existing item
*
* @param {String} name The name of the object to modify
* @param {Any} value The new value
*/
setItem(name, value) {
this._rawEach((iterItem, i, iterName)=> {
if (name === iterName) {
iterItem.item = value;
// break out of loop
return false;
}
});
}
/**
* Moves item to new index
*
* @param {String} name The name of the object being moved
* @param {Integer} index The item's new index
*/
setItemIndex(name, index) {
let item;
let currentIndex = this.getItemIndex(name);
if (index === currentIndex) {
return;
}
item = this._getRawItem(name);
this.removeItem(name);
this._items.splice(index, 0, item);
}
}