Backbone View: Inherit and extend events from parent

Javascriptbackbone.jsBackbone Events

Javascript Problem Overview


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(){
      ????
   }
});

Javascript Solutions


Solution 1 - Javascript

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'
      });
   }
});

Solution 2 - Javascript

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.

Solution 3 - Javascript

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);
  }
});

Solution 4 - Javascript

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'

Solution 5 - Javascript

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.

Solution 6 - Javascript

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

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

Solution 7 - Javascript

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 …

Solution 8 - Javascript

// 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/

Solution 9 - Javascript

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",
}

Solution 10 - Javascript

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.

Solution 11 - Javascript

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);
    };
  }
});

Solution 12 - Javascript

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')

Solution 13 - Javascript

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'
    })
});

Solution 14 - Javascript

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.

Solution 15 - Javascript

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionbrentView Question on Stackoverflow
Solution 1 - JavascriptBobbyView Answer on Stackoverflow
Solution 2 - Javascript34m0View Answer on Stackoverflow
Solution 3 - JavascriptjermelView Answer on Stackoverflow
Solution 4 - JavascriptShuhei KagawaView Answer on Stackoverflow
Solution 5 - JavascriptShaw WView Answer on Stackoverflow
Solution 6 - JavascriptKoen.View Answer on Stackoverflow
Solution 7 - JavascriptyolkView Answer on Stackoverflow
Solution 8 - JavascriptvovanView Answer on Stackoverflow
Solution 9 - JavascriptKafosoView Answer on Stackoverflow
Solution 10 - Javascriptfirebird631View Answer on Stackoverflow
Solution 11 - JavascriptKevin BordersView Answer on Stackoverflow
Solution 12 - JavascriptmikwatView Answer on Stackoverflow
Solution 13 - Javascriptgabriele.gentaView Answer on Stackoverflow
Solution 14 - JavascriptjtrumbullView Answer on Stackoverflow
Solution 15 - JavascriptmachineghostView Answer on Stackoverflow