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(){
????
}
});
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'
});
}
});
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.
this.events
& ParentView.prototype.events
otherwise if both define handlers on the same event the Parent's handler will override the Child's.
{},ParentView.prototype.events,this.events
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?
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 :)
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);
}
});
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'
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.
Short version of @soldier.moth's last suggestion:
var ChildView = ParentView.extend({
events: function(){
return _.extend({}, _.result(ParentView.prototype, 'events') || {}, {
'click' : 'onclickChild'
});
}
});
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 …
// 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/
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",
}
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.
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);
};
}
});
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')
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'
})
});
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
.
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
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.
Success story sharing
parentEvents = _.result(ParentView.prototype, 'events');
instead of 'manually' checking ifevents
is a 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: jsfiddlethis
versus having to call the parent class by instance name. thank you very much for this.