Subversion Repositories Applications.papyrus

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
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
}