Home Manual ReferenceGraphs Source Repository

backbone-es6/src/Utils.js

'use strict';

import _             from 'underscore';
import BackboneProxy from './BackboneProxy.js';

// Private / internal methods ---------------------------------------------------------------------------------------

/**
 * Creates an optimized function that dispatches to an associated Underscore function.
 *
 * @param {number}   length      - Length of variables for given Underscore method to dispatch.
 * @param {string}   method      - Function name of Underscore to invoke.
 * @param {string}   attribute   - Attribute to associate with the Underscore function invoked.
 * @returns {Function}
 */
const s_ADD_METHOD = (length, method, attribute) =>
{
   switch (length)
   {
      case 1:
         return function()
         {
            return _[method](this[attribute]);
         };
      case 2:
         return function(value)
         {
            return _[method](this[attribute], value);
         };
      case 3:
         return function(iteratee, context)
         {
            return _[method](this[attribute], s_CB(iteratee), context);
         };
      case 4:
         return function(iteratee, defaultVal, context)
         {
            return _[method](this[attribute], s_CB(iteratee), defaultVal, context);
         };
      default:
         return function()
         {
            const args = Array.prototype.slice.call(arguments);
            args.unshift(this[attribute]);
            return _[method](...args);
         };
   }
};

/**
 * Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
 *
 * @param {*} iteratee  -
 * @returns {*}
 */
const s_CB = (iteratee) =>
{
   if (_.isFunction(iteratee)) { return iteratee; }
   if (_.isObject(iteratee) && !Utils.isModel(iteratee)) { return s_MODEL_MATCHER(iteratee); }
   if (_.isString(iteratee))
   {
      return function(model)
      {
         return model.get(iteratee);
      };
   }
   return iteratee;
};

/**
 * Creates a matching function against `attrs`.
 *
 * @param {*} attrs -
 * @returns {Function}
 */
const s_MODEL_MATCHER = (attrs) =>
{
   const matcher = _.matches(attrs);
   return (model) =>
   {
      return matcher(model.attributes);
   };
};

/**
 * Provides static utility functions.
 * --------
 *
 * Proxy Backbone class methods to Underscore functions, wrapping the model's `attributes` object or collection's
 * `models` array behind the scenes.
 *
 * `Function#apply` can be slow so we use the method's arg count, if we know it.
 *
 * @example
 * collection.filter(function(model) { return model.get('age') > 10 });
 * collection.each(this.addView);
 */
export default class Utils
{
   /**
    * Adds Underscore methods if they exist from keys of the `methods` hash to `Class` running against the variable
    * defined by `attribute`
    *
    * @param {Class}    Class       -  Class to add Underscore methods to.
    * @param {object}   methods     -  Hash with keys as method names and values as argument length.
    * @param {string}   attribute   -  The variable to run Underscore methods against. Often "attributes"
    */
   static addUnderscoreMethods(Class, methods, attribute)
   {
      _.each(methods, (length, method) =>
      {
         if (_[method]) { Class.prototype[method] = s_ADD_METHOD(length, method, attribute); }
      });
   }

   /**
    * Method for checking whether an unknown variable is an instance of `Backbone.Model`.
    *
    * @param {*}  unknown - Variable to test.
    * @returns {boolean}
    */
   static isModel(unknown)
   {
      return unknown instanceof BackboneProxy.backbone.Model;
   }

   /**
    * Method for checking whether a variable is undefined or null.
    *
    * @param {*}  unknown - Variable to test.
    * @returns {boolean}
    */
   static isNullOrUndef(unknown)
   {
      return unknown === null || typeof unknown === 'undefined';
   }

   /**
    * Throw an error when a URL is needed, and none is supplied.
    */
   static urlError()
   {
      throw new Error('A "url" property or function must be specified');
   }

   /**
    * Wrap an optional error callback with a fallback error event.
    *
    * @param {Model|Collection}  model    - Model or Collection target to construct and error callback against.
    * @param {object}            options  - Options hash to store error callback inside.
    */
   static wrapError(model, options)
   {
      const error = options.error;
      options.error = (resp) =>
      {
         if (error) { error.call(options.context, model, resp, options); }
         model.trigger('error', model, resp, options);
      };
   }
}