Christian Engel
Christian Engel

Reputation: 3778

Ordering backbone views together with collection

I have a collection which contains several items that should be accessible in a list.

So every element in the collection gets it own view element which is then added to the DOM into one container.

My question is: How do I apply the sort order I achieved in the collection with a comparator function to the DOM? The first rendering is easy: you iterate through the collection and create all views which are then appended to the container element in the correct order.

But what if models get changed and are re-ordered by the collection? What if elements are added? I don't want to re-render ALL elements but rather update/move only the necessary DOM nodes.

Upvotes: 5

Views: 3309

Answers (2)

Pierre Ingmansson
Pierre Ingmansson

Reputation: 61

Thanks Vincent for an awesome solution. There's however a problem with the moving of the element, depending on which direction the reindexed element is moving. If it's moving down, the index of the new location doesn't match the index of what's in the DOM. This fixes it:

var Model = Backbone.Model.extend({
    initialize: function () {
        this.bind('change:name', this.onChangeName, this);
    },
    onChangeName: function () {
        var fromIndex, toIndex;

        fromIndex = this.collection.indexOf(this);
        this.collection.sort({silent: true});
        toIndex = this.collection.indexOf(this);
        if (fromIndex !== toIndex)
        {
            this.trigger('reindex', fromIndex, toIndex);
            // or
            // this.collection.trigger('reindex', this, fromIndex, toIndex);
        }
    }
});

And the example listening part:

var View = Backbone.View.extend({
    initialize: function () {
        this.model.bind('reindex', this.onReindex, this);
    },
    onReindex: function (fromIndex, toIndex) {
        var $movingEl, $replacingEl;

        $movingEl = this.$el;
        $replacingEl = $("ul li").eq(newIndex);

        if (fromIndex < toIndex) {
            $replacingEl.after($movingEl);
        } else {
            $replacingEl.before($movingEl);
        }
    }
});

Upvotes: 0

Vincent Briglia
Vincent Briglia

Reputation: 3068

model add

The path where elements are added is rather simple, as you get the index in the options when a model gets added to a collection. This index is the sorted index, based on that if you have a straightforward view, it should be easy to insert your view at a certain index.

sort attribute change

This one is a bit tricky, and I don't have an answer handy (and I've struggled with this at times as well) because the collection doesn't automatically reshuffle its order after you change an attribute the model got sorted on when you initially added it.

from the backbone docs:

Collections with comparator functions 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.

so if you call sort on a collection it will trigger a reset event which you can hook into to trigger a redraw of the whole list.

It's highly ineffective when dealing with lists that are fairly long and can seriously reduce user experience or even induce hangs

So the few things you get walking away from this is knowing you can:

  • always find the index of a model after sorting by calling collection.indexOf(model)
  • get the index of a model from an add event (3rd argument)

Edit:

After thinking about if for a bit I came up with something like this:

var Model = Backbone.Model.extend({
    initialize: function () {
        this.bind('change:name', this.onChangeName, this);
    },
    onChangeName: function ()
    {
        var index, newIndex;

        index = this.collection.indexOf(this);
        this.collection.sort({silent: true});
        newIndex = this.collection.indexOf(this);
        if (index !== newIndex)
        {
            this.trigger('reindex', newIndex);
            // or
            // this.collection.trigger('reindex', this, newIndex);

        }
    }
});

and then in your view you could listen to

var View = Backbone.View.extend({
    initialize: function () {
        this.model.bind('reindex', this.onReindex, this);
    },
    onReindex: function (newIndex)
    {
        // execute some code that puts the view in the right place ilke
        $("ul li").eq(newIndex).after(this.$el);
    }
});

Upvotes: 7

Related Questions