117

Backbone's documentation states:

The events property may also be defined as a function that returns an events hash, to make it easier to programmatically define your events, as well as inherit them from parent views.

How do you inherit a parent's view events and extend them?

Parent View

var ParentView = Backbone.View.extend({
   events: {
      'click': 'onclick'
   }
});

Child View

var ChildView = ParentView.extend({
   events: function(){
      ????
   }
});
brent
  • 1,709
  • 2
  • 13
  • 15

15 Answers15

192

One way is:

var ChildView = ParentView.extend({
   events: function(){
      return _.extend({},ParentView.prototype.events,{
          'click' : 'onclickChild'
      });
   }
});

Another would be:

var ParentView = Backbone.View.extend({
   originalEvents: {
      'click': 'onclick'
   },
   //Override this event hash in
   //a child view
   additionalEvents: {
   },
   events : function() {
      return _.extend({},this.originalEvents,this.additionalEvents);
   }
});

var ChildView = ParentView.extend({
   additionalEvents: {
      'click' : ' onclickChild'
   }
});

To check whether Events is function or object

var ChildView = ParentView.extend({
   events: function(){
      var parentEvents = ParentView.prototype.events;
      if(_.isFunction(parentEvents)){
          parentEvents = parentEvents();
      }
      return _.extend({},parentEvents,{
          'click' : 'onclickChild'
      });
   }
});
Bobby
  • 18,217
  • 15
  • 74
  • 89
  • That's great... Maybe you could update this to show how you would inherit from a ChildView (check if the prototype events is a function or object)... Or maybe I'm overthinking this whole inheritance stuff. – brent Feb 22 '12 at 22:06
  • @brent Sure, just added third case – Bobby Feb 22 '12 at 22:44
  • 15
    If i'm not mistaken you should be able to use `parentEvents = _.result(ParentView.prototype, 'events');` instead of 'manually' checking if `events` is a function. – Koen. Aug 22 '13 at 12:29
  • 3
    @Koen. +1 for mentioning the underscore utility function `_.result`, which I hadn't noticed before. For anyone who's interested, here's a jsfiddle with a bunch of variations on this theme: [jsfiddle](http://jsfiddle.net/QLd6E/) – EleventyOne Jan 04 '14 at 05:54
  • 1
    Just to throw my two cents in here, i believe the second option is the best solution. I say this because of the sheer fact that it is the only method that is truly encapsulated. the only context used is `this` versus having to call the parent class by instance name. thank you very much for this. – jessie james jackson taylor Oct 16 '14 at 01:42
  • To simplify the first and last example above use _.defaults: return _.defaults({ 'click' : 'onclickChild' }, ParentView.prototype.events) – TypingTurtle May 12 '16 at 21:13
79

The soldier.moth answer is a good one. Simplifying it further you could just do the following

var ChildView = ParentView.extend({
   initialize: function(){
       _.extend(this.events, ParentView.prototype.events);
   }
});

Then just define your events in either class in the typical way.

yves amsellem
  • 7,106
  • 5
  • 44
  • 68
34m0
  • 5,755
  • 1
  • 30
  • 22
  • 8
    Good call, though you probably want to swap `this.events` & `ParentView.prototype.events` otherwise if both define handlers on the same event the Parent's handler will override the Child's. – Bobby Apr 06 '12 at 00:03
  • @Soldier.moth did you mean for @34mo to write: `{},ParentView.prototype.events,this.events` ? Just checking as [underscore.extend](http://underscorejs.org/#extend) says: "It's in-order, so the last source will override properties of the same name in previous arguments." – AJP Jun 03 '12 at 09:47
  • @AJP yes `{},ParentView.prototype.events,this.events` is the way it should be. It was originally `_.extend(this.events,ParentView.prototype.events)` – Bobby Jun 04 '12 at 16:13
  • 1
    @Soldier.moth, okay I've edited it to be as `{},ParentView.prototype.events,this.events` – AJP Jun 04 '12 at 22:31
  • @AndrewB. It was waiting to be approved. – Brad Gilbert Jun 05 '12 at 00:39
  • 1
    Obviously this works, but as I know, `delegateEvents` is called in the constructor to bind events. So when you extend it in the `initialize`, how come that it isn't too late? – SelimOber Jul 31 '12 at 08:01
  • @SelimOber Because delegateEvents is called _after_ initialize so anything you set up in initialize will be delegated. – Paul Alexander Oct 12 '12 at 20:51
  • 2
    It's nit-picky, but my issue with this solution is: If you have a diverse and plentiful hierarchy of views, you will inevitably find yourself writing `initialize` in a few cases (then having to deal with managing the hierarchy of that function too) simply to merge the event objects. Seems cleaner to me to keep the `events` merging within itself. That being said, I wouldn't have thought of this approach, and it's always nice to be forced to look at things in a different way :) – EleventyOne Jan 04 '14 at 06:02
  • This is a good solution but it basically creates the same problem with initialize, ie now you have to remember to call ParentView.prototype.initialize. The accepted answer is a little more self-contained – Evan Hobbs Jun 26 '14 at 15:21
  • Agree with @SelimOber, this solution should not work since delegateEvents is called before the initialize function is called: https://github.com/jashkenas/backbone/blob/master/backbone.js#L1199 – Alan Nov 11 '15 at 16:11
  • 1
    this answer is not longer valid because delegateEvents is called before initialize (this is true for version 1.2.3) - it's easy to this in the annotated source. – Roey Nov 29 '15 at 13:07
  • Note that yves ansellem reverted the edit that 34m0 and AJP made in response to soldier.moth's suggestion. – Richard Möhn Aug 13 '18 at 23:32
12

You could also use the defaults method to avoid creating the empty object {}.

var ChildView = ParentView.extend({
  events: function(){
    return _.defaults({
      'click' : 'onclickChild'
    }, ParentView.prototype.events);
  }
});
jermel
  • 2,326
  • 21
  • 19
  • 2
    This causes the parent handlers to be bound after the child handlers. In most cases not a problem, but if a child event should cancel (not override) a parent event it is not possible. – Koen. Aug 22 '13 at 12:41
10

If you use CoffeeScript and set a function to events, you can use super.

class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

class ChildView extends ParentView
  events: ->
    _.extend {}, super,
      'bar' : 'doOtherThing'
Shuhei Kagawa
  • 4,752
  • 1
  • 33
  • 31
6

Wouldn't it be easier to create specialized base constructor from Backbone.View that handles the inheritance of events up the hierarchy.

BaseView = Backbone.View.extend {
    # your prototype defaults
},
{
    # redefine the 'extend' function as decorated function of Backbone.View
    extend: (protoProps, staticProps) ->
      parent = this

      # we have access to the parent constructor as 'this' so we don't need
      # to mess around with the instance context when dealing with solutions
      # where the constructor has already been created - we won't need to
      # make calls with the likes of the following:   
      #    this.constructor.__super__.events
      inheritedEvents = _.extend {}, 
                        (parent.prototype.events ?= {}),
                        (protoProps.events ?= {})

      protoProps.events = inheritedEvents
      view = Backbone.View.extend.apply parent, arguments

      return view
}

This allows us to reduce(merge) the events hash down the hierarchy whenever we create a new 'subclass'(child constructor) by using the redefined extend function.

# AppView is a child constructor created by the redefined extend function
# found in BaseView.extend.
AppView = BaseView.extend {
    events: {
        'click #app-main': 'clickAppMain'
    }
}

# SectionView, in turn inherits from AppView, and will have a reduced/merged
# events hash. AppView.prototype.events = {'click #app-main': ...., 'click #section-main': ... }
SectionView = AppView.extend {
    events: {
        'click #section-main': 'clickSectionMain'
    }
}

# instantiated views still keep the prototype chain, nothing has changed
# sectionView instanceof SectionView => true 
# sectionView instanceof AppView => true
# sectionView instanceof BaseView => true
# sectionView instanceof Backbone.View => also true, redefining 'extend' does not break the prototype chain. 
sectionView = new SectionView { 
    el: ....
    model: ....
} 

By creating a specialized view: BaseView that redefines the extend function, we can have subviews(like AppView, SectionView) that want to inherit their parent view's declared events simply do so by extending from BaseView or one of its derivatives.

We avoid the need to programmatically define our event functions in our subviews, which in most cases need to refer to the parent constructor explicitly.

Shaw W
  • 61
  • 1
  • 2
2

Short version of @soldier.moth's last suggestion:

var ChildView = ParentView.extend({
  events: function(){
    return _.extend({}, _.result(ParentView.prototype, 'events') || {}, {
      'click' : 'onclickChild'
    });
  }
});
Koen.
  • 25,449
  • 7
  • 83
  • 78
2

This would also work:

class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

class ChildView extends ParentView
  events: ->
    _.extend({}, _.result(_super::, 'events') || {},
      'bar' : 'doOtherThing')

Using straight super wasn't working for me, either was manually specifying the ParentView or inherited class.

Access to the _super var which is available within any coffeescript Class … extends …

yolk
  • 729
  • 7
  • 10
2

// ModalView.js
var ModalView = Backbone.View.extend({
 events: {
  'click .close-button': 'closeButtonClicked'
 },
 closeButtonClicked: function() { /* Whatever */ }
 // Other stuff that the modal does
});

ModalView.extend = function(child) {
 var view = Backbone.View.extend.apply(this, arguments);
 view.prototype.events = _.extend({}, this.prototype.events, child.events);
 return view;
};

// MessageModalView.js
var MessageModalView = ModalView.extend({
 events: {
  'click .share': 'shareButtonClicked'
 },
 shareButtonClicked: function() { /* Whatever */ }
});

// ChatModalView.js
var ChatModalView = ModalView.extend({
 events: {
  'click .send-button': 'sendButtonClicked'
 },
 sendButtonClicked: function() { /* Whatever */ }
});

http://danhough.com/blog/backbone-view-inheritance/

vovan
  • 1,460
  • 12
  • 22
1

For Backbone version 1.2.3, __super__ works fine, and may even be chained. E.g.:

// A_View.js
var a_view = B_View.extend({
    // ...
    events: function(){
        return _.extend({}, a_view.__super__.events.call(this), { // Function - call it
            "click .a_foo": "a_bar",
        });
    }
    // ...
});

// B_View.js
var b_view = C_View.extend({
    // ...
    events: function(){
        return _.extend({}, b_view.__super__.events, { // Object refence
            "click .b_foo": "b_bar",
        });
    }
    // ...
});

// C_View.js
var c_view = Backbone.View.extend({
    // ...
    events: {
        "click .c_foo": "c_bar",
    }
    // ...
});

... which - in A_View.js - will result in:

events: {
    "click .a_foo": "a_bar",
    "click .b_foo": "b_bar",
    "click .c_foo": "c_bar",
}
Kafoso
  • 534
  • 3
  • 20
1

I've found a more interesting solutions in this article

It use of the Backbone’s super and ECMAScript’s hasOwnProperty. The second of its progressives examples works like a charm. Here's a bit a code :

var ModalView = Backbone.View.extend({
    constructor: function() {
        var prototype = this.constructor.prototype;

        this.events = {};
        this.defaultOptions = {};
        this.className = "";

        while (prototype) {
            if (prototype.hasOwnProperty("events")) {
                _.defaults(this.events, prototype.events);
            }
            if (prototype.hasOwnProperty("defaultOptions")) {
                _.defaults(this.defaultOptions, prototype.defaultOptions);
            }
            if (prototype.hasOwnProperty("className")) {
                this.className += " " + prototype.className;
            }
            prototype = prototype.constructor.__super__;
        }

        Backbone.View.apply(this, arguments);
    },
    ...
});

You can also do that for ui and attributes.

This example does not take care of the properties set by a function, but the author of the article offers a solution in that case.

firebird631
  • 569
  • 7
  • 10
1

To do this entirely in the parent class and support a function-based events hash in the child class so that children can be agnostic of inheritance (the child will have to call MyView.prototype.initialize if it overrides initialize):

var MyView = Backbone.View.extend({
  events: { /* ... */ },

  initialize: function(settings)
  {
    var origChildEvents = this.events;
    this.events = function() {
      var childEvents = origChildEvents;
      if(_.isFunction(childEvents))
         childEvents = childEvents.call(this);
      return _.extend({}, MyView.prototype.events, childEvents);
    };
  }
});
Greg Ross
  • 3,479
  • 2
  • 27
  • 26
Kevin Borders
  • 2,933
  • 27
  • 32
0

This CoffeeScript solution worked for me (and takes into account @soldier.moth's suggestion):

class ParentView extends Backbone.View
  events: ->
    'foo' : 'doSomething'

class ChildView extends ParentView
  events: ->
    _.extend({}, _.result(ParentView.prototype, 'events') || {},
      'bar' : 'doOtherThing')
mikwat
  • 533
  • 3
  • 11
0

If you are sure that the ParentView has the events defined as object and you don't need to define events dynamically in ChildView it is possible to simplify soldier.moth's answer further by getting rid of the function and using _.extend directly:

var ParentView = Backbone.View.extend({
    events: {
        'click': 'onclick'
    }
});

var ChildView = ParentView.extend({
    events: _.extend({}, ParentView.prototype.events, {
        'click' : 'onclickChild'
    })
});
gabriele.genta
  • 511
  • 5
  • 10
0

A pattern for this that I am fond of is modifying the constructor and adding some additional functionality:

// App View
var AppView = Backbone.View.extend({

    constructor: function(){
        this.events = _.result(this, 'events', {});
        Backbone.View.apply(this, arguments);
    },

    _superEvents: function(events){
        var sooper = _.result(this.constructor.__super__, 'events', {});
        return _.extend({}, sooper, events);
    }

});

// Parent View
var ParentView = AppView.extend({

    events: {
        'click': 'onclick'
    }

});

// Child View
var ChildView = ParentView.extend({

    events: function(){
        return this._superEvents({
            'click' : 'onclickChild'
        });
    }

});

I prefer this method because you do not have to identify the parent -one less variable to change. I use the same logic for attributes and defaults.

jtrumbull
  • 818
  • 9
  • 19
0

Wow, lots of answers here but I thought I'd offer one more. If you use the BackSupport library, it offers extend2. If you use extend2 it automatically takes care of merging events (as well as defaults and similar properties) for you.

Here's a quick example:

var Parent = BackSupport.View.extend({
    events: {
        change: '_handleChange'
    }
});
var Child = parent.extend2({
    events: {
        click: '_handleClick'
    }
});
Child.prototype.events.change // exists
Child.prototype.events.click // exists

https://github.com/machineghost/BackSupport

machineghost
  • 33,529
  • 30
  • 159
  • 234
  • 3
    I like the concept, but, on principle alone, I'd pass on any library that thinks "extend2" is a proper function name. – Yaniv Mar 05 '16 at 13:41
  • I would welcome any suggestions you can offer for what to name a function that's essentially "Backbone.extend, but with improved functionality". Extend 2.0 (`extend2`) was the best I could come up with, and I don't think it's all that terrible: anyone used to Backbone is already used to using `extend`, so this way they don't need to memorize a new command. – machineghost Mar 07 '16 at 18:47
  • Opened an issue on the Github repo about it. :) – Yaniv Mar 13 '16 at 15:38