めっちゃ久しぶりに Knockout.js の話です。最近、仕事で再び使い始めたので Tips 的なものが貯まってきたら、こうやってちょいちょい書いていきます。
Knockout : Home
ASP.NET MVC 側で持っているモデルをそのまま JSON にして Knockout.js に流し込みたかったのですが、いちいち JavaScript 側でも同じ構造を持ったモデルを作るのは非常に面倒なので、今回は Knockout.js に用意されている Mapping プラグインを使ってみました。
Mapping プラグインのドキュメントは公式サイトで提供されています。
Knockout : Mapping
プラグイン本体は GitHub からダウンロードできます。Knockout.js の最新バージョンは 3.1.0 なので互換性を心配しましたが、今のところは特に問題なく使えています。
knockout.mapping/build/output at master · SteveSanderson/knockout.mapping · GitHub
ダウンロードしてきたプラグインへの参照を HTML に追加するだけで準備完了です。*1
基本的な使い方
恐らく一番簡単な使い方としては、ko.mapping.fromJS メソッドを使うことです。
var obj = {
foo: 12345,
bar: 'string',
baz: [ 1, 2, 3, 4, 5 ]
};
var viewModel = ko.mapping.fromJS(obj);
たったこれだけのコードで以下のようなトラッキング可能なモデルが生成されます。
var viewModel = {
foo: ko.observable(12345),
bar: ko.observable('string'),
baz: ko.observableArray([ 1, 2, 3, 4, 5 ])
};
スカラー型の場合には ko.observable が、配列の場合には ko.observableArray が使われるので、多くの場合で手直しなどが必要ありません。
この例では JavaScript オブジェクトを渡していますが、fromJSON メソッドを使うと JSON をそのまま渡すことも出来ます。
var json = '{"foo":12345,"bar":"string","baz":[1,2,3,4,5]}';
var viewModel = ko.mapping.fromJSON(json);
JSON から直接モデルを生成させることが出来るので、ASP.NET MVC から使う場合には以下のように JSON 化して渡しました。
var json = '@Html.Raw(Json.Encode(model))';
var viewModel = ko.mapping.fromJSON(json);
注意点としては Html.Raw ヘルパーを使わないと HTML エンコードされてエラーになるという点ぐらいでしょうか。これで自由に操作可能なモデルを、わざわざ手動で定義することなく作成できました。
ちなみに Observable を使っているモデルから、普通の JavaScript オブジェクトや JSON に変換したい場合には、toJS / toJSON メソッドを使えば簡単に実現できます。
var obj = ko.mapping.toJS(viewModel);
var json = ko.mapping.toJSON(viewModel);
クライアントサイドでリストの項目を動的に追加、削除したりするケースで非常に力を発揮してくれました。
マッピング設定を追加
JavaScript のオブジェクトや JSON をそのまま Observable なモデルに変換したいだけであれば問題無いのですが、マッピング時にモデルの値を操作したい場合がちょいちょい出てきます。
具体的な例を挙げると日付の扱いです。JSON は日付が定義されていないので、そのままでは文字列表現になってしまったり、ASP.NET というか JSON.NET をそのまま使った場合では "/Date(1198908717056)/" という形式になったりとバラバラです。
そのあたりの経緯は以下の記事を参照してください。
Tales from the Evil Empire - Dates and JSON
James Newton-King - Good (Date)Times with Json.NET
JSON 上は文字列であっても、モデルとしては日付として扱いたいので、Mapping プラグインのカスタマイズ機能を使い、変換を定義して解決します。
ko.mapping.fromJS / fromJSON の 2 つ目の引数には、プロパティ名でマッピング時の挙動を定義したオブジェクトを指定できるので、これを使って Date クラスに変換する処理を追加します。
var mapping = {
created_at: {
update: function (options) {
return new Date(parseInt(options.data.substr(6)));
}
}
};
var obj = {
created_at: "/Date(1198908717056)/"
};
var viewModel = ko.mapping.fromJS(obj, mapping);
作成した定義は非常にシンプルなものになりますが、実際に実行してみるとちゃんと Date クラスのインスタンスに変換されていることが確認できます。
さらに特殊な変換が必要であれば、変換の関数内に組み込んでしまえば良いですね。
モデルの更新
Mapping プラグインではオブジェクトや JSON から新しくモデルを作成する以外にも、既存のモデルに対して値の更新を行うことが出来ます。使い方は fromJS / fromJSON の引数として既存のモデルを渡すだけです。
var viewModel = ko.mapping.fromJS({
name: 'kamebuchi'
});
ko.mapping.fromJS({
name: '抱かれたい男 No.1',
}, {}, viewModel);
console.log(viewModel.name());
ちゃんと Observable なプロパティを壊すことなく更新してくれるので、定期的にサーバーからデータを更新するようなアプリケーションで便利だと思います。
いろいろ見てみると機能的には AutoMapper とかに似てますね。細かいカスタマイズや、別ソースを使って既存のモデルへの更新も出来るのが非常に便利でした。