StructureJS

0.15.2

A 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.

File: ts/model/Collection.ts

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;

    
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy