Debugging Javascript (Backbone and Marionette)
Asked Answered
M

4

14

Right now, while I am debugging backbone or marionette using the chrome dev tools, I end up setting break points and whatnot, but once the code pauses, its hard to tell what type of objects i'm working with because chrome labels everything a "child".
(I think because that's the constructor function)

Is there any easy way to either change this declaration or determine which type of model/collection i'm using.

The amount craziness this causes in me wants to start doing something like this:

MyModel = Backbone.Model.Extend({
    // the $$$ puts it at the top of the inspector, the NAME is just for other devs
    $$$NAME = "MyModel",  
    ...
});

I don't really like it, because its... ugly, its a variable... and it only helps when I inspect and expand the variable... it would be great to change the name chrome uses to display it.

In any case, does anyone know how to change the name? or have some other cleaner convention you use?

Thanks!

Matt

Mozambique answered 14/2, 2013 at 0:48 Comment(0)
A
33

Background

It is interesting to look at why the browser uses “child” to display the type of Backbone objects in the console / debugger.

All JavaScript objects have a constructor property, a reference to the function used to create the object. The constructor is used by the browser to display the object’s “type” in the console / debugger. The value of the constructor function’s name property will be used if it is not empty. However, only functions defined using named function expressions get a useful name property:

function A() {  }
console.log(A.name); // 'A' 

Anonymous functions have an empty name property:

var B = function() {  };
console.log(B.name); // ''

So, what happens with anonymous functions? Chrome infers the name of anonymous functions from the name of the variable or property to which the function was first assigned. Here are some examples:

// 1. named function expression - objects will show as “a” in the console
function a() { … }

// 2. anonymous function assigned to variable - objects will show as “b” in the console
var b = function(){ … };

// 3. anonymous function assigned to property of object - objects will show as “container.c” in the debugger
var container = {
    c: function() { … }
};

A more detailed script is available here: http://jsfiddle.net/danmalcolm/Xa7ma/6/

The browser appears to get this name from the source code - there isn’t a JavaScript feature that can tell you at runtime the name of the first variable that a function was assigned to. Other browsers support a convention where a displayName property defined on anonymous constructor functions is used, but this doesn’t currently happen in Chrome: http://code.google.com/p/chromium/issues/detail?id=17356.

Returning to Backbone, assuming you're not using a custom constructor (see below), your type will end up with an anonymous constructor function, created in Backbone's extend function used by Model, View, Collection and Route as follows:

child = function(){ return parent.apply(this, arguments); };

This is why you see “child” next to your Backbone objects in the console / debugger. It is the browser’s best guess at a suitable name for your object’s constructor.

Solutions

To give your objects a better type name, you can supply a named constructor via the first “protoProps” argument when you define your Backbone types. Just add a constructor property that wraps a call to the “parent” constructor as follows:

var Product = Backbone.Model.extend({
    constructor: function Product() {
        Backbone.Model.prototype.constructor.apply(this, arguments);
    }
});

Your Product model instances will now look really nice in the debugger.

It is a bit cumbersome to do this for every View, Model, Collection and Route that you define. You can monkey patch Backbone’s extend function to do the work for you.

You first need to establish a convention for defining the names of your types. Here we're using a __name__ property, which you specify as follows:

var Product = Backbone.Model.extend({
    __name__: 'Product'
    // other props
});

You then replace the extend function used by Model, View, Collection and Route to read this property and add a named constructor to your type. You don’t need to modify backbone.js itself, just include the following in a separate script that is loaded after backbone.js.

(function () {

    function createNamedConstructor(name, constructor) {

        var fn = new Function('constructor', 'return function ' + name + '()\n'
            + '{\n'
            + '    // wrapper function created dynamically for "' + name + '" constructor to allow instances to be identified in the debugger\n'
            + '    constructor.apply(this, arguments);\n'
            + '};');
        return fn(constructor);
    }

    var originalExtend = Backbone.View.extend; // Model, Collection, Router and View shared the same extend function
    var nameProp = '__name__';
    var newExtend = function (protoProps, classProps) {
        if (protoProps && protoProps.hasOwnProperty(nameProp)) {
            // TODO - check that name is a valid identifier
            var name = protoProps[nameProp];
            // wrap constructor from protoProps if supplied or 'this' (the function we are extending)
            var constructor = protoProps.hasOwnProperty('constructor') ? protoProps.constructor : this;
            protoProps = _.extend(protoProps, {
                constructor: createNamedConstructor(name, constructor)
            });
        }
        return originalExtend.call(this, protoProps, classProps);
    };

    Backbone.Model.extend = Backbone.Collection.extend = Backbone.Router.extend = Backbone.View.extend = newExtend;
})();
Allantois answered 22/2, 2013 at 22:7 Comment(1)
Wow, this should just be included in backbone by default.... thanks a million Dan, this is great.Mozambique
C
4

Yes. You can change the console display name by overriding a model/collection/view constructor using a named function expression. It may also be helpul to override toString to control the console output when the model is forced to string type with, say, the + operator:

App.Model = Backbone.Model.extend({

  //define constructor using a named function expression
  constructor: function Model() {
    Backbone.Model.prototype.constructor.apply(this, arguments);
  },

  //override toString to return something more meaningful
  toString: function() {
    return "Model(" + JSON.stringify(this.attributes) + ")";
  }
});

So with:

var model = new Model({id:1,foo:"bar"})

console.log("state: " + model);
console.log(model);

You'll get:

state: Model({"id":1,"foo":"bar"})
► Model
Cari answered 14/2, 2013 at 6:45 Comment(2)
nice! didn't know we could do that :) ... also, we're working on source symbols for Marionette. I'm hoping to have this for v1.0 release. This should help a lot in debugging.Tacita
@DerickBailey, source maps would be awesome. Any chance your work on Marionette could also contribute something back to Backbone core? github.com/documentcloud/backbone/issues/2143Cari
C
2

Consider using this library of this extension for debugging.

Comber answered 7/6, 2013 at 5:39 Comment(1)
The only issue with that extension is that the names of models and views are not displayed. It feels a little like blind mans bluff. The solution by jevakallio seems to be the easiest to implement.Earthaearthborn
T
2

Also try adding

"use strict"

at the top of your application. Instead of giving you stack trace errors like undefined on line 1 of backbone.marionette.js it will output the instance and be more descriptive information like HistoryView was not found.

Toxicosis answered 27/4, 2014 at 3:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.