Позвоночник.js: повторно заполнить или воссоздать представление?


в моем веб-приложении у меня есть список пользователей в таблице слева и панель сведений о пользователе справа. Когда администратор щелкает пользователя в таблице, его данные должны отображаться справа.

у меня есть UserListView и UserRowView слева, и UserDetailView справа. Вещи вроде как работают, но у меня странное поведение. Если я нажму несколько пользователей слева, а затем нажмите кнопку Удалить на одном из них, я получаю последовательные поля подтверждения javascript для всех пользователей, которые были отображается.

похоже, что привязки событий всех ранее отображенных представлений не были удалены, что кажется нормальным. Я не должен делать новый UserDetailView каждый раз на UserRowView? Должен ли я поддерживать представление и изменять его эталонную модель? Должен ли я отслеживать текущее представление и удалять его перед созданием нового? Я вроде как потерялся, и любая идея будет приветствоваться. Спасибо !

вот код левого вида (отображение строки, нажмите событие, правый вид творение)

window.UserRowView = Backbone.View.extend({
    tagName : "tr",
    events : {
        "click" : "click",
    },
    render : function() {
        $(this.el).html(ich.bbViewUserTr(this.model.toJSON()));
        return this;
    },
    click : function() {
        var view = new UserDetailView({model:this.model})
        view.render()
    }
})

и код для правого вида (кнопка удаления)

window.UserDetailView = Backbone.View.extend({
    el : $("#bbBoxUserDetail"),
    events : {
        "click .delete" : "deleteUser"
    },
    initialize : function() {
        this.model.bind('destroy', function(){this.el.hide()}, this);
    },
    render : function() {
        this.el.html(ich.bbViewUserDetail(this.model.toJSON()));
        this.el.show();
    },
    deleteUser : function() {
        if (confirm("Really delete user " + this.model.get("login") + "?")) 
            this.model.destroy();
        return false;
    }
})
7 82

7 ответов:

Я недавно написал об этом в блоге и показал несколько вещей, которые я делаю в своих приложениях для обработки этих сценариев:

http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/

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

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

сначала я создаю BaseView, от которого наследуются все мои представления. Основная идея заключается в том, что мое представление будет содержать ссылку на все события, на которые оно подписано, так что, когда придет время избавиться от представления, все эти привязки будут автоматически развязаны. Вот пример реализации моего BaseView:

var BaseView = function (options) {

    this.bindings = [];
    Backbone.View.apply(this, [options]);
};

_.extend(BaseView.prototype, Backbone.View.prototype, {

    bindTo: function (model, ev, callback) {

        model.bind(ev, callback, this);
        this.bindings.push({ model: model, ev: ev, callback: callback });
    },

    unbindFromAll: function () {
        _.each(this.bindings, function (binding) {
            binding.model.unbind(binding.ev, binding.callback);
        });
        this.bindings = [];
    },

    dispose: function () {
        this.unbindFromAll(); // Will unbind all events this view has bound to
        this.unbind();        // This will unbind all listeners to events from 
                              // this view. This is probably not necessary 
                              // because this view will be garbage collected.
        this.remove(); // Uses the default Backbone.View.remove() method which
                       // removes this.el from the DOM and removes DOM events.
    }

});

BaseView.extend = Backbone.View.extend;

всякий раз, когда представление должно привязываться к событию в модели или коллекции, я бы использовал метод bindTo. Например:

var SampleView = BaseView.extend({

    initialize: function(){
        this.bindTo(this.model, 'change', this.render);
        this.bindTo(this.collection, 'reset', this.doSomething);
    }
});

всякий раз, когда я удаляю представление, я просто вызываю метод dispose, который будет автоматически очищать все:

var sampleView = new SampleView({model: some_model, collection: some_collection});
sampleView.dispose();

я поделился этой техникой с людьми, которые пишут позвоночник.js on Rails " Электронная книга, и я считаю, что это метод, который они приняли для книги.

обновления: 2014-03-24

по состоянию на Backone 0.9.9, listenTo и stopListening были добавлены к событиям с использованием тех же методов bindTo и unbindFromAll, показанных выше. Кроме Того, Вид.удалить вызовы stopListening автоматически, так что привязка и развязка так же просто, как это сейчас:

var SampleView = BaseView.extend({

    initialize: function(){
        this.listenTo(this.model, 'change', this.render);
    }
});

var sampleView = new SampleView({model: some_model});
sampleView.remove();

Это общее состояние. Если вы создаете новое представление каждый раз, все старые представления по-прежнему будут привязаны ко всем событиям. Одна вещь, которую вы можете сделать, это создать функцию на вашем представлении под названием detatch:

detatch: function() {
   $(this.el).unbind();
   this.model.unbind();

затем, прежде чем создать новое представление, обязательно позвоните detatch на старом виде.

конечно, как вы упомянули, Вы всегда можете создать один "подробный" вид и никогда не изменять его. Вы можете привязаться к событию "change" на модели (из представления) для повторной визуализации себе. Добавьте это в свой инициализатор:

this.model.bind('change', this.render)

это приведет к тому, что панель сведений будет повторно отображаться при каждом изменении модели. Вы можете получить более тонкую гранулярность, наблюдая за одним свойством:"change: propName".

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

надеюсь, что это помогает!

чтобы исправить события привязки несколько раз,

$("#my_app_container").unbind()
//Instantiate your views here

используя приведенную выше строку перед созданием экземпляра новых представлений из route, я решил проблему с зомби-представлениями.

Я думаю, что большинство людей начинают с позвоночника будет создавать представление, как в вашем коде:

var view = new UserDetailView({model:this.model});

этот код создает представление зомби, потому что мы могли бы постоянно создавать новое представление без очистки существующего представления. Однако это не удобно для вызова view.dispose () для всех базовых представлений в вашем приложении (особенно если мы создаем представления в цикле for)

Я думаю, что лучшее время для размещения кода очистки-это перед созданием нового представления. Мое решение-создать помощника для этого очистка:

window.VM = window.VM || {};
VM.views = VM.views || {};
VM.createView = function(name, callback) {
    if (typeof VM.views[name] !== 'undefined') {
        // Cleanup view
        // Remove all of the view's delegated events
        VM.views[name].undelegateEvents();
        // Remove view from the DOM
        VM.views[name].remove();
        // Removes all callbacks on view
        VM.views[name].off();

        if (typeof VM.views[name].close === 'function') {
            VM.views[name].close();
        }
    }
    VM.views[name] = callback();
    return VM.views[name];
}

VM.reuseView = function(name, callback) {
    if (typeof VM.views[name] !== 'undefined') {
        return VM.views[name];
    }

    VM.views[name] = callback();
    return VM.views[name];
}

использование виртуальной машины для создания представления поможет очистить любое существующее представление без необходимости вызова представления.распоряжаться.)( Вы можете сделать небольшую модификацию кода от

var view = new UserDetailView({model:this.model});

до

var view = VM.createView("unique_view_name", function() {
                return new UserDetailView({model:this.model});
           });

так что это до вас, если вы хотите, чтобы повторно использовать вид вместо того, чтобы постоянно создавать его, до тех пор, как вид чист, вам не нужно беспокоиться. Просто измените createView на reuseView:

var view = VM.reuseView("unique_view_name", function() {
                return new UserDetailView({model:this.model});
           });

подробный код и атрибуция проводится по https://github.com/thomasdao/Backbone-View-Manager

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

window.User = Backbone.Model.extend({
});

window.MyViewModel = Backbone.Model.extend({
});

window.myView = Backbone.View.extend({
    initialize: function(){
        this.model.on('change', this.alert, this); 
    },
    alert: function(){
        alert("changed"); 
    }
}); 

вы бы установили модель myView в myViewModel, которая будет установлена в пользовательскую модель. Таким образом, если вы установите myViewModel для другого пользователя (т. е. измените его атрибуты), он может вызвать функцию рендеринга в представлении с новыми атрибутами.

одна проблема заключается в том, что это нарушает связь с исходная модель. Вы можете обойти это, либо используя объект коллекции, либо установив пользовательскую модель в качестве атрибута viewmodel. Тогда это будет доступно в качестве объекта myview.модель.получить("модель").

используйте этот метод для очистки дочерних представлений и текущих представлений из памяти.

//FIRST EXTEND THE BACKBONE VIEW....
//Extending the backbone view...
Backbone.View.prototype.destroy_view = function()
{ 
   //for doing something before closing.....
   if (this.beforeClose) {
       this.beforeClose();
   }
   //For destroying the related child views...
   if (this.destroyChild)
   {
       this.destroyChild();
   }
   this.undelegateEvents();
   $(this.el).removeData().unbind(); 
  //Remove view from DOM
  this.remove();  
  Backbone.View.prototype.remove.call(this);
 }



//Function for destroying the child views...
Backbone.View.prototype.destroyChild  = function(){
   console.info("Closing the child views...");
   //Remember to push the child views of a parent view using this.childViews
   if(this.childViews){
      var len = this.childViews.length;
      for(var i=0; i<len; i++){
         this.childViews[i].destroy_view();
      }
   }//End of if statement
} //End of destroyChild function


//Now extending the Router ..
var Test_Routers = Backbone.Router.extend({

   //Always call this function before calling a route call function...
   closePreviousViews: function() {
       console.log("Closing the pervious in memory views...");
       if (this.currentView)
           this.currentView.destroy_view();
   },

   routes:{
       "test"    :  "testRoute"
   },

   testRoute: function(){
       //Always call this method before calling the route..
       this.closePreviousViews();
       .....
   }


   //Now calling the views...
   $(document).ready(function(e) {
      var Router = new Test_Routers();
      Backbone.history.start({root: "/"}); 
   });


  //Now showing how to push child views in parent views and setting of current views...
  var Test_View = Backbone.View.extend({
       initialize:function(){
          //Now setting the current view..
          Router.currentView = this;
         //If your views contains child views then first initialize...
         this.childViews = [];
         //Now push any child views you create in this parent view. 
         //It will automatically get deleted
         //this.childViews.push(childView);
       }
  });