backbone.js: Update model, re-sort and re-render models collection

backbone.js, socket.io

Solution

You can achieve what you want by providing a `comparator` method for your collection.

Example:

ModelCollection = Backbone.Collection.extend({
    comparator: function(a, b) {
        if ( a.get("name") > b.get("name") ) return 1;
        if ( a.get("name") < b.get("name") ) return -1;
        if ( a.get("name") === b.get("name") ) return 0;
    },

    initialize: function() {
        this.on('change:name', function() { this.sort() }, this);
    }
});

The `comparator` in this example will cause your collection to be sorted in ascending order by the `name` attribute of the models inside.

Note that your collection won't be sorted automatically when changing attribute(s) of any of its `models`. By default, sorting happens only when creating new models and adding them to the collection; but the `comparator` will be used by the `collection.sort` method.

The code above takes advantage of this by setting an event listener that simply re-sorts the collection on any `change`s to the `name` attributes of its `models`.

To complete the picture, we set up an appropriate event listener in the `View` associated with the collection to make sure it re-renders on any changes:

CollectionView = Backbone.View.extend({
    initialize: function() {
        this.collection = new ModelCollection();
        this.collection.on('all', function() { this.render() }, this);
    },

    render: function() {
        this.$el.html(this.collection.toJSON());
    }
});

That's it :)

Relevant excerpt from the Backbone documentation:

By default there is no `comparator` for a collection. If you define a `comparator`, it will be used to maintain the collection in sorted order. This means that as models are added, they are inserted at the correct index in `collection.models`. A comparator can be defined as a `sortBy` (pass a function that takes a single argument), as a `sort` (pass a comparator function that expects two arguments), or as a string indicating the attribute to sort by. [...] Collections with a `comparator` will not automatically re-sort if you later change model attributes, so you may wish to call `sort` after changing model attributes that would affect the order.

Problem

In my app I have a socket.io connection that is listening to the backend and getting updates to models held by the clients browser (which retrieves the model by id and calls `set` on the model attribute). I'd like the collection to be sorted, then re-rendered as a whole in order to reflect any new ordering on the models as a result of the `set` (most examples seem to be around individual views being re-rendered). What's a method of achieving this? NB I've got a backbone.js layout lifted pretty verbatim from the example todo app (this is the first backbone app).

Original source