]>
Commit | Line | Data |
---|---|---|
f0cfe83e AD |
1 | define("dijit/tree/ObjectStoreModel", [ |
2 | "dojo/_base/array", // array.filter array.forEach array.indexOf array.some | |
3 | "dojo/aspect", // aspect.before, aspect.after | |
4 | "dojo/_base/declare", // declare | |
5 | "dojo/_base/lang", // lang.hitch | |
6 | "dojo/when" | |
7 | ], function(array, aspect, declare, lang, when){ | |
8 | ||
9 | // module: | |
10 | // dijit/tree/ObjectStoreModel | |
11 | ||
12 | return declare("dijit.tree.ObjectStoreModel", null, { | |
13 | // summary: | |
14 | // Implements dijit/tree/model connecting dijit/Tree to a dojo/store/api/Store that implements | |
15 | // getChildren(). | |
16 | // | |
17 | // If getChildren() returns an array with an observe() method, then it will be leveraged to reflect | |
18 | // store updates to the tree. So, this class will work best when: | |
19 | // | |
20 | // 1. the store implements dojo/store/Observable | |
21 | // 2. getChildren() is implemented as a query to the server (i.e. it calls store.query()) | |
22 | // | |
23 | // Drag and Drop: To support drag and drop, besides implementing getChildren() | |
24 | // and dojo/store/Observable, the store must support the parent option to put(). | |
25 | // And in order to have child elements ordered according to how the user dropped them, | |
26 | // put() must support the before option. | |
27 | ||
28 | // store: dojo/store/api/Store | |
29 | // Underlying store | |
30 | store: null, | |
31 | ||
32 | // labelAttr: String | |
33 | // Get label for tree node from this attribute | |
34 | labelAttr: "name", | |
35 | ||
36 | // root: [readonly] Object | |
37 | // Pointer to the root item from the dojo/store/api/Store (read only, not a parameter) | |
38 | root: null, | |
39 | ||
40 | // query: anything | |
41 | // Specifies datastore query to return the root item for the tree. | |
42 | // Must only return a single item. Alternately can just pass in pointer | |
43 | // to root item. | |
44 | // example: | |
45 | // | {id:'ROOT'} | |
46 | query: null, | |
47 | ||
48 | constructor: function(/* Object */ args){ | |
49 | // summary: | |
50 | // Passed the arguments listed above (store, etc) | |
51 | // tags: | |
52 | // private | |
53 | ||
54 | lang.mixin(this, args); | |
55 | ||
56 | this.childrenCache = {}; // map from id to array of children | |
57 | }, | |
58 | ||
59 | destroy: function(){ | |
60 | // TODO: should cancel any in-progress processing of getRoot(), getChildren() | |
61 | for(var id in this.childrenCache){ | |
62 | this.childrenCache[id].close && this.childrenCache[id].close(); | |
63 | } | |
64 | }, | |
65 | ||
66 | // ======================================================================= | |
67 | // Methods for traversing hierarchy | |
68 | ||
69 | getRoot: function(onItem, onError){ | |
70 | // summary: | |
71 | // Calls onItem with the root item for the tree, possibly a fabricated item. | |
72 | // Calls onError on error. | |
73 | if(this.root){ | |
74 | onItem(this.root); | |
75 | }else{ | |
76 | var res; | |
77 | when(res = this.store.query(this.query), | |
78 | lang.hitch(this, function(items){ | |
79 | //console.log("queried root: ", res); | |
80 | if(items.length != 1){ | |
81 | throw new Error("dijit.tree.ObjectStoreModel: root query returned " + items.length + | |
82 | " items, but must return exactly one"); | |
83 | } | |
84 | this.root = items[0]; | |
85 | onItem(this.root); | |
86 | ||
87 | // Setup listener to detect if root item changes | |
88 | if(res.observe){ | |
89 | res.observe(lang.hitch(this, function(obj){ | |
90 | // Presumably removedFrom == insertedInto == 1, and this call indicates item has changed. | |
91 | //console.log("root changed: ", obj); | |
92 | this.onChange(obj); | |
93 | }), true); // true to listen for updates to obj | |
94 | } | |
95 | }), | |
96 | onError | |
97 | ); | |
98 | } | |
99 | }, | |
100 | ||
101 | mayHaveChildren: function(/*===== item =====*/){ | |
102 | // summary: | |
103 | // Tells if an item has or may have children. Implementing logic here | |
104 | // avoids showing +/- expando icon for nodes that we know don't have children. | |
105 | // (For efficiency reasons we may not want to check if an element actually | |
106 | // has children until user clicks the expando node). | |
107 | // | |
108 | // Application code should override this method based on the data, for example | |
109 | // it could be `return item.leaf == true;`. | |
110 | // item: Object | |
111 | // Item from the dojo/store | |
112 | return true; | |
113 | }, | |
114 | ||
115 | getChildren: function(/*Object*/ parentItem, /*function(items)*/ onComplete, /*function*/ onError){ | |
116 | // summary: | |
117 | // Calls onComplete() with array of child items of given parent item. | |
118 | // parentItem: | |
119 | // Item from the dojo/store | |
120 | ||
121 | var id = this.store.getIdentity(parentItem); | |
122 | if(this.childrenCache[id]){ | |
123 | when(this.childrenCache[id], onComplete, onError); | |
124 | return; | |
125 | } | |
126 | ||
127 | var res = this.childrenCache[id] = this.store.getChildren(parentItem); | |
128 | ||
129 | // User callback | |
130 | when(res, onComplete, onError); | |
131 | ||
132 | // Setup listener in case children list changes, or the item(s) in the children list are | |
133 | // updated in some way. | |
134 | if(res.observe){ | |
135 | res.observe(lang.hitch(this, function(obj, removedFrom, insertedInto){ | |
136 | //console.log("observe on children of ", id, ": ", obj, removedFrom, insertedInto); | |
137 | ||
138 | // If removedFrom == insertedInto, this call indicates that the item has changed. | |
139 | // Even if removedFrom != insertedInto, the item may have changed. | |
140 | this.onChange(obj); | |
141 | ||
142 | if(removedFrom != insertedInto){ | |
143 | // Indicates an item was added, removed, or re-parented. The children[] array (returned from | |
144 | // res.then(...)) has already been updated (like a live collection), so just use it. | |
145 | when(res, lang.hitch(this, "onChildrenChange", parentItem)); | |
146 | } | |
147 | }), true); // true means to notify on item changes | |
148 | } | |
149 | }, | |
150 | ||
151 | // ======================================================================= | |
152 | // Inspecting items | |
153 | ||
154 | isItem: function(/*===== something =====*/){ | |
155 | return true; // Boolean | |
156 | }, | |
157 | ||
158 | fetchItemByIdentity: function(/* object */ keywordArgs){ | |
159 | this.store.get(keywordArgs.identity).then( | |
160 | lang.hitch(keywordArgs.scope, keywordArgs.onItem), | |
161 | lang.hitch(keywordArgs.scope, keywordArgs.onError) | |
162 | ); | |
163 | }, | |
164 | ||
165 | getIdentity: function(/* item */ item){ | |
166 | return this.store.getIdentity(item); // Object | |
167 | }, | |
168 | ||
169 | getLabel: function(/*dojo/data/Item*/ item){ | |
170 | // summary: | |
171 | // Get the label for an item | |
172 | return item[this.labelAttr]; // String | |
173 | }, | |
174 | ||
175 | // ======================================================================= | |
176 | // Write interface, for DnD | |
177 | ||
178 | newItem: function(/* dijit/tree/dndSource.__Item */ args, /*Item*/ parent, /*int?*/ insertIndex, /*Item*/ before){ | |
179 | // summary: | |
180 | // Creates a new item. See `dojo/data/api/Write` for details on args. | |
181 | // Used in drag & drop when item from external source dropped onto tree. | |
182 | ||
183 | return this.store.put(args, { | |
184 | parent: parent, | |
185 | before: before | |
186 | }); | |
187 | }, | |
188 | ||
189 | pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, | |
190 | /*Boolean*/ bCopy, /*int?*/ insertIndex, /*Item*/ before){ | |
191 | // summary: | |
192 | // Move or copy an item from one parent item to another. | |
193 | // Used in drag & drop | |
194 | ||
195 | if(!bCopy){ | |
196 | // In order for DnD moves to work correctly, childItem needs to be orphaned from oldParentItem | |
197 | // before being adopted by newParentItem. That way, the TreeNode is moved rather than | |
198 | // an additional TreeNode being created, and the old TreeNode subsequently being deleted. | |
199 | // The latter loses information such as selection and opened/closed children TreeNodes. | |
200 | // Unfortunately simply calling this.store.put() will send notifications in a random order, based | |
201 | // on when the TreeNodes in question originally appeared, and not based on the drag-from | |
202 | // TreeNode vs. the drop-onto TreeNode. | |
203 | ||
204 | var oldParentChildren = [].concat(this.childrenCache[this.getIdentity(oldParentItem)]), // concat to make copy | |
205 | index = array.indexOf(oldParentChildren, childItem); | |
206 | oldParentChildren.splice(index, 1); | |
207 | this.onChildrenChange(oldParentItem, oldParentChildren); | |
208 | } | |
209 | ||
210 | return this.store.put(childItem, { | |
211 | overwrite: true, | |
212 | parent: newParentItem, | |
213 | before: before | |
214 | }); | |
215 | }, | |
216 | ||
217 | // ======================================================================= | |
218 | // Callbacks | |
219 | ||
220 | onChange: function(/*dojo/data/Item*/ /*===== item =====*/){ | |
221 | // summary: | |
222 | // Callback whenever an item has changed, so that Tree | |
223 | // can update the label, icon, etc. Note that changes | |
224 | // to an item's children or parent(s) will trigger an | |
225 | // onChildrenChange() so you can ignore those changes here. | |
226 | // tags: | |
227 | // callback | |
228 | }, | |
229 | ||
230 | onChildrenChange: function(/*===== parent, newChildrenList =====*/){ | |
231 | // summary: | |
232 | // Callback to do notifications about new, updated, or deleted items. | |
233 | // parent: dojo/data/Item | |
234 | // newChildrenList: Object[] | |
235 | // Items from the store | |
236 | // tags: | |
237 | // callback | |
238 | }, | |
239 | ||
240 | onDelete: function(/*dojo/data/Item*/ /*===== item =====*/){ | |
241 | // summary: | |
242 | // Callback when an item has been deleted. | |
243 | // Actually we have no way of knowing this with the new dojo.store API, | |
244 | // so this method is never called (but it's left here since Tree connects | |
245 | // to it). | |
246 | // tags: | |
247 | // callback | |
248 | } | |
249 | }); | |
250 | }); |