天天看點

翻譯 - 【Dojo Tutorials】Connecting a Store to a Tree

原文:Connecting a Store to a Tree

Dojo Tree元件是一個強大的展示層級資料的工具。該教程将展示如何連接配接tree與store來快速有效的展示層級資料。

介紹

Dojo Tree元件為展示層級資料提供了一種綜合的,熟悉的,直覺的方式。它将展示與資料分離。本文将提到為驅動一個tree提供資料的多種方法。

第一個例子使用一個靜态tree,其資料從一個JSON檔案中擷取。這可以用于通過資料提供導航。第二個例子在這個設計的基礎上擴充新的強大功能,如拖拽,維護一個動态的tree。最後的例子如何懶加載資料。

帶有靜态Store的Tree

靜态的store比較适合用于有限的tree。這個例子中,點選tree的節點将會展示一個相關的圖檔。

第一步是構造資料。我們将使用記憶體store,也就是說store的資料是用JSON編碼表示的,可以包含一些提供的資訊。在這方面,每個節點中name做标簽使用。這個tree有四個子項,每個子項都有name與id。

1 {
 2     "name": "US Goverment",
 3     "id": "root",
 4     "children": [
 5         {
 6             "name": "Congress",
 7             "id": "congress"
 8         },
 9         {
10             "name": "Executive",
11             "id": "exec"
12         },
13         {
14             "name": "Judicial",
15             "id": "judicial"
16         }
17     ]
18 }      

一個tree由實作了dijit/tree/model的接口的對象提供資料。通常,這個對象是dijit/store/ObjectStoreModel的一個執行個體,且有dojo/store存儲。這就是該執行個體工作的方式。

下面的代碼建構一個記憶體store,供給一個ObjectStoreModel,然後用于一個tree。最後,onLoad與onClick事件用于展示相關的圖檔。

1 require([
 2     "dojo/dom",
 3     "dojo/json",
 4     "dojo/store/Memory",
 5     "dijit/tree/ObjectStoreModel",
 6     "dijit/Tree",
 7     "dojo/text!./data/static",
 8     "dojo/domReady!"
 9 ], function(dom, JSON, Memory, ObjectStoreModel, tree, data) {
10     var govermentStore = new Memory({
11         data: [ JSON.parse(data) ],
12         getChildren: function(object) {
13             return object.children || [];
14         }
15     });
16 
17     var govermentModel = new ObjectStoreModel({
18         store: govermentStore,
19         query: {id: 'root'},
20         mayHaveChildren: function(item) {
21             return "children" in item;
22         }
23     });
24 
25     var govermentTree = new Tree({
26         model: govermentModel,
27         onOpenClick: true,
28         onLoad: function() {
29             dom.byId("image").src = "../resources/images/root.jpg";
30         },
31         onClick: function(item) {
32             dom.byId("image").src = "../resources/images/" + item.id + ".jpg";
33         }
34     }, "divTree");
35 
36     govermentTree.startup();
37 });      

注意我們自己實作兩個方法:

  1. getChildren() - 用于傳回給定項的子項清單
  2. mayHaveChildren() - 在這個簡單的表單中它總是傳回真,如果傳回假,則說明隻是檢視這個項,它并沒有子項,目前或以後(由于拖拽或其他資料更新了store)。

動态更新與拖拽

我們将學習如何使用Tree的拖拽功能,以及實時響應資料的變化。

對于一個帶有資料更新的Tree,包括DnD,store需要能夠了解與追蹤父子關系,當一個甚至一系列子項變化時通知tree。基于此,我們将建立關聯格式的資料,每項都指向它的父元素:

1 [
 2     {
 3         "name": "US Government",
 4         "id": "root"
 5     },
 6         {
 7             "name": "Congress",
 8             "id": "congress",
 9             "parent": "root"
10         },
11             {
12                 "name": "House of Representatives",
13                 "id": "house",
14                 "parent": "congress"
15             },
16             {
17                 "name": "Senate",
18                 "id": "senate",
19                 "parent": "congress"
20             },
21         {
22             "name": "Executive",
23             "id": "exec",
24             "parent": "root"
25         },
26              {
27                 "name": "President",
28                 "id": "pres",
29                 "parent": "exec"
30             },
31             {
32                 "name": "Vice President",
33                 "id": "vice-pres",
34                 "parent": "exec"
35             },
36             {
37                 "name": "Secretary of State",
38                 "id": "state",
39                 "parent": "exec"
40             },
41             {
42                 "name": "Cabinet",
43                 "id": "cabinet",
44                 "parent": "exec"
45             },
46                 {
47                     "name": "National Security Council",
48                     "id": "security",
49                     "parent": "cabinet"
50                 },
51                 {
52                     "name": "Council of Economic Advisers",
53                     "id": "economic",
54                     "parent": "cabinet"
55                 },
56                 {
57                     "name": "Office of Management and Budget",
58                     "id": "budget",
59                     "parent": "cabinet"
60                 },
61         {
62             "name": "Judicial",
63             "id": "judicial",
64             "parent": "root"
65         }
66 ]      

不管縮進,這實際是一個扁平化的清單,每個元素指向它的父元素(除了根元素)。

接下來,我們建構store。這個store需要具備一下能力用于ObjectStoreModel與Tree,且能反射動态資料的更新:

  1. 支援通過query方法擷取根節點(store.query()),而不是store.get(),且傳回一個隻有一個元素的清單與observe()方法。ObjectStoreModel将利用observe()的方法來檢測跟節點的改變,例如修改了标簽。
  2. getChildren()方法傳回一個帶有observe()函數的子元素清單。observe()方法用于檢測子節點的變化,對于新的節點加入或有子節點被删除的情況。
  3. put()方法可以用于向一個節點添加子節點。

前兩點用一個Observable包裹的store很容易實作。另外,getRoot()與getChildren()也可以在store作為查詢類實作。

對于第三點,自從dojo/store/Memory不再支援這一選項,我們需要自己添加。代碼像下面這樣:

1 require([
 2     "dojo/aspect",
 3     "dojo/json",
 4     "dojo/query",
 5     "dojo/store/Memory",
 6     "dojo/store/Observable",
 7     "dijit/Tree",
 8     "dijit/tree/ObjectStoreModel",
 9     "dijit/tree/dndSource",
10     "dojo/text!./data/all.json",
11     "dojo/domReady!"
12 ], function(aspect, json, query, Memory, Observable, Tree, ObjectStoreModel, dndSource, data) {
13     
14     // set up the store to get the tree data, plus define the method
15     // to query the children of a node
16     var governmentStore = new Memory({
17         data: json.parse(data),
18         getChildren: function(object) {
19             return this.query({parent: object.id});
20         }
21     });
22 
23     // To support dynamic data changes, including DnD,
24     // the store must support put(child, {parent: parent})
25     // But dojo/store/Memory doesn't, so we have to implement it.
26     // Sine our store is relational, that just amounts to setting child.parent
27     // to the parent's id.
28     aspect.around(governmentStore, "put", function(originalPut) {
29         return function(obj, options) {
30             if(options && options.parent) {
31                 obj.parent = options.parent.id;
32             }
33             return originalPut.call(governmentStore, obj, options);
34         }
35     });
36 
37     // give store Observable interface so Tree can track updates
38     governmentStore = new Observable(governmentStore);
39 });      

最後,添加拖拽支援,我們需要定義一個拖拽控制器:我們将使用标準的dijit/tree/dndStore作為控制器:

1 require([
 2     "dijit/Tree",
 3     "dijit/tree/dndSource",
 4     "dojo/domReady!"
 5 ], function(Tree, dndSource) {
 6     var tree = new Tree({
 7         model: usGov,
 8         // define the drap-n-drop controller
 9         dndController: dndSource
10     }, "tree");
11     tree.startup();
12 });      

現在拖拽操作應該觸發調用通過put()更新資料的model.pasteItem()函數。

代碼操作資料變更

值得注意的是Tree遵循響應資料變更而不是控制器動作的标準MVC原則。這是非常強大的功能因為資料視圖可以響應變化而不關心是什麼引起了變化(也許是代碼操作,拖拽等等)。

是以,添加一個子節點,我們可以使用put()儲存它,指定父節點,Tree可以自動響應。下面的樣例中,一個按鈕出發子對象的添加動作:

1 query("#add-new-child").on("click", function() {
 2     // get the selected object from the tree
 3     var selectedObject = tree.get("selectedItems")[0];
 4     if(!selectedObject) {
 5         return alert("No object selected");
 6     }
 7 
 8     // add a new child item
 9     var childItem = {
10         name: "New child",
11         id: Math.random()
12     };
13 
14     governmentStore.put(childItem, {
15         overwrite: true,
16         parent: selectedObject
17     });
18 });      

我們可以使用同樣的方法移除子節點。我們也可以改變對象的屬性,如name。下面我們将監聽輕按兩下事件彈出輸入框擷取一個新name:

1 tree.on("dbclick", function(object) {
2     object.name = propmt("Enter a new name for the object");
3     governmentStore.put(object);
4 }, true);      

最後,我們來看完整代碼:

1 require([
 2     "dojo/aspect",
 3     "dojo/json",
 4     "dojo/query",
 5     "dojo/store/Memory",
 6     "dojo/store/Observable",
 7     "dijit/Tree",
 8     "dijit/form/Button",
 9     "dijit/tree/ObjectStoreModel",
10     "dijit/tree/dndSource",
11     "dojo/text!./data/all.json",
12     "dojo/domReady!"
13 ], function(aspect, json, query, Memory, Observable, Tree, Button, ObjectStoreModel, dndSource, data) {
14 
15     var governmentStore = new Memory({
16         data: json.parse(data),
17         getChildren: function(object) {
18             return this.query({parent: object.id});
19         }
20     });
21 
22     aspect.around(governmentStore, "put", function(originalPut) {
23         return function(obj, options) {
24             if(options && options.parent) {
25                 obj.parent = options.parent.id;
26             }
27             return originalPut.call(governmentStore, obj, options);
28         }
29     });
30 
31     governmentStore = new Observable(governmentStore);
32 
33     var model = new ObjectStoreModel({
34         store: governmentStore,
35         query: {id: "root"}
36     });
37 
38     var tree = new Tree({
39         model: model,
40         dndController: dndSource
41     }, "tree");
42     tree.startup();
43 
44     var addBtn = new Button({
45         label: "Add Child"
46     }, "add-new-child");
47     addBtn.startup();
48     addBtn.on("click", function() {
49         var selectedObject = tree.get("selectedItems")[0];
50         if(!selectedObject) {
51             return alert("No object selected");
52         }
53 
54         var childItem = {
55             name: "New child",
56             id: Math.random()
57         };
58 
59         governmentStore.put(childItem, {
60             overwrite: true,
61             parent: selectedObject
62         });
63     });
64 
65     var removeBtn = new Button({
66         label: "Remove"
67     }, "remove");
68     removeBtn.startup();
69     removeBtn.on("click", function() {
70         var selectedObject = tree.get("selectedItems")[0];
71         if(!selectedObject) {
72             return alert("No object selected");
73         }
74         governmentStore.remove(selectedObject.id);
75     });
76 
77     tree.on("dblclick", function(object) {
78         var newName = prompt("Enter a new name for the object");
79         if(newName) {
80             object.name = newName;
81             governmentStore.put(object);
82         }
83     }, true);
84 });      

由于拖拽的原因,任一節點都可能擁有子節點,故mayHaveChildren()方法應該傳回false。是以我們去掉了自定義的mayHaveChildren()方法,使用預設的。

懶加載的Tree

當資料集合很大的時候,最好是能夠按需從服務端擷取節點資料(也叫懶加載),這要比直接一次性載入所有資料要優良。

第一步,同樣的還是建構資料。現實中,資料常常存在資料庫中且由像Perservere或CouchDB這樣的REST-full服務提供。為了在執行個體中示範,我們把資料節點分開存在伺服器上不同的檔案中:

data/
    cabinet
    congress
    exec
    root      

然後每個節點應該有個子節點清單(如列出子節點的name,而不是子節點的子節點)。是以,為Congress準備的資料命名為“congress”大體如下:

1 {
 2     "name": "Congress",
 3     "id": "congress",
 4     "children": [
 5         {
 6             "name": "House of Representatives",
 7             "id": "house"
 8  
 9         },
10         {
11             "name": "Senate",
12             "id": "senate"
13         }
14     ]
15 }      

接下來建立我們的store。它将驅動這個Tree。在這裡我們将使用JsonRest store,可以幫助實作懶加載。下面是一個連接配接到我們伺服器的JsonRest的執行個體代碼:

1 require(["dojo/store/JsonRest"], function(JsonRest) {
 2     var usGov = new JsonRest({
 3         target: "data/congress",
 4 
 5         getChildren: function(object) {
 6             return this.get(object.id).then(function(fullObject) {
 7                 return fullObject.children;
 8             });
 9         }
10     });
11 });      

getChildren()擷取到如下的内容:

1 {
2     "name": "Congress",
3     "id": "congress",
4     "children": true
5 }      

是以為了得到子節點資料,它首先需要得到名為“congress”的檔案:

1 {
 2     "name": "Congress",
 3     "id": "congress",
 4     "children": [
 5         {
 6             "name": "House of Representatives",
 7             "id": "house"
 8  
 9         },
10         {
11             "name": "Senate",
12             "id": "senate"
13         }
14     ]
15 }      

下面的代碼來建立模型,tree和上面的那個類似:

1 require([
 2     "dojo/store/JsonRest",
 3     "dijit/Tree",
 4     "dijit/tree/ObjectStoreModel",
 5     "dojo/domReady!"
 6 ], function(JsonRest, Tree, ObjectStoreModel) {
 7     var usGov = new JsonRest({
 8         target: "http://192.168.0.87:3000/",
 9 
10         getChildren: function(object) {
11             return this.get(object.id).then(function(fullObject) {
12                 return fullObject.children;
13             });
14         }
15     });
16 
17     var model = new ObjectStoreModel({
18         store: usGov,
19         getRoot: function(onItem) {
20             this.store.get("root").then(onItem);
21         },
22         mayHaveChildren: function(object) {
23             return "children" in object;
24         }
25     });
26 
27     var tree = new Tree({
28         model: model
29     }, "tree");
30     tree.startup();
31 });      

請注意我們添加另一個自定義個方法getRoot()。這是因為預設的ObjectStoreModel視圖從伺服器執行查詢類擷取根節點。因為我們模拟的是靜态的檔案,不能相應查詢,是以我們重寫了getRoot()方法,也就是取了全部資料。

使用client/server store實作動态更新與拖拽

支援使用client-server store實作像JsonRest那樣的拖拽與動态更新是很複雜的,且超越了本教程所覆寫的範圍。捆綁有dojo的JsonRest store還不太完善,因為那隻是一個用戶端的元件。還需要一個服務端提供資料。

你可以使用像Persevere這樣的包來提供必須要的功能。如果你想實作自己的服務端的RESTful服務,那它應該具有以下功能:

1 檢索一個單獨的元素

使用JsonRest,檢索一個元素常使用類路徑的URL方式,如下:

1 http://server/data/root      

然而,當ObjectStoreModel要擷取Tree的根元素的時候,它用一個查詢,JsonRest發起一個GET請求:

1 http://server/data?id=cabinet      

将會傳回以下内容:

1 [
2     {
3         "name": "Cabinet",
4         "id": "cabinet"
5     }
6 ]      

2 檢索一個元素的子元素

如:

1 http://server/data?parent=cabinet      

傳回父節點為“cabinet”的子元素:

1 [
 2     {
 3         "name": "National Security Council",
 4         "id": "security"
 5     },
 6     {
 7         "name": "Council of Economic Advisers",
 8         "id": "economic"
 9     },
10     {
11         "name": "Office of Management and Budget",
12         "id": "budget"
13     }
14 ]      

3 變更通知

4 PUT與DELETE請求

最後伺服器要有處理PUT與DELETE請求的能力,用于更新,插入,删除。

總結

Tree被設計的将表現與資料模型分離,一個新的store很容易可以被層級邏輯擴充來驅動這個Tree。Tree提供了很重的功能,如鍵盤導航與通路。另外,Tree與store相結合增加了強大的功能,包括懶加載,拖拽,對資料變更的實時響應。我們估計你去深入研究Tree的文檔,以了解它更多的功能,如樣式,自定義圖示,API等。

轉載于:https://www.cnblogs.com/gudaojuanma/p/4046510.html