Marionette bubble event from itemview to parent layoutview?
Asked Answered
C

5

9

I have a layout view with a region, in that region I have a item view that triggers an event but it doesn't seem to be bubbled up to the layout view. Am I doing something wrong or is this designed behavior? I assume the itemview prefix is not added as the parent view is not a collection view? Either way the event is never bubbled to the layout view.

layoutView = Marionette.Layout.extend({
        template: "#layout-template",
        regions: {
            titleRegion: "#job-title-region"
        },
        triggers: {
            "save:clicked" : "onSaveClicked"
        },
        onSaveClicked: function (args) {
            alert('Clicked');
        }
    });

childview = Marionette.ItemView.extend({
        template: "#child-template",
        triggers: {
            "click .js-save": "save:clicked"
        }
    });

UPDATE:

See this fiddle http://jsfiddle.net/7ATMz/11/ I managed to get the layout view to listen to the child event but I have to wire it up outside of the layout view itself and break encapsulation. Can I do this in the layout view in anyway?

Thanks,

Jon

Case answered 31/5, 2013 at 22:47 Comment(0)
B
10

Triggers don't quite work like that: your layout is using them wrong. Triggers are a convenience to raise an event signal given a certain interaction (e.g. a click).

What you want is to use triggerMethod (https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.functions.md#marionettetriggermethod) to trigger a function in your layout. See http://jsfiddle.net/ZxEa5/ Basically, you want this in your show function:

childView.on("btn:clicked", function(){
  layout.triggerMethod("childView:btn:clicked"); 
});

And in your layout:

onChildViewBtnClicked: function(){
   https://leanpub.com/marionette-gentle-introduction
});

Event bubbling only happens automagically with collection?composite views because they're tightly associated with their item views. If you want a layout to monitor one of its child views, you need to set that up on your own.

Shameless plug: if you want to learn more about how to structure and clean up your code with Marionette, you can check out my book (https://leanpub.com/marionette-gentle-introduction) where this type of concept (and its applications) is explained in more detail.

Bathetic answered 1/6, 2013 at 12:37 Comment(1)
In my humble opinion it is more intuitive if a layout would handle all the bubbled events from its children automagically. Otherwise I would except an exception error or at least a warning that an event was not handled at all. None of these happen with latest marionette versionTebet
E
7

I'm not sure when this Marionette feature was introduced, but a much simpler solution would be using the childEvents hash: http://marionettejs.com/docs/v2.4.1/marionette.layoutview.html#layoutview-childevents

...
childEvents: {
  "save:clicked" : "onSaveClicked"
},
...

You could also directly bind the child event to a function outside of LayoutView, if it makes more sense, like this:

layout.on('childview:save:clicked', function(childView) {
  alert('clicked');
}
Eley answered 15/5, 2015 at 12:14 Comment(3)
Tried it and didn't work. Did it work for you? I had to explicitly tell the layout to listenTo its child. For example: this.listenTo(this.announcementRegion.currentView, "poisonpill", () => { this.announcementRegion.empty() });Tebet
No, it works for me just fine, I've tried this for CollectionView, LayoutView and CompositeView.Eley
This is a great, up to date, solution!Muscular
P
4

I recommend using Backbone.Courier for this type of need: https://github.com/rotundasoftware/backbone.courier

Prestidigitation answered 7/10, 2013 at 15:44 Comment(1)
Going beyond this example, would you recommend using Courier over what's already implemented in Marionette for bubbling events from ItemViews to their parent CollectionView?Leonerd
B
1

I implemented a solution for a similar problem as follows. First, I wrote a new method right into the Marionette.View prototype:

Marionette.View.prototype.bubbleMethod = function () {
    var args = _.toArray(arguments)
    var event = args.shift()
    var bubble = event + ':bubble'
    this.triggerMethod.apply(this, [ event ].concat(args))
    this.triggerMethod.apply(this, [ bubble ].concat(args))
}

That will call the regular triggerMethod from Marionette twice: once with your event name as it is intended to be handled, and a second one which is easily recognizable by specialized views, designated to bubble events up.

Then you will need such specialized view and bubble up events that are meant to be bubbled up. You must be careful not to dispatch events like close (or any Marionette events at all) in behalf of other views, because that will cause all sorts of unpredictable behaviors in Regions and Views. The :bubble suffix allows you to easily recognize what's meant to bubble. The bubbling view might look like this:

var BubblingLayout = Marionette.Layout.extend({
    handleBubbles: function (view) {
        var bubble = /:bubble$/
        this.listenTo(view, 'all', function () {
            var args = _.toArray(arguments)
            var event = args.shift()
            if (event.match(bubble)) {
                event = event.replace(bubble, '')
                this.bubbleMethod.apply(this, [ event ].concat(args))
            }
        }, this)
    }
})

The last thing you need to make sure is to be able to bubble events across regions (for Layouts and modules with custom region managers). That can be handled with the show event dispatches from a region, like this:

var BubblingLayout = Marionette.Layout.extend({
    regions: {
        sidebar: '#sidebar'
    },
    initialize: function () {
        this.sidebar.on('show', this.handleBubbles, this)
    },
    handleBubbles: function (view) {
        var bubble = /:bubble$/
        this.listenTo(view, 'all', function () {
            var args = _.toArray(arguments)
            var event = args.shift()
            if (event.match(bubble)) {
                event = event.replace(bubble, '')
                this.bubbleMethod.apply(this, [ event ].concat(args))
            }
        }, this)
    }
})

The last part is to make something actually bubble up, which is easily handled by the new bubbleMethod method:

var MyView = Marionette.ItemView.extend({
    events: {
        'click': 'clickHandler'
    },
    clickHandler: function (ev) {
        // do some stuff, then bubble something else
        this.bubbleMethod('stuff:done')
    }
})

var BubblingLayout = Marionette.Layout.extend({
    regions: {
        sidebar: '#sidebar'
    },
    initialize: function () {
        this.sidebar.on('show', this.handleBubbles, this)
    },
    onRender: function () {
        var view = new MyView()
        this.sidebar.show(view)
    },
    handleBubbles: function (view) {
        var bubble = /:bubble$/
        this.listenTo(view, 'all', function () {
            var args = _.toArray(arguments)
            var event = args.shift()
            if (event.match(bubble)) {
                event = event.replace(bubble, '')
                this.bubbleMethod.apply(this, [ event ].concat(args))
            }
        }, this)
    }
})

Now you can handle bubbled events from any place in your code where you can access an instance of BubblingLayout.

Bryantbryanty answered 7/10, 2013 at 15:33 Comment(0)
E
0

Why not just allow the click event to bubble up the DOM hierarchy and handle it in your Layout view? Something like this (fiddle here):

var MainView = Marionette.Layout.extend({
    template: "#layout-template",
    regions: {
        childRegion: "#childRegion"   
    },
    events: {
        'click #btn': 'handleButtonClick'   
    },
    handleButtonClick: function() {
       alert('btn clicked in child region and bubbled up to layout');
    }
});

var ChildView = Marionette.ItemView.extend({
    template: "#child-template"
    /*
    triggers: {
        "click #btn": "btn:clicked"   
    }*/
});
Evvoia answered 31/5, 2013 at 23:10 Comment(5)
this doesn't seem to work if I raise the event from a view in the layouts child region?Case
Sorry, it was a bit of a guess really. The click event should bubble up the DOM hierarchy to your layout view anyway so you should be able to catch it in your Layout using the usual events object.Evvoia
Sure, see this jsfiddle.net/7ATMz/11 I managed to get the layout view to listen to the child event but I have to wire it up outside of the layout view itself and break encapsulation.Case
You must have strict mime type checking with the external js resources in chrome, try in FF or disable it :)Case
Updated my answer. I think you can just let the click event bubble up the DOM hierarchy to your Layout view and handle it there.Evvoia

© 2022 - 2024 — McMap. All rights reserved.