Closure Library 超入門 〜データソース編〜

はじめに

.NET 歴が長いので、データソースと聞くとデータバインドを連想してしまいます。でも残念ながら、Closure Library の UI フレームワークはデータバインドを提供していません。今後に期待。

Google Closure のデータソース機能は goog.ds 名前空間で提供されていて、

  1. データソース
  2. データマネージャー
  3. エクスプレッション

という3つの要素で構成されています。

データソース

データソースを使えば、JavaScript オブジェクトも JSON も XML も同じ方法でアクセス可能。データソースの種類は JsDataSource の他にも XmlDataSource や JsonDataSource などがあります。よく使うのはおそらく JsDataSource。

ノードを取得して値の取得または設定、というのが操作の基本。ノードにはオブジェクトだけでなく配列も設定できます。

<html>
    <head>
        <title>DataSourceSample</title>
    </head>
    <body>
        <script type="text/javascript" src="closure-library/closure/goog/base.js"></script>
        <script type="text/javascript">
            goog.require("goog.ds.JsDataSource");
        </script>
        <script type="text/javascript">
            var testData = { 
                "tasks": [
                    { key: "foo0", name: "entry0" },
                    { key: "foo1", name: "entry1" },
                    { key: "foo2", name: "entry2" },
                    { key: "foo3", name: "entry3" },
                    { key: "foo4", name: "entry4" }
                ],
                "tags": [
                    { key: "bar0", name: "tag0" }
                ]
            };

            // データソース作成
            var ds1 = new goog.ds.JsDataSource(testData, "sample");


            // ルート取得
            // tasks ノードと tags ノードを格納した NodeList を取得
            var rootNode = ds1.get();
            console.log(rootNode.getCount()); //=> 2
            // getChildNodes でも同じ
            var rootChildNodes = ds1.getChildNodes();
            console.log(rootChildNodes.getCount()); //=> 2


            // tasks ノードを取得
            var tasksNode = ds1.getChildNode("tasks");
            // tasks ノードのパスを表示
            console.log(tasksNode.getDataPath()); //=> sample/tasks
            // 子ノードの数を表示
            var childNodes = tasksNode.getChildNodes();
            console.log(childNodes.getCount()); //=> 5


            // tasks ノードの0番目の子ノードを取得
            var taskNode = childNodes.getByIndex(0);
            console.log(taskNode.getChildNodeValue("name")); //=> entry0
            // [0] でも同じ
            var taskNode2 = tasksNode.getChildNode("[0]");
            console.log(taskNode2.getChildNodeValue("name")); //=> entry0


            // ノードの内容を入れ替えは削除→設定で行う
            // 削除せずに設定しようとするとエラーになる。
            tasksNode.set(null);
            tasksNode.set([
                { key: "key5", name: "item5" },
                { key: "key6", name: "item6" }
            ]);
            console.log(tasksNode.getChildNodes().getCount()); //=> 2
            
        </script>
    </body>
</html>

データマネージャー

データマネージャーを使えば、複数のデータソースをまとめて管理できます。さらに、データマネージャーに登録したデータソースは、内容が変更されるとイベントを発生させるようになります。

発生したイベントを処理するには、データマネージャーにイベントハンドラを登録します。Closure Library はデータバインドを提供していないので、イベントをハンドルし自力で表示をすることになります。

データマネジャーはシングルトンなので、データマネージャーに登録したデータソースにはどこからでもアクセス可能。

<html>
    <head>
        <title>DataManagerSample</title>
    </head>
    <body>
        <script type="text/javascript" src="closure-library/closure/goog/base.js"></script>
        <script type="text/javascript">
            goog.require("goog.ds.JsDataSource");
            goog.require("goog.ds.DataManager");
        </script>
        <script type="text/javascript">
            var testData = { 
                "tasks": [
                    { key: "foo0", name: "entry0" },
                    { key: "foo1", name: "entry1" },
                    { key: "foo2", name: "entry2" },
                    { key: "foo3", name: "entry3" },
                    { key: "foo4", name: "entry4" }
                ]
            };

            // データソースが変更されたとき呼び出される
            function onDataSourceChanged(path) {
                console.log(path);
            }

            // データソース作成
            var ds1 = new goog.ds.JsDataSource(testData, "sample");

            // データマネージャー取得
            // データマネージャーはシングルトン
            var dm = goog.ds.DataManager.getInstance();

            // データソースを管理下に追加
            dm.addDataSource(ds1);

            // イベント登録
            var listenId = dm.addListener(onDataSourceChanged, "$sample/...");

            // データソースを取り出す
            // 絶対パスを指定する必要があるので、頭に $ を付ける
            var ds2 = dm.getDataSource("$sample");
            console.log(ds2.getDataPath()); //=> $sample

            // データソースの値を変更すると
            // onDataSourceChanged が呼ばれる
            var tasksNode = ds2.getChildNode("tasks");
            var childNodes = tasksNode.getChildNodes();
            var taskNode = childNodes.getByIndex(0);
            taskNode.setChildNode("name", "entry012"); //=> $sample/tasks/[0]/name

            // データソースのノードを削除する
            // 対象ノードだけでなく、親ノードもイベントを発生させる
            tasksNode.setChildNode("[4]", null); //=> $sample/tasks/[4]
                                                 //   $sample/tasks
                                                 //   $sample/tasks/count()

            // イベント登録解除
            dm.removeListeners(onDataSourceChanged, listenId);
        </script>
    </body>
</html>

エクスプレッション

データソースのノードを getChildNode を使って毎回たどるのは面倒です。データソースを使うならエクスプレッションは必須。

エクスプレッションを使えば XPath みたいに目的のノードを一発で取得できます。エクスプレッションで取得できるのはデータマネージャーに登録したデータソースだけです。

<html>
    <head>
        <title>ExpressionSample</title>
    </head>
    <body>
        <script type="text/javascript" src="closure-library/closure/goog/base.js"></script>
        <script type="text/javascript">
            goog.require("goog.ds.JsDataSource");
            goog.require("goog.ds.DataManager");
            goog.require("goog.ds.Expr");
        </script>
        <script type="text/javascript">
            var testData = { 
                "tasks": [
                    { key: "foo0", name: "entry0" },
                    { key: "foo1", name: "entry1" },
                    { key: "foo2", name: "entry2" },
                    { key: "foo3", name: "entry3" },
                    { key: "foo4", name: "entry4" }
                ]
            };

            // データソース作成
            var ds1 = new goog.ds.JsDataSource(testData, "sample");

            // データマネージャーに登録
            var dm = goog.ds.DataManager.getInstance();
            dm.addDataSource(ds1);

            // ノードを取得
            var expr1 = goog.ds.Expr.create("$sample/tasks/[0]/name");
            var node = expr1.getNode();
            console.log(node.getDataPath()); //=> $sample/tasks/[0]/name
            console.log(node.get()); //=> entry0

            // ノードの値を直接取得
            var expr2 = goog.ds.Expr.create("$sample/tasks/[0]/name");
            var name = expr2.getValue();
            console.log(name); //=> entry0

            // tasks ノードの値を取得
            var expr3 = goog.ds.Expr.create("$sample/tasks");
            var nodeList = expr3.getNodes();
            console.log(nodeList.getCount()); //=> 5
        </script>
    </body>
</html>

まとめ

JSON だろうと XML だろうと、データソースを使えばツリーのように扱えます。

フル Ajax な Web アプリだとサーバーから JSON でデータを取得すると思うので、JSON をデータソースにしてエクスプレッションで操作するのが Closure 流。