0

I have two views, a MainView and a ChildView. The ChildView is configured and instaciated outside of the MainView instance and injected into the MainView. The MainView shows the ChildView when it is rendered, thanks to the onRender method.

The problem I am having is that if the MainView is rendered a 2nd time, all event listeners on the ChildView are killed. The ChildView is still displayed on the 2nd render, however it doesn't respond to user events (like mouse clicks). Not even its own events.

var MainView = Marionette.Layout.extend({
    regions: {
        'menu': '.menu'
    },
    template: "#sample-template",
    onRender: function() {
        this.menu.show(this.options.menuView);
    }
});

var ChildView = Marionette.ItemView.extend({
    template: '#menu-template',
    triggers: {
        'click .js-click-me': 'clicked'
    },
    onClicked: function() {
        alert('the button was clicked...');
    }
});

// Where the view is used and displayed in a main app region:
var model = new Backbone.Model({
    contentPlacement: "here"
});

var childView = new ChildView({
    model: model
});

var view = new MainView({
    model: model,    
    menuView: childView
});

this.region.show(view);

// A 2nd render kills event listeners in the childView
// Remove this line, and it will work again...
view.render();

I have a jsfiddle that demonstrates this. If you comment out the 2nd render (line 65) you'll see that the events on the ChildView do, in fact, work.

What is the best way to prevent this from happening? Is there a better way to configure and inject a view into another view in Marionette?

David Biehl
  • 301
  • 2
  • 12
  • 1
    I suspect this is a duplicate of http://stackoverflow.com/q/12028835/479863 with some Marionette on top. – mu is too short Sep 16 '13 at 23:13
  • What are you trying to achieve? What triggers your MainView to get re-rendered? – Erik Ahlswede Sep 17 '13 at 00:19
  • @Erik The child view is a menu that can can have different buttons depending on the context the parent view is rendered in. Instead of binding every possible menu event to the parent, I want to bind them to the child which is then injected into the parent. – David Biehl Sep 17 '13 at 02:28
  • @muistooshort Yup, it is more or less a duplication of that issue. By adding an `onRender: function() { this.delegateEvents(); }` to the `ChildView`, the events are preserved. I didn't know that jQuery's `empty` method also removed DOM events. Clever. Now the question is, are the events being delegated twice the first time it renders? And is this really the best way to handle this? – David Biehl Sep 17 '13 at 15:22
  • Here is a [jsfiddle](http://jsfiddle.net/davidbiehl/GdB7N/) that demonstrates that fix. Apparently `delegateEvents` is smart enough not to delegate the events twice on the first render. Win! @muistooshort if you'd like to put an answer, I'll make sure you get the credit. – David Biehl Sep 17 '13 at 15:37
  • We could mark this as a duplicate or you could answer yourself, I don't know my way around Marionette so I'm not comfortable answering myself. – mu is too short Sep 17 '13 at 19:13

2 Answers2

0

So the parent view will re-render when the child view is swapped out with a different one? Typically, in this scenario, I would initialize the parent view to listen to an event that gets triggered when the child view changes.

I tweaked your fiddle to show you what I mean: http://jsfiddle.net/nu6mm/5/

I changed your button template to show the child view changing. Also, this:

<script type="text/html" id="sample-template">
  put some content <%= contentPlacement %>.
  <div class="menu"></div>
</script>

Won't change when the child changes unless you bind the child model to the parent view.

Erik Ahlswede
  • 2,077
  • 2
  • 25
  • 36
  • This isn't quite what I was looking for. Even with this, if you re-render the `ParentView`, the events on the `ChildView` are lost. There could be any number of things that re-render the parent view in my actual program as it is fairly complex. – David Biehl Sep 17 '13 at 15:38
  • Okay. But with my example, the parentView does get re-rendered, and the event still sticks. – Erik Ahlswede Sep 17 '13 at 16:37
  • I didn't see where the `ParentView` got re-rendered, so I added it myself and it killed the events. [jsfiddle](http://jsfiddle.net/davidbiehl/nu6mm/12/) line 84. – David Biehl Sep 17 '13 at 17:19
  • The ParentView re-renders itself when the child view gets set. See line 24 : this.listenTo(this, 'childChange', this.render); – Erik Ahlswede Sep 17 '13 at 20:59
0

As @muistoshort pointed out, this is very similar to Backbone: event lost in re-render with a Marionette twist. The fix is to delegateEvents on the ChildView when it is re-rendered. With Marionette, this is as simple as adding an onRender callback that does this:

var ChildView = Marionette.ItemView.extend({
    template: '#menu-template',
    triggers: {
        'click .js-click-me': 'clicked'
    },
    onClicked: function() {
        alert('the button was clicked...');
    },
    onRender: function() {
        this.delegateEvents();
    }
});

Backbone is smart enough not to delegate the events twice (on the first render) so it works as expected.

Community
  • 1
  • 1
David Biehl
  • 301
  • 2
  • 12