Webアプリケーションでは、左側のテーブルにユーザーリストがあり、右側にユーザー詳細ペインがあります。管理者がテーブル内のユーザーをクリックすると、その詳細が右側に表示されます。
左側にUserListViewとUserRowViewがあり、右側にUserDetailViewがあります。物事は一種の仕事ですが、私は奇妙な行動をしています。左側の一部のユーザーをクリックし、そのうちの1人で[削除]をクリックすると、表示されたすべてのユーザーのjavascript確認ボックスが連続して表示されます。
以前に表示されたすべてのビューのイベントバインディングが削除されていないように見えますが、これは正常なようです。 UserRowViewで毎回新しいUserDetailViewを実行すべきではありませんか?ビューを維持し、その参照モデルを変更する必要がありますか?新しいビューを作成する前に、現在のビューを追跡して削除する必要がありますか?私はちょっと迷子になり、どんなアイデアも歓迎します。ありがとうございました !
左ビューのコードは次のとおりです(行表示、クリックイベント、右ビューの作成)
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;
}
})
最近このことについてブログに書いて、これらのシナリオを処理するためにアプリで行ういくつかのことを示しました。
単一ページのアプリがどんどん大きくなるにつれて、未使用のライブビューを再利用できるようにメモリ内に保持するので、それらを維持することが難しくなるため、常にビューを破棄して作成します。
これは、メモリリークを回避するためにビューをクリーンアップするために使用する手法の簡易バージョンです。
最初に、すべてのビューが継承する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;
ビューがモデルまたはコレクションのイベントにバインドする必要があるときはいつでも、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では、上記の同じbindToおよびunbindFromAllの手法を使用して、listenToおよびstopListeningがイベントに追加されました。また、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();
これは一般的な状態です。毎回新しいビューを作成する場合、すべての古いビューは引き続きすべてのイベントにバインドされます。できることの1つは、ビューにdetatch
という関数を作成することです。
detatch: function() {
$(this.el).unbind();
this.model.unbind();
次に、新しいビューを作成する前に、古いビューでdetatch
を呼び出してください。
もちろん、既に述べたように、いつでも1つの「詳細」ビューを作成でき、変更することはできません。 (ビューから)モデルの「変更」イベントにバインドして、自分自身を再レンダリングできます。これを初期化子に追加します。
this.model.bind('change', this.render)
これを行うと、モデルに変更が加えられるたびに詳細ペインが再レンダリングされます。単一のプロパティ「change:propName」を監視することで、より細かな粒度を取得できます。
もちろん、これを行うには、アイテムビューが参照する共通モデル、および上位レベルのリストビューと詳細ビューが必要です。
お役に立てれば!
イベントバインドを複数回修正するには、
$("#my_app_container").unbind()
//Instantiate your views here
ルートから新しいビューをインスタンス化する前に上記の行を使用して、ゾンビビューに関する問題を解決しました。
Backboneで始まるほとんどの人は、コードのようにビューを作成すると思います。
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];
}
VMを使用してビューを作成すると、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 に投稿されています
1つの代替方法は、一連の新しいビューを作成してからそれらのビューのバインドを解除するのではなく、バインドすることです。次のようなことでこれを達成できます。
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を別のユーザーに設定した場合(つまり、属性を変更した場合)、新しい属性を使用してビュー内のレンダリング関数をトリガーできます。
1つの問題は、これにより元のモデルへのリンクが壊れることです。これを回避するには、コレクションオブジェクトを使用するか、ビューモデルの属性としてユーザーモデルを設定します。次に、これはビューでmyview.model.get( "model")としてアクセス可能になります。
メモリから子ビューと現在のビューをクリアするには、このメソッドを使用します。
//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);
}
});