1

I am currently extending Marionette's base Marionette.View type with the method I named quickClick. I'm doing this to

config/marionette/view.js

(function() {

    define([
        'backbone.marionette'
    ],

    function(Marionette){

        return _.extend(Backbone.Marionette.View.prototype, {

            quickClick: function(e) {
                $(e.target).get(0).click();
            }

        });

    });

}).call(this);

This allows me to call this method from any view I create without having to redefine it per view. Great!

Here's a trimmed down view with the events object still in place:

(function() {

    define([
        'backbone.marionette',
        'app',
        'templates'
    ],
    function(Marionette, App, templates){

        // Define our Sub Module under App
        var List = App.module("SomeApp");

        List.Lessons = Backbone.Marionette.ItemView.extend({

            events: {
                'tap .section-container p.title':         'quickClick'
            }

        });

        // Return the module
        return List;

    });

}).call(this);

In case your wondering, tap is an event I'm using from Hammer.js because this is a mobile app. So, in order to circumvent the 300ms click event delay on iOS and Android, I'm manually triggering a click event when the tap event fires on certain elements.

Now, all of this is working just fine, and I felt it was necessary to describe this in detail, so that an answer could be given with context.

My problem is having to define the events object. I don't mind at all for elements as specific as the one above, .section-container p.title. But, I would like to register a tap event for all <a> tags within every view. It doesn't make sense to keep defining this event in each view I create

events: {
    'tap .section-container p.title':         'quickClick',
    // I don't want to add this to every single view manually
    'tap a': 'quickClick'
}

Instead, of adding this to every view, I thought I would just add an events object to the config/marionette/view.js file where I added a method to the Marionette.View prototype.

Here's what I did

(function() {

    define([
        'backbone.marionette'
    ],

    function(Marionette){

        return _.extend(Backbone.Marionette.View.prototype, {

            events: {
                'tap a': 'quickClick'
            },

            quickClick: function(e) {
                $(e.target).get(0).click();
            }

        });

    });

}).call(this);

Of course, that doesn't work. The events object is overridden each time I need to add events that only apply to that view. Btw, tap a does work when my view does not have its' own events object.

So, my question is: How do I add default events to Marionette's Marionette.View base type?

Jarrod
  • 396
  • 2
  • 19

2 Answers2

5

"Of course, that doesn't work. The events object is overridden each time I need to add events that only apply to that view."

Yes, that seems to be the problem. Here is the part of Marionette that does the event delegation:

// internal method to delegate DOM events and triggers 
_delegateDOMEvents: function(events){
    events = events || this.events;
    if (_.isFunction(events)){ events = events.call(this); }

    var combinedEvents = {};
    var triggers = this.configureTriggers();
    _.extend(combinedEvents, events, triggers);

    Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
  },

One possible solution could be overwriting this (private!) part of Marionette - but it could probably change in new versions of Marionette and you'd always have to make sure that things still work. So this is bad.

But you could do something like this in your subviews.:

events: _.extend(this.prototype.events, {
  'tap .section-container p.title': 'quickClick'
})

If this makes sense for only one 'global' event is another question.

Or you could define an abstract View Class, which does something like that

events: _.extend({'tap a': 'quickClick'}, this.my_fancy_events)

and also defines the quickClick method and then use this view for all you subviews. They then define their events not in 'events' but in 'my_fancy_events'.

django
  • 301
  • 2
  • 5
  • Let me try out a few of these before accepting, but these seem like reasonable solutions. I think, perhaps the last solution would be the one to expand on. I am looking for a way to extend this events object in one place, then in all my subviews, absolutely nothing changes from documented, default Marionette behavior. In other words, I would like to keep the solution out of sight, out of mind. – Jarrod Aug 16 '13 at 14:31
  • I've now had a chance to think on this a bit. I don't see option 1 or 2 as viable. But, option 3 (extending the events object on an abstract View Class) seems somewhat viable. This, of course would mean that I would need to use a custom `my_fancy_events` object instead of the documented `events` object for all of my views that extend from this base View Class. This just doesn't seem like the way to do it. It seems like a fantastic workaround. I'm thinking there has got to be a better way. Hoping some other community member will chime in on this. – Jarrod Aug 19 '13 at 18:30
  • Option 2 doesn't work at all, because 'this' refers to the Window object in that context. I'm having the same problem and I can't find a reliable way to access the superclass for an subclassed object instance. Lots of fudginess in http://stackoverflow.com/questions/8032566/emulate-super-in-javascript – trojjer Nov 14 '13 at 11:24
  • It's a shame that this.__proto__ or this.__super__ don't work, even when I tried extending 'events' within an anonymous function. Chrome tools then report this.prototype and this.__super__ as undefined, with this.__proto__ pointing to the subclass instance itself. – trojjer Nov 14 '13 at 11:30
  • Ah, View.__super__ is Backbone internal and discouraged: https://github.com/jashkenas/backbone/pull/787#issuecomment-3143358 I've just found out that this.constructor.__super__.events refers to the events object from the parent class. It's tempting to use it but the comments on GitHub urge refrain... – trojjer Nov 14 '13 at 11:41
  • Check this out: http://pivotallabs.com/a-convenient-super-method-for-backbone-js/ – trojjer Nov 14 '13 at 11:44
  • I settled for a variant of option 3 in the end, as our minified assets are already swollen and I wanted to avoid adding extra code. The difference is that, as mentioned in my previous posts, an anon function is needed to gain the correct context. Luckily, there's a check for this in the event binding code and the function is invoked transparently. `Backbone.Marionette.ItemView.extend({ events: function() { var base_events = {'click .close': 'closePane'}; return _.extend(base_events, this.extra_events); }, closePane: function(event) { /* ... */ } });` – trojjer Nov 14 '13 at 12:31
  • 1
    +1 for `events: _.extend(this.prototype.events, {` !! – eightyfive Nov 26 '13 at 15:12
1

When extending the views I occasionally find myself in situation when I need to add some extra calls in 'initialize' as well as extend 'events' property to include some new calls. In my abstract view I have a function:

inheritInit: function(args) { 
  this.constructor.__super__.initialize.apply(this, args);
  this.events = _.extend(this.constructor.__super__.events, this.eventsafter);
},

Then, in an extended view, I can call

initialize: function(options) {
  this.inheritInit(arguments)
  //..some extra declarations...
}

and also I can use 'events' property in a regular way.

Serg
  • 251
  • 2
  • 5