lib/backbone_sortable_collection.js
/* 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;
}));