Как обнаружить изменение свойства объекта в массиве с помощью 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 обнаруживает только изменение самого массива (например, если я добавляю новую задачу, оставшееся количество обновляется правильно).
Как я могу это сделать?
Спасибо
Отредактируйте, относительно ответа Энди
Когда я делаю такие вещи:
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
), то ничего не случилось. Это источник моей проблемы...
3 ответа
Просто мои 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 (). Действительно, на массиве, он срабатывает, только если мы:
- Удалить элемент
- Добавить элемент
- Изменить элемент (здесь мы говорим об изменении ссылки)
Так что он не сработает, если вы измените внутреннее свойство элемента в массиве. Если мы добавим слушателя в готовый метод, мы увидим, что он не запускается нашим методом doTask. И вот почему взлом Хорасио работает. Он заменит объект. Другое решение состоит в том, чтобы вручную уведомить об изменении, используя
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-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
геттер обнаруживает изменение. Я тестировал в Chromium 41 и Firefox на Linux.
Вы можете проверить мой код на http://embed.plnkr.co/HXaKsQHjchqwe0P3bjy5/preview
Не могли бы вы дать мне больше информации о том, что вы хотите сделать и чем это отличается от моего кода?
редактировать
После разговора с @romaintaz через Twitter кажется, что проблема возникает только тогда, когда кнопки находятся внутри шаблонов, например:
<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 больше не обнаруживает изменения одного из свойств элемента списка.
На данный момент у меня есть только быстрое и грязное решение: вместо изменения только одного свойства элемента списка, измените весь элемент списка, тогда получатель увидит его.
Пример:
<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>
Планкр здесь: http://embed.plnkr.co/YgqtKgYRaRTZmoKEFwBy/preview
Вы не обновляете наблюдаемый массив "task" в вашем changeState
метод. Измените это на это:
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
}