API Docs for: 0.0.6

src/Collection.js

/**
 * PrototypeJS Model extension - Enables Prototype JS users to fetch / store
 * Models from / to a backend using AJAX / REST.
 * The Collection class stores Models and allows storing / fetching them in a batch.
 *
 * Inspired by (but not copied) [Backbone's Backbone.Collection](http://backbonejs.org/) and Backbone.sync
 *
 *
 * @author Alexander Schenkel <alex@alexi.ch>
 * @copyright 2015 Alexander Schenkel
 * @license Released under the MIT License
 * @class Protobone.Collection
 * @extends Protobone.Base
 * @constructor
 */
var Base = require('./Base.js');
var Model = require('./Model.js');
var Collection = Class.create(Base, {

    /**
     * The URL root for this Collection. Must be set in child classes,
     * e.g. to '/entities/Person', or must be defined in the defined Model.
     *
     * Used by the url() function to build the persistence URL.
     *
     * @property urlRoot
     * @type String
     */
    urlRoot: '',

    /**
     * Defines the model class used when adding models via attribute array.
     *
     * @property model
     * @type {Class}
     */
    model: Model,

    /**
     * Constructor. A data array can be delivered to create Models already during
     * construction time (e.g. `[{name: 'alex'},{name: 'barbara'}]`)
     *
     * @method constructor
     * @param {Array} data Initial data (array of key/value pairs) to create Model's from
     */
    initialize: function($super, data) {
        $super();
        this._registerModelEventHandlers();
        this.models = [];
        this.length = this.models.length;
        this.add(data);
    },

    /**
     * stores event handler functions which will be attached to
     * models on add(): We need a reference to them for
     * later removing the event listeners in remove().
     */
    _registerModelEventHandlers: function() {
        this._modelEventHandlers = {
            updated: this._bubbleUpdatedEvent.bind(this)
        };
    },


    _updateLength: function() {
        this.length = this.models.length;
    },



    /**
     * The add method takes a Model or an array of Models and adds them to the
     * internal collection. Plain objects can also be delivered, which will be
     * transformed to Prototype.Model instances or, if set, to models of the `this.model` class.
     *
     * @method add
     * @param {Array}{Object} data The array / object of model(s) to be added
     */
    add: function(data) {
        var newData;

        if (!Object.isArray(data)) data = [data];
        newData = data.map(function(item) {
            var m = null;
            if (item instanceof Model) {
                m = item;
            } else if (typeof item === 'object') {
                m = new this.model(item);
            }
            if (m) {
                m.on('updated', this._modelEventHandlers.updated);
            }
            return m;
        },this);
        this.models = [this.models, newData].flatten().compact();
        this._updateLength();
        this.fireEvent('add',newData, this);
        return this;
    },

    /**
     * Returns the model by specific ID
     *
     * @method get
     * @param {mixed} id The ID of the model
     * @return {Model} The model if found, or null
     */
    get: function(id) {
        var m = new this.model();
        return this.findBy(m.idAttribute,id);
    },

    /**
     * Find a model by a specific attribute value.
     *
     * @method findBy
     * @param {String} attribute The name of the attribute
     * @param {Mixed} value The value of the attribute
     * @param {Model} Returns the first model with a matching attribute value, or null if none can be found
     */
    findBy: function(attribute,value) {
        var predicateProps = {};
        predicateProps[attribute] = value;
        return this.findWhere(predicateProps);
    },

    /**
     * Like where,
     * @method findWhere
     * @param {Object} predicateProps A key/value hash with attributes/values to match
     * @return {Model} The first matching model
     */
    findWhere: function(predicateProps) {
        var found = null;
        this.forEach(function(item) {
            if (this._attributeMatcher(item, predicateProps)) {
                found = item;
                return false;
            }
        }.bind(this));
        return found;
    },

    /**
     * Helper method for matching a single model against a set of
     * predicate properties (all must match).
     * Returns true if all predicate props match, false if not.
     */
    _attributeMatcher: function(item, predicateProps) {
        var matches = true;
        $H(predicateProps).each(function(pair){
            matches = matches && item.get(pair.key) === pair.value;
        });
        return matches;
    },

    /**
     * alias for add(), to support "array like" behaviour
     *
     * @method push
     * @params {Object} Same as add, data The array / object of model(s) to be added
     */
    push: function(model) {
        return this.add(model);
    },


    /**
     * returns the model at the given index, or null if index is out of bounds
     *
     * @method at
     * @param {Integer} index The index to fetch the model for
     * @return {Model} The model at the index or null
     */
    at: function(index) {
        if (index < 0 || index > this.length-1) return null;
        return this.models[index];
    },

    /**
     * returns the index of the given model, or -1 if the model is not in the collection.
     *
     * @method indexOf
     * @param {Model} model The model to find
     * @return {Integer} The index, or -1 if not in collection
     */
    indexOf: function(model) {
        return this.models.indexOf(model);
    },

    /**
     * loops over all models and calls the given callback with `callback(model,index)`.
     * Return false within the callback to cancel the loop.
     *
     * @method forEach
     * @param {Function} callback The callback for every item to be called. Return false to cancel the loop
     * @param
     */
    forEach: function(callback, scope) {
        var i,ret;
        for (i = 0; i < this.models.length; i++) {
            ret = callback.call(scope,this.models[i],i);
            if (ret === false) break;
        }
    },

    /**
     * Simple application of `filter()` function: returns all models matching
     * the given attributes, e.g.:
     * ```javascript```
     * var c = new Protobone.Collection([
     *     {author: 'Stephen King', title: 'Carrie'},
     *     {author: 'Stephen King', title: 'Needful Things'},
     *     {author: 'Jane Austen', title: 'Persuasion'},
     * ]);
     * var res = c.where({author: 'Stephen King'});
     * // => returns all Models with Stephen King as author
     * ```
     *
     * Multiple attributes are allowed.
     *
     * @method where
     * @see filter
     * @param {Object} predicateProps A key/value hash with attributes/values to match
     * @return {Array} The matching models
     */
    where: function(predicateProps) {
        return this.filter(function(item){
            return this._attributeMatcher(item,predicateProps);
        }.bind(this));
    },

    /**
     * Returns all models matching a given predicate function. The predicate function
     * gets the actual model, and must return `true` if it matches, or `false` if not.
     * Example:
     * ```javascript```
     * var c = new Protobone.Collection([
     *     {author: 'Stephen King', title: 'Carrie'},
     *     {author: 'Stephen King', title: 'Needful Things'},
     *     {author: 'Jane Austen', title: 'Persuasion'},
     * ]);
     * var res = c.filter(function(item){
     *     return item.get('author') === 'Stephen King';
     * });
     * // => returns all Models with Stephen King as author
     * ```
     *
     * @method filter
     * @param {Function} predicate The predicate function as test for each model
     * @return {Array} The matching models
     */
    filter: function(predicate) {
        var res = [];
        this.forEach(function(item) {
            if (predicate(item) === true) {
                res[res.length] = item;
            }
        });
        return res;
    },

    /**
     * Removes the given model(s) (single model or array of models) from the collection.
     * fires a remove event. If the model occurs multiple times, all of them are removed.
     *
     * @method remove
     * @param {Model/Array} The model(s) to be removed
     */
    remove: function(model) {
        // unwind event listeners
        // fire remove event
        var keep = [],
            removed = [];

        if (!Object.isArray(model)) model = [model];
        this.models.each(function(item) {
            if (model.indexOf(item) === -1) {
                keep[keep.length] = item;
            } else {
                item.off('updated',this._modelEventHandlers.updated);
                removed[removed.length] = item;
            }
        }.bind(this));
        this.models = keep;
        this.fireEvent('removed',this,removed);
        this._updateLength();
    },

    /**
     * Removes the model at the index given.
     *
     * @method remove
     * @param {Integer} The index of the model to be removed
     */
    removeAt: function(index) {
        return this.remove(this.at(index));
    },

    _bubbleUpdatedEvent: function() {
        this.fireEvent.apply(this,['updated',$A(arguments)].flatten());
    }
});

// Adding support for JS Modules through browserify / ES 6:
module.exports = Collection;