[Knockout.js基本編]Viewから参照できるのはViewModelオブジェクトとグローバルスコープのみ(っぽい)

2014年06月03日

カテゴリー:

Knockout.jsではView(HTML)側からko.applyBindingsメソッドの引数として渡されるViewModelオブジェクトのプロパティやメソッドを参照することが可能ですが、場合によってはViewModelオブジェクトにはない(ViewModelオブジェクトの外部で定義されている)関数などを使いたくなるケースもあるかと思います。

そのような関数やプロパティをView側参照するにはどうすれば良いのでしょうか?
そもそもView側からは何が参照可能なのか?という部分がよく分かっていなかったので、そのあたりを少し調べてみました。

あくまでも私の検証結果に基づいたまとめですので間違っている部分もあるかもしれませんが、個人的なまとめとして書き残しておきます。

とりあえず結論から

Knockout.jsを使う場合、View(HTML)側から参照できるのは「ViewModelオブジェクトに属するプロパティまたはメソッド」および「グローバルオブジェクトから辿れるプロパティまたはメソッド」です。

【View(HTML)側から参照可能なもの】

  • ViewModelオブジェクトに属するプロパティまたはメソッド
  • グローバルオブジェクトから辿れるプロパティまたはメソッド

前者はko.applyBindingsメソッドの引数として渡したオブジェクトです。View側からこのオブジェクトを参照する場合、このオブジェクトをルートオブジェクトとして、配下のプロパティやメソッドを参照することができます。

後者のグローバルオブジェクトとは、ブラウザで動作するWEBアプリであればwindowオブジェクトを指します。 以下にそのあたりを具体例を交えて説明していきます。

ViewModelの参照

まず最初は最も基本的ケースで、単純にView側からViewModelオブジェクトのプロパティを参照するコードです。

<html>
  <body>
    <span data-bind="text: name"></span>
    <script src="knockout.js"></script>
    <script src="view-model.js"></script>
  </body>
</html>
window.onload = function () {
    'use strict';

    var viewModel = {
            name: 'taro'
        };

    ko.applyBindings(viewModel);
};

上記の例では画面に「taro」と表示されます。ko.applyBindingsメソッドの引数(ここではviewModel)が、View側から見た時のルートオブジェクトになるので、viewModel.nameプロパティをViewから参照するときは「viewModel.name」ではなく「name」です。

次の例は、ViewModelオブジェクトが階層構造(プロパティのネスト)だった場合の例です。

<html>
  <body>
    <span data-bind="text: aaa.bbb.ccc.name"></span>
    <script src="knockout.js"></script>
    <script src="view-model.js"></script>
  </body>
</html>
window.onload = function () {
    'use strict';

    var viewModel = {
            aaa: {
                bbb: {
                    ccc: {
                        name: 'taro'
                    }
                }
            }
        };

    ko.applyBindings(viewModel);
};

画面には、最初のコード例と同じく「taro」と表示されます。階層構造を持ったViewModelオブジェクトをView側から参照する場合でも、通常のJavaScriptと同様ドットシンタックスを使って参照することができます。

foreachループ内からの参照

今度はViewModelオブジェクトに配列が含まれている場合のViewからの参照です。
まずはループを使わずに、配列インデックス番号を指定して直接値を参照するコード例です。

<html>
  <body>
    <span data-bind="text: items[0].name"></span>
    <script src="knockout.js"></script>
    <script src="view-model.js"></script>
  </body>
</html>
window.onload = function () {
    'use strict';
 
    var viewModel = {
            items: [
                { name: 'taro', age: 16 },
                { name: 'jiro', age: 15 },
                { name: 'saburo', age: 14 }
            ]
        };
 
    ko.applyBindings(viewModel);
};

上の例では画面に「taro」と表示されます。ViewModel内の配列を参照する場合も、通常のJavaScript構文を使って配列の要素にアクセスすることができます。
ですが、通常このような使い方をするケースは少なく、大抵の場合は次の例で示すようなforeachバインディングを使って繰り返し処理を行うケースが多いでしょう。

<html>
  <body>
    <ul data-bind="foreach: items">
      <li>
        名前:<span data-bind="text: name"></span>
        年齢:<span data-bind="text: age"></span>
      </li>
    </ul>
    <script src="knockout.js"></script>
    <script src="view-model.js"></script>
  </body>
</html>
window.onload = function () {
    'use strict';

    var viewModel = {
            items: [
                { name: 'taro', age: 16 },
                { name: 'jiro', age: 15 },
                { name: 'saburo', age: 14 }
            ]
        };

    ko.applyBindings(viewModel);
};

foreachバインディングでは、その直下のテンプレート(上記のHTMLの例では4~7行目)が、配列の要素の数だけ繰り返しレンダリングされます。直下のテンプレート側から見た場合のルートオブジェクトは、配列の現在の要素そのものです。

配列の各要素がオブジェクトではなく、プリミティブ(文字列、数値、真偽値)の場合は、上のコード例の様に、現在の要素のプロパティではなく、現在の要素そのものをテンプレートにバインドさせたいはずです。そのような場合はforeachブロック内で使用可能な「$data」という特別な変数を用いることで参照可能です。

<html>
  <body>
    <ul data-bind="foreach: items">
      <li><span data-bind="text: $data"></span></li>
    </ul>
    <script src="knockout.js"></script>
    <script src="view-model.js"></script>
  </body>
</html>
window.onload = function () {
    'use strict';

    var viewModel = {
            items: ['a', 'b', 'c', 'd']
        };

    ko.applyBindings(viewModel);
};

ViewModel外のデータ/関数を参照する

View(HTML)側からはViewModelオブジェクト以外に、グローバルオブジェクト、およびグローバルオブジェクトから辿れるオブジェクトを参照することができます。

JavaScriptの実行環境がブラウザであれば、グローバルスオブジェクトはwindowオブジェクトを指します。
また、関数の外で定義されたオブジェクトはwindowオブジェクトのプロパティになります。

次の例はViewからwindowオブジェクトのscreenプロパティを参照した例です。画面にはスクリーンの高さが表示されます。

<html>
  <body>
    <span data-bind="text: screen.height"></span>
    <script src="knockout.js"></script>
    <script src="view-model.js"></script>
  </body>
</html>
window.onload = function () {
    'use strict';
    ko.applyBindings();
};

この例ではView側からViewModelオブジェクトは一切参照していないので、ko.applyBindings()の引数には何も渡していません。であれば、そもそもko.applyBindings()は不要と思われるかもしれませんが、ここでko.applyBindings()をコールしないと画面には何も表示されなくなります。つまりView側のバインディングが動かなくなるということです。

ViewModelオブジェクトを参照するか否かにかかわらず、View側でバインディングを使用する場合は、必ずko.applyBindings()をコールする必要があるみたいですね。


次の例はグローバルオブジェクトにオブジェクトを作成し、そのメソッドを参照する例です。

<html>
  <body>
    <span data-bind="text: myObj.name"></span><!-- 'taro'が出力される -->
    <span data-bind="text: myObj.func()"></span><!-- 'hoge'が出力される -->
    <script src="knockout.js"></script>
    <script src="view-model.js"></script>
  </body>
</html>
var myObj = {};

myObj.name = 'taro';
myObj.func = function () {
    return 'hoge';
};

window.onload = function () {
    'use strict';

    var viewModel = {};

    ko.applyBindings(viewModel);
};

上のコードのmyObjは関数の外側(グローバルスコープ)で定義されているので、グローバルオブジェクトのプロパティになります。そのため、View側からもアクセスすることが可能です。

以下のmyObjはViewModelオブジェクトに属しておらず、また関数スコープ内で定義されているため、グローバルスコープからも辿れないのでView側から参照することができません。

window.onload = function () {
    'use strict';

    var myObj = {}, //View側からこのオブジェクトを参照することは出来ない
        viewModel = {};

    myObj.name = 'taro';
    ko.applyBindings(viewModel);
};