しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Knockout.js の Mapping プラグインを使ったら凄く捗った件

めっちゃ久しぶりに 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 メソッドを使うことです。

// 元になる JavaScript オブジェクト
var obj = {
    foo: 12345,
    bar: 'string',
    baz: [ 1, 2, 3, 4, 5 ]
};

var viewModel = ko.mapping.fromJS(obj);

たったこれだけのコードで以下のようなトラッキング可能なモデルが生成されます。

// ko.mapping.fromJS を使うと以下のような VM が自動生成される
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 をそのまま渡すことも出来ます。

// JSON 化したオブジェクト
var json = '{"foo":12345,"bar":"string","baz":[1,2,3,4,5]}';

// fromJS メソッドを使った結果と同じ
var viewModel = ko.mapping.fromJSON(json);

JSON から直接モデルを生成させることが出来るので、ASP.NET MVC から使う場合には以下のように JSON 化して渡しました。

// Json ヘルパーを使って JSON にエンコード
var json = '@Html.Raw(Json.Encode(model))';

var viewModel = ko.mapping.fromJSON(json);

注意点としては Html.Raw ヘルパーを使わないと HTML エンコードされてエラーになるという点ぐらいでしょうか。これで自由に操作可能なモデルを、わざわざ手動で定義することなく作成できました。

ちなみに Observable を使っているモデルから、普通の JavaScript オブジェクトや JSON に変換したい場合には、toJS / toJSON メソッドを使えば簡単に実現できます。

// observable を外して普通のオブジェクトに変換
var obj = ko.mapping.toJS(viewModel);

// 同じく JSON に変換
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 クラスのインスタンスに変換されていることが確認できます。

f:id:shiba-yan:20140613172356p:plain

さらに特殊な変換が必要であれば、変換の関数内に組み込んでしまえば良いですね。

モデルの更新

Mapping プラグインではオブジェクトや JSON から新しくモデルを作成する以外にも、既存のモデルに対して値の更新を行うことが出来ます。使い方は fromJS / fromJSON の引数として既存のモデルを渡すだけです。

// 元になるモデルを作成する
var viewModel = ko.mapping.fromJS({
    name: 'kamebuchi'
});

// 新しいオブジェクトで値を更新
ko.mapping.fromJS({
    name: '抱かれたい男 No.1',
}, {}, viewModel);

// "抱かれたい男 No.1" が出力される
console.log(viewModel.name());

ちゃんと Observable なプロパティを壊すことなく更新してくれるので、定期的にサーバーからデータを更新するようなアプリケーションで便利だと思います。

いろいろ見てみると機能的には AutoMapper とかに似てますね。細かいカスタマイズや、別ソースを使って既存のモデルへの更新も出来るのが非常に便利でした。

*1:当然ながら Knockout.js の参照も必要