コレクションエージェント(2)

2015/09/03

前回に引き続き、CollectionAgent クラスを利用して「Todo リスト」アプリを書き換えて行きます。


まずはタスクの「済み(done)」フラグをトグルする機能を動くようにしましょう。

現状、TodoList クラス(app/assets/javascripts/todo_list.es6)の renderTask() メソッドはこうなっています。

  renderTask(m, task) {
    m.class({ completed: task.done });
    m.label(m => {
      m.onclick(e => this.ds.toggleTask(task));
      m.input({ type: 'checkbox', checked: task.done }).sp();
      m.class({ modifying: task.modifying });
      m.span(task.title);
    });
(省略)
  }

<label> タグの中にチェックボックスが埋め込まれていて、ユーザーがそのチェックボックスをクリックすると

this.ds.toggleTask(task)

というコードが実行されます。もう TodoList クラス(this)に ds というプロパティは存在しないので、この部分を次のように書き換えてください。

this.agent.toggleTask(task)

次に TaskCollectionAgent クラスに toggleTask() メソッドを追加します。

  toggleTask(task) {
    this.update(task.id, { task: { done: !task.done } });
  }

親クラス Cape.CollectionAgent で定義されている update() メソッドは、2つの引数を取ります。第1引数は「リソース」のIDで、第2引数は API サーバーに送信するオブジェクトです。

task.id の値が 123 であれば、toggleTask() メソッドは /api/tasks/123 という URL に対して、PATCH メソッドでリクエストを送ります。第2引数に指定されたオブジェクトは JSON 形式の文字列に変換されてサーバーに送信されます。

TaskStore クラスの同名メソッドと比較してみましょう。

  toggleTask(task) {
    $.ajax({
      type: 'PATCH',
      url: '/api/tasks/' + task.id,
      data: { task: { done: !task.done } }
    }).done(data => {
      if (data === 'OK') {
        task.done = !task.done;
        this.propagate();
      }
    });
  }

かなりコード量が減っていますね。ただし、この2つのメソッドはまったく同じことをしているわけではありません。

TaskStoretoggleTask() メソッドでは、API サーバーの応答が 'OK' であるかどうかを調べて、もしそうならば task オブジェクトの done プロパティの真偽を反転させてから、this.propagate() を呼び出しています。この結果、この TaskStore のインスタンスと結び付けられているすべてのコンポーネントが再描画されます。

他方、TaskCollectionAgenttoggleTask() メソッドでは API サーバーの応答を調べません。コレクションエージェントは POST/PATCH/PUT/DELETE メソッドで API サーバーにリクエストを送った場合は、サーバーの応答を受けた後で自動的に自分自身(this)の refresh() メソッドを呼びます。その結果、API サーバーから最新のデータを取得して this.objects プロパティを更新し、“顧客”であるコンポーネントを再描画します。

以上で、タスクの「済み(done)」フラグをトグルする機能を復元できました。ブラウザで動作確認してみましょう。前回の記事の状態から「猫のえさを買う。」にチェックを加え、「歯医者に行く。」のチェックを外すと次のような画面になります。

画面キャプチャ

チェックボックスの状態に応じて、タスクのタイトルのスタイルが変化することを確認してください。


次に、タスクの削除機能を復旧します。

TodoList クラスの renderTask() メソッドから該当部分を抜き出せば次の通りです。

  renderTask(m, task) {
(省略)
    m.onclick(e => {
      if (confirm('Are you sure you want to delete this task?'))
        this.ds.destroyTask(task);
    });
    m.span('Delete', { class: 'button' });
  }

この部分を次のように修正します。

  renderTask(m, task) {
(省略)
    m.onclick(e => {
      if (confirm('Are you sure you want to delete this task?'))
        this.agent.destroyTask(task);
    });
    m.span('Delete', { class: 'button' });
  }

.ds.agent に変えただけです。


TaskCollectionAgent クラスに destroyTask() メソッドを追加します。

  toggleTask(task) {
    this.destroy(task.id);
  }

親クラス Cape.CollectionAgent で定義されている destroy() メソッドは、第1引数として「リソース」のIDを取り、DELETE メソッドで API サーバーにリクエストを送ります。

TaskStore クラスの同名メソッドと比較してみましょう。

  destroyTask(task) {
    $.ajax({
      type: 'DELETE',
      url: '/api/tasks/' + task.id
    }).done(data => {
      if (data === 'OK') this.refresh();
    });
  }

やはりコード量が減っています。

以上で、タスクの削除機能が元通り動くようになりました。ブラウザで動作確認を行ってください(キャプチャ画面の掲載は省略)。


今回のポイントは、コレクションエージェントを利用すると jQuery を利用した場合に比べて、API サーバーにアクセスするコードが簡潔になる、ということです。

次回は、タスクを新規追加する機能と既存のタスクのタイトルを変更する機能を作り直します。