タスクを更新する

2015/07/21

前回は、タスクのタイトル(件名)を修正するフォームを表示しました。

今回は、そのフォームを使ってデータベースを更新する機能を実装します。


本題に入る前に、UI 上の工夫として次のような仕様を取り入れます。

  • 初期状態では新規追加フォームを表示し、編集フォームは表示しない。
  • 「Edit」ボタンがクリックされると、新規追加フォームを消して、編集フォームを表示する。
  • reset() メソッド呼び出しで初期状態に戻す。

どのように TodoList クラスを書き換えればいいでしょうか。演習問題として考えてみてください。


2つの実装例を紹介します。

第1の例では render() メソッドを次のように変更します。

  render(m) {
    m.ul(m => {
      this.ds.tasks.forEach(task => {
        m.li(m => this.renderTask(m, task));
      });
    });
    if (this.editingTask) this.renderUpdateForm(m);
    else this.renderCreateForm(m);
  }

this.renderUpdateForm(m)this.rendercreateForm(m) の順序が入れ替わっている点に注意してください。

ただし、この実装例は Cape.JS 1.1.2 以上でないとうまく動きません。bower.json を次のように書き換えて bower install コマンドを実行してください。

  ...
  "dependencies": {
    "capejs": "~1.1.2",
    "bootstrap": "=3.3.4",
    "fontawesome": "~4.3.0",
    "lodash": "~3.9.3"
  }
}

bootstrap のバージョンが =3.3.4 である点に留意してください。2015年8月1日現在の最新版である 3.3.5 ではうまく動作しません。詳しくは、Stack Overflow に書いた私の質問と回答をご覧ください。

第2の実装例では、renderCreateForm() メソッドと renderUpdateForm() メソッドを次のように変更します。

  renderCreateForm(m) {
    m.formFor('new_task', { visible: !this.editingTask }, m => {
      m.onkeyup(e => this.refresh());
      m.textField('title').sp();
      m.attr({ disabled: this.val('new_task.title').trim() === '' });
      m.onclick(e =>
        this.ds.createTask(this.val('new_task.title', '')));
      m.btn(`Add task #${ this.ds.tasks.length + 1 }`);
    });
  }

  renderUpdateForm(m) {
    m.formFor('task', { visible: !!this.editingTask }, m => {
      m.onkeyup(e => this.refresh());
      m.textField('title').sp();
      m.attr({ disabled: this.val('task.title').trim() === '' });
      m.btn('Update');
      m.btn('Cancel', { onclick: e => this.reset() });
    });
  }

それぞれメソッド定義の内側1行目で formFor() メソッドの第2引数に visible オプションを指定しています。このオプションに false を与えると <form> 要素全体が非表示(display: none)になります。

見え方は同じですが、これら2つの実装例は大きく異なります。前者では1個の <form> 要素が作られるだけですが、後者では2個の <form> 要素が作られます。

本稿では前者を採用したと仮定して次に進みます。


さて、Rails アプリケーション側の API はすでにできています。app/controllers/api/tasks_controller.rb に次のような update アクションが実装済みです。

  def update
    task = Task.find(params[:id])
    if task.update_attributes(task_params)
      render text: 'OK'
    else
      render text: 'NG'
    end
  end

6月14日の「Ajax によるデータの更新」で作りました。

ですから、ここからは割と簡単です。

まず、TaskStore クラスに updateTask() メソッドを追加します。

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

createTask() メソッドの次に挿入してください。

続いて、TodoList クラスの renderUpdateForm() メソッドを次のように書き換えます(下から4行目)。

  renderUpdateForm(m) {
    m.formFor('task', { visible: !!this.editingTask }, m => {
      m.onkeyup(e => this.refresh());
      m.textField('title').sp();
      m.attr({ disabled: this.val('task.title').trim() === '' });
      m.btn('Update', { onclick: e => this.updateTask() });
      m.btn('Cancel', { onclick: e => this.reset() });
    });
  }

そして、TodoList クラスに updateTask() メソッドを次のように実装します。

  updateTask() {
    var task = this.editingTask;
    task.modifying = false;
    this.editingTask = null;
    this.ds.updateTask(task, this.val('edit.title', ''));
  }

ブラウザで動作確認してください。「借りた本を返す」の右の「Edit」ボタンをクリックし、末尾に句点「。」を加えて「Update」ボタンをクリックし、タスクの件名が更新されればOKです。


次回は、タスクの削除機能を実装する予定です。どのような実装になるでしょうか。

読者の皆さんも演習問題として実装法を考えてみてくださいね。