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;