Super in Backbone

JavascriptInheritancebackbone.js

Javascript Problem Overview


When I override the clone() method of a Backbone.Model, is there a way to call this overriden method from my implantation? Something like this:

var MyModel = Backbone.Model.extend({
    clone: function(){
        super.clone();//calling the original clone method
    }
})

Javascript Solutions


Solution 1 - Javascript

You'll want to use:

Backbone.Model.prototype.clone.call(this);

This will call the original clone() method from Backbone.Model with the context of this(The current model).

From Backbone docs:

> Brief aside on super: JavaScript does not provide a simple way to call > super — the function of the same name defined higher on the prototype > chain. If you override a core function like set, or save, and you want > to invoke the parent object's implementation, you'll have to > explicitly call it.

var Note = Backbone.Model.extend({
 set: function(attributes, options) {
 Backbone.Model.prototype.set.apply(this, arguments);
 ...
 }    
});

Solution 2 - Javascript

You can also use the __super__ property which is a reference to the parent class prototype:

var MyModel = Backbone.Model.extend({
  clone: function(){
    MyModel.__super__.clone.call(this);
  }
});

Solution 3 - Javascript

Josh Nielsen found an elegant solution for this, which hides a lot of the ugliness.

Just add this snippet to your app to extend Backbone's model:

Backbone.Model.prototype._super = function(funcName){
    return this.constructor.prototype[funcName].apply(this, _.rest(arguments));
}

Then use it like this:

Model = Backbone.model.extend({
    set: function(arg){
        // your code here

        // call the super class function
        this._super('set', arg);
    }
});

Solution 4 - Javascript

Working from the answers given by geek_dave and charlysisto, I wrote this to add this._super(funcName, ...) support in classes that have multiple levels of inheritance. It's worked well in my code.

Backbone.View.prototype._super = Backbone.Model.prototype._super = function(funcName) {
        // Find the scope of the caller.
        var scope = null;
        var scan = this.__proto__;
        search: while (scope == null && scan != null) {
            var names = Object.getOwnPropertyNames(scan);
            for (var i = 0; i < names.length; i++) {
                if (scan[names[i]] === arguments.callee.caller) {
                    scope = scan;
                    break search;
                }
            }
            scan = scan.constructor.__super__;
        }
        return scan.constructor.__super__[funcName].apply(this, _.rest(arguments));
    };

A year later I've fixed some bugs and made things faster. Below is the code that I use now.

var superCache = {};

// Hack "super" functionality into backbone. 
Backbone.View.prototype._superFn = Backbone.Model.prototype._superFn = function(funcName, _caller) {
    var caller = _caller == null ? arguments.callee.caller : _caller;
    // Find the scope of the caller.
    var scope = null;
    var scan = this.__proto__;
    var className = scan.constructor.className;
    if (className != null) {
        var result = superCache[className + ":" + funcName];
        if (result != null) {
            for (var i = 0; i < result.length; i++) {
                if (result[i].caller === caller) {
                    return result[i].fn;
                }
            }
        }
    }
    search: while (scope == null && scan != null) {
        var names = Object.getOwnPropertyNames(scan);
        for (var i = 0; i < names.length; i++) {
            if (scan[names[i]] === caller) {
                scope = scan;
                break search;
            }
        }
        scan = scan.constructor.__super__;
    }
    var result = scan.constructor.__super__[funcName];
    if (className != null) {
        var entry = superCache[className + ":" + funcName];
        if (entry == null) {
            entry = [];
            superCache[className + ":" + funcName] = entry;
        }
        entry.push({
                caller: caller,
                fn: result
            });
    }
    return result;
};

Backbone.View.prototype._super = Backbone.Model.prototype._super = function(funcName) {
        var args = new Array(arguments.length - 1);
        for (var i = 0; i < args.length; i++) {
            args[i] = arguments[i + 1];
        }
        return this._superFn(funcName, arguments.callee.caller).apply(this, args);
    };

Then given this code:

var A = Backbone.Model.extend({ 
 //   className: "A",
    go1: function() { console.log("A1"); },  
    go2: function() { console.log("A2"); },  
    });
    
var B = A.extend({ 
 //   className: "B",
    go2: function() { this._super("go2"); console.log("B2"); },  
    });
    
var C = B.extend({ 
 //   className: "C",
    go1: function() { this._super("go1"); console.log("C1"); },
    go2: function() { this._super("go2"); console.log("C2"); }  
    });
    
var c = new C();
c.go1();
c.go2();

The output in the console is this:

A1
C1
A2
B2
C2

What's interesting is that class C's call to this._super("go1") scans the class hierarchy until it gets a hit in class A. Other solutions do not do this.

P.S. Uncomment the className entries of the class definitions to enable caching of the _super lookup. (The assumption is that these class names will be unique in the application.)

Solution 5 - Javascript

If you want just to call this._super(); without passing the function name as an argument

Backbone.Controller.prototype._super = function(){
	var fn = Backbone.Controller.prototype._super.caller, funcName;

	$.each(this, function (propName, prop) {
		if (prop == fn) {
			funcName = propName;
		}
	});

    return this.constructor.__super__[funcName].apply(this, _.rest(arguments));
}

Better use this plugin: https://github.com/lukasolson/Backbone-Super

Solution 6 - Javascript

I believe you can cache the original method (although not tested):

var MyModel = Backbone.Model.extend({
  origclone: Backbone.Model.clone,
  clone: function(){
    origclone();//calling the original clone method
  }
});

Solution 7 - Javascript

backbone._super.js, from my gists: https://gist.github.com/sarink/a3cf3f08c17691395edf

// Forked/modified from: https://gist.github.com/maxbrunsfeld/1542120
// 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:
//
//    var Animal = Backbone.Model.extend({
//        word: "",
//        say: function() {
//            return "I say " + this.word;
//        }
//    });
//    var Cow = Animal.extend({
//        word: "moo",
//        say: function() {
//            return Animal.prototype.say.apply(this, arguments) + "!!!"
//        }
//    });
//
//
// You get to write:
//
//    var Animal = Backbone.Model.extend({
//        word: "",
//        say: function() {
//            return "I say " + this.word;
//        }
//    });
//    var Cow = Animal.extend({
//        word: "moo",
//        say: function() {
//            return this._super("say", arguments) + "!!!"
//        }
//    });

(function(root, factory) {
    if (typeof define === "function" && define.amd) {
        define(["underscore", "backbone"], function(_, Backbone) {
            return factory(_, Backbone);
        });
    }
    else if (typeof exports !== "undefined") {
        var _ = require("underscore");
        var Backbone = require("backbone");
        module.exports = factory(_, Backbone);
    }
    else {
        factory(root._, root.Backbone);
    }
}(this, function(_, Backbone) {
    "use strict";

    // Finds the next object up the prototype chain that has a different implementation of the method.
    var findSuper = function(methodName, childObject) {
        var object = childObject;
        while (object[methodName] === childObject[methodName]) {
            object = object.constructor.__super__;
        }
        return object;
    };

    var _super = function(methodName) {
        // 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;
        var parentObject  = findSuper(methodName, currentObject);
        this.__superCallObjects__[methodName] = parentObject;

        // If `methodName` is a function, call it with `this` as the context and `args` as the arguments, if it's an object, simply return it.
        var args = _.tail(arguments);
        var result = (_.isFunction(parentObject[methodName])) ? parentObject[methodName].apply(this, args) : parentObject[methodName];
        delete this.__superCallObjects__[methodName];
        return result;
    };

    // Mix in to Backbone classes
    _.each(["Model", "Collection", "View", "Router"], function(klass) {
        Backbone[klass].prototype._super = _super;
    });

    return Backbone;
}));

Solution 8 - Javascript

In the case that you don't know what the parent class is exactly (multiple inheritance or you want a helper function) then you can use the following:

var ChildModel = ParentModel.extend({
  
  initialize: function() {
    this.__proto__.constructor.__super__.initialize.apply(this, arguments);
    // Do child model initialization.
  }
  
});

With helper function:

function parent(instance) {
  return instance.__proto__.constructor.__super__;
};

var ChildModel = ParentModel.extend({
  
  initialize: function() {
    parent(this).initialize.apply(this, arguments);
    // Do child model initialization.
  }
  
});

Solution 9 - Javascript

Pass the parent class as an option during instantiation:

BaseModel = Backbone.Model.extend({
    initialize: function(attributes, options) {
        var self = this;
        this.myModel = new MyModel({parent: self});
    } 
});

Then in your MyModel you can call parent methods like this

this.options.parent.method(); Keep in mind this creates a retain cycle on the two objects. So to let the garbage collector do it's job you would need to manually destroy the retain on one of the objects when finished with it. If you're application is pretty large. I would encourage you to look more into hierarchal setups so events can travel up to the correct object.

Solution 10 - Javascript

2 functions below, one requires you pass in the function name, the other can "discover" which function we want the super version of

Discover.Model = Backbone.Model.extend({
       _super:function(func) {
        var proto = this.constructor.__super__;
        if (_.isUndefined(proto[func])) {
            throw "Invalid super method: " + func + " does not exist in prototype chain.";
        }
        return proto[func].apply(this, _.rest(arguments));
    },
    _superElegant:function() {
        t = arguments;
        var proto = this.constructor.__super__;
        var name;
        for (name in this) {
            if (this[name] === arguments.callee.caller) {
                console.log("FOUND IT " + name);
                break;
            } else {
                console.log("NOT IT " + name);
            }
        }
        if (_.isUndefined(proto[name])) {
            throw "Super method for: " + name + " does not exist.";
        } else {
            console.log("Super method for: " + name + " does exist!");
        }
        return proto[name].apply(this, arguments);
    },
});

Solution 11 - Javascript

Here is how I would do this:

ParentClassName.prototype.MethodToInvokeName.apply(this);

so for your example this is:

Model.prototype.clone.apply(this)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAndreas K&#246;berleView Question on Stackoverflow
Solution 1 - JavascriptBobbyView Answer on Stackoverflow
Solution 2 - JavascriptcharlysistoView Answer on Stackoverflow
Solution 3 - JavascriptDave CadwalladerView Answer on Stackoverflow
Solution 4 - JavascriptmabView Answer on Stackoverflow
Solution 5 - JavascriptRoman KromView Answer on Stackoverflow
Solution 6 - JavascriptswatkinsView Answer on Stackoverflow
Solution 7 - JavascriptKabir SarinView Answer on Stackoverflow
Solution 8 - JavascriptNathan HadzarigaView Answer on Stackoverflow
Solution 9 - JavascriptBlaine KastenView Answer on Stackoverflow
Solution 10 - JavascriptAlanView Answer on Stackoverflow
Solution 11 - JavascriptAndy StannardView Answer on Stackoverflow