Skip to content

Instantly share code, notes, and snippets.

@maxbrunsfeld
Created December 30, 2011 23:58
Show Gist options
  • Star 32 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save maxbrunsfeld/1542120 to your computer and use it in GitHub Desktop.
Save maxbrunsfeld/1542120 to your computer and use it in GitHub Desktop.
A 'super' method for backbone.js (plain javascript)
// 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);
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);
});
}
});
@chikamichi
Copy link

Pretty cool. Thanks for the inspiration.

@kalebdf
Copy link

kalebdf commented Jun 26, 2012

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

@mmas
Copy link

mmas commented May 13, 2013

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));

@longlho
Copy link

longlho commented Aug 7, 2013

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

this._super('constructor', arguments)

@tyler08
Copy link

tyler08 commented Aug 18, 2013

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 ????

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment