golmansax/backbone-sortable-collection

View on GitHub
lib/backbone_sortable_collection.js

Summary

Maintainability
A
1 hr
Test Coverage
/* istanbul ignore next */
(function (root, factory) {
  'use strict';

  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(['underscore', 'backbone'], factory);
  } else if (typeof exports === 'object') {
    // Node. Does not work with strict CommonJS, but
    // only CommonJS-like enviroments that support module.exports,
    // like Node.
    module.exports = factory(require('underscore'), require('backbone'));
  } else {
    // Browser globals
    root.Backbone.SortableCollection = factory(root._, root.Backbone);
  }
}(this, function (_, Backbone) {
  'use strict';

  function ComparatorRunner() {
    this.init = function (name, comparator, options) {
      var reverse = !!options.reverse;

      if (comparator === 'getter') {
        this.compare = sortByComparator(function (model) {
          return model.get(name);
        }, reverse);
      } else if (comparator.length === 1) {
        this.compare = sortByComparator(comparator, reverse);
      } else {
        this.compare = traditionalComparator(comparator, reverse);
      }

      return this;
    };

    function traditionalComparator(comparator, reverse) {
      if (!reverse) { return comparator; }

      return function (modelA, modelB) {
        return !comparator(modelA, modelB);
      };
    }

    function sortByComparator(comparator, reverse) {
      var reverseFactor = reverse ? -1 : 1;

      return function (modelA, modelB) {
        var valueA = comparator(modelA);
        var valueB = comparator(modelB);

        if (valueA < valueB) {
          return -1 * reverseFactor;
        } else if (valueA > valueB) {
          return 1 * reverseFactor;
        } else {
          return 0;
        }
      };
    }
  }

  function _parseSortUnitObject(sortUnit) {
    var keys = _(sortUnit).keys();

    if (keys.length !== 1) {
      throw 'Following should be in format of ' +
        '{ comparatorName: sortDirection }: ' + JSON.stringify(sortUnit);
    }

    var name = keys[0];
    var direction = sortUnit[name];

    if (direction !== 'asc' && direction !== 'desc') {
      throw 'Sort direction must be either \'asc\' or \'desc\'';
    }

    return { name: name, direction: direction };
  }

  function  _parseSortUnit(sortUnit) {
    if (_(sortUnit).isObject()) {
      return _parseSortUnitObject(sortUnit);
    }

    var name, direction;

    if (sortUnit[0] === '!') {
      name = sortUnit.substring(1);
      direction = 'desc';
    } else {
      name = sortUnit;
      direction = 'asc';
    }

    return { name: name, direction: direction };
  }

  var SortableCollection = Backbone.Collection.extend({
    initialize: function () {
      if (this.defaultSort) { this.changeSort(this.defaultSort); }
    },

    defaultSort: null,

    changeSort: function (sort) {
      this._reverseSort = false;
      this.comparator = this._createComparator(sort);
      this.sort();
    },

    reverseSort: function () {
      this._reverseSort = !this._reverseSort;
      this.sort();
    },

    comparators: {},

    sorts: {},

    _reverseSort: false,

    _createComparatorRunners: function (sort) {
      var self = this;
      var comparators = self.comparators;

      return _(sort).map(function (sortUnit) {
        var parseResult = _parseSortUnit(sortUnit);
        var name = parseResult.name;
        var direction = parseResult.direction;

        var comparator = comparators[name];
        if (!comparator) {
          throw 'Comparator \'' + name + '\' is missing, add it to comparators';
        }

        return new ComparatorRunner().init(name, comparator, {
          reverse: direction === 'desc'
        });
      });
    },

    _createComparator: function (sort) {
      if (_(sort).isString()) {
        // If it is a string, let's try to look up the sort
        sort = this.sorts[sort] || sort;
      }

      if (!_(sort).isArray()) {
        // Ensure that sort is an array
        sort = [sort];
      }

      var comparatorRunners = this._createComparatorRunners(sort);

      return function (modelA, modelB) {
        var index, returnValue;

        for (index = 0; index < comparatorRunners.length; index++) {
          returnValue = comparatorRunners[index].compare(modelA, modelB);
          if (returnValue !== 0) { break; }
        }

        return returnValue * (this._reverseSort ? -1 : 1);
      };
    }
  });

  return SortableCollection;
}));