Developer's Perception

Odd thoughts from a developer.

Extending Backbone

| Comments

For the past half year or so I have been using Backbone. Besides the obvious questions of how to fit the different pieces together, when to use what, and in general understanding the library, I have experimented with different ways of extended the existing Backbone behavior.

Having tried a lot of different methods I ended up using 2 different approaches for 2 different scenarios.

Mixin

You can use the different Backbone modules’ extend function (they all have the same, which is not to be confused with the underscore extend function) to achieve code reuse. For instance if you wanted to create a model with an additional function you could do like this, by extending Backbone.Model:

1
2
3
var someModel = Backbone.Model.extend({
  someFunction: function(){ }
});

This is the normal way to use extend on a model, and Backbone will ensure the prototype chain is set up correctly. This is often used to create hierarchy of base models some potentially with many functions leading to a huge object that is used to extend all models from, like:

1
2
3
4
5
6
7
8
9
10
var motherOfAllModels = Backbone.Model.extend({
   someFunction: function(){ },
   someFunction2: function(){ },
   someFunction3: function(){ },
   someFunction4: function(){ },
   someFunction5: function(){ },
   someFunction6: function(){ }
});

var myModel = motherOfAllModels.extend({});

However, at least in my opinion this is not necessarily a good way of using such a powerful feature. A given model can be extended with a number of features from different objects:

1
2
3
4
5
6
7
8
9
10
11
12
13
var niceObject = function() {
  return {
      someFunction: function(){}
  };
}();

var niceObject2 = function() {
  return {
      someFunction2: function(){}
  };
}();

var myModel = Backbone.Model.extend(niceObject).extend(niceObject2);

This way it lets you build features in small objects that are easy to test seperately. Basically, creating a model becomes a matter of picking the different features you would like have enabled instead of dragging around a huge “base” model or hierarchy of base model.

There is a couple of problems with this use of the extend method. If the object contains methods with same names the last one to be mixed in (extended with) will overwrite the earlier ones and the prototype chain will be kinda of weird.

Wrapping

I have experimented with a number of ways of decorating the behavior of a Backbone modules, and basically found two feasible paths.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var dog = Backbone.Model.extend({
  bark: function(){
      return 'vuf';
  }
});

var bulldog = dog.extend({
  bark: function(){
      return 'vaf ' + dog.prototype.bark.call(this);
  }
});

var frenchBulldog = bulldog.extend({
  bark: function(){
         return 'vif ' + bulldog.prototype.bark.call(this);
    }
});

var muffin = new frenchBulldog();
expect(muffin.bark()).toBe('vif vaf vuf');

This way is probably used widely by Backbone developers, but it has a few challenges.

  • It requires a named var like bulldog to access the prototype.
  • It is pretty much a prototype chain.
  • I can’t make Muffin a french bulldog without also making her a bulldog… (as if that would make sense).
  • Overview of what is happening – usually I will not be able to see that a french bulldog is actually a dog without going elsewhere to look in my code.

Thus, I invented (probably not as the first person though!) a different way of wrapping a Backbone.Model with the behavior of my wishes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var asDog = function(model){
  var oldBark = model.prototype.bark;
  return model.extend({
      bark: function(){
          return this.noise + ' vif ' + oldBark.call(this);
      }
  });
};

var asBulldog = function(model){
  var oldBark = model.prototype.bark;
  return model.extend({
      bark: function(){
          return this.noise + ' vaf ' + oldBark.call(this);
      }
  });
};

var frenchBulldog = asBulldog(asDog(Backbone.Model.extend({
  noise: 'vuf',
  bark: function(){
      return this.noise;
  }
})));

var muffin = new frenchBulldog();
expect(muffin.bark()).toBe('vuf vaf vuf vif vuf');

What is like about this way is:

  • I can “chain” the wrapping.
  • I can make a french bulldog a bulldog without making it a dog.
  • When I find the definition of a french bulldog I can see all the ways of it.

I have not found anyone else who have done a similar implementation, maybe I have looked the wrong places , maybe I just got it wrong and it is really nice with a mother of all base model, or we really benefit from having a long prototype chain.s

Comments