Reverse sort order with Backbone.js

Javascriptbackbone.js

Javascript Problem Overview


With Backbone.js I've got a collection set up with a comparator function. It's nicely sorting the models, but I'd like to reverse the order.

How can I sort the models in descending order rather than ascending?

Javascript Solutions


Solution 1 - Javascript

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'));

Solution 2 - Javascript

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.

Solution 3 - Javascript

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.

Solution 4 - Javascript

My solution was to reverse results after sort.

To prevent double rendering first sort with silent, then trigger 'reset'.

collections.sort({silent:true})
collections.models = collections.models.reverse();
collections.trigger('reset', collections, {});

Solution 5 - Javascript

Modify your comparator function to return some reversely proporitional value instead of returning the data that you are currently. Some code from : http://documentcloud.github.com/backbone/#Collection-comparator

Example:

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

/* Method 1: This sorts by page number */
chapters.comparator = function(chapter) {
  return chapter.get("page");
};

/* Method 2: This sorts by page number in reverse */
chapters.comparator = function(chapter) {
  return -chapter.get("page");
};

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"}));

Solution 6 - Javascript

The following worked well for me:

comparator: function(a, b) {
  // Optional call if you want case insensitive
  name1 = a.get('name').toLowerCase();
  name2 = b.get('name').toLowerCase();

  if name1 < name2
    ret = -1;
  else if name1 > name2
    ret = 1;
  else
    ret = 0;

  if this.sort_dir === "desc"
    ret = -ret
  return ret;
}

collection.sort_dir = "asc";
collection.sort(); // returns collection in ascending order
collection.sort_dir = "desc";
collection.sort(); // returns collection in descending order

Solution 7 - Javascript

I read some where that the Backbone comparator function either uses or mimics the JavaScript Array.prototype.sort() function. This means it uses either 1, -1 or 0 to decide on the sort order of each model in the collection. This constantly updates and the collection stays in the correct order based on the comparator.

Info on Array.prototype.sort() can be found here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort?redirectlocale=en-US&redirectslug=JavaScript%2FReference%2FGlobal_Objects%2FArray%2Fsort

Many answers on this question simply reverse the order just before rending it to the page. This is not ideal.

Using the above article on Mozilla as a guide I was able to keep my collection sorted in reverse order using the following code, then we simply render the collection to page in it's current order and we can use a flag (reverseSortDirection) for reversing the order.

//Assuming this or similar will be in your Backbone.Collection.
sortKey: 'id', //The field name of the item in your collection

reverseSortDirection: false,

comparator: function(a, b) {

  var sampleDataA = a.get(this.sortKey),
      sampleDataB = b.get(this.sortKey);

      if (this.reverseSortDirection) {
        if (sampleDataA > sampleDataB) { return -1; }
        if (sampleDataB > sampleDataA) { return 1; }
        return 0;
      } else {
        if (sampleDataA < sampleDataB) { return -1; }
        if (sampleDataB < sampleDataA) { return 1; }
        return 0;
      }

},

Comparator except's two models, on changes to the collection or model the compartor function runs and changes the order of the collection.

I have tested this approach with Numbers and Strings and it seems to be working perfectly for me.

I really hope this answer can help as I have struggled with this problem many times and usually end up using a work around.

Solution 8 - Javascript

This can be done elegantly by overriding sortBy method. Here is an example

var SortedCollection = Backbone.Collection.extend({

   initialize: function () {
       // Default sort field and direction
       this.sortField = "name";
       this.sortDirection = "ASC";
   },

   setSortField: function (field, direction) {
       this.sortField = field;
       this.sortDirection = direction;
   },

   comparator: function (m) {
       return m.get(this.sortField);
   },

   // Overriding sortBy (copied from underscore and just swapping left and right for reverse sort)
   sortBy: function (iterator, context) {
       var obj = this.models,
           direction = this.sortDirection;

       return _.pluck(_.map(obj, function (value, index, list) {
           return {
               value: value,
               index: index,
               criteria: iterator.call(context, value, index, list)
           };
       }).sort(function (left, right) {
           // swap a and b for reverse sort
           var a = direction === "ASC" ? left.criteria : right.criteria,
               b = direction === "ASC" ? right.criteria : left.criteria;

           if (a !== b) {
               if (a > b || a === void 0) return 1;
               if (a < b || b === void 0) return -1;
           }
           return left.index < right.index ? -1 : 1;
       }), 'value');
   }

});

So you can use it like this:

var collection = new SortedCollection([
  { name: "Ida", age: 26 },
  { name: "Tim", age: 5 },
  { name: "Rob", age: 55 }
]);

//sort by "age" asc
collection.setSortField("age", "ASC");
collection.sort();

//sort by "age" desc
collection.setSortField("age", "DESC");
collection.sort();

This solution does not depend on the field type.

Solution 9 - Javascript

// For numbers, dates, and other number-like things
var things = new Backbone.Collection;
things.comparator = function (a, b) { return b - a };

// For strings
things.comparator = function (a, b) {
  if (a === b) return 0;
  return a < b ? -1 : 1;
};

Solution 10 - Javascript

Here is a really simple solution, for those who simply want to flip the current order. It is useful if you have a table whose columns can be ordered in two directions.

collection.models = collection.models.reverse()

You can combine it with something like this if you are interested in the table case (coffeescript) :

  collection.comparator = (model) ->
    model.get(sortByType)

  collection.sort()

  if reverseOrder
    collection.models = collection.models.reverse()

Solution 11 - Javascript

For reverse order on id:

comparator: function(object) { return (this.length - object.id) + 1; }

Solution 12 - Javascript

I had a slightly different requirement, in that I am displaying my models in a table, and I wanted to be able to sort them by any collumn (model property) and either ascending or descending.

It has taken a fair amount of mucking around to get all the cases working (where values can be strings, empty strings, dates, numbers. However I think this is pretty close.

    inverseString: function(str)
    {
        return str.split("").map(function (letter) { return String.fromCharCode(-(letter.charCodeAt(0))); }).join("");
    }
    getComparisonValue: function (val, desc)
    {
        var v = 0;
        // Is a string
        if (typeof val === "string")
        {
            // Is an empty string, upgrade to a space to avoid strange ordering
            if(val.length === 0)
            {
                val = " ";
                return desc ? this.inverseString(val) : val;
            }                

            // Is a (string) representing a number
            v = Number(val);
            if (!isNaN(v))
            {
                return desc ? -1 * v : v;
            }
            // Is a (string) representing a date
            v = Date.parse(val);
            if (!isNaN(v))
            {
                return desc ? -1 * v : v;
            }
            // Is just a string
            return desc ? this.inverseString(val) : val;
        }
        // Not a string
        else
        {
            return desc ? -1 * val : val;
        }
    },

use:

    comparator: function (item)
    {
        // Store the collumn to 'sortby' in my global model
        var property = app.collections.global.get("sortby");
         
        // Store the direction of the sorting also in the global model
        var desc = app.collections.global.get("direction") === "DESC";

        // perform a comparison based on property & direction
        return this.getComparisonValue(item.get(property), desc);
    },

Solution 13 - Javascript

I just overrode the sort function to reverse the models array upon completion. Also I held off triggering the 'sort' event until after the reverse has been completed.

    sort : function(options){

        options = options || {};

        Backbone.Collection.prototype.sort.call(this, {silent:true});
        this.models.reverse();

        if (!options.silent){
            this.trigger('sort', this, options);
        }

        return this;
    },

Solution 14 - Javascript

Recently ran into this issue and just decided to add an rsort method.

    Collection = Backbone.Collection.extend({
            comparator:function(a, b){
                return a>b;
            },
            rsort:function(){
                var comparator = this.comparator;
    
                this.comparator = function(){
                    return -comparator.apply(this, arguments);
                }
                this.sort();
    
                this.comparator = comparator;
    
            }
    });

Hopefully someone will find this useful

Solution 15 - Javascript

Here's a quick and easy way to reverse-sort a collection's models using UnderscoreJS

 var reverseCollection = _.sortBy(this.models, function(model) {
   return self.indexOf(model) * -1;
 });

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDrew Dara-AbramsView Question on Stackoverflow
Solution 1 - JavascriptInfeligoView Answer on Stackoverflow
Solution 2 - JavascriptAndrew NewdigateView Answer on Stackoverflow
Solution 3 - JavascriptAndrew De AndradeView Answer on Stackoverflow
Solution 4 - JavascriptTomasz TrelaView Answer on Stackoverflow
Solution 5 - JavascriptDhruvPathakView Answer on Stackoverflow
Solution 6 - JavascriptrbhitchcockView Answer on Stackoverflow
Solution 7 - JavascriptFasaniView Answer on Stackoverflow
Solution 8 - JavascriptAman MahajanView Answer on Stackoverflow
Solution 9 - JavascriptwprlView Answer on Stackoverflow
Solution 10 - JavascriptdezmanView Answer on Stackoverflow
Solution 11 - JavascriptmzzlView Answer on Stackoverflow
Solution 12 - JavascriptJosh McView Answer on Stackoverflow
Solution 13 - Javascriptuser2686693View Answer on Stackoverflow
Solution 14 - JavascriptDiego AlejosView Answer on Stackoverflow
Solution 15 - JavascriptJohn Ryan CottamView Answer on Stackoverflow