Nicolas S.Xu
Nicolas S.Xu

Reputation: 14534

How to avoid double event binding in Backbonejs View?

var RowView = Backbone.View.extend({
    tagName: 'li',
    className: 'course-row',
    events: {
        "click .table-row": "rowClick" // problem here!
    },
    initialize: function() {},
    template: _.template(tpl),
    render: function() {

        var rowStr = this.template({course: this.model});
        $(this.el).append(rowStr);
    },
    rowClick: function (e) {
        alert(e);
    }
});

Above code defines a Backbone Row view. I'd like to create and render several several rows in a table, so I did:

data.forEach(function(rowData){
    var row = new RowView({model: course, el: mountPoint});
    row.render();
});

If I create 2 rows, the event is bind twice( 2 alert msg if I click on the row), 3 rows 3 times. I avoided this problem by defining the event at parent views, but if I really need attach event handler at each row, how do I avoid the double event biding problem?

Upvotes: 0

Views: 177

Answers (1)

mu is too short
mu is too short

Reputation: 434665

You're doing this:

data.forEach(function(rowData){
    var row = new RowView({model: course, el: mountPoint});
    row.render();
});

That means that your view will ignore its tagName and className and just go ahead and bind itself to the el you supply because:

this.el can be resolved from a DOM selector string or an Element; otherwise it will be created from the view's tagName, className, id and attributes properties.

A view's events are bound to its el using delegation and you're binding multiple views (which happen to be the same view "class") so of course you're getting multiple event bindings.

If your rows really are supposed to be <li class="course-row"> elements then you want to do a couple things:

  1. Let the RowViews create and own their own els. Keep the tagName and className attributes in your RowView and stop passing the el to the constructor. You should change $(this.el) to this.$el too, there's no need to create another jQuery object when Backbone has already stashed one away for you.

  2. Return this from RowView's render, this is standard practice and makes the next step a bit nicer.

  3. Whatever creates your RowViews should create them, render them, and then put them inside a container. You appear to be trying to use mountPoint (which is presumable a <ul>) as the container so you want to say:

    data.forEach(function(rowData) {
        var row = new RowView({model: course});
        mountPoint.append(row.render().el);
        // This is why (2) above -----^^^
    });
    

    That assumes that mountPoint is a jQuery instance, if that's not the case then you'd want to say $(mountPoint).append(row.render().el) instead (or do a var $mountPoint = $(mountPoint) above the forEach and use $mountPoint.append inside it).

A personal rule of thumb: if you're passing el to a view constructor then you're almost certainly making a mistake. If you know exactly why you're passing el to the constructor, know the consequences, and know how to deal with them then you're adult enough to run with scissors so go right ahead.

Upvotes: 2

Related Questions