しばやん雑記

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

knockout.js でテンプレートを動的に切り替える

こんばんは、最近は knockout.js のことばかり弄っていて、そろそろ ASP.NET MVC のことも書けよと言われそうですが、MVC 4 には knockout.js 標準で入ってるし問題ないです(免罪符

相変わらずお仕事で knockout.js 2.0.0 を使いまくっているので、やたらとノウハウが蓄積されていってます。やっぱり仕事として弄ると、逃げられないので良いですね(白目 なので、ASP.NET MVC でのお仕事やってみたいなーと思ってますが、それは転職しないと叶いそうありません。

まあ、そのあたりはどうでもいいとして、今日はテンプレートを動的に切り替えてみたいと思います。

knockout.js では if/ifnot などのフローを使って、HTML の出し分けというのは可能です。例えば以下のようなコードです。

<!-- items の中身を出力 -->
<!-- ko template: { name: "sample", foreach: items } --><!-- /ko -->

<script id="sample" type="text/html">
<!-- ko if: type == 1 -->
<p>type == 1 の時のテンプレート</p>
<!-- /ko -->

<!-- ko if: type == 2 -->
<p>type == 2 の時のテンプレート</p>
<!-- /ko -->
</script>

sample テンプレートの中で if を使って HTML の出し分けを行っています。テンプレートが小規模な時などはこれで十分な場合もあります。

しかし、1 つの巨大なテンプレートを作ってしまうと最新のブラウザでも目に見えて DOM 構築に時間がかかるようになってしまいます。knockout.js は内部で DOM 全体のクローンを作ってからいろいろとやっているのかもしれません。

このように大規模なテンプレートを出し分けしたい場合には、テンプレート自体をそれぞれに用意して出しわける方法を使いましょう。先にサンプルコードを出しておきます。

<!-- items の中身を出力 -->
<!-- ko template: { name: templateSelector, foreach: items } --><!-- /ko -->

<script id="sample_1" type="text/html">
<p>type == 1 の時のテンプレート</p>
</script>

<script id="sample_2" type="text/html">
<p>type == 2 の時のテンプレート</p>
</script>

<script type="text/javascript">
var viewModel = {
    items: ko.observableArray()
};

// データから使用するテンプレート名を決定する
viewModel.templateSelector = function (item) {
    if (item.type == 1) {
        return "sample_1";
    } else if (item.type == 2) {
        return "sample_2";
    }
};

// とりあえずダミーデータをセット
viewModel.items.push({ type: 1 });
viewModel.items.push({ type: 2 });

ko.applyBindings(viewModel);
</script>

変わった点は個別のテンプレートとして分離したこと、そして template バインディングの name に templateSelector という関数を設定しているところです。

name に関数を指定すると、配列要素のレンダリングごとに呼び出され、要素の情報から使用するテンプレート名を決定することが出来ます。WPF/Silverlight で言うところの DataTemplateSelector に該当する機能だと思ってください。

そして今までは大きな sample テンプレートを使っていたので、レンダリングに時間がかかる場合がありましたが、個別のテンプレートとして分離したことで大幅な高速化も実現できました。ちなみに私は個別のテンプレートを別ファイルにしておいて、Smarty の include を使って読み込むようにしています。