41

I have a view called Pannel which is just a background with a close button. I want to extend that view to one called PannelAdvanced. How would I do that with backbone.js?

Right now all the examples have Backbone.View.Extend but those just extend Backbone.View; I want to extend my PannelView.

Machavity
  • 30,841
  • 27
  • 92
  • 100
Chapsterj
  • 6,483
  • 20
  • 70
  • 124
  • It works exactly the same way - `Panel.extend({ ... })` – nrabinowitz Oct 12 '11 at 04:14
  • I have my Panel = Backbone.View.extend() then I have advancedPanel = Panel.extend() but when I do a console log for a variable in the initialize method of Panel inside of advancedPanel it shows up as undefined. – Chapsterj Oct 12 '11 at 04:43
  • Can I just call Panel.extend() without instantiating Panel first. – Chapsterj Oct 12 '11 at 04:59

3 Answers3

99

The easiest way to inherit a view is to do what other people have already suggested in the comments:

var Pannel = Backbone.View.extend({
});

var PannelAdvanced = Pannel.extend({
});

But like you've noted in your comments, if you have an initialize method in Pannel, then it won't be called if you also have an initialize method in PannelAdvanced, so you have to call Pannel's initialize method explicitly:

var Pannel = Backbone.View.extend({
   initialize: function(options){
      console.log('Pannel initialized');
      this.foo = 'bar';
   }
});

var PannelAdvanced = Pannel.extend({
   initialize: function(options){
      Pannel.prototype.initialize.apply(this, [options])
      console.log('PannelAdvanced initialized');
      console.log(this.foo); // Log: bar
   }
});

It's a bit ugly because if you have a lot of Views that inherit from Pannel, then you'll have to remember to call Pannel's initialize from all of them. Even worse, if Pannel doesn't have an initialize method now but you choose to add it in the future, then you'll need to go to all of the inherited classes in the future and make sure they call Pannel's initialize. So here's an alternative way to define Pannel so that your inherited views don't need to call Pannel's initialize method:

var Pannel = function (options) {

    // put all of Panel's initialization code here
    console.log('Pannel initialized');
    this.foo = 'bar';

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

_.extend(Pannel.prototype, Backbone.View.prototype, {

    // put all of Panel's methods here. For example:
    sayHi: function () {
        console.log('hello from Pannel');
    }
});

Pannel.extend = Backbone.View.extend;


// other classes inherit from Panel like this:
var PannelAdvanced = Pannel.extend({

    initialize: function (options) {
        console.log('PannelAdvanced initialized');
        console.log(this.foo);
    }
});

var pannelAdvanced = new PannelAdvanced(); //Log: Pannel initialized, PannelAdvanced initialized, bar
pannelAdvanced.sayHi(); // Log: hello from Pannel
Johnny Oshika
  • 54,741
  • 40
  • 181
  • 275
  • 1
    This pattern doesn't work with events being declared in both child and parent as the parent's events key in the dict is overwritten by the child's when you call `var PannelAdvanced = Pannel.extend({events:{...}})`. You can declare a parentsEvents key in the parent, and call then `this.delgateEvents(this.parentEvents)` in the parent constructor but then due to [this line](https://github.com/documentcloud/backbone/blob/master/backbone.js#L1310) in backbone, they will again be dropped due to the child view. – AJP Mar 01 '13 at 15:06
  • 2
    Actually you can do it. Declare the child events in a childEvents key or whatever, then in the parent, declare an `events` function with: `return _.extend({"click .parentClass": "parentFunction"}, this.childEvents)` – AJP Mar 01 '13 at 15:20
  • Using `_super` is not the best choice. https://github.com/jashkenas/backbone/pull/787#issuecomment-14327908 – Thiago Festa Jan 07 '14 at 17:31
  • I agree. It's better to use prototype. I updated my answer to reflect this. – Johnny Oshika Jan 08 '14 at 18:25
29

This is one of the reasons I like using Coffeescript so much. Things like inheritance are so much nicer. To piggyback @JohnnyO's correct answer, I can say the same thing in Coffeescript:

class Panel extends Backbone.View
    initialize: ->
        console.log 'Panel initialized'
        @foo = 'bar'

class PanelAdvanced extends Panel
    initialize: ->
        super
        console.log 'PanelAdvanced initialized'
        console.log @foo
Brian Genisio
  • 47,787
  • 16
  • 124
  • 167
  • 6
    I translated that to javascript just to see if I get it write: http://jsfiddle.net/7qssz/ `super` becomes `Panel.prototype.initialize.apply(this, arguments);` – NicoGranelli Aug 07 '12 at 01:29
  • I wasn't able to get this to work while passing arguments. They don't show up in `this.options`. – maletor Jun 26 '13 at 19:13
  • @Maletor: What version of Backbone are you using? It was added around the 0.9.9 version or something like that. – Brian Genisio Jun 29 '13 at 17:29
  • Works. @NicoGranelli your code is not 1:1 translation from Coffeescript. Please [see and run the code in jsbin](http://jsbin.com/yosubifewu/2/edit?html,js,output). Select _Convert to Javascript_, from the CoffeeScript popup. – aMarCruz Apr 22 '15 at 00:09
8

To piggyback further a bit:

I liked @JohnnyO's approach but wanted to confirm that a resulting view was still capable of doing everything it's supposed to. Given his approach, I didn't suspect there would be any issues, but I wanted to be a bit more certain.

So, I took a minute and adapted the Backbone.js Views test suite to the multiple inheritance technique @JohnnyO proposes.

You can run the results at http://jsfiddle.net/dimadima/nPWuG/. All tests pass.

My base and extended views:

var RegularView = function (options) {
  // All of this code is common to both a `RegularView` and `SuperView`
  // being constructed.
  this.color = options && (options.color || 'Green');

  // If execution arrives here from the construction of
  // a `SuperView`, `Backbone.View` will call `initialize`
  // that belongs to `SuperView`. This happens because here
  // `this` is `SuperView`, and `Backbone.View`, applied with
  // the current `this` calls `this.initialize.apply(this, arguments)`
  Backbone.View.apply(this, arguments)
};

RegularView.extend = Backbone.View.extend;

_.extend(RegularView.prototype, Backbone.View.prototype, {
  // Called if a `RegularView` is constructed`,
  // Not called if a `SuperView` is constructed.
  initialize: function () {
    console.log('RegularView initialized.');
  },

  say_hi: function() {
    console.log('Regular hi!');
  }

});

var SuperView = RegularView.extend({
  // Called if a `SuperView` is constructed`,
  // Not called if a `RegularView` is constructed.
  initialize: function(options) {
    console.log('SuperView initialized.')
  },

  say_hi: function() {
    console.log('Super hi!');
  }
})

For the test suite, I grabbed the latest views tests from GitHub and replaced occurrences of Backbone.View with RegularView. The tests then use RegularView and the results of RegularView.extend() to make sure both do what they're supposed to.

Dmitry Minkovsky
  • 36,185
  • 26
  • 116
  • 160