Reverse sort order with Backbone.js

Well, you can return negative values from comparator. If we take, for example, the example from Backbone's site and want to reverse the order, it will look like this:

var Chapter  = Backbone.Model;
var chapters = new Backbone.Collection;

chapters.comparator = function(chapter) {
  return -chapter.get("page"); // Note the minus!
};

chapters.add(new Chapter({page: 9, title: "The End"}));
chapters.add(new Chapter({page: 5, title: "The Middle"}));
chapters.add(new Chapter({page: 1, title: "The Beginning"}));

alert(chapters.pluck('title'));

Personally, I'm not that happy with any of the solutions given here:

  • The multiplying by -1 solution fails to work when the sort type is non-numeric. Although there are ways around this (by calling Date.getTime() for example) these cases are specific. I would like a general way to reverse the direction of any sort, without having to worry about the specific type of the field being sorted.

  • For the string solution, constructing a string one character at a time seems like a performance bottleneck, especially when using large collections (or indeed, large sort field strings)

Here's a solution that works nicely for String, Date and Numeric fields:

Firstly, declare a comparator that will reverse the result of a result of a sortBy function, like this:

function reverseSortBy(sortByFunction) {
  return function(left, right) {
    var l = sortByFunction(left);
    var r = sortByFunction(right);

    if (l === void 0) return -1;
    if (r === void 0) return 1;

    return l < r ? 1 : l > r ? -1 : 0;
  };
}

Now, if you want to reverse the direction of the sort, all we need to do is:

var Chapter  = Backbone.Model;
var chapters = new Backbone.Collection;

// Your normal sortBy function
chapters.comparator = function(chapter) {
  return chapter.get("title"); 
};


// If you want to reverse the direction of the sort, apply 
// the reverseSortBy function.
// Assuming the reverse flag is kept in a boolean var called reverseDirection 
if(reverseDirection) {
   chapters.comparator = reverseSortBy(chapters.comparator);
}

chapters.add(new Chapter({page: 9, title: "The End"}));
chapters.add(new Chapter({page: 5, title: "The Middle"}));
chapters.add(new Chapter({page: 1, title: "The Beginning"}));

alert(chapters.pluck('title'));

The reason this works is that Backbone.Collection.sort behaves differently if the sort function has two arguments. In this case, it behaves in the same manner as the comparator passed into Array.sort. This solution works by adapting the single argument sortBy function into a two argument comparator and reversing the result.


Backbone.js's collection comparator relies on the Underscore.js method _.sortBy. The way sortBy is implemented ends up "wrapping" up javascript .sort() in a way that makes sorting strings in reverse difficult. Simple negation of the string ends up returning NaN and breaks the sort.

If you need to perform a reverse sort with Strings, such as reverse alphabetical sort, here's a really hackish way of doing it:

comparator: function (Model) {
  var str = Model.get("name");
  str = str.toLowerCase();
  str = str.split("");
  str = _.map(str, function(letter) { 
    return String.fromCharCode(-(letter.charCodeAt(0)));
  });
  return str;
}

It's by no means pretty, but it is a "string negator". If you don't have any qualms with modifying native object types in javascript, you could make you code clearer by extracting the string negator and adding it as a method on String.Prototype. However you probably only want to do this if you know what you are doing, because modifying native object types in javascript can have unforeseen consequences.