C# Advent Calendar 2011 で書いた knockout.js の記事が割と好評なので、調子に乗って knockout.js の解説を書いてしまおうという作戦です。
何故 knockout.js かというのは、今仕事で knockout.js を全力で使っているという非常にシンプルな理由です。今回 knockout.js を使ったケースではフォームを動的に追加・削除が必要で、さらにそのフォーム内にもさらに別のフォームを動的に追加・削除・編集が必要という、結構複雑な画面です。
最初は jQuery Templates だけを使っていたのですが、クライアントサイドでもデータを保持する必要があったので knockout.js を使ってみたところ、一気にコードがシンプルになりました。割と惚れ込んでいます。
まずは knockout.js 自体の説明を行いたいので、とりあえず公式ページから Key concepts を引っ張ってきました。
- Declarative Bindings
- Automatic UI Refresh
- Dependency Tracking
- Templating
日本語にすると「宣言型バインディング」「UI の自動更新」「依存関係の追跡」「テンプレート」といったところでしょうか。余談になりますが、WPF や Silverlight をやったことのある人ならどこかで聞いたことのあるようなキーワードが勢揃いだと思います。
それでは一つずつ例を挙げつつ解説していきたいと思います。
Declarative Bindings(宣言型バインディング)
knockout.js では data-bind 属性を使ってバインディングの宣言を行うようになっています。例えば span の内容を name というプロパティに結び付けたい場合には以下のように宣言します。
<span data-bind="text: name"></span>
data-bind 属性の値には JSON に似た形式で書く必要があります。この例では text バインディングを使って、name プロパティを結び付けています。text バインディングは DOM で言う innerText や textContent に値をセットする機能を持ちます。
text バインディング以外にも html/css/value/checked など数多くのバインディングが用意されています。
Automatic UI Refresh
先程の例では span の内容に対してバインディングを行いましたが、内容の変更以外にも visible バインディングを使えば要素の表示・非表示を制御出来ます。以下の例では text が空じゃないときだけ div 要素が表示されます。
<div data-bind="visible: text().length > 0"> はうはう </div>
最大の特徴ともいえるのが template バインディングで、配列から複数の UI 要素を動的に生成することが出来ます。パラメータはいくつか指定できますが、foreach パラメータで配列を指定するのがお手軽でおススメです。
<ul data-bind="template: { name: 'listTemplate', foreach: items }"></ul> <script id="listTemplate" type="text/x-jquery-tmpl"> <!-- テンプレートタグ、データバインディングの両方が利用可能 --> <li><span>${name}</span> - <span data-bind="text: name"></span></li> </script>
当然ながら配列の変更を追跡しているので、要素を追加した場合には対応した UI 要素も追加表示されます。
Dependency Tracking
複数のプロパティの値を利用したバインディングを行いたい場合用に dependentObservable というメソッドが用意されています。引数として値を返すだけの関数オブジェクトを指定するだけです。
// x, y は ko.observable viewModel.total = ko.dependentObservable(function() { return this.x() + this.y(); }, viewModel);
この例では x と y の値を足したものを返しているだけですが、x と y のどちらかの値が変わると、自動的に total プロパティにも変更が反映されるようになっています。
Templating
バージョン 1.2.1 では jQuery Templates をテンプレートエンジンとして使うことで、オブジェクトの配列から複数の UI 要素を一気に生成することが出来ます。先程、UI 要素生成で使用したやつですね。
現在 RC 版がリリースされている 1.3.0 ではテンプレートエンジンが内蔵されているので、jQuery Templates が不要になっています。残念ながら jQuery Templates は正式版リリース前に開発が終わってしまいましたので、1.3.0 からは内蔵テンプレートエンジンを使う機会が多くなると思います。
基本的な使い方
まずは簡単なサンプルコードで説明を行っていきたいと思います。姓、名、年齢を入力するフォームと、それを出力するための領域を今回用意しました。
<div> <label>姓:<input type="text" data-bind="value: lastName" /></label><br /> <label>名:<input type="text" data-bind="value: firstName" /></label><br /> <label>年齢:<input type="text" data-bind="value: age" /></label> </div> <br /> <div> 氏名:<span data-bind="text: fullName"></span> <br /> 年齢:<span data-bind="text: age"></span>歳 </div> <script type="text/javascript"> var viewModel = { lastName: ko.observable(""), firstName: ko.observable(""), age: ko.observable() }; viewModel.fullName = ko.dependentObservable(function() { return this.lastName() + " " + this.firstName(); }, viewModel); ko.applyBindings(viewModel); </script>
サンプルコードは以下のリンクから動いているものが確認できます。
http://jsfiddle.net/shibayan/kPUyN/
それではフォームに何か入力してみてください。そしてフォーカスが外れた瞬間に入力内容が出力されていることが分かると思います。ここからは少し細かく説明をしていきます。
オブジェクトを作成して viewModel という名前の変数に入れているだけですが、プロパティの値として ko.observable メソッドを呼び出した結果をセットしています。knockout.js の心臓部と言っても過言ではないのが ko.observable メソッドで、値の保持や変更追跡を行うオブジェクトを作成して返してくれます。
var viewModel = { lastName: ko.observable(""), firstName: ko.observable(""), age: ko.observable() };
注意点としてはプロパティの値はオブジェクトになっているので、値の取得と設定は普通のプロパティのように行えません。そのままだと関数オブジェクトが返ってきて残念な結果になります。
// 引数無しで呼び出してあげると値が取れる var lastName = viewModel.lastName(); // 引数をセットして呼び出すと値を設定 viewModel.lastName("ほげほげ");
そして Key concepts の説明時にも出てきた dependentObservable ですが、ここでは単純に姓と名の間にスペースを入れて結合し、フルネームを作成しているだけです。
viewModel.fullName = ko.dependentObservable(function() { return this.lastName() + " " + this.firstName(); }, viewModel);
そして viewModel の準備が完了したら ko.applyBindings を呼び出してバインディングを有効にします。引数には作成した viewModel を指定するのを忘れずに。
ko.applyBindings(viewModel);
これで後は knockout が変更追跡や UI の自動生成を行ってくれます。非常に簡単ですね!
knockout 1.3.0
まずは開発者である Steven Sanderson さんのブログを確認しておきましょう。
と思ったのですが、github を確認したところ 2.0.0 rc というものが公開されていました…。恐らくは 1.3.0 → 2.0.0 に変わっただけだと思うのですが、変更点がまだよくわかっていませんので、後日別エントリでまとめてみたいと思います。
ちなみにバージョン 1.3.0 ではバインディングのコンテキストを明示的に指定出来たり、テンプレートエンジンが内蔵されていたり、バインディングプロバイダが拡張できるようになっていたりします。個人的には以下のようなコードでテンプレートを実現できる 1.3.0 の記法は好みです。
<!-- 子要素の li がそのままテンプレートとして扱われる --> <ul data-bind="foreach: items"> <li><span data-bind="text: name"></span> - <span data-bind="text: age"></span></li> </ul>
今回 knockout.js の説明を長々と書きましたが、実は MIX11 の時に knockout.js 開発者の Steven Sanderson さんがデモをやっていたみたいです。
30 分近くの動画なので時間がある時にでもどうぞ。knockout.js は非常に使い勝手がいいライブラリなので、これからも使っていきたいですね!