Ele*_*One 7 javascript jquery backbone.js backbone-views
注意:据我所知,其他库(例如,Marionette)可以大大简化基于视图的问题.但是,我们假设这不是一个选项.
假设我们有一个给定"记录"(即模型)的父视图.该父视图有两个子视图,一个用于显示记录的属性,另一个用于编辑它们(假设在这种情况下编辑就地不合适).到目前为止,每当我需要删除/显示子视图时,我确实调用remove了传出视图和new传入视图,因此我每次都在销毁/创建它们.这很简单,易于编码/管理.
然而,似乎有必要弄清楚是否有任何可行的替代方法(看似默认的)删除/创建方法 - 特别是因为之前已被问过几次,但从未完全回答(例如,使用Backbone交换视图?)
所以我一直试图弄清楚如何让两个子视图在父视图中共享一个元素,从而阻止我每次都必须remove和new它们一起.当一个人需要活动时,它会被渲染而另一个被"沉默"(即,不响应事件).因此,它们会不断更换,直到删除父视图,然后将它们全部删除.
我想出的可以在这里找到:http://jsfiddle.net/nLcan/
注意:交换是在RecordView.editRecord和中执行的RecordView.onRecordCancel.
虽然这似乎工作得很好,但我有一些担忧:
(1)即使在"非活动"视图中事件绑定被静音,两个视图是否可能存在设置相同元素的问题?只要呈现"活动"视图,它似乎不应该是一个问题.
(2)当两个子视图remove调用(即Backbone.View.remove)时,它调用this.$el.remove.现在,当删除第一个子视图时,这实际上将删除它们共享的DOM元素.因此,当remove在第二个子视图上调用时,没有要删除的DOM元素,我想知道是否这可能使该子视图难以清理自己 - 特别是如果它本身已经创建了许多DOM元素,那么在第一个子视图被渲染时写入....或者如果涉及子视图...... 似乎可能存在关于内存泄漏的潜在问题.
抱歉,我知道这有点令人费解.我在知识库的边界是正确的,所以我不完全理解这里涉及的所有潜在问题(因此问题).但我确实希望有人处理类似的问题,并能就这一切提供明智的意见.
无论如何,这里是完整的(简化示例)代码:
// parent view for displaying/editing a record. creates its own DOM element.
var RecordView = Backbone.View.extend({
tagName : "div",
className : "record",
events : {
"click button[name=edit]" : "editRecord",
"click button[name=remove]" : "removeRecord",
},
initialize : function(settings){
// create the two subviews. one for displaying the field(s) and
// one for editing them. they both listen for our cleanup event
// which causes them to remove themselves. the display view listens
// for an event telling it to update its data.
this.displayView = new RecordDisplayView(settings);
this.displayView.listenTo(this,"cleanup",this.displayView.remove);
this.displayView.listenTo(this,"onSetData",this.displayView.setData);
this.editView = new RecordEditView(settings);
this.editView.listenTo(this,"cleanup",this.editView.remove);
// the editView will tell us when it's finished.
this.listenTo(this.editView,"onRecordSave",this.onRecordSave);
this.listenTo(this.editView,"onRecordCancel",this.onRecordCancel);
this.setData(settings.data,false);
this.isEditing = false;
this.activeView = this.displayView;
// we have two elements within our recordView, one for displaying the
// the header of the record (i.e., info that doesn't change) and
// one for displaying the subView. the subView element will be
// bound to BOTH of our subviews.
this.html = "<div class='header'></div><div class='sub'></div>";
},
render : function(){
// for an explanation of why .empty() is called first, see: https://stackoverflow.com/questions/21031852/backbone-view-delegateevents-not-re-binding-events-to-subview
this.$el.empty().html(this.html);
this.$(".header").empty().html("<p>Record ID: "+this.data.id+"</p><p><button name='edit'>Edit</button><button name='remove'>Remove</button></p>");
this.delegateEvents(); // allows for re-rendering
this.renderSubView();
return this;
},
// the subviews SHARE the same element.
renderSubView : function() {
this.activeView.setElement(this.$(".sub")).render();
},
remove : function() {
this.stopListening(this.displayView);
this.stopListening(this.editView);
this.trigger("cleanup");
this.displayView = null;
this.editView = null;
return Backbone.View.prototype.remove.call(this);
},
// notify will only be false upon construction call
setData : function(data,notify) {
this.data = data;
if ( notify ) {
this.trigger("onSetData",data);
}
},
/* Triggered Events */
editRecord : function(event) {
if ( !this.isEditing ) {
this.isEditing = true;
this.activeView.silence(); // silence the old view (i.e., display)
this.activeView = this.editView;
this.renderSubView();
}
event.preventDefault();
},
removeRecord : function(event) {
this.remove(); // triggers `remove` on both subviews
event.preventDefault();
},
/* Triggered Events from editView */
onRecordSave : function(data) {
this.setData(data,true);
this.onRecordCancel();
},
onRecordCancel : function() {
this.isEditing = false;
this.activeView.silence(); // silence the old view (i.e., edit)
this.activeView = this.displayView;
this.renderSubView();
}
});
// child view of RecordView. displays the attribute. takes over an existing DOM element.
var RecordDisplayView = Backbone.View.extend({
events : {
// if steps are not taken to silence this view, this event will trigger when
// the user clicks 'cancel' on the editView!
"click button[name=cancel]" : "onCancel"
},
initialize : function(settings){
this.setData(settings.data);
},
setData : function(data) {
this.data = data;
},
render : function(){
this.$el.empty().html("<p><strong>Field:</strong> "+this.data.field+"</p>");
return this;
},
remove : function() {
this.trigger("cleanup");
this.data = null;
return Backbone.View.prototype.remove.call(this);
},
// the view is still attached to a particular element in the DOM, however we do not
// want it to respond to any events (i.e., it's sharing an element but that element has
// been rendered to by another view, so we want to make this view invisible for the time
// being).
silence : function() {
this.undelegateEvents();
},
/* Triggered Events */
onCancel : function() {
alert("I'M SPYING ON YOU! USER PRESSED CANCEL BUTTON!");
}
});
// subView of RecordView. displays a form for editing the record's attributes. takes over an existing DOM element.
var RecordEditView = Backbone.View.extend({
events : {
"click button[name=save]" : "saveRecord",
"click button[name=cancel]" : "cancelRecord"
},
initialize : function(settings){
this.data = settings.data;
},
render : function(){
this.html = "<form><textarea name='field' rows='10'>"+this.data.field+"</textarea><p><button name='save'>Save</button><button name='cancel'>Cancel</button></p></form>";
this.$el.empty().html(this.html);
return this;
},
remove : function() {
this.trigger("cleanup");
this.data = null;
return Backbone.View.prototype.remove.call(this);
},
silence : function() {
this.undelegateEvents();
},
/* Triggered Events */
saveRecord : function(event){
this.data.field = this.$("form textarea[name=field]").val();
this.trigger("onRecordSave",this.data);
event.preventDefault();
},
cancelRecord : function(event){
event.preventDefault();
this.trigger("onRecordCancel");
}
});
// presumably this RecordView instance would be in a list of some sort, along with a bunch of other RecordViews.
var v1 = new RecordView({
data : {id:10,field:"Hi there. I'm some text!"}
});
$("#content").empty().html(v1.render().$el);
//$("#content").empty().html(v1.render().$el); (re-rendering would work fine)
Run Code Online (Sandbox Code Playgroud)
好的,这是我的解决方案:
Rather than treating backbone like a "blackbox", I just looked through its code for Backbone.View.remove and Backbone.View.setElement. They are both painfully simple and thus we can easily remove Backbone.View from the picture altogether, and just deal with jQuery. Once you do that, replicating this behaviour in jQuery alone and going through it seems to demonstrate that there are absolutely no issues whatsoever in this approach.
// equivalent of creating a parent view with a subview through backbone, assuming both
// creating new DOM elements
var parentView = $("<div></div>").attr("id","parent").html("<div class='sub'></div>");
// equivalent to assigning two subviews to the same element in a parent view. no
// problems here.
var subView1 = parentView.find(".sub");
var subView2 = parentView.find(".sub");
// they both reference the same element (outside DOM still), so both would have data
// of 'idx' = 2. there are no problems with this.
subView1.data("idx",1);
subView2.data("idx",2);
// add parentView to the DOM, which adds the element that subView1 and 2 reference.
$("#content").append(parentView);
// equivalent to rendering one subview in backbone and using setElement to swap.
// again, no problems with any of this. you can see that the setElement calls
// happening again and again would be redundant.
subView1 = parentView.find(".sub");
var activeSubView = subView1;
activeSubView.html("subView1: " + subView1.data("idx")); // subView1: 2
subView2 = parentView.find(".sub");
activeSubView = subView2;
activeSubView.html("subView2: " + subView2.data("idx")); // subView2: 2
// when you `remove`, all it does is remove the element from the DOM and empty out
// its jQuery data ("idx") and unbind all the events. nothing is "destroyed". you
// still have a reference to it, so it won't be gc'd. the only difference between
// `remove` and `detach` is that `detach` keeps the jQuery data and events. there
// is no need to `remove` the subViews explicitly, as they are children of the
// parent and so when the parent is removed from the DOM, they come with it.
//subView1.remove();
//subView2.remove();
parentView.remove();
// all of the HTML inside the parentView and subView elements still exists. their events are
// gone and their jQuery data is gone.
console.log(subView1.html()); // "subView2: 2"
console.log(parentView.html()); // "<div class="sub">subView2: 2</div>"
console.log(subView1.data("idx")); // undefined
console.log(subView2.data("idx")); // undefined
// this will ensure that they are cleaned up.
parentView = subView1 = subView2 = null;
Run Code Online (Sandbox Code Playgroud)