お疲れ様です。
思えばKnockoutJS触るようになってからまだ3ヶ月かそこらです。そこまで語れるようなコアな知識に踏み込めるか?というとそこまで探求しきれてはいません。なので初学者向けに何か書けるといいな、と考えました。
KnockoutJSを使おうと思ったきっかけは3ヶ月くらい前、Rebuild.fmで宮川さんがAngularをdisりながら「JavaScript得意な人はKnockoutが薄くていいよ、っていうけどね」と言うのを聞いたことでした。自分も前の仕事でAngularの規模感(というか学習コスト)につらみを感じていたので、新しく開発する前にちょっと触ってみるか、と興味を持ちました。
勉強しはじめて、わりとすぐ新しい開発で導入したのですが、その中で実際に何をしたのかを振り返ると
- 公式チュートリアルを3周くらいやる
- Railsにうまいことフィットさせる方法を実装しながら模索
この2つだけです。
本当にこれだけ。単純にアプリを作るということで言えばKnockoutJSの学習コストはとても低い。
KnockoutJS公式チュートリアル
公式チュートリアルは5つの小さなサンプルのWebアプリをブラウザ内エディタで実装して動かしてみる、という構成になっています。
1. イントロダクション
チュートリアル一つ目は「データバインディングという概念」そのものと、それをKnockoutJSでどう実装するかについてが主題です。
データバインディングはJavaScriptフレームワークのメイン機能の一つで、「モデルを監視し、テンプレートと紐づけ、モデルに変更があった場合にはテンプレートも自動で更新する」というもの。
KnockoutJSでは一つのモデルを監視するのにViewModel内でko.observable()という関数を用います。
function AppViewModel() {
this.firstName = ko.observable("Bert");
this.lastName = ko.observable("Bertington");
}
2. リストや集合の取り扱い
複数のモデルを監視したい場合はどうしたらよいか、を学ぶのが2つ目のチュートリアルです。
リストの監視にはko.observableArray()という関数を使います。
監視されたオブジェクトはほとんど配列のように使え、push, pop, spliceなどで状態を変更できます。そしてその変化が自動でテンプレート側に更新される。
ただ、この関数が監視するのは「配列としての状態」のみであり、要素数の増減は監視しますが、その中身を監視する機能は実はありません。
リストの中身まで監視するには、一つ一つに対してko.observable()してやる必要があります。
self.seats = ko.observableArray([
new SeatReservation("Steve", self.availableMeals[0]),
new SeatReservation("Bert", self.availableMeals[0])
]);
self.addSeat = function() {
self.seats.push(new SeatReservation("", self.availableMeals[0]));
}
self.removeSeat = function(seat) { self.seats.remove(seat) }
3. Single Page Application
三つ目のチュートリアルはSPAというタイトルですが、機能的な主題はREST APIのうち一覧&詳細を見るアプリケーションの実装です。
例にあるのはメールクライアントで、カテゴリごとのメール一覧をAPIから取ってきて、どれか一つを選択すると詳細も見れるよ、という内容です。
Sammyというプラグインを用いることで綺麗にAPIとの連携を実装することができます。
Sammy(function() {
this.get('#:folder', function() {
self.chosenFolderId(this.params.folder);
self.chosenMailData(null);
$.get("/mail", { folder: this.params.folder }, self.chosenFolderData);
});
this.get('#:folder/:mailId', function() {
self.chosenFolderId(this.params.folder);
self.chosenFolderData(null);
$.get("/mail", { mailId: this.params.mailId }, self.chosenMailData);
});
this.get('', function() { this.app.runRoute('get', '#Inbox') });
}).run();
API叩いて返ってきた内容を監視しているオブジェクトにいれています。
4. カスタムバインディング
KnockoutJSではテンプレートにdata-bind="" という属性をつけて、""の中に入れたい機能をバインドする、という使い方をします。
<span data-bind="text: value"></span>
上のやつだとspanの中にvalueに入った値が出るようにバインドするわけですが、この場合の"text"にあたる部分をbindingHandlerといい、自作して定義することができます。
実際にはDOM操作をモデル側のロジックと分離したい場合に使うことが多いです。
DOMの振る舞いを共通で定義したい(表示後5秒間フラッシュする、スライドする、etc.)場合に定義しといて、適宜パラメータ渡してうまく動くようにしておきます。
ko.bindingHandlers.fadeVisible = {
init: function(element, valueAccessor) {
var shouldDisplay = valueAccessor();
$(element).toggle(shouldDisplay);
},
update: function(element, valueAccessor) {
var shouldDisplay = valueAccessor();
shouldDisplay ? $(element).fadeIn() : $(element).fadeOut();
}
};
上は、valueAccessor(バインドされた値にアクセスする関数)の返す値がtrueかfalseかによってその要素を表示するか否かを決めるbindingHandlerです。状態が変わったら、効果付きで要素を(もわわんと)フェードイン・フェードアウトします。
5. データの永続化
チュートリアルの最後の主題は「APIから取ってきたリストに変更を加えて保存する」機能をTODOリストで実装する、というものです。
公式でも書いてますがJSフレームワークのチュートリアルではTODOを実装するのが半ばお約束です。
ポイントとして「formをPOSTする」以上のものはありませんが、一応Railsで用意されている_destroyフラグをサポートしていて、これを使ってPOSTにパラメータ渡すことでサーバ側で要素を削除することができたりという便利さもあります。
observableArrayの要素を削除する際には通常remove()を使いますが、destroy()を使うと、配列からは消えずに_destroyが加えられる、という仕様になっています。
self.removeTask = function(task) { self.tasks.destroy(task) }; //=> taskに_destroyフラグがつけられる
ただ、基本的にはformのsubmitイベントを紐づけてjQueryの$.ajax()とかでリクエストを飛ばすだけなのであまり真新しい感じはしません。
まあだからこそすんなり理解できたわけではありますが。
self.save = function() {
$.ajax("/tasks", {
data: ko.toJSON({ tasks: self.tasks }),
type: "post", contentType: "application/json",
success: function(result) { alert(result) }
});
};
まとめ
以上、かなり雑にKnockoutJS公式チュートリアルの内容について説明しましたがいかがだったでしょうか。
ちょっと雑すぎましたね。日程も遅れてるし。
ですが、これをみて「こんなアホでもKnockoutできるんだ、簡単そう」と思って興味を持ってくれる方が一人でも出れば幸いです。
よい年末を!