みなさんこんにちは。江口です。
今回は Todo 管理アプリケーションにさらに手を加えて、アニメーションや HTML5 のローカルストレージを使った保存機能を追加してみましょう。
完成形のデモは こちら です。 ソースコードは こちら(Github) からダウンロード出来ます。

ViewModel と Model を分離する
ここまで、簡単のため Model の責務となるコードも ViewModel にまとめて実装を進めて来ましたが、今回でコード量が増えそうなので次のように ViewModel と Model に分離しました。View の表示に必要な Model の情報は ViewModel を経由して取り出します。
(function () {
function Todo(summary, done) {
this.summary = summary;
this.done = ko.observable(done);
}
function Model() {
var self = this;
self.todoList = ko.observableArray([new Todo('learn knockout', true)]);
self.addTodo = function (summary, done) {
self.todoList.push(new Todo(summary, done));
};
self.deleteTodo = function (todo) {
self.todoList.remove(todo);
};
self.archive = function () {
self.todoList.remove(function (todo) {
return todo.done();
});
};
}
function ViewModel(model) {
var self = this;
self.todoList = model.todoList;
self.deleteTodo = model.deleteTodo;
self.archive = model.archive;
self.todoSummary = ko.observable('');
self.addTodo = function () {
if (self.canAddTodo()) {
model.addTodo(self.todoSummary(), false);
self.todoSummary('');
}
};
self.canAddTodo = ko.computed(function () {
return self.todoSummary().length > 0;
});
}
var model = new Model();
ko.applyBindings(new ViewModel(model));
})();
アニメーションの設定
Todo の追加/削除時にスライドするようなアニメーションを設定してみましょう。アニメーションの効果を付けるために jQuery を使用するので次の script タグを追加します。
<script type="text/javascript" src="jquery-2.0.2.min.js"></script>
foreach バインディングには、主にアニメーションの効果を付けるためのオプション afterAdd と beforeRemove でコールバック関数を指定することができます。これらのコールバックは ObservableArray の要素が追加や削除されたタイミングで呼び出されるので、ここでアニメーション効果を与えてやることが可能です。ではまず、ViewModel にコールバック関数 showTodo と hideTodo を追加します。
function ViewModel(model) {
var self = this;
// 一部省略
self.showTodo = function (elem) {
if (elem.nodeType === 1) {
$(elem).hide().slideDown();
}
};
self.hideTodo = function (elem) {
if (elem.nodeType === 1) {
$(elem).slideUp(function () {
$(elem).remove();
});
}
};
}
コールバック関数として使用するメソッドを ViewModel に追加したので、あとは View でこのコールバック関数を設定するだけです。オプションを指定する場合には、foreach バインディングの値の書式が連想配列になるので、{data:todoList, afterAdd: showTodo, beforeRemove: hideTodo} のように指定します。
<ul data-bind="foreach: {data:todoList, afterAdd: showTodo, beforeRemove: hideTodo}">
<li class="todoItem" data-bind="css: {doneItem: done}">
<input type="checkbox" data-bind="checked: done"/>
<span class="summary" data-bind="text: summary"></span>
<span class="delete redLink" data-bind="click: $root.deleteTodo">delete</span>
</li>
</ul>
以上で、Todo の追加や削除がスライドアニメーションで表示されるようになりました。
データを保存する
Todo の一覧や完了状態をローカルストレージに保存して、ページを閉じても次に開いた時に以前の状態を復元できるようにしてみましょう。ここでは、todoList を JSON フォーマットの文字列に変換するのですが、メソッドを持つオブジェクトは JSON 文字列に変換することができません。そのため、done がメソッド(observable)である Todo オブジェクトはそのままでは文字列化できません。そのため、プロパティだけのオブジェクトに変換するメソッドを Todo オブジェクトに追加します。
Todo.prototype.toPlainObject = function () {
return {
summary: this.summary,
done: this.done()
};
};
あとは、localStrage への保存処理を save メソッドとして Model に追加します。localStorage オブジェクトは引数として与えるようにしています。またここでは、ko.utils で提供されているユーティリティメソッド arrayMap と stringifyJson を使用しています。
function Model(localStorage) {
var self = this;
// 一部省略
self.save = function () {
if (localStorage) {
// todoList を変換する
var list = ko.utils.arrayMap(self.todoList(), function (todo) {
return todo.toPlainObject();
});
var data = ko.utils.stringifyJson(list);
localStorage.setItem('todoList', data);
}
};
}
この save メソッドはページから離脱するときに実行したいので、jQuery で beforeunload イベントの発生時に実行されるように設定します。
var model = new Model(window.localStorage);
if (window.localStorage) {
$(window).on('beforeunload', model.save); // 保存処理を追加
}
ko.applyBindings(new ViewModel(model));
データを読み込む
最後に、ローカルストレージに保存したデータを読み込む処理 load メソッドとして ViewModel に追加し、Model の生成後に呼び出されるようにします。
function Model(localStorage) {
var self = this;
// 一部省略
self.load = function () {
if (localStorage) {
var data = localStorage.getItem('todoList');
if (data) {
self.todoList.removeAll();
var list = ko.utils.parseJson(data);
ko.utils.arrayForEach(list, function (todo) {
self.addTodo(todo.summary, todo.done);
})
}
}
};
}
var model = new Model(window.localStorage);
if (window.localStorage) {
model.load(); // 読み込み処理を追加
$(window).on('beforeunload', model.save);
}
ko.applyBindings(new ViewModel(model));
これで、ローカルストレージ機能に対応しているブラウザであれば Todo の状態が保存されるようになりました。
以上、三回に渡って Todo 管理アプリケーションを作りながら Knockout の機能について紹介してきました。次回以降は Observable やバインディングについて、更に詳しい解説を予定しています。おたのしみに。