Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delegating events to sub-views in Backbone.js

We all know doing something like this is bad:

<ul>
  <li>Item</li>
  <li>Item</li>
  ... 500 more list items
</ul>

and then...

$("ul li").bind("click", function() { ... });

I've been looking through a lot of Backbone examples / guides and the following seems to be a standard approach to rendering a list with items, based from a collection of models.

var ListView = Backbone.View.extend() {

  tagName: 'ul',

  render: function() {
    this.collection.each(function(item) {
      var view = new ListItemView({model: item});
      $(this.el).append(view.render().el);
    });
    return this;
  }
});

A list item view:

var ListItemView = Backbone.View.extend() {

  tagName: 'li',

  events: {
   'click' : 'log'
  }

  log : function() {
    console.log(this.model.get("title"));
  }

  render: function() {
    $(this.el).html(this.template(this.model.toJSON()));
    return this;
  }
});

If I'm not mistaken, instantiating the listView with a collection with 500 models, gives me 500 click events, one for each row. This is bad right?

I know Backbone has built in event delegation for namespaced events:

events : {
  'click li' : 'log'
}

I suppose I could put this in my ListView, and it would only create one click event for the entire list, but then I wouldn't be able access to model data corresponding to the clicked list item.

What patterns do backbone developers use to solve this typical problem?

like image 476
Daniel Avatar asked Dec 22 '11 12:12

Daniel


3 Answers

Derick Bailey wrote a detailed blog post about this dilemma, you can check it out here: http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/

like image 119
Paul Avatar answered Nov 05 '22 23:11

Paul


You can associate the instance with an element like so:

events : {
  'click li' : 'log'
},

log: function( e ) {
var elm = e.currentTarget //Same as `this` in normally bound jQuery event


jQuery.data( elm, "viewInstance" ).log( e );
},

Then:

var ListItemView = Backbone.View.extend() {

  tagName: 'li',

  log : function() {
    console.log(this.model.get("title");
  }

  render: function() {
        //Associate the element with the instance
    $(this.el).html(this.template(this.model.toJSON())).data( "viewInstance", this );
    return this;
  }
});
like image 2
Esailija Avatar answered Nov 05 '22 23:11

Esailija


Keep track of the subviews from the parent view. Then when adding the subview add it to the hash as well as add the cid to the el of the subview. This way have a pointer to the subview and could perform operations on its model etc...

I have not tested this exact code below so THIS may be wrong in a place or two but I have tested this general principle. I have also omitted the listitemview code.

var ListView = Backbone.View.extend() {
  subViews: {},
  tagName: 'ul',
  events: {
    'click li' : 'clickItem'
  },
  clickItem: function(event){
     var id = event.currentTarget.cid;
     var subView = this.subViews[id];


  },
  render: function() {

    this.collection.each(function(item) {
      var view = new ListItemView({model: item});
      this.subViews[view.cid] = view;
      subEl = view.render().el;
      subEl.cid = view.cid;
      $(this.el).append(subEl);
    });
    return this;
  }
});
like image 2
mjamesb Avatar answered Nov 05 '22 23:11

mjamesb