backbone.jsmarionettebackbone-viewsbackbone-events

DelegateEvents: Child itemViews's not removed on deletion of model from collection


I've a collectionview ItineraryEditorView that is swapped in and out of a layout region. The same instance of collectionView is swapped in using region.show instead of creating new instance for collectioView every time. The render method on the collection calls delegateEvents() method.

    ItineraryEditorView = Marionette.CollectionView.extend({
    tagName: "div",
    emptyView: EmptyItineraryDayView,
    itemView: ItineraryDayView,
    initialize: function (options) {            
    },  
    render: function() {
        Marionette.CollectionView.prototype.render.apply(this);
        this.delegateEvents();
    },
});

The view is swapped out by closing region:

plannerLayout.itineraryEditorRegion.close();

Swapping in the view:

itineraryEditorView.delegateEvents();
plannerLayout.itineraryEditorRegion.show(itineraryEditorView);

Deletion of a model is done by listening to the bubbled up event from itemView to collectionView (works, no issues here)

itineraryEditorView.on("itemview:delete:day", function(childView, model)    {
    days.remove(model); //days coll is passed in to coll view on instance creation
    holiday.save();
});

I'd expect that this would be enough to ensure the the child itemViews in the collection view are removed/closed when a model is removed from the underlying days collection. I can see that the collection is actually modified and and saved on the server,however the collection view is not altered. This scenario of course works when before the collection view is swapped out first time. The coll. view does update itself after I re-visit another tab and come back to this tab (thus the entire collection is rendered- confirming the change/deletion)

What is the best way get around this issue? If I recreate the instance of collection view, it does cause to lose other "on" handles in the code.


Solution

  • There's no need to call delegateEvents manually - Backbone does this in the view's constructor. When you close the region, the region will close your view removing all internal references and unbinding all events, preparing it for garbage collection. Once a view has been close()'d, it should not be used again. The fix to your problem should be to new up another instance of your ItineraryEditorView and pass that to the region on show.

    Also, it's not necessary to call region.close() before calling region.show(view); the first thing show does is to close() the current view, if any. My recommendation to modify your code as follows:

    ItineraryEditorView = Marionette.CollectionView.extend({
        // tagName: "div", // <-- unnecessary as written - default tag
    
        emptyView: EmptyItineraryDayView,
    
        itemView: ItineraryDayView
    
        // this empty override is unnecessary as written - recommend removing it
        // initialize: function (options) {           
        // },
    
        // this override is unnecessary as written - remove it:
        // render: function() {
        //     Marionette.CollectionView.prototype.render.apply(this);
        //     this.delegateEvents();
        // },
    });
    

    ...

    // plannerLayout.itineraryEditorRegion.close(); <-- delete this line; it's not needed
    // itineraryEditorView.delegateEvents();        <-- ditto
    

    EDITED FROM ORIGINAL POST

    // define a delegate to handle events shared across multiple instances of the view:
    function handleDeleteChild(childView, model) {
        days.remove(model); //days coll is passed in to coll view on instance creation
        holiday.save();
    });
    
    // create the ItineraryEditorView
    // NOTE: As written this will likely conflict with your current code;
    //       update as required
    var itineraryEditorView = new ItineraryEditorView(options);
    
    // bind event listeners to it
    itineraryEditorView.on("itemview:delete:day", handleDeleteChild);
    
    // show it
    plannerLayout.itineraryEditorRegion.show(itineraryEditorView);