表示順の入れ替え(1)

2015/09/09

今回から3回に渡って、タスクの表示順を入れ替える機能を私たちの「Todo リスト」アプリケーションに追加していきます。


事前準備として、少しソースコードの整理を行います。TodoList クラスの renderTask() メソッドのコードをご覧ください。

  renderTask(m, task) {
    m.class({ completed: task.done });
    m.label(m => {
      m.onclick(e => this.agent.toggleTask(task));
      m.input({ type: 'checkbox', checked: task.done }).sp();
      m.class({ modifying: task.modifying });
      m.span(task.title);
    });
    m.onclick(e => this.editTask(task));
    m.span('Edit', { class: 'button' });
    m.onclick(e => {
      if (confirm('Are you sure you want to delete this task?'))
        this.agent.destroyTask(task);
    });
    m.span('Delete', { class: 'button' });
  }

長すぎるので分割しましょう。

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

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

これに合わせて render() メソッドのコードを書き換えます。これが書き換え前。

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

4行目を書き換えます。

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

ECMAScript6 のアロー関数の中身が複数のステートメントを含むときは、中括弧({})で囲む必要があります。


では、実装に取りかかりましょう。まずは、上下移動のボタンを設置します。

  renderButtons(m, task) {
    m.onclick(e => this.editTask(task));
    m.span('Edit', { class: 'button' });
    m.onclick(e => {
      if (confirm('Are you sure you want to delete this task?'))
        this.agent.destroyTask(task);
    });
    m.span('Delete', { class: 'button' });
    m.span({ class: 'button' }, m => m.fa('arrow-circle-up'));
    m.span({ class: 'button' }, m => m.fa('arrow-circle-down'));
  }

下から3行目と2行目が追加されています。

マークアップビルダーの fa() メソッドは Font Awesome のアイコンを表示するためのタグを生成します。

ブラウザでトップページを表示すると次のような画面となります。上向きと下向きの矢印がボタンの中に表示されていますね。

画面キャプチャ

もう少し工夫してみましょう。一番目の行の上ボタンと最後の行の下ボタンは無効にした方がいいですね。

まず、render() メソッドを次のように変更します。

  render(m) {
    m.ul(m => {
      this.agent.objects.forEach((task, index) => {
        m.li(m => {
          this.renderTask(m, task);
          this.renderButtons(m, task, index);
        });
      });
    });
    if (this.editingTask) this.renderUpdateForm(m);
    else this.renderCreateForm(m);
  }

変更点は2箇所。3行目の forEach(task =>forEach((task, index) => に書き換えて、6行目の this.renderButtons(m, task);this.renderButtons(m, task, index); に書き換えました。

ECMAScript6 のアロー関数の引数が1個の時は括弧(())を省略できますが、引数が0個または複数個の場合は括弧で囲む必要があります。

そして、renderbuttons() メソッドを書き換えます。

  renderButtons(m, task, index) {
    m.onclick(e => this.editTask(task));
    m.span('Edit', { class: 'button' });
    m.onclick(e => {
      if (confirm('Are you sure you want to delete this task?'))
        this.agent.destroyTask(task);
    });
    m.span('Delete', { class: 'button' });
    if (index === 0) m.class('disabled');
    m.span({ class: 'button' }, m => m.fa('arrow-circle-up'));
    if (index === this.agent.objects.length - 1) m.class('disabled');
    m.span({ class: 'button' }, m => m.fa('arrow-circle-down'));
  }

変更点は3箇所。まず、renderbuttons() メソッドに第3パラメータ index を追加しました。そして、下から5行目と3行目に if で始まるステートメントを追加しました。

下から5行目では index == 0 のときに上ボタンを囲む <span> タグの class 属性に disabled を追加しています。下から3行目では index == this.agent.object.length - 1 、すなわち最後の行のときに下ボタンを囲む <span> タグの class 属性に disabled を追加しています。

続いて、スタイルシート(app/assets/stylesheets/todo_list.es6)を次のように書き換えてください。

#todo-list {
  label.completed span {
    color: #888;
    text-decoration: line-through;
  }
  button[disabled] {
    color: #888;
  }
  button + button {
    margin-left: 4px;
  }
  span.modifying {
    font-weight: bold;
    color: #800;
  }
  span.button {
    cursor: pointer;
    background-color: #888;
    color: #fff;
    margin-left: 4px;
    padding: 4px 8px;
    font-size: 60%;
  }
  span.button.disabled {
    cursor: not-allowed;
    background-color: #ccc;
  }
}

下から5行目から2行目までが追加されています。ブラウザをリロードすると、次のような画面となります。

画面キャプチャ


今回はここまでとしましょう。次回は、タスクの順序を入れ替えるためのサーバー側 API を作ります。