StructureJS
0.15.2A class based utility library for building modular and scalable web platform applications. Features opt-in classes and utilities which provide a solid foundation and toolset to build your next project.
- import BaseModel from '../model/BaseModel';
- import EventDispatcher from '../event/EventDispatcher';
- import BaseEvent from '../event/BaseEvent';
- import Util from '../util/Util';
- /**
- * The Collection class provides a way for you to manage your models.
- *
- * @class Collection
- * @extends EventDispatcher
- * @module StructureJS
- * @submodule model
- * @requires Extend
- * @requires EventDispatcher
- * @requires BaseEvent
- * @constructor
- * @param baseModelType {BaseModel} Pass a class that extends BaseModel and the data added to the collection will be created as that type.
- * @author Robert S. (www.codeBelt.com)
- * @example
- * let data = [{ make: 'Tesla', model: 'Model S', year: 2014 }, { make: 'Tesla', model: 'Model X', year: 2016 }];
- *
- * // Example of adding data to a collection
- * let collection = new Collection();
- * collection.add(data);
- *
- * // Example of adding data to a collection that will create a CarModel model for each data object passed in.
- * let collection = new Collection(CarModel);
- * collection.add(data);
- */
- class Collection extends EventDispatcher
- {
- /**
- * The list of models in the collection.
- *
- * @property models
- * @type {Array.<any>}
- * @readOnly
- */
- public models:Array<any> = [];
- /**
- * The count of how many models are in the collection.
- *
- * @property length
- * @type {int}
- * @default 0
- * @readOnly
- * @public
- */
- public length:number = 0;
- /**
- * A reference to a BaseModel type that will be used in the collection.
- *
- * @property _modelType
- * @type {any}
- * @protected
- */
- protected _modelType:any = null;
- constructor(baseModelType:any = null)
- {
- super();
- this._modelType = baseModelType;
- }
- /**
- * Adds model or an array of models to the collection.
- *
- * @method add
- * @param model {Any|Array} Single or an array of models to add to the current collection.
- * @param [silent=false] {boolean} If you'd like to prevent the event from being dispatched.
- * @public
- * @chainable
- * @example
- * collection.add(model);
- *
- * collection.add([model, model, model, model]);
- *
- * collection.add(model, true);
- */
- public add(model:any, silent:boolean = false):any
- {
- if (model == null)
- {
- return;
- }
- // If the model passed in is not an array then make it.
- const models:any = (model instanceof Array) ? model : [model];
- const len:number = models.length;
- for (let i:number = 0; i < len; i++)
- {
- // Only add the model if it does not exist in the the collection.
- if (this.has(models[i]) === false)
- {
- if (this._modelType !== null && (models[i] instanceof this._modelType) === false)
- {
- // If the modelType is set and the data is not already a instance of the modelType
- // then instantiate it and pass the data into the constructor.
- this.models.push(new (<any>this)._modelType(models[i]));
- }
- else
- {
- // Pass the data object to the array.
- this.models.push(models[i]);
- }
- this.length = this.models.length;
- }
- }
- if (silent === false)
- {
- this.dispatchEvent(new BaseEvent(BaseEvent.ADDED));
- }
- return this;
- }
- /**
- * Removes a model or an array of models from the collection.
- *
- * @method remove
- * @param model {Object|Array} Model(s) to remove
- * @param [silent=false] {boolean} If you'd like to prevent the event from being dispatched.
- * @public
- * @chainable
- * @example
- * collection.remove(model);
- *
- * collection.remove([model, model, model, model]);
- *
- * collection.remove(model, true);
- */
- public remove(model:any, silent:boolean = false):any
- {
- // If the model passed in is not an array then make it.
- const models:any = (model instanceof Array) ? model : [model];
- for (let i:number = models.length - 1; i >= 0; i--)
- {
- // Only remove the model if it exists in the the collection.
- if (this.has(models[i]) === true)
- {
- this.models.splice(this.indexOf(models[i]), 1);
- this.length = this.models.length;
- }
- }
- if (silent === false)
- {
- this.dispatchEvent(new BaseEvent(BaseEvent.REMOVED));
- }
- return this;
- }
- /**
- * Checks if a collection has an model.
- *
- * @method has
- * @param model {Object} Item to check
- * @return {boolean}
- * @public
- * @example
- * collection.has(model);
- */
- public has(model:any):boolean
- {
- return this.indexOf(model) > -1;
- }
- /**
- * Returns the array index position of the Base Model.
- *
- * @method indexOf
- * @param model {Object} get the index of.
- * @return {int}
- * @public
- * @example
- * collection.indexOf(model);
- */
- public indexOf(model:any):number
- {
- return this.models.indexOf(model);
- }
- /**
- * Finds an object by an index value.
- *
- * @method get
- * @param index {int} The index integer of the model to get
- * @return {Object} the model
- * @public
- * @example
- * let model = collection.get(1);
- */
- public get(index:number):any
- {
- return this.models[index] || null;
- }
- /**
- * Examines each element in a collection, returning an array of all elements that have the given properties.
- * When checking properties, this method performs a deep comparison between values to determine if they are equivalent to each other.
- * @method findBy
- * @param arg {Object|Array}
- * @return {Array.<any>} Returns a list of found object's.
- * @public
- * @example
- * // Finds all Base Model that has 'Robert' in it.
- * collection.findBy("Robert");
- * // Finds any Base Model that has 'Robert' or 'Heater' or 23 in it.
- * collection.findBy(["Robert", "Heather", 32]);
- *
- * // Finds all Base Models that same key and value you are searching for.
- * collection.findBy({ name: 'apple', organic: false, type: 'fruit' });
- * collection.findBy([{ type: 'vegetable' }, { name: 'apple', 'organic: false, type': 'fruit' }]);
- */
- public findBy(arg:any):Array<any>
- {
- // If properties is not an array then make it an array object.
- const list:Array<any> = (arg instanceof Array) ? arg : [arg];
- let foundItems:Array<any> = [];
- const len:number = list.length;
- let prop:any;
- for (let i:number = 0; i < len; i++)
- {
- prop = list[i];
- // Adds found Base Model to the foundItems array.
- if ((typeof prop === 'string') || (typeof prop === 'number') || (typeof prop === 'boolean'))
- {
- // If the model is not an object.
- foundItems = foundItems.concat(this._findPropertyValue(prop));
- }
- else
- {
- // If the model is an object.
- foundItems = foundItems.concat(this._where(prop));
- }
- }
- // Removes all duplicated objects found in the temp array.
- return Util.unique(foundItems);
- }
- /**
- * Loops through the models array and creates a new array of models that match all the properties on the object passed in.
- *
- * @method _where
- * @param propList {Object|Array}
- * @return {Array.<any>} Returns a list of found object's.
- * @protected
- */
- protected _where(propList:any):Array<any>
- {
- // If properties is not an array then make it an array object.
- const list:Array<any> = (propList instanceof Array) ? propList : [propList];
- const foundItems:Array<any> = [];
- const itemsLength:number = this.models.length;
- const itemsToFindLength:number = list.length;
- let hasMatchingProperty:boolean = false;
- let doesModelMatch:boolean = false;
- let model:any;
- let obj:any;
- let key:any;
- let j:number;
- for (let i:number = 0; i < itemsToFindLength; i++)
- {
- obj = list[i];
- for (j = 0; j < itemsLength; j++)
- {
- hasMatchingProperty = false;
- doesModelMatch = true;
- model = this.models[j];
- for (key in obj)
- {
- // Check if the key value is a property.
- if (obj.hasOwnProperty(key) && model.hasOwnProperty(key))
- {
- hasMatchingProperty = true;
- if (obj[key] !== model[key])
- {
- doesModelMatch = false;
- break;
- }
- }
- }
- if (doesModelMatch === true && hasMatchingProperty === true)
- {
- foundItems.push(model);
- }
- }
- }
- return foundItems;
- }
- /**
- * Loops through all properties of an object and check to see if the value matches the argument passed in.
- *
- * @method _findPropertyValue
- * @param arg {String|Number|Boolean>}
- * @return {Array.<any>} Returns a list of found object's.
- * @protected
- */
- protected _findPropertyValue(arg):Array<any>
- {
- // If properties is not an array then make it an array object.
- const list = (arg instanceof Array) ? arg : [arg];
- const foundItems:Array<any> = [];
- const itemsLength:number = this.models.length;
- const itemsToFindLength:number = list.length;
- let propertyValue:any;
- let value:any;
- let model:any;
- let key:any;
- let j:any;
- for (let i:number = 0; i < itemsLength; i++)
- {
- model = this.models[i];
- for (key in model)
- {
- // Check if the key value is a property.
- if (model.hasOwnProperty(key))
- {
- propertyValue = model[key];
- for (j = 0; j < itemsToFindLength; j++)
- {
- value = list[j];
- // If the Base Model property equals the string value then keep a reference to that Base Model.
- if (propertyValue === value)
- {
- // Add found Base Model to the foundItems array.
- foundItems.push(model);
- break;
- }
- }
- }
- }
- }
- return foundItems;
- }
- /**
- * Clears or remove all the models from the collection.
- *
- * @method clear
- * @param [silent=false] {boolean} If you'd like to prevent the event from being dispatched.
- * @public
- * @chainable
- * @example
- * collection.clear();
- */
- public clear(silent:boolean = false):any
- {
- this.models = [];
- this.length = 0;
- if (silent === false)
- {
- this.dispatchEvent(new BaseEvent(BaseEvent.CLEAR));
- }
- return this;
- }
- /**
- * Creates and returns a new collection object that contains a reference to the models in the collection cloned from.
- *
- * @method clone
- * @returns {Collection}
- * @public
- * @example
- * let clone = collection.clone();
- */
- public clone():Collection
- {
- const clonedBaseModel:Collection = new (<any>this).constructor(this._modelType);
- clonedBaseModel.add(this.models.slice(0));
- return clonedBaseModel;
- }
- /**
- * Creates a JSON object of the collection.
- *
- * @method toJSON
- * @returns {Array.<any>}
- * @public
- * @example
- * let arrayOfObjects = collection.toJSON();
- */
- public toJSON():Array<any>
- {
- if (this._modelType !== null)
- {
- const list:Array<any> = [];
- const len:number = this.length;
- for (let i:number = 0; i < len; i++)
- {
- list[i] = this.models[i].toJSON();
- }
- return list;
- }
- else
- {
- return Util.clone(this.models);
- }
- }
- /**
- * Creates a JSON string of the collection.
- *
- * @method toJSONString
- * @returns {string}
- * @public
- * @example
- * let str = collection.toJSONString();
- */
- public toJSONString():string
- {
- return JSON.stringify(this.toJSON());
- }
- /**
- * Converts the string json data into an Objects and calls the {{#crossLink "Collection/add:method"}}{{/crossLink}} method to add the objects to the collection.
- *
- * @method fromJSON
- * @param json {string}
- * @public
- * @chainable
- * @example
- * collection.fromJSON(str);
- */
- public fromJSON(json):any
- {
- const parsedData:any = JSON.parse(json);
- this.add(parsedData);
- return this;
- }
- /**
- * Allows you to sort models that have one or more common properties, specifying the property or properties to use as the sort keys
- *
- * @method sortOn
- * @param propertyName {string}
- * @param [sortAscending=true] {boolean}
- * @public
- * @return {Array<any>} Returns the list of models in the collection.
- * @example
- * collection.sortOn('name');
- * collection.sortOn('name', false);
- */
- public sortOn(propertyName:string, sortAscending:boolean = true):Array<any>
- {
- if (sortAscending === false)
- {
- return this.sort(function (a, b)
- {
- if (a[propertyName] < b[propertyName])
- {
- return 1;
- }
- if (a[propertyName] > b[propertyName])
- {
- return -1;
- }
- return 0;
- });
- }
- else
- {
- return this.sort(function (a, b)
- {
- if (a[propertyName] > b[propertyName])
- {
- return 1;
- }
- if (a[propertyName] < b[propertyName])
- {
- return -1;
- }
- return 0;
- });
- }
- }
- /**
- * Specifies a function that defines the sort order. If omitted, the array is sorted according to each character's Unicode code
- * point value, according to the string conversion of each element.
- *
- * @method sort
- * @param [sortFunction=null] {Function}
- * @public
- * @return {Array.<any>} Returns the list of models in the collection.
- * @example
- * let sortByDate = function(a, b){
- * return new Date(a.date) - new Date(b.date)
- * }
- *
- * collection.sort(sortByDate);
- */
- public sort(sortFunction = null):Array<any>
- {
- this.models.sort(sortFunction);
- return this.models;
- }
- /**
- * The filter method creates a new array with all elements that pass the test implemented by the provided function.
- *
- * @method filter
- * @param callback {Function} Function to test each element of the array. Invoked with arguments (element, index, array). Return true to keep the element, false otherwise.
- * @param [callbackScope=null] Optional. Value to use as this when executing callback.
- * @public
- * @return {Array.<any>} Returns the list of models in the collection.
- * @example
- * let isOldEnough = function(model){
- * return model.age >= 21;
- * }
- *
- * let list = collection.filter(isOldEnough);
- */
- public filter(callback:any, callbackScope:any = null):Array<any>
- {
- return this.models.filter(callback, callbackScope);
- }
- /**
- * Convenient way to get a list of property values.
- *
- * @method pluck
- * @param propertyName {string} The property name you want the values from.
- * @param [unique=false] {string} Pass in true to remove duplicates.
- * @return {Array.<any>}
- * @public
- * @example
- * collection.add([{name: 'Robert'}, {name: 'Robert'}, {name: 'Chris'}]);
- *
- * let list = collection.pluck('name');
- * // ['Robert', 'Robert', 'Chris']
- *
- * let list = collection.pluck('name', true);
- * // ['Robert', 'Chris']
- */
- public pluck(propertyName:string, unique:boolean = false):Array<any>
- {
- let list:Array<any> = [];
- for (let i = 0; i < this.length; i++) {
- if (this.models[i].hasOwnProperty(propertyName) === true) {
- list[i] = this.models[i][propertyName];
- }
- }
- if (unique === true) {
- list = Util.unique(list);
- }
- return list;
- }
- /**
- * Convenient way to group models into categories/groups by a property name.
- *
- * @method groupBy
- * @param propertyName {string} The string value of the property you want to group with.
- * @return {any} Returns an object that is categorized by the property name.
- * @public
- * @example
- * collection.add([{name: 'Robert', id: 0}, {name: 'Robert', id: 1}, {name: 'Chris', id: 2}]);
- *
- * let list = collection.groupBy('name');
- *
- * // {
- * // Robert: [{name: 'Robert', id: 0}, {name: 'Robert', id: 1}]
- * // Chris: [{name: 'Chris', id: 2}]
- * // }
- */
- public groupBy(propertyName):any
- {
- let model:any;
- let groupName:string;
- let groupList:any = {};
- // Loop through all the models in this collection.
- for (let i:number = 0; i < this.length; i++) {
- model = this.models[i];
- // Get the value from the property name passed in and uses that as the group name.
- groupName = model[propertyName];
- if (groupList[groupName] == null) {
- groupList[groupName] = [];
- }
- groupList[groupName].push(model);
- }
- return groupList;
- }
- /**
- * Changes the order of the models so that the last model becomes the first model, the penultimate model becomes the second, and so on.
- *
- * @method reverse
- * @public
- * @return {Array.<any>} Returns the list of models in the collection.
- * @example
- * collection.reverse();
- */
- public reverse():Array<any>
- {
- return this.models.reverse();
- }
- }
- export default Collection;