]>
Commit | Line | Data |
---|---|---|
f0cfe83e AD |
1 | define("dijit/tree/ForestStoreModel", [ |
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 | }); |