Knockout.js で Multiple View (Partial View) をエレガントに切り替える方法


環境 : 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 を構築することで、提供されているログイン処理などをそのまま活用した形で、アプリケーション本体を構築できるようになっています。

 

Comments (0)

Skip to main content