В моем веб-приложении у меня есть список пользователей в таблице слева и панель сведений о пользователях справа. Когда администратор щелкает пользователя в таблице, его данные должны отображаться справа.
У меня есть 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;
}
})
источник
delete view
в роутере?Я всегда уничтожаю и создаю представления, потому что по мере того, как мое одностраничное приложение становится все больше и больше, становится трудно поддерживать неиспользуемые живые представления в памяти только для того, чтобы я мог их повторно использовать.
Вот упрощенная версия техники, которую я использую для очистки моих представлений, чтобы избежать утечек памяти.
Сначала я создаю BaseView, от которого наследуются все мои представления. Основная идея состоит в том, что мой View будет хранить ссылку на все события, на которые он подписан, так что, когда придет время избавиться от View, все эти привязки будут автоматически отвязаны. Вот пример реализации моего 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;
Всякий раз, когда View необходимо привязать к событию в модели или коллекции, я бы использовал метод 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();
Я поделился этой техникой с людьми, которые пишут электронную книгу «Backbone.js on Rails», и я считаю, что это метод, который они использовали для этой книги.
Обновление: 2014-03-24
Начиная с Backone 0.9.9, listenTo и stopListening были добавлены к событиям с использованием тех же методов bindTo и unbindFromAll, показанных выше. Кроме того, View.remove автоматически вызывает 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
старое представление.Конечно, как вы упомянули, вы всегда можете создать одно «подробное» представление и никогда не изменять его. Вы можете привязаться к событию «изменения» в модели (из представления), чтобы заново отрендерить себя. Добавьте это в свой инициализатор:
this.model.bind('change', this.render)
Это приведет к повторному рендерингу панели деталей КАЖДЫЙ раз, когда в модель вносятся изменения. Вы можете получить более тонкую детализацию, наблюдая за единственным свойством: "change: propName".
Конечно, для этого требуется общая модель, на которую ссылается представление элемента, а также представление списка более высокого уровня и представление сведений.
Надеюсь это поможет!
источник
this.model.unbind()
это неправильно для меня, потому что он отключает все события из этой модели, включая события, относящиеся к другим представлениям того же пользователя. Более того, чтобы вызватьdetach
функцию, мне нужно сохранить статическую ссылку на представление, и мне это совсем не нравится. Я подозреваю, что есть еще кое-что, чего я еще не понял ...Чтобы исправить многократную привязку событий,
$("#my_app_container").unbind() //Instantiate your views here
Использование приведенной выше строки перед созданием экземпляра новых представлений из маршрута решило проблему, с которой я столкнулся с представлениями зомби.
источник
Я думаю, что большинство людей, начинающих с Backbone, создадут представление, как в вашем коде:
var view = new UserDetailView({model:this.model});
Этот код создает зомби-представление, потому что мы можем постоянно создавать новое представление без очистки существующего представления. Однако вызывать view.dispose () для всех Backbone Views в вашем приложении неудобно (особенно, если мы создаем представления в цикле 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]; }
Использование виртуальной машины для создания вашего представления поможет очистить любое существующее представление без необходимости вызывать view.dispose (). Вы можете внести небольшие изменения в свой код из
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 на другого пользователя (т. Е. Измените его атрибуты), тогда он может запустить функцию рендеринга в представлении с новыми атрибутами.
Одна из проблем заключается в том, что это разрывает связь с исходной моделью. Вы можете обойти это, используя объект коллекции или установив модель пользователя как атрибут модели представления. Тогда это будет доступно в представлении как myview.model.get («модель»).
источник
Используйте этот метод для удаления дочерних и текущих представлений из памяти.
//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); } });
источник