public
Last active

A 'super' method for backbone.js (plain javascript)

  • Download Gist
backbone_super.js
JavaScript
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
// This method gives you an easier way of calling super
// when you're using Backbone in plain javascript.
// It lets you avoid writing the constructor's name multiple
// times. You still have to specify the name of the method.
//
// So instead of having to write:
//
// User = Backbone.Model.extend({
// save: function(attrs) {
// this.beforeSave(attrs);
// return User.__super__.save.apply(this, arguments);
// }
// });
//
// You get to write:
//
// User = Backbone.Model.extend({
// save: function(attrs) {
// this.beforeSave(attrs);
// return this._super("save", arguments);
// }
// });
//
 
;(function(Backbone) {
 
// The super method takes two parameters: a method name
// and an array of arguments to pass to the overridden method.
// This is to optimize for the common case of passing 'arguments'.
function _super(methodName, args) {
 
// Keep track of how far up the prototype chain we have traversed,
// in order to handle nested calls to _super.
this._superCallObjects || (this._superCallObjects = {});
var currentObject = this._superCallObjects[methodName] || this,
parentObject = findSuper(methodName, currentObject);
this._superCallObjects[methodName] = parentObject;
 
var result = parentObject[methodName].apply(this, args || []);
delete this._superCallObjects[methodName];
return result;
}
 
// Find the next object up the prototype chain that has a
// different implementation of the method.
function findSuper(methodName, childObject) {
var object = childObject;
while (object[methodName] === childObject[methodName]) {
object = object.constructor.__super__;
}
return object;
}
 
_.each(["Model", "Collection", "View", "Router"], function(klass) {
Backbone[klass].prototype._super = _super;
});
 
})(Backbone);
backbone_super_spec.js
JavaScript
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
window.context = window.describe;
 
describe("_super", function() {
var Friend, Animal, Mammal, Pet, Dog, CockerSpaniel;
 
beforeEach(function() {
Friend = Backbone.Model.extend({
greet: function(personName, timeOfDay) {
return "Good " + timeOfDay + ", " + personName + ". My name is " + this.get("name") + ".";
}
});
 
// super needs to work even when there are classes in the
// inheritance hierarchy that do not override the method.
Animal = Friend.extend({ eats: "food" });
 
Mammal = Animal.extend({
greet: function(personName, timeOfDay) {
return this._super("greet", arguments) + " I'm a mammal.";
}
});
 
Pet = Mammal.extend({ livesInCaptivity: true });
 
Dog = Pet.extend({
greet: function(person, timeOfDay) {
return this._super("greet", arguments) + " Ruff ruff!";
}
});
 
CockerSpaniel = Dog.extend({ cute: true });
 
spyOn(Friend.prototype, 'greet').andCallThrough();
spyOn(Mammal.prototype, 'greet').andCallThrough();
spyOn(Dog.prototype, 'greet').andCallThrough();
});
 
context("when used only once in the inheritance hierarchy", function() {
context("in the class's own implementation of the method", function() {
beforeEach(function() {
this.friend = new Mammal({ name: "Benjie" });
});
 
itCallsTheOverriddenMethodCorrectly();
});
 
context("in a superclass's implementation of the method", function() {
beforeEach(function() {
this.friend = new Pet({ name: "Benjie" });
});
 
itCallsTheOverriddenMethodCorrectly();
 
it("does not call the object's own method more than once", function() {
this.friend.greet("Barbara", "morning");
expect(Mammal.prototype.greet.callCount).toBe(1);
});
});
});
 
context("when used twice in the inheritance hierarchy", function() {
context("with the first case happening in the class's own implementation", function() {
beforeEach(function() {
this.friend = new Dog({ name: "Benjie" });
});
 
itCallsTheOverriddenMethodCorrectly();
 
it("calls both of the ancestor classes' methods", function() {
var greeting = this.friend.greet("Barbara", "morning");
expect(greeting).toContain("I'm a mammal.");
});
});
 
context("with the first case happening in a superclass's implementation", function() {
beforeEach(function() {
this.friend = new CockerSpaniel({ name: "Benjie" });
});
 
itCallsTheOverriddenMethodCorrectly();
 
it("does not call the object's own method more than once", function() {
this.friend.greet("Barbara", "morning");
expect(Dog.prototype.greet.callCount).toBe(1);
});
});
});
 
context("when the overridden method calls super by referencing its constructor explicitly", function() {
beforeEach(function() {
Mammal.prototype.greet = function(personName, timeOfDay) {
return Mammal.__super__.greet.apply(this, arguments) + " I'm a mammal.";
}
spyOn(Mammal.prototype, 'greet').andCallThrough();
 
this.friend = new CockerSpaniel({ name: "Benjie" });
});
 
itCallsTheOverriddenMethodCorrectly();
});
 
function itCallsTheOverriddenMethodCorrectly() {
it("passes the given arguments to the overridden method", function() {
var greeting = this.friend.greet("Barbara", "morning");
expect(greeting).toContain("Good morning, Barbara.");
});
 
it("calls the overridden method on the recieving object", function() {
var greeting = this.friend.greet("Barbara", "morning");
expect(greeting).toContain("My name is Benjie.");
});
 
it("calls the overridden method only once", function() {
this.friend.greet("Barbara", "morning");
expect(Friend.prototype.greet.callCount).toBe(1);
expect(Mammal.prototype.greet.callCount).toBe(1);
});
 
it("can be called multiple times with the same results", function() {
var greeting = this.friend.greet("Barbara", "morning");
expect(this.friend.greet("Barbara", "morning")).toBe(greeting);
expect(this.friend.greet("Barbara", "morning")).toBe(greeting);
});
}
});

Pretty cool. Thanks for the inspiration.

I like this simple approach! Max @maxbrunsfeld, what is the license on this Gist? MIT?

Thanks!
Also, you can use _super to return attributes, not only methods, just change:

var result = parentObject[methodName].apply(this, args || []);

to:

var result = parentObject[methodName]; // Attribute. Maybe change the variable name.
if (_.isFunction(result)) result = result.apply(this, _.rest(arguments));

hmm doesn't seem to work when overriding constructor, for ex:

this._super('constructor', arguments)

i agree it doesn't work with this._super('initialize', arguments) , i use the latest backbone js release. It seems that the argument are not passed ????

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.