こんばんは、最近は 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 を使って読み込むようにしています。