Justin Meyer himself gave me a very good explanation and got this issue solved right away. Now I'll do my best to explain it for you.
This is the problematic "Mark all as complete" function:
- '#toggle-all click': function(el, ev) {
- var toggle = el.prop('checked');
- can.each(this.options.todos, function(todo) {
- todo.attr('complete', toggle).save(); // See Note #1
- });
- }
Note #1: Because we are changing
this.options.todos itself in this line, this triggers the
sort() method (see note #2 at the bottom), which in turn changes the order of the list in a way that the next element treated by
can.each() is not the one we expect it to be.
The solution is to copy the list into an array and then pass the copy to
can.each(). Doing so, the array on which
can.each() iterates doesn't get reordered and everything goes smoothly:
- '#toggle-all click': function(el, ev) {
- var toggle = el.prop('checked');
- can.each(can.makeArray(this.options.todos), function(todo) {
- todo.attr('complete', toggle).save();
- });
- }
- /* OR */
- '#toggle-all click': function(el, ev) {
- var toggle = el.prop('checked');
- can.each(Array.prototype.slice.call(this.options.todos, 0), function(todo) {
- todo.attr('complete', toggle).save();
- });
- }
Here is the
working fiddle.
Note #2: As part of the architectural design of CanJS, the developers decided that any change to an attribute is caught by live-binding and acted upon immediately, without waiting for any iteration to finish. Nice choice IMO!
And here is an
explanatory working fiddle. Please activate your console!
So, issue solved. Big thanks to Justin.