27

Given an instance of Backbone model, how can I know the "class" (so to speak) of this instance ?

For instance:

class Car extends Backbone.Model

mycar = new Car()

And what I need is:

mycar.modelName # => 'Car'
rxgx
  • 5,089
  • 2
  • 35
  • 43
Blacksad
  • 14,906
  • 15
  • 70
  • 81
  • 1
    You need to be more specific. A model could have multiple views. The view has the .el reference which AFAIK should be enough, *if you are considering views*. But that may not be what you're asking about. – JayC Mar 13 '12 at 14:34
  • Ah, nevermind. You're talking programmatically. – JayC Mar 13 '12 at 14:35
  • Why do you need the classname? To create a new model of same type or to access a class level function or something else? Its interesting question. – websymphony Mar 13 '12 at 14:47
  • @websymphony I need it because I have a mix-in which at a certain stage requires the class name. – Blacksad Mar 13 '12 at 14:56
  • There won't be a notion of class in JavaScript for another 5 years (ECMAScript 6+?). The object creation syntax of CoffeeScript causes much confusion to those just getting familiar with the language. Using `Object.create` and an `extend` method may be an easier way to implement inheritance via mixins. – rxgx Jun 05 '12 at 02:56

5 Answers5

23

I tried Mitch's suggestion but in Chrome, model.constructor.name is always "" for me.

I chose instead to use a convention. I now create a class property using the second param of extend that works as follows:

directory.models.SomeModel = Backbone.Model.extend({ 
    //  usual model instance stuff
}, {
    modelType: "SomeModel"   // class property
});

I found that I can always get what I want like this:

var mt = model.constructor.modelType;
Anton Rudeshko
  • 1,039
  • 2
  • 11
  • 20
14

As of writing you can get a model's class name by doing the following:

model.constructor.name

This works for me in the Chrome browser.

Mitch
  • 1,535
  • 13
  • 21
  • 15
    Note that this is not going to work if you have a minifier like Jammit, the constructor will change after packaging. – dB. Oct 09 '12 at 14:54
10

It's problematic in general, I think. I was going to suggest some of the options mentioned here ( How do I get the name of an object's type in JavaScript? ) but I was having issues with the first option. You could always extend all your models to have a function that returned a "name" for the model, but I can understand why you might find that less than satisfactory.

Community
  • 1
  • 1
JayC
  • 7,053
  • 2
  • 25
  • 41
  • 1
    I agree, either you solve it manually as @JayC suggests or you are talking about a very old generic JS concern still not resolved. – fguillen Mar 13 '12 at 15:07
0

This is already resolved in all browsers except for IE, but a polyfill is easy to make. The answer is Function.name:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/name

Definitely take advantage of this! In ES6 classes, a constructor's Function.name is the class name, so in ES6:

class Car { /*...*/ }
var c = new Car();
console.log(c.constructor.name); // "Car"

In current ES5, not all libraries take advantage of this. Backbone, for example, does not. :( Backbone constructors are anonymous functions, so

var m = new Backbone.Model();
console.log(m.constructor.name); // "undefined" :(

unfortunately. Like other's have said, if you're using Backbone, you'll have to make your own naming system.

Here's a polyfill for Function.name:

// Polyfill for Function.name on browsers that do not support it (IE):
// See: http://stackoverflow.com/questions/6903762/function-name-not-supported-in-ie
if (!(function f() {}).name) {
    Object.defineProperty(Function.prototype, 'name', {
        get: function() {
            var name = this.toString().match(/^\s*function\s*(\S*)\s*\(/)[1];

            // For better performance only parse once, and then cache the
            // result through a new accessor for repeated access.
            Object.defineProperty(this, 'name', { value: name });

            return name;
        }
    });
}
trusktr
  • 44,284
  • 53
  • 191
  • 263
0

You can query the inheritance tree (aka prototype chain) for your object instance like this:

object.__proto__.__proto__.__proto__

You can see what is available. Any type name property will have to be added by you to each view and/or model manually. This is the cleanest way to go. I recommend this. Simply add 'className: "MyCompany.MyProject.MyArea.MyView"' to each of your views and/or models.

If you don't add a className property to your objects, there is a slightly hacky way to still get it (eg for debug output purposes):

Assuming you do something like: "MyCompany.MyProject.MyArea.MyView = Backbone.View.extend({" the view/model class name is in a global namespace var name. From the object instance we cannot reach that global var name. So while not really ideal, the best we can do is traverse the namespace looking for the var:

function findBackboneClassName(ns, object, prefix, depth) {
    prefix = typeof prefix !== 'undefined' ? prefix : "";
    depth = typeof depth !== 'undefined' ? depth : 0;
    if (depth > 5) return null;

    for (i in ns) {
        try { ns[i]; } catch (e) { continue; }
        if (/top|window|document|parent|frames|self|chrome|form|theForm/.test(i)) continue;
        //console.log(">" + prefix + "." + i + " " + typeof(ns[i]));

        if (ns[i] == object.constructor) {
            //console.log("Found:", prefix + "." + i);
            return prefix + "." + i;
        }

        if (typeof (ns[i]) == "object") {
            var r = findBackboneClassName(ns[i], object, prefix + (prefix != "" ? "." : "") + i, depth + 1);
            if (r != null) return r;
        }
    }
    return null;
}
findBackboneClassName(window, myBackboneViewInstance);

Ideally you are using a namespace for your types other than "window". That makes it much cleaner and easier to traverse. You simply replace "window" with the base of your namespace and pass in the desired prefix if you want it prefixed properly. You can also remove a couple lines like the try..catch and the if..continue.

findBackboneClassName(MyCompany.MyProject, myBackboneViewInstance, "MyCompany.MyProject");

The trick of getting class name from objectInstance.constructor does not work for backbone because of the way it does inheritance. All ctors look the same for me: "function () { return parent.apply(this, arguments); }".

Curtis Yallop
  • 6,696
  • 3
  • 46
  • 36