kidcapital
kidcapital

Reputation: 5174

Backbone class model not inheriting defaults

I have a backbone class:

class BaseClass extends Backbone.Model
   defaults:
    first_name: "John"

class ExtendClass extends BaseClass
   defaults:
    last_name: "Doe"

However, when I make a new ExtendClass, it does not have first_name.

Am I misunderstanding how Backbone extends?

Upvotes: 1

Views: 105

Answers (2)

istos
istos

Reputation: 2662

The extend() helper in Backbone allows you to extend prototypes. In plain JavaScript without Backbone's extend method we could setup prototype chains like in the following example:

function BaseClass(opts) {
  // Here `this.defaults` will point to `BaseClass.prototype.defaults` 
  // when we create an instance of BaseClass. 
  // 
  // When we create an instance of ChildClass `this.defaults` will 
  // point to `ChildClass.prototype.defaults` if it exists, otherwise
  // it will point to `BaseClass.prototype.defaults`.
  this.attrs = _.extend({}, this.defaults, opts);

  // Call `BaseClass.prototype.initialize` or `ChildClass.prototype.initialize`, 
  // depending on what instance we're creating.
  this.initialize();
}

BaseClass.prototype.defaults = {
  first_name: 'anonymous',
  friend_count: 500
};

BaseClass.prototype.initialize = function() {
  console.log('Hello ' + this.attrs.first_name);
};


function ChildClass(opts) {
  // Let the base class set the `attrs` property 
  // and call the initialize() method.
  BaseClass.call(this, opts);
}

// Set the prototype chain
ChildClass.prototype = Object.create(BaseClass.prototype);

// Create own `defaults` object, therefore overriding
// the one higher in the prototype chain.
ChildClass.prototype.defaults = {
  last_name: 'backbone',
  parent_name: 'Unknown'
};

ChildClass.prototype.initialize = function() {
  // Do something...
};

From the example above you can see that the ChildClass sets its own defaults property on its prototype, therefore when one of ChildClass instances queries for defaults, JavaScript will find the one in the ChildClass prototype object and not look any further.

This is exactly what happens in the background with Backbone. Backbone will look for a property named defaults and will merge it with the instance's attributes, and because ChildClass has its own defaults property, Backbone will not look any further, and it will not merge the BaseClass defaults automatically for you.

However, in Backbone you can set the defaults property to a function, and you can then manually access the BaseClass's defaults in order to merge them with the ChildClass's defaults. This manual wiring is intentional as Backbone doesn't want to assume anything about your object hierarchies (in this case your Models).

In JavaScript that would look like this:

var Note = Backbone.Model.extend({
  defaults: {
    title: 'Untitled',
    message: 'Lorem ipsum dolor...'
  },

  initialize: function() { /* ... */ },
});

var PrivateNote = Note.extend({
  defaults: function() {
    var defaults = _.extend({}, { date: new Date() }, Note.prototype.defaults);

    // You can also do this if you don't want to specify `Note` by its name:
    // var defaults = _.extend({}, { date: new Date() }, this.constructor.__super__.defaults);

    // `__super__` is specific to Backbone and in this case 
    // it gives you access to `Note.prototype`.

    return defaults;
  },

  initialize: function() { /* ... */ },
});

Now we get what we want:

var private = new PrivateNote();
private.toJSON();
//=> {date: Mon Feb 09 2015 00:57:14 GMT-0500 (EST), title: "Untitled", message: "Lorem ipsum dolor..."}

Note instances are untouched as we would expect:

var note = new Note()
note.toJSON();
//=> {title: "Untitled", message: "Lorem ipsum dolor..."}

Here is a js2.coffee example: Merge defaults with Parent defaults

Upvotes: 2

mu is too short
mu is too short

Reputation: 434665

I think you're misunderstanding how CoffeeScript extends. When you say:

class A
    p: ...
class B extends A
    p: ...

The B::p value completely hides A::p, CoffeeScript won't try to merge them or chain them or do anything special.

The defaults in a Backbone model can be an object or a function so you can use functions and CoffeeScript's super to merge them yourself:

class BaseClass extends Backbone.Model
   defaults: ->
       first_name: "John"

class ExtendClass extends BaseClass
   defaults: ->
       _(super).extend(last_name: "Doe")

That will give you both first_name and last_name in the ExtendClass defaults.

Demo: http://jsfiddle.net/ambiguous/wz6usg36/

Upvotes: 3

Related Questions