みなさんこんにちは。江口です。
前回 に引き続き Todo 管理アプリケーションに手を加えながら、前回紹介しきれなかった機能とその使い方について紹介します。
前回と同じになりますが Todo 管理アプリケーションの完成形のデモは こちら です。 ソースコードは こちら(Github) からダウンロード出来ます。
Todo の数を表示する
まずは、現在の Todo の数をリアルタイムに表示してみましょう。Todo の数は todoList が保持している配列の長さの値がそのまま利用できるので、これを利用します。todoList を引数なしで呼び出すことで、普通の配列として todoList の内容が取り出せるので、あとは length プロパティを取り出すと Todo の数がわかります。
ここでは、Todo の数を表示するのには仮想要素バインディングを使用します。Knockout のバインディングは何らかの要素に data-bind 属性を追加することで UI 上に表示しますが、その他に HTML コメントをつかってバインディングを利用することができます。仮想要素バインディングは <!–ko–> と <!–/ko–> をつかって次のように記述することができます。
<span class="counter"><!--ko text: todoList().length--><!--/ko--> items</span>
仮想要素バインディングを使用することで、余計な要素を増やすこと無くバインディングを使うことができます。
空の Todo を追加できないようにする
今の Todo 管理アプリケーションは、入力が空の状態でも Todo を追加することが出来てしまうので、入力が空の時はボタンを disabled にして空の Todo を追加できないようにしてみましょう。
input / textarea / select タグの要素の enable / disable の状態を切り替えるには、enable バインディングまたは disable バインディングを使用します。enable バインディングを使用した場合、バインドしている値が true の時にだけ要素が有効になります。disable バインディングはその逆です。
ここでは、todoSummary に1文字以上の入力があった場合にボタンを有効にしたいのですが、このように他の observable に依存した値を定義するのには computed observable という機能を使用することができます。
computed observable を使用すると、他の observable に依存した値を状態として持つことができます。また、依存している observable が更新されると Knockout の持つ依存性追跡機能により自身も自動的に更新されます。 computed observable を作成するには、ko.computed に値を評価する関数を与えて実行します。例えば今回の場合は、todoSummary が 空の時に true になるような observable を作成したいので次のような canAddTodo プロパティを ViewModel に追加します。
self.canAddTodo = ko.computed(function () { return self.todoSummary().length > 0; });
この computed observable で使用している評価関数は todoSummary に依存しているので、todoSummary の状態が変わると評価関数が再度評価されて canAddTodo の状態も自動的に更新されます。これを enable バインディングを使ってボタンにバインドすると、入力欄に何か入力がある時だけ有効になるボタンにすることができます。
<input type="submit" value="add" data-bind="enable: canAddTodo"/>
ただ、実は todoSummary の値が同期されるのはデフォルトではフォーカスが外れたタイミング(正確には change イベントの発生タイミング)になっているので入力を開始した時点ではまだボタンが有効にはなりません。これは、追加パラメータの valueUpdate で更新タイミングを指定することで変更することができます。 ここに afterkeydown を指定することで、更新タイミングがキーダウン後になるのでリアルタイムに todoSummary の値を同期するようになります。
<input type="text" data-bind="value: todoSummary, valueUpdate: 'afterkeydown'" placeholder="todo"/>
これで、入力を開始するとすぐにボタンの状態が変更されるようになりました。
Todo の削除機能を追加する
Todo の右下に表示している delete の表示をクリックされたらその Todo を削除できるようにしてみましょう。
まずは、引数で指定されたTodo オブジェクトを todoList から削除するメソッド deleteTodo を ViewModel に追加します。引数には、foreach バインディング内でクリックされた要素と対応する todoList 内のオブジェクトが Knockout から自動的に渡されます。
self.deleteTodo = function (todo) { self.todoList.remove(todo); };
あとはこの deleteTodo メソッドを delete の文字を表示している span 要素と click バインディングでバインドするだけですが、ここで少し注意する点があります。foreach バインディングの内側では、バインディングコンテキストが ViewModel から todoList になっているので ViewModel の deleteTodo を指定するには親コンテキストを取得する必要があります。親コンテキストを取得するには、$parent を使って次のように記述します。なお今回の場合、deleteTodo はViewModel にあるので、 $root を使って指定することもできます。
<span class="delete redLink" data-bind="click: $parent.deleteTodo">delete</span>
完了した Todo をまとめて削除する
最後に、完了状態になっている Todo をまとめて削除する機能を実装しましょう。まずは、addTodo メソッド内の Todo オブジェクトを変更して、チェックボックスの状態をもつための done プロパティを追加します。これは、チェックボックスとバインドする必要があるので初期値が false の observable として定義します。
self.addTodo = function () { if (self.canAddTodo()) { var todo = { summary: self.todoSummary(), done: ko.observable(false) }; self.todoList.push(todo); self.todoSummary(''); } };
次に、checked バインディングを使用してチェックボックスと Todo の done プロパティをバインドします。これで、Todo のチェックボックスの状態と done が同期できるようになります。
<input type="checkbox" data-bind="checked: done"/>
次に、完了している Todo をまもめて削除する処理を ViewModel のメソッドとして追加します。observableArray には関数を引数に指定して、条件に一致した要素を削除するという便利な remove メソッドが用意されているので、これを使用します。
self.archive = function () { self.todoList.remove(function (todo) { return todo.done(); }); };
あとは、この archive メソッドを View の要素とクリックバインディングでバインドすれば完成です。
<span class="archive redLink" data-bind="click:archive">archive</span>
以上、前回紹介しきれなかった Knockout の機能について紹介してきました。次回は、この Todo 管理アプリケーションに jQuery を使ったアニメーションやローカルストレージを使った保存機能を追加する予定です。おたのしみに。