環境 : Visual Studio 2013
こんにちは。
今日は小ネタです。でも、Visual Studio 2013 を活用してもらう上で大切なので、あえて詳説します。(前回投稿の続きです。)
前回の投稿「Visual Studio 2013 の Single Page Application (SPA) テンプレートを使った開発 (Knockout.js)」の最後に記載しましたが、Visual Studio 2013 が作成する Single Page Application (SPA) のテンプレートではエレガントなビューの切り替えをおこなっています。ページ自体のポストバックをおこなわずに、ログイン画面 (Views/Home/_Login.cshtml)、ユーザー登録画面 (Views/Home/_Register.cshtml)、アプリケーション本体 (Views/Home/Home.cshtml) などの画面の切り替えをおこないます。(こうした処理も、徹底的に Single Page Application のアーキテクチャを使って作成されているわけです。)
今日はその辺りをちゃんと解説 (補足) しておきたいと思います。
Single Page Application で複数のビューを動的に切り替えたい場合、すぐに思いつくのは、Node の表示・非表示 (visible) を変更して、その下の Element の表示を動的に切り替える方法ではないでしょうか ? しかし、Visual Studio 2013 の SPA のプロジェクト・テンプレートでは、knockout の特徴をうまく利用することで、もっとエレガントにこうした View の切り替えをおこなっています。
そもそも “with” は何者か ? (What is “with” region ?)
ここで紹介する knockout の “with” は、もともとは、Region (スコープ的なもの) を設定するために使用されます。
例えば、以下は、実行結果を見るまでもないと思いますが、Total が 200000、Count が 5 と表示されます。(これは、ごく一般的な knockout の記述です。)
<!DOCTYPE html><html><head> <title></title> @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/knockout") <script> $(document).ready(function () { var ordersystemViewModel = { products: { total: ko.observable(200000), count: ko.observable(5) } }; ko.applyBindings(ordersystemViewModel); }); </script></head><body> Total = <span data-bind="text: products.total"></span> <br /> Count = <span data-bind="text: products.count"></span> <br /></body></html>
これが、”with” を使うと、下記の通り記述できます。
今回は、HTML のコメントを使っていますが、”if”、”foreach” などと同じように、data-bind 属性を使って記述しても構いません。
. . .<body> <!-- ko with: products --> Total = <span data-bind="text: total"></span> <br /> Count = <span data-bind="text: count"></span> <br /> <!-- /ko --></body>. . .
“with” のもう 1 つの顔
さて、ここからが本題ですが、実は、この “with” は、null や undefined が明示的に指定された場合には、そのブロック内の要素を無視します。つまり、表示されません。
例えば、以下のサンプル・コードの場合、きっと、knockout のエラー (例外) が発生するか、あるいは、ページが表示されたとしても「Total = 」、「Count = 」と表示されるであろうと想像するでしょう。
しかし、それは間違いです。
<!DOCTYPE html><html><head> <title></title> @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/knockout") <script> $(document).ready(function () { var ordersystemViewModel = { products: { total: ko.observable(200000), count: ko.observable(5) }, customers : null }; ko.applyBindings(ordersystemViewModel); }); </script></head><body> <!-- ko with: customers --> Total = <span data-bind="text: total"></span> <br /> Count = <span data-bind="text: count"></span> <br /> <!-- /ko --></body></html>
この実行結果は、下図の通り、「Total = 」、「Count = 」すら表示されません。
この “with” のブロック全体がスキップされているためです。
例えば、下記のコードでは、[Change !] ボタンを押すたびに、下図の通り画面の表示を切り替えることができます。
<!DOCTYPE html><html><head> <title></title> @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/knockout") <script> $(document).ready(function () { var orgdata = { total: ko.observable(200000), count: ko.observable(5) }; var ordersystemViewModel = { products: ko.observable(orgdata) }; ordersystemViewModel.toggle = function () { if(this.products()) this.products(null); else this.products(orgdata); }; ko.applyBindings(ordersystemViewModel); }); </script></head><body> <!-- ko with: products --> Total = <span data-bind="text: total"></span> <br /> Count = <span data-bind="text: count"></span> <br /> <!-- /ko --> <button data-bind="click: toggle">Change !</button></body></html>
美しい Multiple View のための実装
この仕組みと knockout.js の “computed” を組み合わせると、この投稿でテーマとしている Multiple View の切り替えがエレガントに実装できます。(“computed” については「3つのMVC系人気フレームワーク、Backbone.js/AngularJS/Knockout.js」を参照してください。)
通常、View は多数 (複数) 存在しますが、そのうち、どれか 1 つのみを表示し、他の View はすべて表示されないようにする必要があるためです。
例えば、”computed” を使って以下の通り実装することで、[View name] のテキスト ボックスに入力した View のみが動的に表示されます。
<!DOCTYPE html><html><head> <title></title> @Scripts.Render("~/bundles/jquery") @Scripts.Render("~/bundles/knockout") <script> $(document).ready(function () { function productViewModel() { totalPrice = ko.observable(200000); }; function customerViewModel() { totalCustomer = ko.observable(6); }; function supplierViewModel() { totalPartner = ko.observable(58); }; function ordersystemViewModel() { var self = this; self.currentView = ko.observable("product"); self.products = ko.computed(function () { if (self.currentView() == "product") return new productViewModel(); else return null; }); self.customers = ko.computed(function () { if (self.currentView() == "customer") return new customerViewModel(); else return null; }); self.suppliers = ko.computed(function () { if (self.currentView() == "supplier") return new supplierViewModel(); else return null; }); }; ko.applyBindings(new ordersystemViewModel()); }); </script></head><body> <!-- ko with: products --> This is Product View ! <br /> Total = <span data-bind="text: totalPrice"></span> <br /> <!-- /ko --> <!-- ko with: customers --> This is Customer View ! <br /> Total = <span data-bind="text: totalCustomer"></span> <br /> <!-- /ko --> <!-- ko with: suppliers --> This is Supplier View ! <br /> Total = <span data-bind="text: totalPartner"></span> <br /> <!-- /ko --> View name <input type="text" data-bind="value: currentView" /></body></html>
ここでは、上記の products、customers、suppliers のそれぞれについて “computed” を実装していますが、Visual Studio 2013 の SPA のテンプレートのように、これらの各 computed の処理を ordersystemViewModel のメンバーとして共通化すると良いでしょう。(すべて似たような処理なので。。。) また、上述の “with” を使った 3 つのビューは、ASP.NET MVC の Partial View を使って異なる .cshtml ファイルで実装できます。Visual Studio 2013 の SPA のテンプレートを是非参考にしてみてください。
上記のコードからもわかるように、この方式が優れている点は、ビューの分割をしやすくしているだけでなく、ViewModel のほうも分離できる点です。(“with” で region をわけているため。)
Visual Studio 2013 の SPA のテンプレートでは、Scripts/app/home.viewmodel.js の HomeViewModel に必要な View Model を追加し、Views/Home/_Home.cshtml の Partial View を構築することで、提供されているログイン処理などをそのまま活用した形で、アプリケーション本体を構築できるようになっています。
Categories: Uncategorized
2 replies»