Joe
Joe

Reputation: 47609

Getting backbone.js to run a function after constructing a Collection?

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

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.

Upvotes: 5

Views: 4791

Answers (2)

mix3d
mix3d

Reputation: 4333

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.

Upvotes: 0

Tom Tu
Tom Tu

Reputation: 9593

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
}

Upvotes: 7

Related Questions