Backbone.js Empty Array Attribute
I'm running into an odd issue with a Backbone.js Model where an array member is being shown as blank. It looks something like this:
var Session = Backbone.Model.extend({
defaults: {
// ...
widgets: []
},
addWidget: function (widget) {
var widgets = this.get("widgets");
widgets.push(widget);
this.trigger("change:widgets", this, widgets);
},
// ...
// I have a method on the model to grabbing a member of the array
getWidget: function (id) {
console.log(this.attributes);
console.log(this.attributes.widgets);
// ...
}
});
I then add a widget via addWidget
. When trying getWidget
the result I get (in Chrome) is this:
Object
widgets: Array[1]
0: child
length: 1
__proto__: Array[0]
__proto__: Object
[]
It's showing that widgets is not empty when logging this.attributes
but it's shown as empty when logging this.attributes.widgets
. Does anyone know what would cause this?
EDIT I've changed the model to instantiate the widgets array in the initialization method to avoid references across multiple instances, and I started using backbone-nested with no luck.
Solution 1:
Be careful about trusting the console, there is often asynchronous behavior that can trip you up.
You're expecting console.log(x)
to behave like this:
- You call
console.log(x)
. -
x
is dumped to the console. - Execution continues on with the statement immediately following your
console.log(x)
call.
But that's not what happens, the reality is more like this:
- You call
console.log(x)
. - The browser grabs a reference to
x
, and queues up the "real"console.log
call for later. - Various other bits of JavaScript run (or not).
- Later, the
console.log
call from (2) gets around to dumping the current state ofx
into the console but thisx
won't necessarily match thex
as it was in (2).
In your case, you're doing this:
console.log(this.attributes);
console.log(this.attributes.widgets);
So you have something like this at (2):
attributes.widgets
^ ^
| |
console.log -+ |
console.log -----------+
and then something is happening in (3) which effectively does this.attributes.widgets = [...]
(i.e. changes the attributes.widget
reference) and so, when (4) comes around, you have this:
attributes.widgets // the new one from (3)
^
|
console.log -+
console.log -----------> widgets // the original from (1)
This leaves you seeing two different versions of widgets
: the new one which received something in (3) and the original which is empty.
When you do this:
console.log(_(this.attributes).clone());
console.log(_(this.attributes.widgets).clone());
you're grabbing copies of this.attributes
and this.attributes.widgets
that are attached to the console.log
calls so (3) won't interfere with your references and you see sensible results in the console.
That's the answer to this:
It's showing that widgets is not empty when logging
this.attributes
but it's shown as empty when loggingthis.attributes.widgets
. Does anyone know what would cause this?
As far as the underlying problem goes, you probably have a fetch
call somewhere and you're not taking its asynchronous behavior into account. The solution is probably to bind to an "add"
or "reset"
event.
Solution 2:
Remember that []
in JS is just an alias to new Array()
, and since objects are passed by reference, every instance of your Session model will share the same array object. This leads to all kinds of problems, including arrays appearing to be empty.
To make this work the way you want, you need to initialize your widgets array in the constructor. This will create a unique widget array for each Session object, and should alleviate your problem:
var Session = Backbone.Model.extend({
defaults: {
// ...
widgets: false
},
initialize: function(){
this.set('widgets',[]);
},
addWidget: function (widget) {
var widgets = this.get("widgets");
widgets.push(widget);
this.trigger("change:widgets", this, widgets);
},
// ...
// I have a method on the model to grabbing a member of the array
getWidget: function (id) {
console.log(this.attributes);
console.log(this.attributes.widgets);
// ...
}
});