Last Updated: February 25, 2016
·
6.837K
· thure

How to bind events for non-instance Backbone view modules.

If you've ever developed an application using RequireJS (or any modularizing technique) and Backbone, and if you're accustomed to building views in such a way that their el attributes point to a parent element rather than themselves and some parent object is the one assigning the views' models and els, you may have discovered a weakness in Backbone's event binding mechanism. Here's some troublesome code:

define([ 'jquery'
  , 'underscore'
  , 'backbone'
  , 'text!views/filter-tag.html'
], function($, _, Backbone, filterTag){

  return Backbone.View.extend({

    render: function(){
      var self = this;
      this.$tag = $(filterTag);
      this.$el.append(self.$tag);
    },

    events: {
      'click': 'select'
    },

    select: function(e){
      console.log(this);
    }

  });

});

Notice that the module here doesn't return a Backbone view instance, it just returns the extended Backbone view for some other module to make instances from.

With such code, you'll discover that if you click on any views created by this module, every view created by this module will call select. It's really a mess.

This is because of Backbone's mechanism for binding events. I'd explain it, but if you're really curious you'll read the beautifully documented source code.

Anyway, what you'll need to do is bind the event yourself. Don't worry, it's not that big of a deal. Get rid of the events object, add this.bindEvents() to the render function, and then add the bindEvents function that should look like this:

bindEvents: function(){
  var self = this;
  this.$tag.on('click', function(){
    self.select.apply(self, arguments)
  });
}

This way each instance of the view has events bound to the individual instance.

Using apply allows event, etc to be passed to your callback (which is in this case, for the sake of example, a function called select).

The full, functioning example looks like this:

define([ 'jquery'
  , 'underscore'
  , 'backbone'
  , 'text!views/filter-tag.html'
], function($, _, Backbone, filterTag){

  return Backbone.View.extend({

    render: function(){
      var self = this;
      this.$tag = $(filterTag);
      this.$el.append(self.$tag);
      this.bindEvents();
    },

    bindEvents: function(){
      var self = this;
      this.$tag.on('click', function(){
        self.select.apply(self, arguments)
      });
    },

    select: function(e){
      console.log(this);
    }

  });

});

2 Responses
Add your response

Much thanks! I was pounding my head against this because my view tag was a tr as part of a table and needed a click event to fire on the entire tr. Can't really just wrap that with a div, if you know what I mean.

over 1 year ago ·

Glad this helped you!

over 1 year ago ·