Super in Backbone

46,511

Solution 1

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

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

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

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

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

Share:
46,511
Andreas Köberle
Author by

Andreas Köberle

Updated on July 16, 2022

Comments

  • Andreas Köberle
    Andreas Köberle almost 2 years

    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
        }
    })
    
  • Mauvis Ledford
    Mauvis Ledford over 11 years
    Just a little background on this answer: __super__ is a reference to the parent's prototype that Backbone framework makes every time a Backbone model, collection, router, or view is extended. While it's not a standard property it does work cross-browser since its framework generated. However, even the Backbone official docs don't mention this and say to use Backbone.Model.prototype.set.call(this, attributes, options); method. Both seem to work fine, though.
  • Mikael Lepistö
    Mikael Lepistö almost 11 years
    Backbone docs seems to suggest e.g. Backbone.Model.prototype.set.apply(this, arguments); what is the difference between using prototype.func_name.apply(...) and prototype.func_name.call(...) ?
  • Bobby
    Bobby almost 11 years
    @MikaelLepistö see the question stackoverflow.com/questions/1986896/…
  • MikeSchinkel
    MikeSchinkel almost 11 years
    @MauvisLedford Is your example code correct or should the .set. be .clone. for the OP's use case?
  • Mauvis Ledford
    Mauvis Ledford almost 11 years
    Mine was just a non-related example. It would be Backbone.Model.prototype.clone.call(this, attributes, options); in his case.
  • Jason M
    Jason M almost 10 years
    You could also use: this.constructor.__super__
  • monastic-panic
    monastic-panic over 9 years
    this only works when there is only one level of implementation A.foo -> B.foo, in the case of A.foo -> B.foo -> C.foo you will get a stack overflow due to B's this refering to itself
  • Tobias Cudnik
    Tobias Cudnik about 9 years
    This wont work with the full prototype chain. If the direct super class' prototype doesn't contain the method, there'll be an exception.
  • mab
    mab almost 9 years
    My answer below fixes the problem of having more than one level of inheritance,
  • Emile Bergeron
    Emile Bergeron about 8 years
    That's not an inheritance example and not the same "parent" that is discussed in this thread. Also, the var self = this is unnecessary in this case since it's not inside a callback function losing the context.
  • Blaine Kasten
    Blaine Kasten about 8 years
    Truth. I wrote this code when I was young and dumb. Thanks for the update here. Feel free to edit the post and update it. I don't write Backbone anymore, so I don't feel like I can confidently update it.
  • Emile Bergeron
    Emile Bergeron over 7 years
    It should be Backbone.Model.prototype.clone and this.origclone(). It's equivalent to Backbone.Model.prototype.clone.call(this).
  • Emile Bergeron
    Emile Bergeron over 7 years
    Editing it won't help since it's not answering the question at hand. I think the only option would be to remove the answer altogether.
  • Emile Bergeron
    Emile Bergeron over 7 years
    @JasonM nope, this.constructor is not guaranteed to be MyModel and it would make a stack overflow if MyModel was used as a parent class.
  • I_ATE_YOUR_WORK_FILES
    I_ATE_YOUR_WORK_FILES over 5 years
    I wish I could upvote this answer more than once. First sensible description of super I've found on the internet. This concept always confused me, it's quite otherworldly from standard JavaScript...