2150 |
mathias |
1 |
if(!dojo._hasResource["dijit.Tree"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
|
|
|
2 |
dojo._hasResource["dijit.Tree"] = true;
|
|
|
3 |
dojo.provide("dijit.Tree");
|
|
|
4 |
|
|
|
5 |
dojo.require("dojo.fx");
|
|
|
6 |
|
|
|
7 |
dojo.require("dijit._Widget");
|
|
|
8 |
dojo.require("dijit._Templated");
|
|
|
9 |
dojo.require("dijit._Container");
|
|
|
10 |
dojo.require("dojo.cookie");
|
|
|
11 |
|
|
|
12 |
dojo.declare(
|
|
|
13 |
"dijit._TreeNode",
|
|
|
14 |
[dijit._Widget, dijit._Templated, dijit._Container, dijit._Contained],
|
|
|
15 |
{
|
|
|
16 |
// summary
|
|
|
17 |
// Single node within a tree
|
|
|
18 |
|
|
|
19 |
// item: dojo.data.Item
|
|
|
20 |
// the dojo.data entry this tree represents
|
|
|
21 |
item: null,
|
|
|
22 |
|
|
|
23 |
isTreeNode: true,
|
|
|
24 |
|
|
|
25 |
// label: String
|
|
|
26 |
// Text of this tree node
|
|
|
27 |
label: "",
|
|
|
28 |
|
|
|
29 |
isExpandable: null, // show expando node
|
|
|
30 |
|
|
|
31 |
isExpanded: false,
|
|
|
32 |
|
|
|
33 |
// state: String
|
|
|
34 |
// dynamic loading-related stuff.
|
|
|
35 |
// When an empty folder node appears, it is "UNCHECKED" first,
|
|
|
36 |
// then after dojo.data query it becomes "LOADING" and, finally "LOADED"
|
|
|
37 |
state: "UNCHECKED",
|
|
|
38 |
|
|
|
39 |
templateString:"<div class=\"dijitTreeNode dijitTreeExpandLeaf dijitTreeChildrenNo\" waiRole=\"presentation\"\n\t><span dojoAttachPoint=\"expandoNode\" class=\"dijitTreeExpando\" waiRole=\"presentation\"\n\t></span\n\t><span dojoAttachPoint=\"expandoNodeText\" class=\"dijitExpandoText\" waiRole=\"presentation\"\n\t></span\n\t>\n\t<div dojoAttachPoint=\"contentNode\" class=\"dijitTreeContent\" waiRole=\"presentation\">\n\t\t<div dojoAttachPoint=\"iconNode\" class=\"dijitInline dijitTreeIcon\" waiRole=\"presentation\"></div>\n\t\t<span dojoAttachPoint=\"labelNode\" class=\"dijitTreeLabel\" wairole=\"treeitem\" tabindex=\"-1\"></span>\n\t</div>\n</div>\n",
|
|
|
40 |
|
|
|
41 |
postCreate: function(){
|
|
|
42 |
// set label, escaping special characters
|
|
|
43 |
this.setLabelNode(this.label);
|
|
|
44 |
|
|
|
45 |
// set expand icon for leaf
|
|
|
46 |
this._setExpando();
|
|
|
47 |
|
|
|
48 |
// set icon and label class based on item
|
|
|
49 |
this._updateItemClasses(this.item);
|
|
|
50 |
|
|
|
51 |
if(this.isExpandable){
|
|
|
52 |
dijit.setWaiState(this.labelNode, "expanded", this.isExpanded);
|
|
|
53 |
}
|
|
|
54 |
},
|
|
|
55 |
|
|
|
56 |
markProcessing: function(){
|
|
|
57 |
// summary: visually denote that tree is loading data, etc.
|
|
|
58 |
this.state = "LOADING";
|
|
|
59 |
this._setExpando(true);
|
|
|
60 |
},
|
|
|
61 |
|
|
|
62 |
unmarkProcessing: function(){
|
|
|
63 |
// summary: clear markup from markProcessing() call
|
|
|
64 |
this._setExpando(false);
|
|
|
65 |
},
|
|
|
66 |
|
|
|
67 |
_updateItemClasses: function(item){
|
|
|
68 |
// summary: set appropriate CSS classes for item (used to allow for item updates to change respective CSS)
|
|
|
69 |
this.iconNode.className = "dijitInline dijitTreeIcon " + this.tree.getIconClass(item);
|
|
|
70 |
this.labelNode.className = "dijitTreeLabel " + this.tree.getLabelClass(item);
|
|
|
71 |
},
|
|
|
72 |
|
|
|
73 |
_updateLayout: function(){
|
|
|
74 |
// summary: set appropriate CSS classes for this.domNode
|
|
|
75 |
var parent = this.getParent();
|
|
|
76 |
if(parent && parent.isTree && parent._hideRoot){
|
|
|
77 |
/* if we are hiding the root node then make every first level child look like a root node */
|
|
|
78 |
dojo.addClass(this.domNode, "dijitTreeIsRoot");
|
|
|
79 |
}else{
|
|
|
80 |
dojo.toggleClass(this.domNode, "dijitTreeIsLast", !this.getNextSibling());
|
|
|
81 |
}
|
|
|
82 |
},
|
|
|
83 |
|
|
|
84 |
_setExpando: function(/*Boolean*/ processing){
|
|
|
85 |
// summary: set the right image for the expando node
|
|
|
86 |
|
|
|
87 |
// apply the appropriate class to the expando node
|
|
|
88 |
var styles = ["dijitTreeExpandoLoading", "dijitTreeExpandoOpened",
|
|
|
89 |
"dijitTreeExpandoClosed", "dijitTreeExpandoLeaf"];
|
|
|
90 |
var idx = processing ? 0 : (this.isExpandable ? (this.isExpanded ? 1 : 2) : 3);
|
|
|
91 |
dojo.forEach(styles,
|
|
|
92 |
function(s){
|
|
|
93 |
dojo.removeClass(this.expandoNode, s);
|
|
|
94 |
}, this
|
|
|
95 |
);
|
|
|
96 |
dojo.addClass(this.expandoNode, styles[idx]);
|
|
|
97 |
|
|
|
98 |
// provide a non-image based indicator for images-off mode
|
|
|
99 |
this.expandoNodeText.innerHTML =
|
|
|
100 |
processing ? "*" :
|
|
|
101 |
(this.isExpandable ?
|
|
|
102 |
(this.isExpanded ? "-" : "+") : "*");
|
|
|
103 |
},
|
|
|
104 |
|
|
|
105 |
expand: function(){
|
|
|
106 |
// summary: show my children
|
|
|
107 |
if(this.isExpanded){ return; }
|
|
|
108 |
// cancel in progress collapse operation
|
|
|
109 |
if(this._wipeOut.status() == "playing"){
|
|
|
110 |
this._wipeOut.stop();
|
|
|
111 |
}
|
|
|
112 |
|
|
|
113 |
this.isExpanded = true;
|
|
|
114 |
dijit.setWaiState(this.labelNode, "expanded", "true");
|
|
|
115 |
dijit.setWaiRole(this.containerNode, "group");
|
|
|
116 |
|
|
|
117 |
this._setExpando();
|
|
|
118 |
|
|
|
119 |
this._wipeIn.play();
|
|
|
120 |
},
|
|
|
121 |
|
|
|
122 |
collapse: function(){
|
|
|
123 |
if(!this.isExpanded){ return; }
|
|
|
124 |
|
|
|
125 |
// cancel in progress expand operation
|
|
|
126 |
if(this._wipeIn.status() == "playing"){
|
|
|
127 |
this._wipeIn.stop();
|
|
|
128 |
}
|
|
|
129 |
|
|
|
130 |
this.isExpanded = false;
|
|
|
131 |
dijit.setWaiState(this.labelNode, "expanded", "false");
|
|
|
132 |
this._setExpando();
|
|
|
133 |
|
|
|
134 |
this._wipeOut.play();
|
|
|
135 |
},
|
|
|
136 |
|
|
|
137 |
setLabelNode: function(label){
|
|
|
138 |
this.labelNode.innerHTML="";
|
|
|
139 |
this.labelNode.appendChild(document.createTextNode(label));
|
|
|
140 |
},
|
|
|
141 |
|
|
|
142 |
_setChildren: function(/* Object[] */ childrenArray){
|
|
|
143 |
// summary:
|
|
|
144 |
// Sets the children of this node.
|
|
|
145 |
// Sets this.isExpandable based on whether or not there are children
|
|
|
146 |
// Takes array of objects like: {label: ...} (_TreeNode options basically)
|
|
|
147 |
// See parameters of _TreeNode for details.
|
|
|
148 |
|
|
|
149 |
this.destroyDescendants();
|
|
|
150 |
|
|
|
151 |
this.state = "LOADED";
|
|
|
152 |
var nodeMap= {};
|
|
|
153 |
if(childrenArray && childrenArray.length > 0){
|
|
|
154 |
this.isExpandable = true;
|
|
|
155 |
if(!this.containerNode){ // maybe this node was unfolderized and still has container
|
|
|
156 |
this.containerNode = this.tree.containerNodeTemplate.cloneNode(true);
|
|
|
157 |
this.domNode.appendChild(this.containerNode);
|
|
|
158 |
}
|
|
|
159 |
|
|
|
160 |
// Create _TreeNode widget for each specified tree node
|
|
|
161 |
dojo.forEach(childrenArray, function(childParams){
|
|
|
162 |
var child = new dijit._TreeNode(dojo.mixin({
|
|
|
163 |
tree: this.tree,
|
|
|
164 |
label: this.tree.getLabel(childParams.item)
|
|
|
165 |
}, childParams));
|
|
|
166 |
this.addChild(child);
|
|
|
167 |
var identity = this.tree.store.getIdentity(childParams.item);
|
|
|
168 |
nodeMap[identity] = child;
|
|
|
169 |
if(this.tree.persist){
|
|
|
170 |
if(this.tree._openedItemIds[identity]){
|
|
|
171 |
this.tree._expandNode(child);
|
|
|
172 |
}
|
|
|
173 |
}
|
|
|
174 |
}, this);
|
|
|
175 |
|
|
|
176 |
// note that updateLayout() needs to be called on each child after
|
|
|
177 |
// _all_ the children exist
|
|
|
178 |
dojo.forEach(this.getChildren(), function(child, idx){
|
|
|
179 |
child._updateLayout();
|
|
|
180 |
});
|
|
|
181 |
}else{
|
|
|
182 |
this.isExpandable=false;
|
|
|
183 |
}
|
|
|
184 |
|
|
|
185 |
if(this._setExpando){
|
|
|
186 |
// change expando to/form dot or + icon, as appropriate
|
|
|
187 |
this._setExpando(false);
|
|
|
188 |
}
|
|
|
189 |
|
|
|
190 |
if(this.isTree && this._hideRoot){
|
|
|
191 |
// put first child in tab index if one exists.
|
|
|
192 |
var fc = this.getChildren()[0];
|
|
|
193 |
var tabnode = fc ? fc.labelNode : this.domNode;
|
|
|
194 |
tabnode.setAttribute("tabIndex", "0");
|
|
|
195 |
}
|
|
|
196 |
|
|
|
197 |
// create animations for showing/hiding the children (if children exist)
|
|
|
198 |
if(this.containerNode && !this._wipeIn){
|
|
|
199 |
this._wipeIn = dojo.fx.wipeIn({node: this.containerNode, duration: 150});
|
|
|
200 |
this._wipeOut = dojo.fx.wipeOut({node: this.containerNode, duration: 150});
|
|
|
201 |
}
|
|
|
202 |
|
|
|
203 |
return nodeMap;
|
|
|
204 |
},
|
|
|
205 |
|
|
|
206 |
_addChildren: function(/* object[] */ childrenArray){
|
|
|
207 |
// summary:
|
|
|
208 |
// adds the children to this node.
|
|
|
209 |
// Takes array of objects like: {label: ...} (_TreeNode options basically)
|
|
|
210 |
|
|
|
211 |
// See parameters of _TreeNode for details.
|
|
|
212 |
var nodeMap = {};
|
|
|
213 |
if(childrenArray && childrenArray.length > 0){
|
|
|
214 |
dojo.forEach(childrenArray, function(childParams){
|
|
|
215 |
var child = new dijit._TreeNode(
|
|
|
216 |
dojo.mixin({
|
|
|
217 |
tree: this.tree,
|
|
|
218 |
label: this.tree.getLabel(childParams.item)
|
|
|
219 |
}, childParams)
|
|
|
220 |
);
|
|
|
221 |
this.addChild(child);
|
|
|
222 |
nodeMap[this.tree.store.getIdentity(childParams.item)] = child;
|
|
|
223 |
}, this);
|
|
|
224 |
|
|
|
225 |
dojo.forEach(this.getChildren(), function(child, idx){
|
|
|
226 |
child._updateLayout();
|
|
|
227 |
});
|
|
|
228 |
}
|
|
|
229 |
|
|
|
230 |
return nodeMap;
|
|
|
231 |
},
|
|
|
232 |
|
|
|
233 |
deleteNode: function(/* treeNode */ node){
|
|
|
234 |
node.destroy();
|
|
|
235 |
|
|
|
236 |
var children = this.getChildren();
|
|
|
237 |
if(children.length == 0){
|
|
|
238 |
this.isExpandable = false;
|
|
|
239 |
this.collapse();
|
|
|
240 |
}
|
|
|
241 |
|
|
|
242 |
dojo.forEach(children, function(child){
|
|
|
243 |
child._updateLayout();
|
|
|
244 |
});
|
|
|
245 |
},
|
|
|
246 |
|
|
|
247 |
makeExpandable: function(){
|
|
|
248 |
//summary
|
|
|
249 |
// if this node wasn't already showing the expando node,
|
|
|
250 |
// turn it into one and call _setExpando()
|
|
|
251 |
this.isExpandable = true;
|
|
|
252 |
this._setExpando(false);
|
|
|
253 |
}
|
|
|
254 |
});
|
|
|
255 |
|
|
|
256 |
dojo.declare(
|
|
|
257 |
"dijit.Tree",
|
|
|
258 |
dijit._TreeNode,
|
|
|
259 |
{
|
|
|
260 |
// summary
|
|
|
261 |
// This widget displays hierarchical data from a store. A query is specified
|
|
|
262 |
// to get the "top level children" from a data store, and then those items are
|
|
|
263 |
// queried for their children and so on (but lazily, as the user clicks the expand node).
|
|
|
264 |
//
|
|
|
265 |
// Thus in the default mode of operation this widget is technically a forest, not a tree,
|
|
|
266 |
// in that there can be multiple "top level children". However, if you specify label,
|
|
|
267 |
// then a special top level node (not corresponding to any item in the datastore) is
|
|
|
268 |
// created, to father all the top level children.
|
|
|
269 |
|
|
|
270 |
// store: String||dojo.data.Store
|
|
|
271 |
// The store to get data to display in the tree
|
|
|
272 |
store: null,
|
|
|
273 |
|
|
|
274 |
// query: String
|
|
|
275 |
// query to get top level node(s) of tree (ex: {type:'continent'})
|
|
|
276 |
query: null,
|
|
|
277 |
|
|
|
278 |
// childrenAttr: String
|
|
|
279 |
// one ore more attributes that holds children of a tree node
|
|
|
280 |
childrenAttr: ["children"],
|
|
|
281 |
|
|
|
282 |
templateString:"<div class=\"dijitTreeContainer\" style=\"\" waiRole=\"tree\"\n\tdojoAttachEvent=\"onclick:_onClick,onkeypress:_onKeyPress\">\n\t<div class=\"dijitTreeNode dijitTreeIsRoot dijitTreeExpandLeaf dijitTreeChildrenNo\" waiRole=\"presentation\"\n\t\tdojoAttachPoint=\"rowNode\"\n\t\t><span dojoAttachPoint=\"expandoNode\" class=\"dijitTreeExpando\" waiRole=\"presentation\"\n\t\t></span\n\t\t><span dojoAttachPoint=\"expandoNodeText\" class=\"dijitExpandoText\" waiRole=\"presentation\"\n\t\t></span\n\t\t>\n\t\t<div dojoAttachPoint=\"contentNode\" class=\"dijitTreeContent\" waiRole=\"presentation\">\n\t\t\t<div dojoAttachPoint=\"iconNode\" class=\"dijitInline dijitTreeIcon\" waiRole=\"presentation\"></div>\n\t\t\t<span dojoAttachPoint=\"labelNode\" class=\"dijitTreeLabel\" wairole=\"treeitem\" tabindex=\"0\"></span>\n\t\t</div>\n\t</div>\n</div>\n",
|
|
|
283 |
|
|
|
284 |
isExpandable: true,
|
|
|
285 |
|
|
|
286 |
isTree: true,
|
|
|
287 |
|
|
|
288 |
// persist: Boolean
|
|
|
289 |
// enables/disables use of cookies for state saving.
|
|
|
290 |
persist: true,
|
|
|
291 |
|
|
|
292 |
// dndController: String
|
|
|
293 |
// class name to use as as the dnd controller
|
|
|
294 |
dndController: null,
|
|
|
295 |
|
|
|
296 |
//parameters to pull off of the tree and pass on to the dndController as its params
|
|
|
297 |
dndParams: ["onDndDrop","itemCreator","onDndCancel","checkAcceptance", "checkItemAcceptance"],
|
|
|
298 |
|
|
|
299 |
//declare the above items so they can be pulled from the tree's markup
|
|
|
300 |
onDndDrop:null,
|
|
|
301 |
itemCreator:null,
|
|
|
302 |
onDndCancel:null,
|
|
|
303 |
checkAcceptance:null,
|
|
|
304 |
checkItemAcceptance:null,
|
|
|
305 |
|
|
|
306 |
_publish: function(/*String*/ topicName, /*Object*/ message){
|
|
|
307 |
// summary:
|
|
|
308 |
// Publish a message for this widget/topic
|
|
|
309 |
dojo.publish(this.id, [dojo.mixin({tree: this, event: topicName}, message||{})]);
|
|
|
310 |
},
|
|
|
311 |
|
|
|
312 |
postMixInProperties: function(){
|
|
|
313 |
this.tree = this;
|
|
|
314 |
this.lastFocused = this.labelNode;
|
|
|
315 |
|
|
|
316 |
this._itemNodeMap={};
|
|
|
317 |
|
|
|
318 |
this._hideRoot = !this.label;
|
|
|
319 |
|
|
|
320 |
if(!this.store.getFeatures()['dojo.data.api.Identity']){
|
|
|
321 |
throw new Error("dijit.tree requires access to a store supporting the dojo.data Identity api");
|
|
|
322 |
}
|
|
|
323 |
|
|
|
324 |
if(!this.cookieName){
|
|
|
325 |
this.cookieName = this.id + "SaveStateCookie";
|
|
|
326 |
}
|
|
|
327 |
|
|
|
328 |
// if the store supports Notification, subscribe to the notification events
|
|
|
329 |
if(this.store.getFeatures()['dojo.data.api.Notification']){
|
|
|
330 |
this.connect(this.store, "onNew", "_onNewItem");
|
|
|
331 |
this.connect(this.store, "onDelete", "_onDeleteItem");
|
|
|
332 |
this.connect(this.store, "onSet", "_onSetItem");
|
|
|
333 |
}
|
|
|
334 |
},
|
|
|
335 |
|
|
|
336 |
postCreate: function(){
|
|
|
337 |
// load in which nodes should be opened automatically
|
|
|
338 |
if(this.persist){
|
|
|
339 |
var cookie = dojo.cookie(this.cookieName);
|
|
|
340 |
this._openedItemIds = {};
|
|
|
341 |
if(cookie){
|
|
|
342 |
dojo.forEach(cookie.split(','), function(item){
|
|
|
343 |
this._openedItemIds[item] = true;
|
|
|
344 |
}, this);
|
|
|
345 |
}
|
|
|
346 |
}
|
|
|
347 |
|
|
|
348 |
// make template for container node (we will clone this and insert it into
|
|
|
349 |
// any nodes that have children)
|
|
|
350 |
var div = document.createElement('div');
|
|
|
351 |
div.style.display = 'none';
|
|
|
352 |
div.className = "dijitTreeContainer";
|
|
|
353 |
dijit.setWaiRole(div, "presentation");
|
|
|
354 |
this.containerNodeTemplate = div;
|
|
|
355 |
|
|
|
356 |
if(this._hideRoot){
|
|
|
357 |
this.rowNode.style.display="none";
|
|
|
358 |
}
|
|
|
359 |
|
|
|
360 |
this.inherited("postCreate", arguments);
|
|
|
361 |
|
|
|
362 |
// load top level children
|
|
|
363 |
this._expandNode(this);
|
|
|
364 |
|
|
|
365 |
if(this.dndController){
|
|
|
366 |
if(dojo.isString(this.dndController)){
|
|
|
367 |
this.dndController= dojo.getObject(this.dndController);
|
|
|
368 |
}
|
|
|
369 |
var params={};
|
|
|
370 |
for (var i=0; i<this.dndParams.length;i++){
|
|
|
371 |
if(this[this.dndParams[i]]){
|
|
|
372 |
params[this.dndParams[i]]=this[this.dndParams[i]];
|
|
|
373 |
}
|
|
|
374 |
}
|
|
|
375 |
this.dndController= new this.dndController(this, params);
|
|
|
376 |
}
|
|
|
377 |
|
|
|
378 |
this.connect(this.domNode,
|
|
|
379 |
dojo.isIE ? "onactivate" : "onfocus",
|
|
|
380 |
"_onTreeFocus");
|
|
|
381 |
},
|
|
|
382 |
|
|
|
383 |
////////////// Data store related functions //////////////////////
|
|
|
384 |
|
|
|
385 |
mayHaveChildren: function(/*dojo.data.Item*/ item){
|
|
|
386 |
// summary
|
|
|
387 |
// User overridable function to tell if an item has or may have children.
|
|
|
388 |
// Controls whether or not +/- expando icon is shown.
|
|
|
389 |
// (For efficiency reasons we may not want to check if an element has
|
|
|
390 |
// children until user clicks the expando node)
|
|
|
391 |
|
|
|
392 |
return dojo.some(this.childrenAttr, function(attr){
|
|
|
393 |
return this.store.hasAttribute(item, attr);
|
|
|
394 |
}, this);
|
|
|
395 |
},
|
|
|
396 |
|
|
|
397 |
getItemChildren: function(/*dojo.data.Item*/ parentItem, /*function(items)*/ onComplete){
|
|
|
398 |
// summary
|
|
|
399 |
// User overridable function that return array of child items of given parent item,
|
|
|
400 |
// or if parentItem==null then return top items in tree
|
|
|
401 |
var store = this.store;
|
|
|
402 |
if(parentItem == null){
|
|
|
403 |
// get top level nodes
|
|
|
404 |
store.fetch({ query: this.query, onComplete: onComplete});
|
|
|
405 |
}else{
|
|
|
406 |
// get children of specified node
|
|
|
407 |
var childItems = [];
|
|
|
408 |
for (var i=0; i<this.childrenAttr.length; i++){
|
|
|
409 |
childItems= childItems.concat(store.getValues(parentItem, this.childrenAttr[i]));
|
|
|
410 |
}
|
|
|
411 |
// count how many items need to be loaded
|
|
|
412 |
var _waitCount = 0;
|
|
|
413 |
dojo.forEach(childItems, function(item){ if(!store.isItemLoaded(item)){ _waitCount++; } });
|
|
|
414 |
|
|
|
415 |
if(_waitCount == 0){
|
|
|
416 |
// all items are already loaded. proceed..
|
|
|
417 |
onComplete(childItems);
|
|
|
418 |
}else{
|
|
|
419 |
// still waiting for some or all of the items to load
|
|
|
420 |
function onItem(item){
|
|
|
421 |
if(--_waitCount == 0){
|
|
|
422 |
// all nodes have been loaded, send them to the tree
|
|
|
423 |
onComplete(childItems);
|
|
|
424 |
}
|
|
|
425 |
}
|
|
|
426 |
dojo.forEach(childItems, function(item){
|
|
|
427 |
if(!store.isItemLoaded(item)){
|
|
|
428 |
store.loadItem({item: item, onItem: onItem});
|
|
|
429 |
}
|
|
|
430 |
});
|
|
|
431 |
}
|
|
|
432 |
}
|
|
|
433 |
},
|
|
|
434 |
|
|
|
435 |
getItemParentIdentity: function(/*dojo.data.Item*/ item, /*Object*/ parentInfo){
|
|
|
436 |
// summary
|
|
|
437 |
// User overridable function, to return id of parent (or null if top level).
|
|
|
438 |
// It's called with args from dojo.store.onNew
|
|
|
439 |
return this.store.getIdentity(parentInfo.item); // String
|
|
|
440 |
},
|
|
|
441 |
|
|
|
442 |
getLabel: function(/*dojo.data.Item*/ item){
|
|
|
443 |
// summary: user overridable function to get the label for a tree node (given the item)
|
|
|
444 |
return this.store.getLabel(item); // String
|
|
|
445 |
},
|
|
|
446 |
|
|
|
447 |
getIconClass: function(/*dojo.data.Item*/ item){
|
|
|
448 |
// summary: user overridable function to return CSS class name to display icon
|
|
|
449 |
},
|
|
|
450 |
|
|
|
451 |
getLabelClass: function(/*dojo.data.Item*/ item){
|
|
|
452 |
// summary: user overridable function to return CSS class name to display label
|
|
|
453 |
},
|
|
|
454 |
|
|
|
455 |
_onLoadAllItems: function(/*_TreeNode*/ node, /*dojo.data.Item[]*/ items){
|
|
|
456 |
// sumary: callback when all the children of a given node have been loaded
|
|
|
457 |
var childParams=dojo.map(items, function(item){
|
|
|
458 |
return {
|
|
|
459 |
item: item,
|
|
|
460 |
isExpandable: this.mayHaveChildren(item)
|
|
|
461 |
};
|
|
|
462 |
}, this);
|
|
|
463 |
|
|
|
464 |
dojo.mixin(this._itemNodeMap,node._setChildren(childParams));
|
|
|
465 |
|
|
|
466 |
this._expandNode(node);
|
|
|
467 |
},
|
|
|
468 |
|
|
|
469 |
/////////// Keyboard and Mouse handlers ////////////////////
|
|
|
470 |
|
|
|
471 |
_onKeyPress: function(/*Event*/ e){
|
|
|
472 |
// summary: translates keypress events into commands for the controller
|
|
|
473 |
if(e.altKey){ return; }
|
|
|
474 |
var treeNode = dijit.getEnclosingWidget(e.target);
|
|
|
475 |
if(!treeNode){ return; }
|
|
|
476 |
|
|
|
477 |
// Note: On IE e.keyCode is not 0 for printables so check e.charCode.
|
|
|
478 |
// In dojo charCode is universally 0 for non-printables.
|
|
|
479 |
if(e.charCode){ // handle printables (letter navigation)
|
|
|
480 |
// Check for key navigation.
|
|
|
481 |
var navKey = e.charCode;
|
|
|
482 |
if(!e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey){
|
|
|
483 |
navKey = (String.fromCharCode(navKey)).toLowerCase();
|
|
|
484 |
this._onLetterKeyNav( { node: treeNode, key: navKey } );
|
|
|
485 |
dojo.stopEvent(e);
|
|
|
486 |
}
|
|
|
487 |
}else{ // handle non-printables (arrow keys)
|
|
|
488 |
var map = this._keyHandlerMap;
|
|
|
489 |
if(!map){
|
|
|
490 |
// setup table mapping keys to events
|
|
|
491 |
map = {};
|
|
|
492 |
map[dojo.keys.ENTER]="_onEnterKey";
|
|
|
493 |
map[dojo.keys.LEFT_ARROW]="_onLeftArrow";
|
|
|
494 |
map[dojo.keys.RIGHT_ARROW]="_onRightArrow";
|
|
|
495 |
map[dojo.keys.UP_ARROW]="_onUpArrow";
|
|
|
496 |
map[dojo.keys.DOWN_ARROW]="_onDownArrow";
|
|
|
497 |
map[dojo.keys.HOME]="_onHomeKey";
|
|
|
498 |
map[dojo.keys.END]="_onEndKey";
|
|
|
499 |
this._keyHandlerMap = map;
|
|
|
500 |
}
|
|
|
501 |
if(this._keyHandlerMap[e.keyCode]){
|
|
|
502 |
this[this._keyHandlerMap[e.keyCode]]( { node: treeNode, item: treeNode.item } );
|
|
|
503 |
dojo.stopEvent(e);
|
|
|
504 |
}
|
|
|
505 |
}
|
|
|
506 |
},
|
|
|
507 |
|
|
|
508 |
_onEnterKey: function(/*Object*/ message){
|
|
|
509 |
this._publish("execute", { item: message.item, node: message.node} );
|
|
|
510 |
this.onClick(message.item, message.node);
|
|
|
511 |
},
|
|
|
512 |
|
|
|
513 |
_onDownArrow: function(/*Object*/ message){
|
|
|
514 |
// summary: down arrow pressed; get next visible node, set focus there
|
|
|
515 |
var returnNode = this._navToNextNode(message.node);
|
|
|
516 |
if(returnNode && returnNode.isTreeNode){
|
|
|
517 |
returnNode.tree.focusNode(returnNode);
|
|
|
518 |
return returnNode;
|
|
|
519 |
}
|
|
|
520 |
},
|
|
|
521 |
|
|
|
522 |
_onUpArrow: function(/*Object*/ message){
|
|
|
523 |
// summary: up arrow pressed; move to previous visible node
|
|
|
524 |
|
|
|
525 |
var nodeWidget = message.node;
|
|
|
526 |
var returnWidget = nodeWidget;
|
|
|
527 |
|
|
|
528 |
// if younger siblings
|
|
|
529 |
var previousSibling = nodeWidget.getPreviousSibling();
|
|
|
530 |
if(previousSibling){
|
|
|
531 |
nodeWidget = previousSibling;
|
|
|
532 |
// if the previous nodeWidget is expanded, dive in deep
|
|
|
533 |
while(nodeWidget.isExpandable && nodeWidget.isExpanded && nodeWidget.hasChildren()){
|
|
|
534 |
returnWidget = nodeWidget;
|
|
|
535 |
// move to the last child
|
|
|
536 |
var children = nodeWidget.getChildren();
|
|
|
537 |
nodeWidget = children[children.length-1];
|
|
|
538 |
}
|
|
|
539 |
}else{
|
|
|
540 |
// if this is the first child, return the parent
|
|
|
541 |
// unless the parent is the root of a tree with a hidden root
|
|
|
542 |
var parent = nodeWidget.getParent();
|
|
|
543 |
if(!(this._hideRoot && parent === this)){
|
|
|
544 |
nodeWidget = parent;
|
|
|
545 |
}
|
|
|
546 |
}
|
|
|
547 |
|
|
|
548 |
if(nodeWidget && nodeWidget.isTreeNode){
|
|
|
549 |
returnWidget = nodeWidget;
|
|
|
550 |
}
|
|
|
551 |
|
|
|
552 |
if(returnWidget && returnWidget.isTreeNode){
|
|
|
553 |
returnWidget.tree.focusNode(returnWidget);
|
|
|
554 |
return returnWidget;
|
|
|
555 |
}
|
|
|
556 |
},
|
|
|
557 |
|
|
|
558 |
_onRightArrow: function(/*Object*/ message){
|
|
|
559 |
// summary: right arrow pressed; go to child node
|
|
|
560 |
var nodeWidget = message.node;
|
|
|
561 |
var returnWidget = nodeWidget;
|
|
|
562 |
|
|
|
563 |
// if not expanded, expand, else move to 1st child
|
|
|
564 |
if(nodeWidget.isExpandable && !nodeWidget.isExpanded){
|
|
|
565 |
this._expandNode(nodeWidget);
|
|
|
566 |
}else if(nodeWidget.hasChildren()){
|
|
|
567 |
nodeWidget = nodeWidget.getChildren()[0];
|
|
|
568 |
}
|
|
|
569 |
|
|
|
570 |
if(nodeWidget && nodeWidget.isTreeNode){
|
|
|
571 |
returnWidget = nodeWidget;
|
|
|
572 |
}
|
|
|
573 |
|
|
|
574 |
if(returnWidget && returnWidget.isTreeNode){
|
|
|
575 |
returnWidget.tree.focusNode(returnWidget);
|
|
|
576 |
return returnWidget;
|
|
|
577 |
}
|
|
|
578 |
},
|
|
|
579 |
|
|
|
580 |
_onLeftArrow: function(/*Object*/ message){
|
|
|
581 |
// summary: left arrow pressed; go to parent
|
|
|
582 |
|
|
|
583 |
var node = message.node;
|
|
|
584 |
var returnWidget = node;
|
|
|
585 |
|
|
|
586 |
// if not collapsed, collapse, else move to parent
|
|
|
587 |
if(node.isExpandable && node.isExpanded){
|
|
|
588 |
this._collapseNode(node);
|
|
|
589 |
}else{
|
|
|
590 |
node = node.getParent();
|
|
|
591 |
}
|
|
|
592 |
if(node && node.isTreeNode){
|
|
|
593 |
returnWidget = node;
|
|
|
594 |
}
|
|
|
595 |
|
|
|
596 |
if(returnWidget && returnWidget.isTreeNode){
|
|
|
597 |
returnWidget.tree.focusNode(returnWidget);
|
|
|
598 |
return returnWidget;
|
|
|
599 |
}
|
|
|
600 |
},
|
|
|
601 |
|
|
|
602 |
_onHomeKey: function(){
|
|
|
603 |
// summary: home pressed; get first visible node, set focus there
|
|
|
604 |
var returnNode = this._navToRootOrFirstNode();
|
|
|
605 |
if(returnNode){
|
|
|
606 |
returnNode.tree.focusNode(returnNode);
|
|
|
607 |
return returnNode;
|
|
|
608 |
}
|
|
|
609 |
},
|
|
|
610 |
|
|
|
611 |
_onEndKey: function(/*Object*/ message){
|
|
|
612 |
// summary: end pressed; go to last visible node
|
|
|
613 |
|
|
|
614 |
var returnWidget = message.node.tree;
|
|
|
615 |
|
|
|
616 |
var lastChild = returnWidget;
|
|
|
617 |
while(lastChild.isExpanded){
|
|
|
618 |
var c = lastChild.getChildren();
|
|
|
619 |
lastChild = c[c.length - 1];
|
|
|
620 |
if(lastChild.isTreeNode){
|
|
|
621 |
returnWidget = lastChild;
|
|
|
622 |
}
|
|
|
623 |
}
|
|
|
624 |
|
|
|
625 |
if(returnWidget && returnWidget.isTreeNode){
|
|
|
626 |
returnWidget.tree.focusNode(returnWidget);
|
|
|
627 |
return returnWidget;
|
|
|
628 |
}
|
|
|
629 |
},
|
|
|
630 |
|
|
|
631 |
_onLetterKeyNav: function(message){
|
|
|
632 |
// summary: letter key pressed; search for node starting with first char = key
|
|
|
633 |
var node = startNode = message.node;
|
|
|
634 |
var key = message.key;
|
|
|
635 |
do{
|
|
|
636 |
node = this._navToNextNode(node);
|
|
|
637 |
//check for last node, jump to first node if necessary
|
|
|
638 |
if(!node){
|
|
|
639 |
node = this._navToRootOrFirstNode();
|
|
|
640 |
}
|
|
|
641 |
}while(node !== startNode && (node.label.charAt(0).toLowerCase() != key));
|
|
|
642 |
if(node && node.isTreeNode){
|
|
|
643 |
// no need to set focus if back where we started
|
|
|
644 |
if(node !== startNode){
|
|
|
645 |
node.tree.focusNode(node);
|
|
|
646 |
}
|
|
|
647 |
return node;
|
|
|
648 |
}
|
|
|
649 |
},
|
|
|
650 |
|
|
|
651 |
_onClick: function(/*Event*/ e){
|
|
|
652 |
// summary: translates click events into commands for the controller to process
|
|
|
653 |
var domElement = e.target;
|
|
|
654 |
|
|
|
655 |
// find node
|
|
|
656 |
var nodeWidget = dijit.getEnclosingWidget(domElement);
|
|
|
657 |
if(!nodeWidget || !nodeWidget.isTreeNode){
|
|
|
658 |
return;
|
|
|
659 |
}
|
|
|
660 |
|
|
|
661 |
if(domElement == nodeWidget.expandoNode ||
|
|
|
662 |
domElement == nodeWidget.expandoNodeText){
|
|
|
663 |
// expando node was clicked
|
|
|
664 |
if(nodeWidget.isExpandable){
|
|
|
665 |
this._onExpandoClick({node:nodeWidget});
|
|
|
666 |
}
|
|
|
667 |
}else{
|
|
|
668 |
this._publish("execute", { item: nodeWidget.item, node: nodeWidget} );
|
|
|
669 |
this.onClick(nodeWidget.item, nodeWidget);
|
|
|
670 |
this.focusNode(nodeWidget);
|
|
|
671 |
}
|
|
|
672 |
dojo.stopEvent(e);
|
|
|
673 |
},
|
|
|
674 |
|
|
|
675 |
_onExpandoClick: function(/*Object*/ message){
|
|
|
676 |
// summary: user clicked the +/- icon; expand or collapse my children.
|
|
|
677 |
var node = message.node;
|
|
|
678 |
if(node.isExpanded){
|
|
|
679 |
this._collapseNode(node);
|
|
|
680 |
}else{
|
|
|
681 |
this._expandNode(node);
|
|
|
682 |
}
|
|
|
683 |
},
|
|
|
684 |
|
|
|
685 |
onClick: function(/* dojo.data */ item, /*TreeNode*/ node){
|
|
|
686 |
// summary: user overridable function for executing a tree item
|
|
|
687 |
},
|
|
|
688 |
|
|
|
689 |
_navToNextNode: function(node){
|
|
|
690 |
// summary: get next visible node
|
|
|
691 |
var returnNode;
|
|
|
692 |
// if this is an expanded node, get the first child
|
|
|
693 |
if(node.isExpandable && node.isExpanded && node.hasChildren()){
|
|
|
694 |
returnNode = node.getChildren()[0];
|
|
|
695 |
}else{
|
|
|
696 |
// find a parent node with a sibling
|
|
|
697 |
while(node && node.isTreeNode){
|
|
|
698 |
returnNode = node.getNextSibling();
|
|
|
699 |
if(returnNode){
|
|
|
700 |
break;
|
|
|
701 |
}
|
|
|
702 |
node = node.getParent();
|
|
|
703 |
}
|
|
|
704 |
}
|
|
|
705 |
return returnNode;
|
|
|
706 |
},
|
|
|
707 |
|
|
|
708 |
_navToRootOrFirstNode: function(){
|
|
|
709 |
// summary: get first visible node
|
|
|
710 |
if(!this._hideRoot){
|
|
|
711 |
return this;
|
|
|
712 |
}else{
|
|
|
713 |
var returnNode = this.getChildren()[0];
|
|
|
714 |
if(returnNode && returnNode.isTreeNode){
|
|
|
715 |
return returnNode;
|
|
|
716 |
}
|
|
|
717 |
}
|
|
|
718 |
},
|
|
|
719 |
|
|
|
720 |
_collapseNode: function(/*_TreeNode*/ node){
|
|
|
721 |
// summary: called when the user has requested to collapse the node
|
|
|
722 |
|
|
|
723 |
if(node.isExpandable){
|
|
|
724 |
if(node.state == "LOADING"){
|
|
|
725 |
// ignore clicks while we are in the process of loading data
|
|
|
726 |
return;
|
|
|
727 |
}
|
|
|
728 |
if(this.lastFocused){
|
|
|
729 |
// are we collapsing a descendant with focus?
|
|
|
730 |
if(dojo.isDescendant(this.lastFocused.domNode, node.domNode)){
|
|
|
731 |
this.focusNode(node);
|
|
|
732 |
}else{
|
|
|
733 |
// clicking the expando node might have erased focus from
|
|
|
734 |
// the current item; restore it
|
|
|
735 |
this.focusNode(this.lastFocused);
|
|
|
736 |
}
|
|
|
737 |
}
|
|
|
738 |
node.collapse();
|
|
|
739 |
if(this.persist && node.item){
|
|
|
740 |
delete this._openedItemIds[this.store.getIdentity(node.item)];
|
|
|
741 |
this._saveState();
|
|
|
742 |
}
|
|
|
743 |
}
|
|
|
744 |
},
|
|
|
745 |
|
|
|
746 |
_expandNode: function(/*_TreeNode*/ node){
|
|
|
747 |
// summary: called when the user has requested to expand the node
|
|
|
748 |
|
|
|
749 |
// clicking the expando node might have erased focus from the current item; restore it
|
|
|
750 |
var t = node.tree;
|
|
|
751 |
if(t.lastFocused){ t.focusNode(t.lastFocused); }
|
|
|
752 |
|
|
|
753 |
if(!node.isExpandable){
|
|
|
754 |
return;
|
|
|
755 |
}
|
|
|
756 |
|
|
|
757 |
var store = this.store;
|
|
|
758 |
var getValue = this.store.getValue;
|
|
|
759 |
|
|
|
760 |
switch(node.state){
|
|
|
761 |
case "LOADING":
|
|
|
762 |
// ignore clicks while we are in the process of loading data
|
|
|
763 |
return;
|
|
|
764 |
|
|
|
765 |
case "UNCHECKED":
|
|
|
766 |
// need to load all the children, and then expand
|
|
|
767 |
node.markProcessing();
|
|
|
768 |
var _this = this;
|
|
|
769 |
var onComplete = function(childItems){
|
|
|
770 |
node.unmarkProcessing();
|
|
|
771 |
_this._onLoadAllItems(node, childItems);
|
|
|
772 |
};
|
|
|
773 |
this.getItemChildren(node.item, onComplete);
|
|
|
774 |
break;
|
|
|
775 |
|
|
|
776 |
default:
|
|
|
777 |
// data is already loaded; just proceed
|
|
|
778 |
if(node.expand){ // top level Tree doesn't have expand() method
|
|
|
779 |
node.expand();
|
|
|
780 |
if(this.persist && node.item){
|
|
|
781 |
this._openedItemIds[this.store.getIdentity(node.item)] = true;
|
|
|
782 |
this._saveState();
|
|
|
783 |
}
|
|
|
784 |
}
|
|
|
785 |
break;
|
|
|
786 |
}
|
|
|
787 |
},
|
|
|
788 |
|
|
|
789 |
////////////////// Miscellaneous functions ////////////////
|
|
|
790 |
|
|
|
791 |
blurNode: function(){
|
|
|
792 |
// summary
|
|
|
793 |
// Removes focus from the currently focused node (which must be visible).
|
|
|
794 |
// Usually not called directly (just call focusNode() on another node instead)
|
|
|
795 |
var node = this.lastFocused;
|
|
|
796 |
if(!node){ return; }
|
|
|
797 |
var labelNode = node.labelNode;
|
|
|
798 |
dojo.removeClass(labelNode, "dijitTreeLabelFocused");
|
|
|
799 |
labelNode.setAttribute("tabIndex", "-1");
|
|
|
800 |
this.lastFocused = null;
|
|
|
801 |
},
|
|
|
802 |
|
|
|
803 |
focusNode: function(/* _tree.Node */ node){
|
|
|
804 |
// summary
|
|
|
805 |
// Focus on the specified node (which must be visible)
|
|
|
806 |
|
|
|
807 |
// set focus so that the label will be voiced using screen readers
|
|
|
808 |
node.labelNode.focus();
|
|
|
809 |
},
|
|
|
810 |
|
|
|
811 |
_onBlur: function(){
|
|
|
812 |
// summary:
|
|
|
813 |
// We've moved away from the whole tree. The currently "focused" node
|
|
|
814 |
// (see focusNode above) should remain as the lastFocused node so we can
|
|
|
815 |
// tab back into the tree. Just change CSS to get rid of the dotted border
|
|
|
816 |
// until that time
|
|
|
817 |
if(this.lastFocused){
|
|
|
818 |
var labelNode = this.lastFocused.labelNode;
|
|
|
819 |
dojo.removeClass(labelNode, "dijitTreeLabelFocused");
|
|
|
820 |
}
|
|
|
821 |
},
|
|
|
822 |
|
|
|
823 |
_onTreeFocus: function(evt){
|
|
|
824 |
var node = dijit.getEnclosingWidget(evt.target);
|
|
|
825 |
if(node != this.lastFocused){
|
|
|
826 |
this.blurNode();
|
|
|
827 |
}
|
|
|
828 |
var labelNode = node.labelNode;
|
|
|
829 |
// set tabIndex so that the tab key can find this node
|
|
|
830 |
labelNode.setAttribute("tabIndex", "0");
|
|
|
831 |
dojo.addClass(labelNode, "dijitTreeLabelFocused");
|
|
|
832 |
this.lastFocused = node;
|
|
|
833 |
},
|
|
|
834 |
|
|
|
835 |
//////////////// Events from data store //////////////////////////
|
|
|
836 |
|
|
|
837 |
|
|
|
838 |
_onNewItem: function(/*Object*/ item, parentInfo){
|
|
|
839 |
//summary: callback when new item has been added to the store.
|
|
|
840 |
|
|
|
841 |
var loadNewItem; // should new item be displayed in tree?
|
|
|
842 |
|
|
|
843 |
if(parentInfo){
|
|
|
844 |
var parent = this._itemNodeMap[this.getItemParentIdentity(item, parentInfo)];
|
|
|
845 |
|
|
|
846 |
// If new item's parent item not in tree view yet, can safely ignore.
|
|
|
847 |
// Also, if a query of specified parent wouldn't return this item, then ignore.
|
|
|
848 |
if(!parent ||
|
|
|
849 |
dojo.indexOf(this.childrenAttr, parentInfo.attribute) == -1){
|
|
|
850 |
return;
|
|
|
851 |
}
|
|
|
852 |
}
|
|
|
853 |
|
|
|
854 |
var childParams = {
|
|
|
855 |
item: item,
|
|
|
856 |
isExpandable: this.mayHaveChildren(item)
|
|
|
857 |
};
|
|
|
858 |
if(parent){
|
|
|
859 |
if(!parent.isExpandable){
|
|
|
860 |
parent.makeExpandable();
|
|
|
861 |
}
|
|
|
862 |
if(parent.state=="LOADED" || parent.isExpanded){
|
|
|
863 |
var childrenMap=parent._addChildren([childParams]);
|
|
|
864 |
}
|
|
|
865 |
}else{
|
|
|
866 |
// top level node
|
|
|
867 |
var childrenMap=this._addChildren([childParams]);
|
|
|
868 |
}
|
|
|
869 |
|
|
|
870 |
if(childrenMap){
|
|
|
871 |
dojo.mixin(this._itemNodeMap, childrenMap);
|
|
|
872 |
//this._itemNodeMap[this.store.getIdentity(item)]=child;
|
|
|
873 |
}
|
|
|
874 |
},
|
|
|
875 |
|
|
|
876 |
_onDeleteItem: function(/*Object*/ item){
|
|
|
877 |
//summary: delete event from the store
|
|
|
878 |
//since the object has just been deleted, we need to
|
|
|
879 |
//use the name directly
|
|
|
880 |
var identity = this.store.getIdentity(item);
|
|
|
881 |
var node = this._itemNodeMap[identity];
|
|
|
882 |
|
|
|
883 |
if(node){
|
|
|
884 |
var parent = node.getParent();
|
|
|
885 |
parent.deleteNode(node);
|
|
|
886 |
this._itemNodeMap[identity]=null;
|
|
|
887 |
}
|
|
|
888 |
},
|
|
|
889 |
|
|
|
890 |
_onSetItem: function(/*Object*/ item){
|
|
|
891 |
//summary: set data event on an item in the store
|
|
|
892 |
var identity = this.store.getIdentity(item);
|
|
|
893 |
node = this._itemNodeMap[identity];
|
|
|
894 |
|
|
|
895 |
if(node){
|
|
|
896 |
node.setLabelNode(this.getLabel(item));
|
|
|
897 |
node._updateItemClasses(item);
|
|
|
898 |
}
|
|
|
899 |
},
|
|
|
900 |
|
|
|
901 |
_saveState: function(){
|
|
|
902 |
//summary: create and save a cookie with the currently expanded nodes identifiers
|
|
|
903 |
if(!this.persist){
|
|
|
904 |
return;
|
|
|
905 |
}
|
|
|
906 |
var ary = [];
|
|
|
907 |
for(var id in this._openedItemIds){
|
|
|
908 |
ary.push(id);
|
|
|
909 |
}
|
|
|
910 |
dojo.cookie(this.cookieName, ary.join(","));
|
|
|
911 |
}
|
|
|
912 |
});
|
|
|
913 |
|
|
|
914 |
}
|