View Inheritance in Backbone

Backbone.js is interesting because it gives you the basic framework of an MVC application without enforcing patterns on you. However, in larger web application it's always important to ensure your structure promotes code reuse and good organization for future development. One such way is through inheritance of views, and in this post I will describe how to create an inheritance chain using Backbone Views.

Motivation

Backbone views provide a basic implementation to represent a view, but there are also areas where further functionality could be required, such as an improved handling of view disposal, or just general application logic you feel a subset of your views should have.

In our application, we want to be able to create subclasses of Backbone.View for disposable views, drop down views, table views, row views, cell views and so on, whilst maintaining the ability to inherit further in the future. Inheriting from Backbone.View isn't as easy as it seems, especially when you consider that you would like children to inherit events and attributes of the parent, whilst also specifying their own. To this end, I set about creating an extensible inheritance chain between my views that would suit my development needs now and in the future.

Subclassing Backbone.View

For our first example, let's assume we want to define a BaseView from which our other views will inherit from. This view will contain the logic surrounding inheriting events and attributes. You might expect to inherit from Backbone.View like so:

var BaseView = Backbone.View.extend({
  // baseview
});

However, this solution can cause several issues. Let's delve a little deeper into this.

Prototype Properties vs. Instance Variables

When you use extend on Backbone.View and specify properties, they aren't specific to every instance of BaseView you create. They're properties on the prototype of Backbone.View, not variables on the instance of your BaseView. Consider the following:

var BaseView = Backbone.View.extend({
    events : {
        'click .clickable': 'handleClick'
    }
});

var ChildOne = BaseView.extend({
    // here, we expect events to have a click handler
});

var ChildTwo = BaseView.extend({
    initialize: function() {
        // add our event
        this.events['drag .draggable'] = 'handleDrag';
    }
});

var c1 = new ChildOne();
var c2 = new ChildTwo();

c1.events;
// we expect this to only have a click handler, but it actually has a click
// and a drag handler

c2.events;

As shown, the modification of events in ChildTwo has affected ChildOne's event object, even though they should be completely separate extensions.

Extend works by extending the Backbone.View.prototype with the object literal you specify. When we edit the events object in ChildTwo, we're actually editing the prototype of BaseView, rather than the instance of ChildTwo. So, ChildOne will also have the same modifications because it uses the prototype of BaseView. Baddie!

There are actually two underlying problems here. Firstly, we want instance variables rather than prototype properties, so that every instance of our BaseView or ChildViews get their own events object. Secondly, we don't want to have to declare a new events object every time, as the parent may have events we want to keep and we don't want to duplicate code across children. So, let's solve them:

Backbone Inheritance with Instance Variables

We need to adjust the way we inherit from Backbone.View. Firstly, we define our BaseView as a function, which will serve as our constructor. In this constructor, we can then define the instance variables we want every instance of our BaseView to have.

We then extend the prototype to copy over all the prototype properties from Backbone.View, and assign the extend to our extend.

var BaseView = function(options) {
    Backbone.View.call(this, options);
}

_.extend(BaseView.prototype, Backbone.View.prototype, {
    // base functions will be implemented here
});

BaseView.extend = Backbone.View.extend;

This is pretty simple, we're calling Backbone.View in our constructor, and then extending the Backbone.View prototype. This gives us the opportunity to define the instance variables in the constructor of BaseView.

Inherit values from a parent's prototype property

Backbone's event and attribute properties are on the prototype of Backbone.View. However, we don't want to overwrite the objects in every child class, and would rather have the events concatenated with the events of the parent. This requires some changes to our BaseView:

var BaseView = function(options) {
    this.inheritedEvents = [];

    Backbone.View.call(this, options);
}

_.extend(BaseView.prototype, Backbone.View.prototype, {
    baseEvents: {},

    events: function() {
        var e = _.extend({}, this.baseEvents);

        _.each(this.inheritedEvents, function(events) {
          e = _.extend(e, events);
        });

        return e;
    },

    addEvents: function(eventObj) {
        this.inheritedEvents.push(eventObj);
    }
});

BaseView.extend = Backbone.View.extend;

Let's look at what we're doing here. Firstly, we define the instance variable inheritedEvents, which is an array of event objects. When a child wants to add an object of arrays to be handled, it calls this.addEvents(eventObj) to have it added to the array. Because this is an instance variable, the events are individual to each instance and we don't need to override the parent's object.

We've also defined a prototype property called baseEvents, which is where you would put any events common across all BaseView instances.

Finally, we've changed events to be a function which returns the concatenation of the base events and all inherited events.

A child could add its own events like so:

var Child = BaseView.extend({
  initialize: function() {
    this.addEvents({
      'click .clickable': 'handleClick'
    });
  }
});

and it would still have the events defined in BaseView.

Subclassing Children - the full chain

Once we have this step handled, we can apply it throughout a chain of children. As long as each potential parent inherits from its parent in the correct way, introducing new children is seamless provided you do not override the events function with an object literal, and call addEvents to add your events.

Future Development

Attributes

The attributes object is handled differently. Unlike events, it cannot be a function, but it is used in Backbone's make function to create the element. For this reason, we need to keep the attributes object an object literal and also up-to-date as children add attributes to it. One solution is to make attributes an instance variable, and have it kept up-to-date with addAttributes like so:

var BaseView = function(options) {
    this.attributes = {};

    Backbone.View.apply(this, [options]);
};

_.extend(BaseView.prototype, Backbone.View.prototype, {
    baseAttributes: {},

    addAttributes: function(attr) {
        this.attributes = _.extend(this.attributes, attr);
        this.updateAttributes();
    },

    updateAttributes: function() {
        this.$el.attr(this.attributes);
    }
}

I'm concerned at the processing overhead of this (though I can't see there being much of an issue, as typically you'd add attributes when you initialize a view, before the element is in the DOM) of this, however, and would like to do some more investigation and I am open to ideas as to how to improve it.

Inheritance of Functions

Things like functions which are defined on the prototype are still overriden by children, which may not be desirable. A common pattern would be something like:

var BaseView = function(options) {
    Backbone.View.call(this, options);
}

_.extend(BaseView.prototype, Backbone.View.prototype, {
    initialize: function() {
      // perform BaseView initialize
      this.initializeInternal();
    }
});

BaseView.extend = Backbone.View.extend;

In this case, you'd have your children override initializeInternal. However, as your inheritance chain gets deeper, you may want to perform step-by-step initialization as you get further down the chain, and overriding initializeInternal would cause a lot of duplicate code at each childless view. A solution such as creating an initialize chain may work here, and having the root view have a chain of functions to call on initialize. Something like:

this.initializeChain = [];
this.initialize = _.chain.apply(this, this.initializeChain);

This code is untested, and I'm not sure where and how you would define your initialization functions without completely changing the pattern of creating views. Currently, we keep our functional logic as low down the inheritance chain as possible to avoid a lot of overriding.

Conclusion

By inheriting from Backbone.View you improve the readability (i.e. views are less cluttered), promote code reuse and make future developments easy. Although how to inherit from views isn't obvious at first, and Backbone's way of handling events and attributes make things a little tricky, I think being able to perform classical inheritance with Backbone is an incredibly important concept for large-scale web applications.

MORE BY STEVEN

A Webapp By Another Name III

Handling Validation Error Messages in Struts2

blog comments powered by Disqus