Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to limit the number change events when multiple attributes are set?

I have noticed that when multiple attributes of a Backbone model are set like so

model.set({
    att1:val1,
    att2:val2
});

two change events are triggered. I was wrongly assuming that only one change event would be triggered after all the attributes had been set.

This might not seem like a problem, but it is when a function is bound to att1 that also uses the value of att2. In other words, when you do this

model.bind('change:att1', func1);
...
func1 = function() {
    var att2 = model.get('att2');
}

the variable att2 will be set to the old value of the model's attribute att2.

The question is how to prevent this in an elegant manner. Of course, one option is to set att2 before setting att1 or to bind to att2 (instead of att1), but it seems that this is only a viable option in simple situations. The latter option also assumes that the attributes are set in the order in which they are listed in the set method (which is the case I think).

I have run into this issue several times hence my question. The issue is that it took me some time to realize what was actually happening.

On a final note, just like you can pass {silent:true} as an option of the set method, it would be nice to have an option {group:true} (or something like that) indicating that the change events should only be fired after all the attributes have been set.

like image 986
Bart Jacobs Avatar asked Jan 26 '12 11:01

Bart Jacobs


1 Answers

In more complex situations i'd go for custom events.

instead of binding to a change:att1 or change:att2 i'd look for a specific custom event, that you trigger after you have set all attributes you wanted to change on the model.

model.set({
    att1:val1,
    att2:val2
});
model.trigger('contact:updated'); // you can chose your custom event name yourself

model.bind('contact:updated', func1);
...
func1 = function() {
    var att2 = model.get('att2');
}

downside on this idea is you have to add a new line of code everywhere you want to trigger the event. if this happens alot you might like to change or override the model.set() to do it for you, but then you're already changing backbone code, don't know how you feel about that.

EDIT

after looking into the sourcecode of backbone, i noticed the change event is triggered right after the change:attribute triggers. (proven by the snippit below)

// Fire `change:attribute` events.
for (var attr in changes) {
  if (!options.silent) this.trigger('change:' + attr, this, changes[attr], options);
}

// Fire the `"change"` event, if the model has been changed.
if (!alreadyChanging) {
  if (!options.silent && this._changed) this.change(options);
  this._changing = false;
}

while the this.change(options); refers to this:

change: function(options) {
  this.trigger('change', this, options);
  this._previousAttributes = _.clone(this.attributes);
  this._changed = false;
},

so if you would be binding to the change event instead of the specific change:argument event, you will arrive at a callback function after both (or all) attributes are changed.

the only downside is, it will trigger on ANY change, even if you change a third or fourth attribute. you need to calculate that in...

small example of how it works on jsfiddle http://jsfiddle.net/saelfaer/qm8xY/

like image 109
Sander Avatar answered Sep 20 '22 19:09

Sander