Knockoutの小技集 〜左右二つのリストで移動可能なセレクトボックス(1)〜

第2回は、2つの左右のボックスを互いに移動できるセレクトリストです。

完成形

リポジトリは同じです。これ、よく見るんですけど何ていう名前が適切なんですかね…?とりあえず、RichFacesのネーミングを借りて、List Shuffleとしました。まぁシャッフル機能は追々作っていくとして、ひとまず、左右の移動ができるようにします。

Model

まずはモデルから。

  function Item(name, value) {
    this.name = name;
    this.value = value;
  }

結論から言えば、これくらいの機能だったら、ビルトインバインディングをいくつか組み合わせるだけで簡単にできてしまいます。ただ、Model, ViewModelをどのようにデザインするかはいくつかの選択肢があると思います。

今回は項目の名前と値だけを保持するようにしました。他の案としては、「自分が左のリストと右のリストのどちらにいるか」といった状態を持つことも考えられます。

ViewModel

  function ViewModel() {
    this.leftItems = ko.observableArray([
      new Item("Item1", 111),
      new Item("Item2", 222),
      new Item("Item3", 333)
    ]);
    this.rightItems = ko.observableArray([
      new Item("Item4", 444)
    ]);

    this.selectedLeft = ko.observableArray([]);
    this.selectedRight = ko.observableArray([]);
  }

以下の4つを管理します。

  • 左のリスト項目
  • 左のリストで選択されている項目
  • 右のリスト項目
  • 右のリストで選択されている項目

また、ボタンクリック時の処理として、一方の選択項目を他方のリストに移動する関数を作っておきます。

  ViewModel.prototype.moveLeft = function() {
    this.move(this.selectedRight, this.rightItems, this.leftItems);
  };

  ViewModel.prototype.moveRight = function() {
    this.move(this.selectedLeft, this.leftItems, this.rightItems);
  };

  ViewModel.prototype.move = function(selectedItems, fromList, toList) {
    selectedItems().forEach(function(selectedItem) {
      toList.push(selectedItem);
      fromList.remove(selectedItem);
    }, this);
  };

画面を考えずにロジックだけ作りこんでいけるので、頭の中が整理できて楽です。

View

<table>
  <tbody>
    <tr>
      <td>
        <select data-bind="options: leftItems, optionsText: 'name', selectedOptions: selectedLeft" 
          size="10" multiple="true"></select>
      </td>
      <td>
        <button data-bind="click: moveRight">&gt;&gt;</button>
        <br />
        <button data-bind="click: moveLeft">&lt;&lt;</button>
      </td>
      <td>
        <select data-bind="options: rightItems, optionsText: 'name', selectedOptions: selectedRight"
          size="10" multiple="true"></select>
      </td>
    </tr>
  <tbody>
</table>

レイアウトは手を抜いてtableで作りました。

htmlを組み立てて、素直にバインディングを当てはめていくだけですね。一つポイントとして、optionsValueバインディングの使用の有無があります。ちょっと意識しないと、observableArray#remove()しても項目が消えない、みたいなことになるかも。

optionsValueを指定しない場合(上記例)

選択された項目を保持するためのselectedLeft, selectedRightは、ItemのobservableArrayになります。

optionsValueを指定する場合(optionsValue="value")

選択された項目を保持するためのselectedLeft, selectedRightは、ItemのvalueプロパティのobservableArrayになります。

次回予告

今回作ったものに、項目をダブルクリックすると他方のリストに移動する機能を追加します。