Disappearing models in Backbone collection
-
22-10-2019 - |
Question
I've got a Backbone model line
that contains a collection of the model Stop
.
At some point, I want to iterate through the stops in the line and get the total travel time along the line, using the Underscore function reduce
.
This does not work, however. It seems that something happens with the collection at some point. It seems to contain only one object without meaningful attributes, although I know for a fact that it has been populated with four stop-models with valid attributes.
The model:
App.models.Line = Backbone.Model.extend({
initialize: function() {
var stops = new App.models.Stops({
model: App.models.Stop,
id: this.get("id")
});
stops.fetch();
this.set({
stops: stops
});
this.set({unique: true});
this.calculateTotalTime();
},
calculateTotalTime: function() {
this.get("stops").each(function(num) {
console.log("Model!");
});
console.log("Unique: ", this.get("unique"));
}
});
Console printout is:
Model!
Unique: true
There should be four "Model!", since the number of models is four.
The strangest thing is that everything works just fine in the console:
window.line.get("stops").each(function(num) {
console.log("Model!");
});
Model!
Model!
Model!
Model!
The JS is compiled with Sprockets:
//= require ./init
//= require ./lib/jquery
//= require ./lib/underscore
//= require ./lib/backbone
//= require ./lib/raphael
//= require_tree ./controllers
//= require_tree ./models
//= require_tree ./views
//= require ./main
init.js:
window.App = {};
App.views = [];
App.models = [];
main.js:
$(function() {
window.line = new App.models.Line({name: "4", id: 4});
window.lineView = new App.views.Line({model: line});
$("#outer").append(lineView.render().el);
});
Some other strange behaviour:
console.log(this.get("stops"))
in the model yields this fairly normal object:
child
_byCid: Object
_byId: Object
_onModelEvent: function () { [native code] }
_removeReference: function () { [native code] }
id: 4
length: 4
models: Array[4]
0: Backbone.Model
1: Backbone.Model
2: Backbone.Model
3: Backbone.Model
length: 4
__proto__: Array[0]
__proto__: ctor
But calling console.log(this.get("stops").models)
, which should yield the array, returns only this, an array of a single object with no useful attributes:
[
Backbone.Model
_callbacks: Object
_changed: false
_changing: false
_escapedAttributes: Object
_previousAttributes: Object
attributes: Object
id: 4
model: function (){ return parent.apply(this, arguments); }
__proto__: Object
cid: "c1"
id: 4
__proto__: Object
]
I suspect this is all down to some misunderstanding about the nature of this
. Glad for any help provided.
Solution
stops.fetch()
is an asynchronous process, so the code that you have written right after it will likely fire before the results of the fetch have come back from the server.
you'll need to modify your code to run everything after the fetch comes back. the easiest way to do this is with the reset
event from the stops
collection:
App.models.Line = Backbone.Model.extend({
initialize: function() {
var stops = new App.models.Stops({
model: App.models.Stop,
id: this.get("id")
});
// store the stops, and then bind to the event
this.set({stops: stops});
stops.bind("reset", this.stopsLoaded, this);
stops.fetch();
},
stopsLoaded: function(){
// get the stops, now that the collection has been populated
var stops = this.get("stops");
this.set({unique: true});
this.calculateTotalTime();
},
calculateTotalTime: function() {
this.get("stops").each(function(num) {
console.log("Model!");
});
console.log("Unique: ", this.get("unique"));
}
});
the reason it works in your console is because by the time you type out the code to evaluate the model's stops, the fetch
call has already returned and populated the collection.
hope that helps