Магистраль: событие потеряно при повторной визуализации

У меня есть супер-просмотр, который отвечает за рендеринг суб-просмотров. При повторном рендеринге супер-просмотра все события во вложенных представлениях теряются.

Это пример:

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();

Когда я нажимаю на кнопку " Нажмите меня", событие срабатывает. Если я выполню composer.render() опять все выглядит примерно так же, но событие click больше не вызывается.

Проверьте работающий jsFiddle.

3 ответа

Решение

Когда вы делаете это:

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

Вы фактически говорите это:

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

а также empty убивает события на все внутри this.$el:

Чтобы избежать утечек памяти, jQuery удаляет другие конструкции, такие как данные и обработчики событий, из дочерних элементов перед удалением самих элементов.

Таким образом, вы теряете delegate вызов, который связывает события на this.subView и SubView#render не буду связывать их.

Вам нужно проскользнуть this.subView.delegateEvents() позвонить в this.$el.html() но вам нужно, чтобы это произошло после empty(), Вы можете сделать это так:

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

Демо: http://jsfiddle.net/ambiguous/57maA/1/

Или вот так:

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

Демо: http://jsfiddle.net/ambiguous/4qrRa/

Или ты мог remove и воссоздать this.subView при рендеринге и обходе проблемы таким образом (но это может вызвать другие проблемы...).

Здесь есть более простое решение, которое не сводит на нет регистрации событий: jQuery.detach(),

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

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

Это изменение, вероятно, предпочтительнее по причинам производительности, хотя:

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

this.subView.$el.detach();

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

// or

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

Очевидно, что это упрощение, которое соответствует примеру, где подчиненное представление является единственным содержимым родителя. Если это действительно так, вы можете просто перерисовать вложенный вид. Если бы был другой контент, вы могли бы сделать что-то вроде:

var children = array[];

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

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

// ...

this.$el.append( children );

или же

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

  subView.$el.detach();

} );

// ...

Кроме того, в исходном коде, повторенном в ответе @ mu, объект DOM передается jQuery.html(), но этот метод задокументирован только как принимающий строки HTML:

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

Документированная подпись для jQuery.html():

.html( htmlString )

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

Когда используешь $(el).empty() он удаляет все дочерние элементы в выбранном элементе И удаляет ВСЕ события (и данные), которые связаны с любыми (дочерними) элементами внутри выбранного элемента (el).

Чтобы сохранить события привязанными к дочерним элементам, но при этом удалить дочерние элементы, используйте:

$(el).children().detach(); вместо $(.el).empty();

Это позволит вашему представлению успешно отрендериться с событиями, все еще связанными и работающими.

Другие вопросы по тегам