Skip to content

Instantly share code, notes, and snippets.

@nicknisi
Forked from maxbrunsfeld/backbone_super.js
Created August 8, 2012 12:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicknisi/3294816 to your computer and use it in GitHub Desktop.
Save nicknisi/3294816 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);
});
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment