Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Backbone bootstrapped collection doesn't initialize correctly

I have an issue, that was really hard to notice, because for the most part everything works. It was only when I tried to manipulate my data in my collections initialize function that I found a problem.

The backbone docs at http://backbonejs.org/#Collection-constructor

"If you define an initialize function, it will be invoked when the collection is created."

so I interpreted that as, my initialize function won't run until after my models are set. "That sounds ideal," said I, but then I ran into this.

My bootstrap code is as follows:

new MyCollection(<?php if ($data) {echo json_encode($data);} ?>);

My collection:

var MyCollection = Backbone.Collection.extend({
    model: MyModel,

    initialize: function() { 
        console.log(this); 
        console.log(this.length);
        this.each(function(model) {
            console.log(model);
        });
    } 
});  

I got strange results.

The first console.log(this); was a collection object as expected:

{ 
    .... 
    models: [3], 
    length: 3 
    .... 
} 

and the second console(this.length); printed out the number 0

the console inside this.each() didn't show up.

What's happening?

like image 547
nackjicholson Avatar asked Sep 22 '12 01:09

nackjicholson


1 Answers

The Collection constructor looks like this:

var Collection = Backbone.Collection = function(models, options) {
  //...
  this._reset();
  this.initialize.apply(this, arguments);
  //...
  this.reset(models, {silent: true, parse: options.parse});
  //...
};

Step by step:

  1. The this._reset() call does a this.length = 0.
  2. The this.initialize.apply(...) is the call to your initialize method.
  3. The this.reset(...) will call add to add the models. The add call will update the collection's models and length properties.

So, when initialize is called, you'll have this.length == 0 and this.models will be an empty array since only _reset will have been called here. Now we can easily see why this.each doesn't do anything and why console.log(this.length) says 0.

But why does console.log(this) tell us that we have a populated collection? Well, console.log doesn't happen right away, it just grabs references to its arguments and logs something to the console a little bit later; by the time console.log gets around to putting something in the console, you'll have gotten through (3) above and that means that you'll have the this.models and this.length that you're expecting to see. If you say

console.log(this.toJSON());

or:

console.log(_(this.models).clone())

you'll see the state of things when console.log is called rather than the state of things when console.log writes to the console.

The documentation isn't exactly explicit about what is supposed to be ready when initialize is called so you're stuck tracing through the source. This isn't ideal but at least the Backbone source is clean and straight forward.

You'll notice that initialize is called like this:

this.initialize.apply(this, arguments);

The arguments in there means that initialize will receive the same arguments as the constructor so you could look in there if you wanted:

initialize: function(models, options) {
    // The raw model data will be in `models` so do what
    // needs to be done.
}
like image 69
mu is too short Avatar answered Sep 20 '22 01:09

mu is too short