Как обнаружить изменение свойства объекта в массиве с помощью 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
}
Другие вопросы по тегам