はじめに
.NET 歴が長いので、データソースと聞くとデータバインドを連想してしまいます。でも残念ながら、Closure Library の UI フレームワークはデータバインドを提供していません。今後に期待。
Google Closure のデータソース機能は goog.ds 名前空間で提供されていて、
- データソース
- データマネージャー
- エクスプレッション
という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>