如何使用 Polymer 检测数组中对象的 属性 的变化?
How to detect change in a property of an object in an array with Polymer?
假设我想用 Polymer (v0.5.5) 创建一个待办事项列表。在我的元素中,我定义了一个 属性 tasks
,这是一个包含对象列表的数组,例如 { name: 'foo', done: false }
。我想显示剩余任务数,所以我需要检测数组中包含的对象的属性 done
何时发生变化。
这是代码的摘录:
<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>
对于 Firefox,它可以工作,但不能用于 Chrome (41.x)。事实上,Chrome 只检测数组本身的变化(例如,如果我添加一个新任务,remaining 计数会正确更新)。
我该怎么做?
谢谢
编辑,关于安迪的回答
当我做那种事时:
var tasks = tasks: [
{name: "foo", done: false},
{name: "bar", done: true},
{name: "baz", done: false}
];
Object.observe(tasks, function() { alert('Modification'); }
如果我对数组本身进行修改(如 tasks.push({...})
),则会显示一个弹出窗口。但是,如果我更改数组中包含的对象的 属性(例如 tasks[0].done = true
),则什么也不会发生。这就是我问题的根源...
您没有在 changeState
方法中更新观察到的 "task" 数组。改成这样:
changeState: function(e) {
this.tasks[e.target.getAttribute('data-task-id')].done = !this.tasks[e.target.getAttribute('data-task-id')].done;
}
基本上,您是通过重新分配数组的值来创建一个局部变量 - 在您显式设置它之前没有反向引用。现在上面的代码很长,所以你也可以这样做:
changeState: function(e) {
var _task = this.tasks[e.target.getAttribute('data-task-id')];
_task.done = !_task.done;
// re assign
this.tasks[e.target.getAttribute('data-task-id')] = _task;e
}
恐怕我不明白这个问题,罗曼。
我用下面的代码测试过:
<polymer-element name="my-component" attributes="status count">
<template>
<style>
</style>
<div >
<h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>
<div
style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
on-click="{{ doTask }}"
>
Click to mark as done
</div>
<div>
{{ tasks[0].done }}
</div>
</div>
</template>
<script>
Polymer("my-component", {
tasks: [
{name: "foo", done: false},
{name: "bar", done: true}
],
get remaining() {
return this.tasks.filter(function(t) { return !t.done }).length;
},
doTask: function() {
this.tasks[0].done = true;
}
});
</script>
</polymer-element>
当我点击按钮时,标签的值发生变化,即 remining
getter 检测到变化。我已经在 Linux.
上的 Chromium 41 和 Firefox 中进行了测试
您可以在 http://embed.plnkr.co/HXaKsQHjchqwe0P3bjy5/preview
上测试我的代码
能否请您提供更多信息,说明您想做什么以及它与我的代码有何不同?
编辑
通过 Twitter 与@romaintaz 交谈后,问题似乎只发生在按钮位于 if 模板内时,如下所示:
<div >
<h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>
<template repeat="{{ task, taskIndex in tasks }}">
<template if="{{task.done}}">
<button
style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
on-click="{{ doTask }}"
>Click 1</button>
</template>
<template if="{{!task.done}}">
<button
style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
on-click="{{ doTask }}"
>Click 2</button>
</template>
</template>
<div>
{{ tasks[0].done }}
</div>
在这种情况下,removing
getter 不再检测列表的一项属性的更改。
目前我只有一个 quick'n'dirty 解决方案:与其只更改列表项中的一个 属性,不如更改整个列表项,然后 getter 看到它。
示例:
<polymer-element name="my-component" attributes="status count">
<template>
<style>
</style>
<div >
<h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>
<template repeat="{{ task, taskIndex in tasks }}">
<template if="{{task.done}}">
<button
style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
on-click="{{ doTask }}"
>Click 1</button>
</template>
<template if="{{!task.done}}">
<button
style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
on-click="{{ doTask }}"
>Click 2</button>
</template>
</template>
<div>
{{ tasks[0].done }}
</div>
</div>
</template>
<script>
Polymer("my-component", {
tasks: [
{name: "foo", done: false},
{name: "bar", done: true}
],
get remaining() {
return this.tasks.filter(function(t) { return !t.done }).length;
},
doTask: function() {
tmp = this.tasks[0];
tmp.done = true
this.tasks[0] = {};
this.tasks[0] = tmp;
},
observe: {
tasks: 'validate'
},
validate: function(oldValue, newValue) {
}
});
</script>
</polymer-element>
Plunkr 在这里:http://embed.plnkr.co/YgqtKgYRaRTZmoKEFwBy/preview
只是我的 2 美分
事实上,问题与模板中是否存在按钮无关。
您只需在列表中使用重复模板即可重现问题。
下面的代码对此进行了演示。
<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>
其实我觉得很自然。当我们看规范时Object.observe()。实际上在一个数组上,只有当我们:
- 删除一个元素
- 添加一个元素
- 修改一个项目(这里我们说
参考变化)
因此,如果您更改数组中某个元素的内部属性,它将不会触发。
如果我们在 ready 方法中添加一个监听器,我们将看到它不是由我们的 doTask 方法触发的。这就是 Horacio 的 hack 起作用的原因。他更换了对象。
另一种解决方案是使用
手动通知更改
Object.getNotifier(this.tasks).notify
以下是完整版
<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>
我很难理解为什么没有模板也能正常工作。如果我们保持我们的听众很好,我们会发现它不是调用,而是更新...
假设我想用 Polymer (v0.5.5) 创建一个待办事项列表。在我的元素中,我定义了一个 属性 tasks
,这是一个包含对象列表的数组,例如 { name: 'foo', done: false }
。我想显示剩余任务数,所以我需要检测数组中包含的对象的属性 done
何时发生变化。
这是代码的摘录:
<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>
对于 Firefox,它可以工作,但不能用于 Chrome (41.x)。事实上,Chrome 只检测数组本身的变化(例如,如果我添加一个新任务,remaining 计数会正确更新)。
我该怎么做?
谢谢
编辑,关于安迪的回答
当我做那种事时:
var tasks = tasks: [
{name: "foo", done: false},
{name: "bar", done: true},
{name: "baz", done: false}
];
Object.observe(tasks, function() { alert('Modification'); }
如果我对数组本身进行修改(如 tasks.push({...})
),则会显示一个弹出窗口。但是,如果我更改数组中包含的对象的 属性(例如 tasks[0].done = true
),则什么也不会发生。这就是我问题的根源...
您没有在 changeState
方法中更新观察到的 "task" 数组。改成这样:
changeState: function(e) {
this.tasks[e.target.getAttribute('data-task-id')].done = !this.tasks[e.target.getAttribute('data-task-id')].done;
}
基本上,您是通过重新分配数组的值来创建一个局部变量 - 在您显式设置它之前没有反向引用。现在上面的代码很长,所以你也可以这样做:
changeState: function(e) {
var _task = this.tasks[e.target.getAttribute('data-task-id')];
_task.done = !_task.done;
// re assign
this.tasks[e.target.getAttribute('data-task-id')] = _task;e
}
恐怕我不明白这个问题,罗曼。
我用下面的代码测试过:
<polymer-element name="my-component" attributes="status count">
<template>
<style>
</style>
<div >
<h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>
<div
style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
on-click="{{ doTask }}"
>
Click to mark as done
</div>
<div>
{{ tasks[0].done }}
</div>
</div>
</template>
<script>
Polymer("my-component", {
tasks: [
{name: "foo", done: false},
{name: "bar", done: true}
],
get remaining() {
return this.tasks.filter(function(t) { return !t.done }).length;
},
doTask: function() {
this.tasks[0].done = true;
}
});
</script>
</polymer-element>
当我点击按钮时,标签的值发生变化,即 remining
getter 检测到变化。我已经在 Linux.
您可以在 http://embed.plnkr.co/HXaKsQHjchqwe0P3bjy5/preview
上测试我的代码能否请您提供更多信息,说明您想做什么以及它与我的代码有何不同?
编辑
通过 Twitter 与@romaintaz 交谈后,问题似乎只发生在按钮位于 if 模板内时,如下所示:
<div >
<h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>
<template repeat="{{ task, taskIndex in tasks }}">
<template if="{{task.done}}">
<button
style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
on-click="{{ doTask }}"
>Click 1</button>
</template>
<template if="{{!task.done}}">
<button
style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
on-click="{{ doTask }}"
>Click 2</button>
</template>
</template>
<div>
{{ tasks[0].done }}
</div>
在这种情况下,removing
getter 不再检测列表的一项属性的更改。
目前我只有一个 quick'n'dirty 解决方案:与其只更改列表项中的一个 属性,不如更改整个列表项,然后 getter 看到它。
示例:
<polymer-element name="my-component" attributes="status count">
<template>
<style>
</style>
<div >
<h1>You have {{ tasks.length }} tasks, {{ remaining }} remaining</h1>
<template repeat="{{ task, taskIndex in tasks }}">
<template if="{{task.done}}">
<button
style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
on-click="{{ doTask }}"
>Click 1</button>
</template>
<template if="{{!task.done}}">
<button
style="border: solid 1px red; padding: 20px; border-radius: 20px; display: inline-block;"
on-click="{{ doTask }}"
>Click 2</button>
</template>
</template>
<div>
{{ tasks[0].done }}
</div>
</div>
</template>
<script>
Polymer("my-component", {
tasks: [
{name: "foo", done: false},
{name: "bar", done: true}
],
get remaining() {
return this.tasks.filter(function(t) { return !t.done }).length;
},
doTask: function() {
tmp = this.tasks[0];
tmp.done = true
this.tasks[0] = {};
this.tasks[0] = tmp;
},
observe: {
tasks: 'validate'
},
validate: function(oldValue, newValue) {
}
});
</script>
</polymer-element>
Plunkr 在这里:http://embed.plnkr.co/YgqtKgYRaRTZmoKEFwBy/preview
只是我的 2 美分
事实上,问题与模板中是否存在按钮无关。 您只需在列表中使用重复模板即可重现问题。 下面的代码对此进行了演示。
<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>
其实我觉得很自然。当我们看规范时Object.observe()。实际上在一个数组上,只有当我们:
- 删除一个元素
- 添加一个元素
- 修改一个项目(这里我们说 参考变化)
因此,如果您更改数组中某个元素的内部属性,它将不会触发。 如果我们在 ready 方法中添加一个监听器,我们将看到它不是由我们的 doTask 方法触发的。这就是 Horacio 的 hack 起作用的原因。他更换了对象。 另一种解决方案是使用
手动通知更改Object.getNotifier(this.tasks).notify
以下是完整版
<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>
我很难理解为什么没有模板也能正常工作。如果我们保持我们的听众很好,我们会发现它不是调用,而是更新...