5

I may be completely missing something here, but I have the following:

  • a Model which encapsulates 'all' the data (all JSON loaded from one URL)
  • the model has one (or more) Collections which it is instantiating with the data it got on construction
  • some code which I want to run on the Collection when the data is initialized and loaded

My question is about the composed Collection. I could do this outside the scope of the Collection, but I'd rather encapsulate it (otherwise what's the point of making it a 'class' with an initializer etc).

  1. I thought I could put that code in the initialize() function, but that runs before the model has been populated, so I don't have access to the models that comprise the collection (this.models is empty).

  2. Then I thought I could bind to an event, but no events are triggered after initialization. They would be if I loaded the Collection with a fetch from its own endpoint, but I'm not doing that, I'm initializing the collection from pre-existing data.

My question: How to get initialize code to run on the Collection immediately after it is initialized with data (i.e. this.models isn't empty).

Is it possible to do this without having to get 'external' code involved?

Okay here is the demo code, perhaps this will explain things better.

var Everything = Backbone.Model.extend({
    url: "/static/data/mydata.json",
    parse: function(data)
    {
        this.set("things", new Things(data.things, {controller: this}));
    }
});

var Thing = Backbone.Model.extend({
});

var Things = Backbone.Collection.extend({
  model: Thing,
  initialize: function(data, options)
  {
      // HERE I want access to this.models. 
      // Unfortunately it has not yet been populated.
      console.log("initialize");
      console.log(this.models);
      // result: []

      // And this event never gets triggered either!
      this.on("all", function(eventType)
      {
          console.log("Some kind of event happend!", eventType);
      });
  }
});

var everything = new Everything();
everything.fetch();

// Some manual poking to prove that the demo code above works:

// Run after everything has happened, to prove collection does get created with data
setTimeout(function(){console.log("outside data", everything.get("things").models);}, 1000);
// This has the expected result, prints a load of models.


// Prove that the event hander works.
setTimeout(function(){console.log("outside trigger", everything.get("things").trigger("change"));}, 1000);
// This triggers the event callback.
Joe
  • 46,419
  • 33
  • 155
  • 245

2 Answers2

7

Unfortunately for you the collection gets set with data only after it was properly initialized first and models are reset using silent: true flag which means the event won't trigger.

If you really wanted to use it you can cheat it a bit by delaying execution of whatever you want to do to next browser event loop using setTimeout(..., 0) or the underscore defer method.

initialize: function(data, options) {

     _.defer(_.bind(this.doSomething, this));
},

doSomething: function() {

    // now the models are going to be available
}
Tom Tu
  • 9,573
  • 35
  • 37
  • I was hoping to do the construction of the collection synchronously (so, for example, it could be used on the next line of `Everything.parse`). Obviously defer wouldn't do that. I may have to move the logic out of the collection. A change request to Backbone.js perhaps... – Joe Feb 06 '12 at 09:38
  • Using 'defer' turns out to be ideal if you want to install EventListener's in webCGM. The webCGM diagram is loaded as an independent object – and begins life as dumb picture. You have to explicitly tell it to start sending you the events – which you can then pipe back into the backbone infrastructure. I suspect the same technique could be used to interact with other plug-ins (SVG/PDF etc) many of which don't play well in the broswer. – Tony Eastwood Nov 08 '13 at 16:42
0

Digging this an old question. I had a similar problem, and got some help to create this solution:

By extending the set function we can know when the collection's data has been converted to real models. (Set gets called from .add and .reset, which means it is called during the core function instantiating the Collection class AND from fetch, regardless of reset or set in the fetch options. A dive into the backbone annotated source and following the function flow helped here)

This way we can have control over when / how we get notified without hacking the execution flow.

var MyCollection = Backbone.Collection.extend({
  url: "http://private-a2993-test958.apiary-mock.com/notes",
  initialize: function () {
    this.listenToOnce(this, 'set', this.onInitialized)
  },

  onInitialized:function(){
    console.log("collection models have been initialized:",this.models )
  },

  set: function(models,options){
    Backbone.Collection.prototype.set.call(this, models, options);
    this.trigger("set");
  }
})

//Works with Fetch!
var fetchCollection= new MyCollection()
fetchCollection.fetch();

//Works with initializing data
var colData = new MyCollection([
        {id:5, name:'five'},
        {id:6, name:'six'},
        {id:7, name:'seven'},
        {id:8, name:'eight'}
     ])

//doesn't trigger the initialized function
colData.add(new Backbone.Model({id:9,name:'nine'};

Note: If we dont use .listenToOnce, then we will also get onInitialized called every time a model is added to or changed in the collection as well.

mix3d
  • 4,122
  • 2
  • 25
  • 47