Search code examples
javascriptbackbone.jsbackbone-events

Backbone: event lost in re-render


I have super-View who is in charge of rendering sub-Views. When I re-render the super-View all the events in the sub-Views are lost.

This is an example:

var SubView = Backbone.View.extend({
    events: {
        "click": "click"
    },

    click: function(){
        console.log( "click!" );
    },

    render: function(){
        this.$el.html( "click me" );
        return this;
    }
});

var Composer = Backbone.View.extend({
    initialize: function(){
        this.subView = new SubView();
    },

    render: function(){
        this.$el.html( this.subView.render().el );             
    }
});


var composer = new Composer({el: $('#composer')});
composer.render();

When I click in the click me div the event is triggered. If I execute composer.render() again everything looks pretty the same but the click event is not triggered any more.

Check the working jsFiddle.


Solution

  • When you do this:

    this.$el.html( this.subView.render().el );
    

    You're effectively saying this:

    this.$el.empty();
    this.$el.append( this.subView.render().el );
    

    and empty kills the events on everything inside this.$el:

    To avoid memory leaks, jQuery removes other constructs such as data and event handlers from the child elements before removing the elements themselves.

    So you lose the delegate call that binds events on this.subView and the SubView#render won't rebind them.

    You need to slip a this.subView.delegateEvents() call into this.$el.html() but you need it to happen after the empty(). You could do it like this:

    render: function(){
        console.log( "Composer.render" );
        this.$el.empty();
        this.subView.delegateEvents();
        this.$el.append( this.subView.render().el );             
        return this;
    }
    

    Demo: http://jsfiddle.net/ambiguous/57maA/1/

    Or like this:

    render: function(){
        console.log( "Composer.render" );
        this.$el.html( this.subView.render().el );             
        this.subView.delegateEvents();
        return this;
    }
    

    Demo: http://jsfiddle.net/ambiguous/4qrRa/

    Or you could remove and re-create the this.subView when rendering and sidestep the problem that way (but this might cause other problems...).