先日、Backbone.js と比較しながら Knockout.js を試してみました。
Knockout.js よりも Backbone.js のほうが多機能だけど、コードの書きやすさは Knockout.js の方が上、というのが個人的な評価です。特にデータバインディングとテンプレートはスバラシイ。
たった一度サンプルを作っただけでおしまい、というのは寂しいので、Knockout.js でもう少し遊ぶことにします。今回は Knockout.js と jQuery Mobile を組み合わせることに挑戦。お題は先日同様 ToDo アプリで。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"/> <meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1"/> <title>Knockout Mobile</title> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.css" /> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script> <script type="text/javascript" src="http://code.jquery.com/mobile/1.0.1/jquery.mobile-1.0.1.min.js"></script> <script type="text/javascript" src="https://github.com/downloads/SteveSanderson/knockout/knockout-2.0.0.js"></script> </head> <body> <!--タスク一覧ページ--> <div id="list" data-role="page"> <div data-role="header"> <h1>タスクリスト</h1> <a href="#" data-bind="event: { click: newTask }" data-icon="plus">追加</a> <a href="#" data-bind="event: { click: toggleShowAll }"><!-- ko ifnot:isShowAll-->すべて<!-- /ko--><!-- ko if:isShowAll-->未完了<!-- /ko--></a> </div> <div data-role="content"> <ul id="tasklist" data-role="listview" data-bind="foreach: tasks"> <!--ko if:isVisible--> <li><a href="#" data-bind="text: name, event: { click: edit }"></a></li> <!--/ko--> </ul> </div> </div> <!--タスク編集ページ--> <div id="edit" data-role="page" data-bind="with: currentTask"> <div data-role="header"> <h1><!--ko ifnot:isNew-->タスク編集<!--/ko--><!--ko if:isNew-->タスク登録<!--/ko--></h1> </div> <div data-role="content"> <div class="input"> <label for="name">タスクの内容:</label> <input type="text" data-bind="value: name, valueUpdate: 'keyup'" /> </div> <div class="actions"> <!--ko if:isNew--> <!--新規作成のとき表示されるボタン--> <input type="button" value="登録" data-bind="event: { click: save }" /> <!--/ko--> <!--ko ifnot:isNew--> <!--編集のとき表示されるボタン群--> <input type="button" value="編集終了" data-bind="event: { click: save }" /> <!--ko ifnot:completed--> <input type="button" value="完了" data-bind="event: { click: done }" /> <!--/ko--> <!--ko if:completed--> <input type="button" value="完了取消" data-bind="visible: completed, event: { click: undone }" /> <!--/ko--> <input type="button" value="削除" data-bind="event: { click: destroy }" /> <!--/ko--> </div> </div> </div> <script type="text/javascript"> (function() { /** * タスクを表します。 */ var TaskViewModel = function(taskList) { // 登録先タスクリスト this.taskList_ = taskList; // タスク名 this.name = ko.observable(""); // 完了かどうか this.completed = ko.observable(false); // 新規作成中かどうか this.isNew = ko.observable(true); // 一覧に表示するかどうか this.isVisible = ko.dependentObservable(function() { // 未完了なら表示。 // 完了済みで、全表示が有効なら表示。 if (this.completed()) { return this.taskList_.isShowAll(); } else { return true; } }, this); }; /** * タスク一覧ページに移動します。 */ TaskViewModel.prototype.moveList = function() { $("#tasklist").listview("refresh"); $.mobile.changePage("#list"); }; /** * タスクを保存します。 */ TaskViewModel.prototype.save = function() { if (this.isNew()) { this.isNew(false); this.taskList_.tasks.push(this); } this.moveList(); }; /** * タスクを削除します。 */ TaskViewModel.prototype.destroy = function() { this.taskList_.tasks.remove(this); this.moveList(); }; /** * タスクの編集画面に移動します。 */ TaskViewModel.prototype.edit = function() { this.taskList_.currentTask(this); // jQuery Mobile のテーマを適用し直す。 // 動的に DOM を書き変えたら、jQuery Mobile のテーマが適用されないので、 // page メソッドでテーマを適用し直す。 // 同じ要素に2回以上 page は作用しないので、テーマを解除してから適用し直す。 $("#edit").removeData("page"); $("#edit").page(); $.mobile.changePage("#edit"); }; /** * タスクを完了します。 */ TaskViewModel.prototype.done = function() { this.completed(true); this.moveList(); }; /** * タスクの完了を取り消します。 */ TaskViewModel.prototype.undone = function() { this.completed(false); this.moveList(); }; /** * タスクリストを表します。 */ var taskListViewModel = { /** * 編集中のタスク */ currentTask: ko.observable(), /** * 登録されたタスク一覧 */ tasks: ko.observableArray(), /** * 完了済みも表示するかどうか */ isShowAll: ko.observable(false), /** * 新しいタスクを作成します。 */ newTask: function() { var task = new TaskViewModel(this) task.edit(); return false; }, /** * 完了済みを表示するかどうかを切り替えます。 */ toggleShowAll: function() { this.isShowAll(!this.isShowAll()); $("#tasklist").listview("refresh"); } }; // ビューにバインド ko.applyBindings(taskListViewModel); })(); </script> </body> </html>
コピペで動くはずです。今回もコードはスッキリしています。ビューモデルを変更したら自動でビューに反映されるので、HTML を更新するコードを書かなくていいのが大きい。要素の表示・非表示でさえも自動でやってくれます。データバインディングはクセになりますよ。
ただ、ビューモデルにページ移動のコードを書くしかなかったのが無念です。jQuery Mobile のスタイルを適用し直している部分も同様。でも、これしか実装が思いつきませんでした。力不足。本当はビューモデルで DOM を操作したくなかったんだ。
Backbone.js だとビューのクラスを作成するので、 ページ移動やスタイルの適用はビューに書けるんですけどね。一応、Knockut.js では data-bind の中に複雑な JavaScript を書けるみたいですけど、それをやってしまったら HTML がカオスになってしまいます。ダメ、ゼッタイ。まぁ、今回は私の力が足りなかっただけなんでしょうけど。