Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inserting an item into a backbone.js collection

Is there a straightforward way to insert a new model item into the middle of a backbone.js Collection and then update the collection's View to include the new item in the correct position?

I'm working on a control to add/delete items from a list. Each list item has its own Model and View, and I have a View for the entire collection as well.

Each item view has a Duplicate button that clones the item's model and inserts it into the collection at the index position below the item that was clicked.

Inserting the item into the collection was straightforward, but I'm having trouble figuring out how to update the collection view. I've been trying something like this:

ListView = Backbone.View.extend({
    el: '#list-rows',
    initialize: function () {
      _.bindAll(this);
      this.collection = new Items();
      this.collection.bind('add', this.addItem);
      this.render();
    },
    render: function () {
      this.collection.each(this.addItems);
      return this;
    },
    addItem: function (item) {
      var itemView = new ItemView({ model: item }),
          rendered = itemView.render().el,
          index = this.collection.indexOf(item),
          rows = $('.item-row');

      if (rows.length > 1) {
        $(rows[index - 1]).after(rendered);
      } else {
        this.$el.append(rendered);
      }
    }
}

This implementation is sort of working, but I'm getting strange bugs when I add a new item. I'm sure I can sort those out, but ...

There's a voice in my head keeps telling me that there's a better way to do this. Having to manually figure out where to insert a new ItemView seems really hacky--shouldn't the collection view know how to rerender the collection already?

Any suggestions?

like image 679
Josh Earl Avatar asked Mar 06 '12 14:03

Josh Earl


2 Answers

I don't think re-rendering the whole collection when adding a new element is the best solution. It's slower than inserting the new item at the right place, especially if the list is long.

Also, consider the following scenario. You load a few items in your collections, and then you add n more items (say the user clicks a "load more" button). To do this you would call the fetch() method passing add: true as one of the options. As the data is received back from the server, the 'add' event is fired n times and you'd end up re-rendering your list n times.

I'm actually using a variant of the code in your question, here's my callback to the 'add' event:

var view, prev, prev_index;
view = new ItemView({ model: new_item }).render().el;
prev_index = self.model.indexOf(new_item) - 1;
prev = self.$el.find('li:eq(' + prev_index + ')');
if (prev.length > 0) {
    prev.after(view);
} else {
    self.$el.prepend(view);
}

So, essentially I'm just using the :eq() jQuery selector instead of getting all the elements as you do, should use less memory.

like image 195
nicolagi Avatar answered Oct 15 '22 17:10

nicolagi


The usual way I'm doing is let the ListView render each ItemView in its render function. Then I just bind the add event to the render function, like this:

ListView = Backbone.View.extend({
  el: '#list-rows'
  , initialize: function () {
    _.bindAll(this);
    this.collection = new Items();
    this.collection.bind('add', this.render);
    this.render();
  }
  , render: function () {
    this.$el.empty();
    var self = this;
    this.collection.each(function(item) {
      self.$el.append(new ItemView({ model: item }).render().el);
    });
    return this;
  }
}

Everytime you call this.collection.add(someModel, {at: index}), the view will be re-rendered accordingly.

like image 22
Sơn Trần-Nguyễn Avatar answered Oct 15 '22 18:10

Sơn Trần-Nguyễn