Let's say I want to create a todo list with Polymer (v0.5.5). In my element, I define a property tasks
, which is an array containing a list of objects like { name: 'foo', done: false }
. I want to display the number of remaining tasks, so I need to detect when the property done
of an object included in the array is changed.
Here is an extract of the code:
<polymer-element name="todo-list">
<template>
<span>You have {{ tasks.length }} tasks, {{ remaining }} remaining</span>
...
</template>
<script>
Polymer({
tasks: [
{name: "foo", done: false},
{name: "bar", done: true}
],
get remaining() {
return this.tasks.filter(function(t) { return !t.done }).length;
}
changeState: function(e) {
var _task = this.tasks[e.target.getAttribute('data-task-id')];
_task.done = !_task.done;
}
});
</script>
</polymer-element>
With Firefox, it is working but not with Chrome (41.x). Indeed, Chrome only detect the change of the array itself (for example, if I add a new task, the remaining count is updated correctly).
How do I do that?
Thanks
Edit, regarding Andy answer
When I do that kind of thing:
var tasks = tasks: [
{name: "foo", done: false},
{name: "bar", done: true},
{name: "baz", done: false}
];
Object.observe(tasks, function() { alert('Modification'); }
and if I do a modification in the array itself (like in tasks.push({...})
), then a popup is displayed. But if I change a property of an object contained in the array (e.g. tasks[0].done = true
), then nothing happen. That's the source of my problem...
Just my 2 cents
In fact the problem is independent of the presence of the button in a template. You just have to use a repeat template on a list to reproduce the problem. The code below demonstrates it.
<h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>
<button
style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
on-click="{{ doTask }}"
>
Click to mark as done
</button>
<ul>
<template repeat="{{ task in othertasks }}">
<li>{{task}}</li>
</template>
</ul>
<div>
{{ tasks[0].done }}
</div>
</template>
<script>
Polymer({
tasks: [
{name: "foo", done: false},
{name: "bar", done: true}
],
othertasks: ["foo","bar"],
get remaining() {
return this.tasks.filter(function(t) { return !t.done }).length;
},
doTask: function () {
this.tasks[0].done = true;
}
});
</script>
</polymer-element>
I actually find it rather natural. When we look at the specification Object.observe (). Indeed on an Array, it is triggered only if we:
So it will not fire if you change an internal property of an element in the array. If we add a listener, in a ready method , we will see that it is not triggered by our doTask method. And this why that the Horacio 's hack works . He replace the object. Another solution is to manually notify the change using
Object.getNotifier(this.tasks).notify
Below is a full version
<polymer-element name="todo-list">
<template>
<h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>
<button
style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
on-click="{{ doTask }}"
>
Click to mark as done
</button>
<ul>
<template repeat="{{ task in othertasks }}">
<li>{{task}}</li>
</template>
</ul>
<div>
{{ tasks[0].done }}
</div>
</template>
<script>
Polymer({
tasks: [
{name: "foo", done: false},
{name: "bar", done: true}
],
othertasks: ["foo","bar"],
ready: function () {
Object.observe(this.tasks, function(change) {
console.log(change); // What change
});
},
get remaining() {
return this.tasks.filter(function(t) { return !t.done }).length;
},
doTask: function () {
this.tasks[0].done = true;
Object.getNotifier(this.tasks).notify({
type: 'update',
name: '0'
});
}
});
</script>
</polymer-element>
I have more trouble understanding why it works without the template. If we keep our listener well we see that it is not call but remaining is updating ...