Backbone.js : 뷰를 다시 채우거나 다시 만드시겠습니까?
내 웹 응용 프로그램에서 왼쪽 테이블에 사용자 목록이 있고 오른쪽에 사용자 세부 정보 창이 있습니다. 관리자가 테이블에서 사용자를 클릭하면 세부 정보가 오른쪽에 표시되어야합니다.
왼쪽에는 UserListView와 UserRowView가 있고 오른쪽에는 UserDetailView가 있습니다. 일이 잘 되긴하지만 이상한 행동을 해요. 왼쪽에서 일부 사용자를 클릭 한 다음 그 중 하나에서 삭제를 클릭하면 표시된 모든 사용자에 대해 연속적인 자바 스크립트 확인 상자가 나타납니다.
이전에 표시된 모든보기의 이벤트 바인딩이 제거되지 않은 것 같습니다. 이는 정상적인 것 같습니다. 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를 만듭니다. 기본 아이디어는 내 뷰가 구독하는 모든 이벤트에 대한 참조를 유지하므로 뷰를 삭제할 때 모든 바인딩이 자동으로 바인딩 해제됩니다. 다음은 내 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"ebook을 작성하는 사람들과 공유했으며 이것이 그들이 책에 채택한 기술이라고 믿습니다.
업데이트 : 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();
이것은 일반적인 조건입니다. 매번 새보기를 만들면 모든 이전보기가 모든 이벤트에 바인딩됩니다. 한 가지 할 수있는 일은 뷰에 다음과 같은 함수를 만드는 것입니다 detatch
.
detatch: function() {
$(this.el).unbind();
this.model.unbind();
그런 다음 새보기를 만들기 전에 detatch
이전보기 를 호출해야합니다 .
Of course, as you mentioned, you can always create one "detail" view and never change it. You can bind to the "change" event on the model (from the view) to re-render yourself. Add this to your initializer:
this.model.bind('change', this.render)
Doing that will cause the details pane to re-render EVERY time a change is made to the model. You can get finer granularity by watching for a single property: "change:propName".
Of course, doing this requires a common model that the item View has reference to as well as the higher level list view and the details view.
Hope this helps!
To fix events binding multiple times,
$("#my_app_container").unbind()
//Instantiate your views here
Using the above line before instantiating the new Views from route, solved the issue I had with zombie views.
I think most people start with Backbone will create the view as in your code:
var view = new UserDetailView({model:this.model});
This code creates zombie view, because we might constantly create new view without cleanup existing view. However it's not convenient to call view.dispose() for all Backbone Views in your app (especially if we create views in for loop)
I think the best timing to put cleanup code is before creating new view. My solution is to create a helper to do this cleanup:
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];
}
Using VM to create your view will help cleanup any existing view without having to call view.dispose(). You can do a small modification to your code from
var view = new UserDetailView({model:this.model});
to
var view = VM.createView("unique_view_name", function() {
return new UserDetailView({model:this.model});
});
So it is up to you if you want to reuse view instead of constantly creating it, as long as the view is clean, you don't need to worry. Just change createView to reuseView:
var view = VM.reuseView("unique_view_name", function() {
return new UserDetailView({model:this.model});
});
Detailed code and attribution is posted at https://github.com/thomasdao/Backbone-View-Manager
One alternative is to bind, as opposed to creating a series of new views and then unbinding those views. You'd accomplish this doing something like:
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");
}
});
You'd set the model of myView to myViewModel, which would be set to a User model. This way, if you set myViewModel to another user (i.e., changing its attributes) then it could trigger a render function in the view with the new attributes.
One problem is that this breaks the link to the original model. You could get around this by either using a collection object, or by setting the user model as an attribute of the viewmodel. Then, this would be accessible in the view as myview.model.get("model").
Use this method for clearing the child views and current views from memory.
//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);
}
});
참고URL : https://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view
'program tip' 카테고리의 다른 글
파이썬 시간 비교 (0) | 2020.09.24 |
---|---|
한 클래스를 제외한 모든 요소에 대해 CSS 규칙을 만드는 방법은 무엇입니까? (0) | 2020.09.24 |
Rails database.yml을 관리하는 방법 (0) | 2020.09.24 |
왼쪽 조인이있는 상위 1 (0) | 2020.09.24 |
오류 : 매개 변수 1에 제공된 기본 인수 (0) | 2020.09.24 |