Backbone: event lost in re-render

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...).


There's a simpler solution here that doesn't blow away the event registrations in the first place: jQuery.detach().

http://jsfiddle.net/ypG8U/1/

this.subView.render().$el.detach().appendTo( this.$el );   

This variation is probably preferable for performance reasons though:

http://jsfiddle.net/ypG8U/2/

this.subView.$el.detach();

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

// or

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

Obviously this is a simplification that matches the example, where the sub view is the only content of the parent. If that was really the case, you could just re-render the sub view. If there were other content you could do something like:

var children = array[];

this.$el.children().detach();

children.push( subView.render().el );

// ...

this.$el.append( children );

or

_( this.subViews ).each( function ( subView ) {

  subView.$el.detach();

} );

// ...

Also, in your original code, and repeated in @mu's answer, a DOM object is passed to jQuery.html(), but that method is only documented as accepting strings of HTML:

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

Documented signature for jQuery.html():

.html( htmlString )

http://api.jquery.com/html/#html2