In my backbone app I have a View backed by a Model and I'm storing the models in a Collection. There are several of these views on the page and only one can be selected at a time. Therefore, I have another View that contains a collection of these Views. When the user clicks on an unfocused view, the View sets the Model's "focused" attribute to true. The set on the model triggers a change event which is handled by the view itself (adds a focused class to its $el) and the ViewCollection view, which loops through all of the Views in the collection and unfocuses any of them that are focused (other than the one that was just clicked).
This all works great when I click at a moderate speed. However, when I click very rapidly I am seeing inconcistencies in the Model's set behavior. Namely, when I set the model's "focused" attribute to true, the next click event shows that that Model's "focused" attribute is still false.
Code:
var M = Backbone.Model.extend({
defaults : {
"name" : "",
"focused" : true
}
}),
L = Backbone.Collection.extend({
model : M
}),
V = Backbone.View.extend({
events : {
"click .button" : "focus"
}
initialize: function() {
this.model.bind("change:focused", this.handleFocusChange, this)
},
focus: function() {
console.log("Focus: " + this.model.get("name") + ".focused = " + this.model.get("focused"));
if (this.model.get("focused")) {
return;
}
this.model.set({focused: true}); // triggers change event
this.model.save(this.model);
console.log("Focus: " + this.model.get("name") + ".focused = " + this.model.get("focused"));
},
unfocus: function() {
console.log("Unfocus: " + this.model.get("name") + ".focused = " + this.model.get("focused"));
if (!this.model.get("focused")) {
return;
}
this.model.set({focused: false}); // triggers change event
this.model.save(this.model);
console.log("Unfocus: " + this.model.get("name") + ".focused = " + this.model.get("focused"));
},
handleFocusChange: function(model, focused) {
if (focused) {
console.log("Handling focus change on " + this.model.get("name") + ", " + this.model.cid);
this.$('.button').addClass("focused");
} else {
console.log("Handling unfocus change on " + this.model.get("nickname") + ", " + this.model.cid);
this.$('.button').removeClass("focused");
}
}
}),
ViewCollection = Backbone.View.extend({
initialize : function(options) {
_.bindAll(this, 'initViews');
this._views = [];
this.collection.each(this.initViews);
},
initViews : function(model) {
var view = new V({
model : model
});
this._views.push(view);
view.model.bind("change:focused", this.focusChanged, this);
},
focusChanged: function(changed, focused) {
if (!focused) {
return;
}
_.each(this._views, function(view) {
if (view.model.id !== changed.id) {
view.unfocus();
}
});
}
})
Here is the output from the log statements above:
Click
Focus: A.focused = false
Handling focus change on A, c10
Unfocus: B.focused = false
Unfocus: C.focused = false
Unfocus: D.focused = true
Handling ufocus change on D, c7
Unfocus: D.focused = false
Focus: A.focused = true <-- good!
Click
Focus: C.focused = false
Handling focus change on C, c9
Unfocus: B.focused = false
Unfocus: A.focused = false <-- bad!
Unfocus: D.focused = false
Focus: C.focused = true
As you can see, I clicked first on A and then on C. The changed event was fired and both the View and the Collection behaved appropriately. However, on the second click, A's "focused" attribute is false when it should be true. Again, this only happens when I click on the views very rapidly. I cannot reproduce this issue when I click at a normal speed.
Any help would be greatly appreciated!