草庐IT

javascript - Backbone .View : Swapping out two child views that share an element in the parent view

coder 2024-07-26 原文

注意:我知道其他库(例如 Marionette)可以大大简化基于 View 的问题。但是,让我们假设这不是一个选项。

假设我们有一个给定“记录”(即模型)的父 View 。该父 View 有两个 subview ,一个用于显示记录的属性,一个用于编辑它们(假设在这种情况下不适合就地编辑)。到目前为止,每当我需要删除/显示 subview 时,我都会调用 remove。在即将离任的观点和new在传入 View 中,所以我每次都在销毁/创建它们。这很简单,易于编码/管理。

然而,弄清楚是否有任何可行的替代方法(看似默认的)删除/创建方法似乎很重要 - 特别是因为它之前被问过几次,但从未完全回答(例如,Swap out views with Backbone?)

所以我一直在想办法让两个 subview 共享父 View 中的一个元素,从而避免我不得不 removenew他们每次。当一个需要激活时,它被渲染而另一个被“沉默”(即不响应事件)。所以它们会不断地换出,直到父 View 被移除,然后它们一起被移除。

可以在这里找到我的想法:http://jsfiddle.net/nLcan/

注意:交换在 RecordView.editRecord 中执行和 RecordView.onRecordCancel .

虽然这看起来工作得很好,但我有一些担忧:

(1) 即使事件绑定(bind)在“非事件” View 上被静音,将两个 View 设置为同一元素是否会出现问题?只要仅呈现“事件” View ,这似乎就不是问题。

(2) 当两个 subview 都有remove调用(即 Backbone.View.remove )它调用 this.$el.remove .现在,当第一个 subview 被移除时,这实际上将移除它们共享的 DOM 元素。因此,当remove在第二个 subview 上调用没有要删除的 DOM 元素,我想知道这是否会使该 subview 难以自行清理 - 特别是如果它本身创建了许多 DOM 元素,然后在第一个 subview 已呈现....或者如果涉及 subview ...似乎可能存在关于此处内存泄漏的潜在问题。

抱歉,据我所知,这有点令人费解。我在我的知识库的边界上是正确的,所以我不完全理解这里涉及的所有潜在问题(因此是问题)。但我确实希望有人处理过类似的问题,并且可以就这一切提出明智的意见。

无论如何,这是完整的(简化示例)代码:

// 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)

最佳答案

好的,这是我的解决方案:

我没有将 backbone 视为“黑盒”,而是查看了 Backbone.View.removeBackbone.View.setElement 的代码。它们都非常简单,因此我们可以轻松地从图片中完全删除 Backbone.View,而只处理 jQuery。一旦你这样做了,单独在 jQuery 中复制这种行为并通过它似乎证明这种方法绝对没有任何问题。

// 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;

关于javascript - Backbone .View : Swapping out two child views that share an element in the parent view,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21053919/

有关javascript - Backbone .View : Swapping out two child views that share an element in the parent view的更多相关文章

随机推荐