Can Backbone render a collection in reverse order?
Asked Answered
F

8

7

I'm using a Signalr hub to subscribe to events on the server. What an event is dispatched to a hub, its successfully adding the item to a Marionette CollectionView. This, in turn, is rendered to a table.

Because the table of events is essentially a blotter, I'd like the events in reverse order and preferably only keep n-number of events.

Can Backbone 'automatically' re-render a collection in reverse order?

Fullmer answered 19/3, 2012 at 12:19 Comment(0)
S
11

To go through collection in the reverse order I usually use a construction like this:

_.each(collection.last(collection.length).reverse(), function(model){ });
Spearwort answered 14/12, 2012 at 10:35 Comment(0)
U
8

There is a thread on this topic at https://github.com/marionettejs/backbone.marionette/issues/78

Although Backbone keeps the collection sorted once you define a comparator, as @breischl pointed out, Marionette does not automatically re-render the CollectionView when order changes. In fact, Marionette listens to the add event on the collection and appends a new ItemView.

If you want your CollectionView to always display items in reverse chronological order, and you want new items added to be prepended instead of appended, then override the appendHtml method in your CollectionView as follows:

var MyCollectionView = Backbone.Marionette.CollectionView.extend({
  appendHtml: function(collectionView, itemView){
    collectionView.$el.prepend(itemView.el);
  }
});

If you want to be able to insert at a particular location as @dira mentioned in the comment, there is a solution posted at the link above on github by sberryman that I reproduce here for convenience (disclaimer: I haven't tested the code below personally):

Change appendHtml to:

appendHtml: function(collectionView, itemView) {
  var itemIndex;
  itemIndex = collectionView.collection.indexOf(itemView.model);
  return collectionView.$el.insertAt(itemIndex, itemView.$el);
}

And add the following to extend jQuery to provide insertAt function:

(function($) {
  return jQuery.fn.insertAt = function(index, element) {
    var lastIndex;
    if (index <= 0) return this.prepend(element);
    lastIndex = this.children().size();
    if (index >= lastIndex) return this.append(element);
    return $(this.children()[index - 1]).after(element);
  };
})(jQuery);
Uranography answered 9/5, 2012 at 4:46 Comment(1)
note that in Marionette 2.x, appendHtml was changed to attachHtml. Hope that helps others! This was exactly what I needed to display the collection in reverse order without messing the order for other logic in my web appIntermolecular
F
1

Usually you'll have the rendering take place in your Backbone.View 'subclass'. So you have something like:

render: function() {
  this.collection.each( function(model) {
    // some rendering of each element
  }, this );
}

this.collection is presumably a Backbone.Collection subclass, and so you can just use underscore.js methods on it to get it in whatever order you like:

this.collection.reverse().each( ... )
this.collection.sort( function(m) { ... } ).each( ... )

Etc.

Of course, you are getting a single element from your backend, and you want to insert it in the right place without re-rendering the whole thing! So in that case just go old school and insert your sort key as a rel attribute or data attribute on the elements, and use that to insertAfter or similar with jQuery in your renderNewItem (or similar) method.

Fulgent answered 19/3, 2012 at 13:29 Comment(2)
No reverse()-method is available in underscore.js methods unfortunately.Majolica
Haha good point! Not really a cornerstone of my answer, though. You could reverse it using a different approach: _.each( _.toArray(this.collection).reverse(), function(m) { ... } );. Or if you were using coffeescript you could just write a for loop with a -1 step, etc.Fulgent
I
1

Backbone automatically keeps Collections in sorted order. If you want to use a non-default sort, define a comparator() function on your Collection and it will use that instead. The comparator can take either one or two arguments, see the Backbone documentation for details.

You can then render your collection in an .each() loop, and it will come out in the correct order. Adding new items to the view in sorted order is up to you, though.

Involute answered 20/3, 2012 at 0:0 Comment(2)
Dira, good point, that does make it easier. But ultimately you still have to get them into the DOM in the correct spot.Involute
Yeah, so you use jquery's nth-child and after() or before()Shupe
D
1

From what you describe, you don't need to re-render the collection in reverse order. Just add an event for add on your collection in that view and have it call a function that renders the item just added and prepends it to the table.

this.collection.on('add', this.addItem);
Diplomat answered 20/3, 2012 at 5:9 Comment(0)
P
0

You can reverse your models in a collection like so...

this.collection.models = this.collection.models.reverse()
Paronym answered 10/12, 2014 at 6:29 Comment(0)
A
0

If you use lodash instead of underscore you can also do this:

_(view.collection.models).reverse();
Applicant answered 18/3, 2015 at 7:23 Comment(0)
F
0

As the BackBone doesn't support reverse iteration of the collection (and it's just waste of resources to reverse or worse sort the collection) the easiest and fastest approach is to use the for loop with decreasing index over models in the collection.

for (var i = collection.length - 1; i >= 0; i--) {
    var model = collection.models[i];

    // your logic
}

It's not that elegant as sorting or reversing the collection using Underscore but the performace is much better. Try to compare different loops here just to know what costs you to write foreach instead of classic for.

Freshen answered 23/9, 2015 at 12:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.