1 | define([ |
---|
2 | "dojo/_base/array", // array.indexOf array.some |
---|
3 | "dojo/_base/declare", // declare |
---|
4 | "dojo/_base/kernel", // global |
---|
5 | "dojo/_base/lang", // lang.hitch |
---|
6 | "./TreeStoreModel" |
---|
7 | ], function(array, declare, kernel, lang, TreeStoreModel){ |
---|
8 | |
---|
9 | // module: |
---|
10 | // dijit/tree/ForestStoreModel |
---|
11 | |
---|
12 | return declare("dijit.tree.ForestStoreModel", TreeStoreModel, { |
---|
13 | // summary: |
---|
14 | // Interface between a dijit.Tree and a dojo.data store that doesn't have a root item, |
---|
15 | // a.k.a. a store that has multiple "top level" items. |
---|
16 | // |
---|
17 | // description: |
---|
18 | // Use this class to wrap a dojo.data store, making all the items matching the specified query |
---|
19 | // appear as children of a fabricated "root item". If no query is specified then all the |
---|
20 | // items returned by fetch() on the underlying store become children of the root item. |
---|
21 | // This class allows dijit.Tree to assume a single root item, even if the store doesn't have one. |
---|
22 | // |
---|
23 | // When using this class the developer must override a number of methods according to their app and |
---|
24 | // data, including: |
---|
25 | // |
---|
26 | // - onNewRootItem |
---|
27 | // - onAddToRoot |
---|
28 | // - onLeaveRoot |
---|
29 | // - onNewItem |
---|
30 | // - onSetItem |
---|
31 | |
---|
32 | // Parameters to constructor |
---|
33 | |
---|
34 | // rootId: String |
---|
35 | // ID of fabricated root item |
---|
36 | rootId: "$root$", |
---|
37 | |
---|
38 | // rootLabel: String |
---|
39 | // Label of fabricated root item |
---|
40 | rootLabel: "ROOT", |
---|
41 | |
---|
42 | // query: String |
---|
43 | // Specifies the set of children of the root item. |
---|
44 | // example: |
---|
45 | // | {type:'continent'} |
---|
46 | query: null, |
---|
47 | |
---|
48 | // End of parameters to constructor |
---|
49 | |
---|
50 | constructor: function(params){ |
---|
51 | // summary: |
---|
52 | // Sets up variables, etc. |
---|
53 | // tags: |
---|
54 | // private |
---|
55 | |
---|
56 | // Make dummy root item |
---|
57 | this.root = { |
---|
58 | store: this, |
---|
59 | root: true, |
---|
60 | id: params.rootId, |
---|
61 | label: params.rootLabel, |
---|
62 | children: params.rootChildren // optional param |
---|
63 | }; |
---|
64 | }, |
---|
65 | |
---|
66 | // ======================================================================= |
---|
67 | // Methods for traversing hierarchy |
---|
68 | |
---|
69 | mayHaveChildren: function(/*dojo/data/Item*/ item){ |
---|
70 | // summary: |
---|
71 | // Tells if an item has or may have children. Implementing logic here |
---|
72 | // avoids showing +/- expando icon for nodes that we know don't have children. |
---|
73 | // (For efficiency reasons we may not want to check if an element actually |
---|
74 | // has children until user clicks the expando node) |
---|
75 | // tags: |
---|
76 | // extension |
---|
77 | return item === this.root || this.inherited(arguments); |
---|
78 | }, |
---|
79 | |
---|
80 | getChildren: function(/*dojo/data/Item*/ parentItem, /*function(items)*/ callback, /*function*/ onError){ |
---|
81 | // summary: |
---|
82 | // Calls onComplete() with array of child items of given parent item, all loaded. |
---|
83 | if(parentItem === this.root){ |
---|
84 | if(this.root.children){ |
---|
85 | // already loaded, just return |
---|
86 | callback(this.root.children); |
---|
87 | }else{ |
---|
88 | this.store.fetch({ |
---|
89 | query: this.query, |
---|
90 | onComplete: lang.hitch(this, function(items){ |
---|
91 | this.root.children = items; |
---|
92 | callback(items); |
---|
93 | }), |
---|
94 | onError: onError |
---|
95 | }); |
---|
96 | } |
---|
97 | }else{ |
---|
98 | this.inherited(arguments); |
---|
99 | } |
---|
100 | }, |
---|
101 | |
---|
102 | // ======================================================================= |
---|
103 | // Inspecting items |
---|
104 | |
---|
105 | isItem: function(/* anything */ something){ |
---|
106 | return (something === this.root) ? true : this.inherited(arguments); |
---|
107 | }, |
---|
108 | |
---|
109 | fetchItemByIdentity: function(/* object */ keywordArgs){ |
---|
110 | if(keywordArgs.identity == this.root.id){ |
---|
111 | var scope = keywordArgs.scope || kernel.global; |
---|
112 | if(keywordArgs.onItem){ |
---|
113 | keywordArgs.onItem.call(scope, this.root); |
---|
114 | } |
---|
115 | }else{ |
---|
116 | this.inherited(arguments); |
---|
117 | } |
---|
118 | }, |
---|
119 | |
---|
120 | getIdentity: function(/* item */ item){ |
---|
121 | return (item === this.root) ? this.root.id : this.inherited(arguments); |
---|
122 | }, |
---|
123 | |
---|
124 | getLabel: function(/* item */ item){ |
---|
125 | return (item === this.root) ? this.root.label : this.inherited(arguments); |
---|
126 | }, |
---|
127 | |
---|
128 | // ======================================================================= |
---|
129 | // Write interface |
---|
130 | |
---|
131 | newItem: function(/* dijit/tree/dndSource.__Item */ args, /*Item*/ parent, /*int?*/ insertIndex){ |
---|
132 | // summary: |
---|
133 | // Creates a new item. See dojo/data/api/Write for details on args. |
---|
134 | // Used in drag & drop when item from external source dropped onto tree. |
---|
135 | if(parent === this.root){ |
---|
136 | this.onNewRootItem(args); |
---|
137 | return this.store.newItem(args); |
---|
138 | }else{ |
---|
139 | return this.inherited(arguments); |
---|
140 | } |
---|
141 | }, |
---|
142 | |
---|
143 | onNewRootItem: function(/* dijit/tree/dndSource.__Item */ /*===== args =====*/){ |
---|
144 | // summary: |
---|
145 | // User can override this method to modify a new element that's being |
---|
146 | // added to the root of the tree, for example to add a flag like root=true |
---|
147 | }, |
---|
148 | |
---|
149 | pasteItem: function(/*Item*/ childItem, /*Item*/ oldParentItem, /*Item*/ newParentItem, /*Boolean*/ bCopy, /*int?*/ insertIndex){ |
---|
150 | // summary: |
---|
151 | // Move or copy an item from one parent item to another. |
---|
152 | // Used in drag & drop |
---|
153 | if(oldParentItem === this.root){ |
---|
154 | if(!bCopy){ |
---|
155 | // It's onLeaveRoot()'s responsibility to modify the item so it no longer matches |
---|
156 | // this.query... thus triggering an onChildrenChange() event to notify the Tree |
---|
157 | // that this element is no longer a child of the root node |
---|
158 | this.onLeaveRoot(childItem); |
---|
159 | } |
---|
160 | } |
---|
161 | this.inherited(arguments, [childItem, |
---|
162 | oldParentItem === this.root ? null : oldParentItem, |
---|
163 | newParentItem === this.root ? null : newParentItem, |
---|
164 | bCopy, |
---|
165 | insertIndex |
---|
166 | ]); |
---|
167 | if(newParentItem === this.root){ |
---|
168 | // It's onAddToRoot()'s responsibility to modify the item so it matches |
---|
169 | // this.query... thus triggering an onChildrenChange() event to notify the Tree |
---|
170 | // that this element is now a child of the root node |
---|
171 | this.onAddToRoot(childItem); |
---|
172 | } |
---|
173 | }, |
---|
174 | |
---|
175 | // ======================================================================= |
---|
176 | // Handling for top level children |
---|
177 | |
---|
178 | onAddToRoot: function(/* item */ item){ |
---|
179 | // summary: |
---|
180 | // Called when item added to root of tree; user must override this method |
---|
181 | // to modify the item so that it matches the query for top level items |
---|
182 | // example: |
---|
183 | // | store.setValue(item, "root", true); |
---|
184 | // tags: |
---|
185 | // extension |
---|
186 | console.log(this, ": item ", item, " added to root"); |
---|
187 | }, |
---|
188 | |
---|
189 | onLeaveRoot: function(/* item */ item){ |
---|
190 | // summary: |
---|
191 | // Called when item removed from root of tree; user must override this method |
---|
192 | // to modify the item so it doesn't match the query for top level items |
---|
193 | // example: |
---|
194 | // | store.unsetAttribute(item, "root"); |
---|
195 | // tags: |
---|
196 | // extension |
---|
197 | console.log(this, ": item ", item, " removed from root"); |
---|
198 | }, |
---|
199 | |
---|
200 | // ======================================================================= |
---|
201 | // Events from data store |
---|
202 | |
---|
203 | _requeryTop: function(){ |
---|
204 | // reruns the query for the children of the root node, |
---|
205 | // sending out an onSet notification if those children have changed |
---|
206 | var oldChildren = this.root.children || []; |
---|
207 | this.store.fetch({ |
---|
208 | query: this.query, |
---|
209 | onComplete: lang.hitch(this, function(newChildren){ |
---|
210 | this.root.children = newChildren; |
---|
211 | |
---|
212 | // If the list of children or the order of children has changed... |
---|
213 | if(oldChildren.length != newChildren.length || |
---|
214 | array.some(oldChildren, function(item, idx){ return newChildren[idx] != item;})){ |
---|
215 | this.onChildrenChange(this.root, newChildren); |
---|
216 | } |
---|
217 | }) |
---|
218 | }); |
---|
219 | }, |
---|
220 | |
---|
221 | onNewItem: function(/* dojo/data/api/Item */ item, /* Object */ parentInfo){ |
---|
222 | // summary: |
---|
223 | // Handler for when new items appear in the store. Developers should override this |
---|
224 | // method to be more efficient based on their app/data. |
---|
225 | // description: |
---|
226 | // Note that the default implementation requeries the top level items every time |
---|
227 | // a new item is created, since any new item could be a top level item (even in |
---|
228 | // addition to being a child of another item, since items can have multiple parents). |
---|
229 | // |
---|
230 | // If developers can detect which items are possible top level items (based on the item and the |
---|
231 | // parentInfo parameters), they should override this method to only call _requeryTop() for top |
---|
232 | // level items. Often all top level items have parentInfo==null, but |
---|
233 | // that will depend on which store you use and what your data is like. |
---|
234 | // tags: |
---|
235 | // extension |
---|
236 | this._requeryTop(); |
---|
237 | |
---|
238 | this.inherited(arguments); |
---|
239 | }, |
---|
240 | |
---|
241 | onDeleteItem: function(/*Object*/ item){ |
---|
242 | // summary: |
---|
243 | // Handler for delete notifications from underlying store |
---|
244 | |
---|
245 | // check if this was a child of root, and if so send notification that root's children |
---|
246 | // have changed |
---|
247 | if(array.indexOf(this.root.children, item) != -1){ |
---|
248 | this._requeryTop(); |
---|
249 | } |
---|
250 | |
---|
251 | this.inherited(arguments); |
---|
252 | }, |
---|
253 | |
---|
254 | onSetItem: function(/* item */ item, |
---|
255 | /* attribute-name-string */ attribute, |
---|
256 | /* Object|Array */ oldValue, |
---|
257 | /* Object|Array */ newValue){ |
---|
258 | // summary: |
---|
259 | // Updates the tree view according to changes to an item in the data store. |
---|
260 | // Developers should override this method to be more efficient based on their app/data. |
---|
261 | // description: |
---|
262 | // Handles updates to an item's children by calling onChildrenChange(), and |
---|
263 | // other updates to an item by calling onChange(). |
---|
264 | // |
---|
265 | // Also, any change to any item re-executes the query for the tree's top-level items, |
---|
266 | // since this modified item may have started/stopped matching the query for top level items. |
---|
267 | // |
---|
268 | // If possible, developers should override this function to only call _requeryTop() when |
---|
269 | // the change to the item has caused it to stop/start being a top level item in the tree. |
---|
270 | // tags: |
---|
271 | // extension |
---|
272 | |
---|
273 | this._requeryTop(); |
---|
274 | this.inherited(arguments); |
---|
275 | } |
---|
276 | |
---|
277 | }); |
---|
278 | |
---|
279 | }); |
---|