在setElement上取消委派子视图事件

Marionette CompositeView undelegating childview events on setElement

本文关键字:视图 事件 委派 取消 setElement      更新时间:2023-09-26

我有一个marionette compositeview,我用它来创建一个应用程序上的个人资料页面的项目列表。对于子视图,我从已经存在的ItemView扩展。

当我在compositeview onRender函数中使用this.setElement(this.el.innerHTML)时,子视图中设置的所有事件不再被触发,甚至更多,在浏览器的检查器工具的控制台上触发它们,什么也不做。

然而,当我不使用setElement,容器div被添加到我的标记,但现在所有的事件在子视图工作。

有人能帮我理解一下吗?

我使用的集合有一个自定义的clone方法。

我正在使用一个全局集合,它在每次读取时更新并存储在缓存中。

当我实际实例化我的视图时,集合已经被使用,并且主layout视图中的一个区域已经填充了一个类似于我想要渲染的项目列表。

我是这样实例化我的视图的:

var currentUser = Profile.get('username');
        // Perform changes to global collection
        Items.url = API + '/items/search?q=' + currentUser + '&size=20';
        Items.parse = function (response) {
            if (!response.results) {
                return response;
            } else {
                return response.results;
            }
        };
        Items.fetch(
            {success: function (collection, response, options) {
                this.listOfItems = new View.itemListProfilePage({
                    template: TemplIds.profilePagePostedItems,
                    parentClass: 'profile-cols',
                    collection:  Items, // global collection
                    filterAttr: {user: currentUser},
                    isFiltered: true,
                    lazyLoad: true,
                    childViewContainer: '#profile-items',
                    childView: View.itemProfilePage.extend({
                        template: TemplIds.item
                    })
                });
                Backbone.trigger('main:show', this.listOfItems); //'main:show' is an event in layoutview which calls region.show
            },
                remove: false
        });

My compositeview:

View.itemListProfilePage = Marionette.CompositeView.extend({
    collection: null,       //original collection cloned later for filtering
    fetch: null,            //promise for fetched items
    lazyView: null,
    options: {
        parentClass: '',
        filterAttr: {},
        isFiltered: false,
        lazyLoad: false
    },
    initialize: function () {
        this.stopListening(this.collection);
        //Change collection property and re-apply events
        this.collection = this.collection.clone(this.options.filterAttr, this.options.isFiltered);
        this._initialEvents();
        this.collection.reset(this.collection.where(this.options.filterAttr), {reset: true});
        this.listenTo(Backbone, 'edit:profileItems', this.addEditClassToSection);
    },
    onRender: function () {
        this.setElement(this.el.innerHTML, true);
    },
    onShow: function () {
        if (this.options.parentClass) {
            this.el.parentElement.className = this.options.parentClass;
        }
    },
    addEditClassToSection: function (options) {
        if ( options.innerHTML !== 'edit' ) {
            this.el.classList.add('edit-mode');
        } else {
            this.el.classList.remove('edit-mode');
        }
    },
}

ItemView:

View.Item = Marionette.ItemView.extend({
    model: null,
    numLikes: null,             //live set of DOM elements containing like counter
    modalItem: null,            //view class with further details about the item to be used within a modal       
    events: {
        'click img.highlight': 'showModal'
    },
    initialize: function (options) {
        var itemWithHeader;     //extended item view class with header at the top and no footer
        var addToCart;
        //Set up all like-related events
        this.listenTo(this.model, "change:numLikes", this.updateNumLikes);
        this.listenTo(this.model, "change:liked", this.updateLiked);
        //Set up the view classes to be used within the modal on click
        itemWithHeader = View.ItemWithHeader.extend({
            template: this.template,
            model: this.model               //TODO: move to inside itemDetails
        });
        itemAddToCart = View.ItemAddToCart.extend({
            template: TemplIds.itemAddCart,
            model: this.model               //TODO: move to inside itemDetails
        });
        this.modalItem = View.ItemDetails.extend({
            template: TemplIds.itemDetails,
            model: this.model,
            withHeader: itemWithHeader,
            addToCart: itemAddToCart
        });
    },
    onRender: function () {
        var imgContainerEl; 
        var likeButtonEl;
        //Get rid of the opinionated div
        this.setElement(this.el.innerHTML);
        this.numLikes = this.el.getElementsByClassName('num');
        //Add the like button to the image
        likeButtonEl = new View.LikeButton({
            template: TemplIds.likeButton,
            model: this.model
        }).render().el;
        this.el.firstElementChild.appendChild(likeButtonEl);    //insert button inside img element
    },
    showModal: function (evt) {
        var modalView = new View.Modal({
            views: {
                'first': {view: this.modalItem}
            }
        });
        Backbone.trigger('modal:show', modalView);
    },
});

我的列表中每个单独项目的itemView:

View.itemProfilePage =  View.Item.extend({
    events: _.extend({},View.Item.prototype.events, {
            'click .delete-me': 'destroyView'
        }
    ),
    onRender: function () {
        View.Item.prototype.onRender.call(this);
        this.deleteButtonEl = new View.itemDeleteButton({
            template: TemplIds.deleteButton
        }).render().el;
        this.el.firstElementChild.appendChild(this.deleteButtonEl);
    },
    destroyView: function (evt) {
        this.model.destroy();
    }
});

简短的回答是,您应该使用setElement

Backbone特别使用额外的容器div来限定/绑定视图的事件。当您使用setElement时,您正在更改父元素的内容。由于您是在onRender函数中执行此操作的,该函数在模板被渲染并且事件已经被绑定之后调用,因此您正在丢失事件绑定。

如果你打算使用Marionette和Backbone,正确的做法是期望并利用渲染视图时生成的"额外"div包装器。您可以通过在视图类上使用classNameidtagName视图属性来控制"包装器"div的标记。