Instantly share code, notes, and snippets.

@kjantzer /README.md
Last active Apr 17, 2018

Embed
What would you like to do?
Backbone.js & Underscore.js Natural Sorting

Backbone.js & Underscore.js Natural Sorting Algorithm

Backbone is great but I continually found the default sorting method limited due to the following:

Disc 1, Disc 2, … Disc 10 would become Disc 1, Disc 10, Disc 2

With the help of Jim Palmer's naturalSort.js, I was able to integrate natrual sorting into Backbone.Collection.

Backbone collections are automatically sorted and now, by simply adding sortType:"natural", your collections can be sorted naturally.

// unsorted data
var data = [
	{label: 'Disc 1'}, 
	{label: 'Disc 2'}, 
	{label: 'Disc 10'}, 
	{label: 'Disc 3'},
	{label: 'Disc 11'}
];

var Collection = Backbone.Collection.extend({
	comparator: function(m){
		return m.get('label');
	}
});
	
var NatrualCollection = Backbone.Collection.extend({
	sortType: 'natural',
	comparator: function(m){
		return m.get('label');
	}
});

var coll = new Collection(data);
var collNat = new NatrualCollection(data);

console.log('Not sorted naturally', coll.pluck('label'));
console.log('Sorted naturall', collNat.pluck('label'));

Underscore and Vanilla JS

An underscore.js method is also available

var sorted = _.sortByNat(data, function(m){ return m.label });

console.log(_.pluck(sorted, 'label'));

Lastly, natural sort can be called directly on an Array

["Disc 1", "Disc 2", "Disc 10", "Disc 3", "Disc 11"].sortNat();
/*
* Backbone.js & Underscore.js Natural Sorting
*
* @author Kevin Jantzer <https://gist.github.com/kjantzer/7027717>
* @since 2013-10-17
*
* NOTE: make sure to include the Natural Sort algorithm by Jim Palmer (https://github.com/overset/javascript-natural-sort)
*/
// add _.sortByNat() method
_.mixin({
sortByNat: function(obj, value, context) {
var iterator = _.isFunction(value) ? value : function(obj){ return obj[value]; };
return _.pluck(_.map(obj, function(value, index, list) {
return {
value: value,
index: index,
criteria: iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria;
var b = right.criteria;
return naturalSort(a, b);
}), 'value');
}
});
// add _.sortByNat to Backbone.Collection
Backbone.Collection.prototype.sortByNat = function(value, context) {
var iterator = _.isFunction(value) ? value : function(model) {
return model.get(value);
};
return _.sortByNat(this.models, iterator, context);
};
// new Natural Sort method on Backbone.Collection
Backbone.Collection.prototype.sortNat = function(options) {
if (!this.comparator) throw new Error('Cannot sortNat a set without a comparator');
options || (options = {});
if (_.isString(this.comparator) || this.comparator.length === 1) {
this.models = this.sortByNat(this.comparator, this);
} else {
this.models.sortNat(_.bind(this.comparator, this));
}
if (!options.silent) this.trigger('sort', this, options);
return this;
};
// save the oringal sorting method
Backbone.Collection.prototype._sort = Backbone.Collection.prototype.sort;
// override the default sort method to determine if "regular" or "natural" sorting should be used
Backbone.Collection.prototype.sort = function(){
if( this.sortType && this.sortType === 'natural' )
Backbone.Collection.prototype.sortNat.apply(this, arguments);
else
Backbone.Collection.prototype._sort.apply(this, arguments);
};
/*
* Natural Sort algorithm for Javascript - Version 0.7 - Released under MIT license
* Author: Jim Palmer (based on chunking idea from Dave Koelle)
* https://github.com/overset/javascript-natural-sort
*/
function naturalSort (a, b) {
var re = /(^-?[0-9]+(\.?[0-9]*)[df]?e?[0-9]?$|^0x[0-9a-f]+$|[0-9]+)/gi,
sre = /(^[ ]*|[ ]*$)/g,
dre = /(^([\w ]+,?[\w ]+)?[\w ]+,?[\w ]+\d+:\d+(:\d+)?[\w ]?|^\d{1,4}[\/\-]\d{1,4}[\/\-]\d{1,4}|^\w+, \w+ \d+, \d{4})/,
hre = /^0x[0-9a-f]+$/i,
ore = /^0/,
i = function(s) { return naturalSort.insensitive && (''+s).toLowerCase() || ''+s },
// convert all to strings strip whitespace
x = i(a).replace(sre, '') || '',
y = i(b).replace(sre, '') || '',
// chunk/tokenize
xN = x.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'),
yN = y.replace(re, '\0$1\0').replace(/\0$/,'').replace(/^\0/,'').split('\0'),
// numeric, hex or date detection
xD = parseInt(x.match(hre)) || (xN.length != 1 && x.match(dre) && Date.parse(x)),
yD = parseInt(y.match(hre)) || xD && y.match(dre) && Date.parse(y) || null,
oFxNcL, oFyNcL;
// first try and sort Hex codes or Dates
if (yD)
if ( xD < yD ) return -1;
else if ( xD > yD ) return 1;
// natural sorting through split numeric strings and default strings
for(var cLoc=0, numS=Math.max(xN.length, yN.length); cLoc < numS; cLoc++) {
// find floats not starting with '0', string or 0 if not defined (Clint Priest)
oFxNcL = !(xN[cLoc] || '').match(ore) && parseFloat(xN[cLoc]) || xN[cLoc] || 0;
oFyNcL = !(yN[cLoc] || '').match(ore) && parseFloat(yN[cLoc]) || yN[cLoc] || 0;
// handle numeric vs string comparison - number < string - (Kyle Adams)
if (isNaN(oFxNcL) !== isNaN(oFyNcL)) { return (isNaN(oFxNcL)) ? 1 : -1; }
// rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
else if (typeof oFxNcL !== typeof oFyNcL) {
oFxNcL += '';
oFyNcL += '';
}
if (oFxNcL < oFyNcL) return -1;
if (oFxNcL > oFyNcL) return 1;
}
return 0;
}
// extend Array to have a natural sort
Array.prototype.sortNat = function(){
return Array.prototype.sort.call(this, naturalSort)
}
@paulbjensen

This comment has been minimized.

Show comment
Hide comment
@paulbjensen

paulbjensen Mar 31, 2014

Thank you so much for this, I could kiss you.

paulbjensen commented Mar 31, 2014

Thank you so much for this, I could kiss you.

@pilgreen

This comment has been minimized.

Show comment
Hide comment
@pilgreen

pilgreen Apr 30, 2014

Thank you so very very much.

pilgreen commented Apr 30, 2014

Thank you so very very much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment