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