1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 
 25 /**
 26  * Creates a Tree Item.
 27  * @constructor
 28  * @class
 29  * This class implements a tree item widget.
 30  *
 31  * @author Ross Dargahi
 32  * 
 33  * @param {hash}	params				a hash of parameters
 34  * @param {DwtComposite}      params.parent	the parent widget
 35  * @param {number}      params.index 			the index at which to add this control among parent's children
 36  * @param {string}      params.text 					the label text for the tree item
 37  * @param {string}      params.imageInfo			the icon for the left end of the tree item
 38  * @param {string}      params.extraInfo				the icon for the right end of the tree item
 39  * @param {string}      params.expandNodeImage		the icon to use for expanding tree item (instead of default)
 40  * @param {string}      params.collapseNodeImage     the icon to use for collapsing tree item (instead of default)
 41  * @param {string}      params.className				the CSS class
 42  * @param {constant}      params.posStyle				the positioning style (see {@link DwtControl})
 43  * @param {boolean}      params.deferred				if <code>true</code>, postpone initialization until needed.
 44  * @param {boolean}      params.selectable			if <code>true</code>, this item is selectable
 45  * @param {boolean}      params.forceNotifySelection	force notify selection even if checked style
 46  * @param {boolean}      params.forceNotifyAction		force notify action even if checked style
 47  * @param {boolean}      params.singleClickAction		if <code>true</code>, an action is performed in single click
 48  * @param {AjxCallback}      params.dndScrollCallback	the callback triggered when scrolling of a drop area for an object being dragged
 49  * @param {string}      params.dndScrollId			the id
 50  * @param {boolean}    params.arrowDisabled
 51  * @param {boolean}     params.dynamicWidth		if <code>true</code>, the table should be width auto instead of the default fixed
 52  *
 53  * @extends		DwtComposite		
 54  */
 55 DwtTreeItem = function(params) {
 56 
 57     if (arguments.length == 0) { return; }    
 58 
 59     params = Dwt.getParams(arguments, DwtTreeItem.PARAMS);
 60 	var parent = params.parent;
 61 	if (parent instanceof DwtTree) {
 62 		this._tree = parent;
 63 	} else if (parent instanceof DwtTreeItem) {
 64 		this._tree = parent._tree;
 65 	} else {
 66 		throw new DwtException("DwtTreeItem parent must be a DwtTree or DwtTreeItem", DwtException.INVALIDPARENT, "DwtTreeItem");
 67 	}
 68 
 69 	this._origClassName = params.className || "DwtTreeItem";
 70 	this._textClassName = [this._origClassName, "Text"].join("-");
 71 	this._selectedClassName = this._origClassName + ' ' + [this._origClassName, DwtCssStyle.SELECTED].join("-");
 72 	this._selectedFocusedClassName = this._selectedClassName + ' ' + [this._origClassName, DwtCssStyle.SELECTED, DwtCssStyle.FOCUSED].join("-");
 73 	this._actionedClassName = this._origClassName + ' ' + [this._origClassName, DwtCssStyle.ACTIONED].join("-");
 74 	this._dragOverClassName = this._origClassName + ' ' + [this._origClassName, DwtCssStyle.DRAG_OVER].join("-");
 75     this._treeItemTextClass = "DwtTreeItem-Text";
 76     this._treeItemExtraImgClass = "DwtTreeItem-ExtraImg";
 77 
 78 	this._dynamicWidth = params.dynamicWidth;
 79 
 80 	params.deferred = (params.deferred !== false);
 81 	params.className = 'DwtTreeItem-Control';
 82 	DwtComposite.call(this, params);
 83 
 84 	this._imageInfoParam = params.imageInfo;
 85 	this._extraInfo = params.extraInfo;
 86 	this._textParam = params.text;
 87 	this._deferred = params.deferred;
 88 	this._expandNodeImage = params.expandNodeImage || "NodeExpanded";
 89 	this._collapseNodeImage = params.collapseNodeImage || "NodeCollapsed";
 90 	this._itemChecked = false;
 91 	this._initialized = false;
 92 	this._selectionEnabled = Boolean(params.selectable !== false);
 93 	this._forceNotifySelection = Boolean(params.forceNotifySelection);
 94 	this._actionEnabled = true;
 95 	this._forceNotifyAction = Boolean(params.forceNotifyAction);
 96 	this._dndScrollCallback = params.dndScrollCallback;
 97 	this._dndScrollId = params.dndScrollId;
 98 	this._arrowDisabled = params.arrowDisabled;
 99 
100 	if (params.singleClickAction) {
101 		this._singleClickAction = true;
102 		this._selectedFocusedClassName = this._selectedClassName = this._textClassName;
103 		this._hoverClassName = [this._origClassName, DwtCssStyle.HOVER].join("-");
104 	} else {
105 		this._hoverClassName = this._textClassName;
106 	}
107 
108 	// if our parent is DwtTree or our parent is initialized and is not deferred
109 	// type or is expanded, then initialize ourself, else wait
110 	if (parent instanceof DwtTree || (parent._initialized && (!parent._deferred || parent._expanded)) || !params.deferred) {
111 		this._initialize(params.index);
112 	} else {
113 		parent._addDeferredChild(this, params.index);
114 		this._index = params.index;
115 	}
116 };
117 
118 DwtTreeItem.PARAMS = ["parent", "index", "text", "imageInfo", "deferred", "className", "posStyle",
119 					  "forceNotifySelection", "forceNotifyAction"];
120 
121 DwtTreeItem.prototype = new DwtComposite;
122 DwtTreeItem.prototype.constructor = DwtTreeItem;
123 
124 DwtTreeItem.prototype.isDwtTreeItem = true;
125 DwtTreeItem.prototype.toString = function() { return "DwtTreeItem"; };
126 
127 DwtTreeItem.prototype.TEMPLATE = "dwt.Widgets#ZTreeItem";
128 
129 DwtTreeItem.prototype.role = "treeitem";
130 DwtTreeItem.prototype.isFocusable = true;
131 
132 DwtTreeItem.prototype._checkBoxVisible = true; // Assume it's shown, if check style
133 
134 // Consts
135 
136 DwtTreeItem._NODECELL_DIM = "16px";
137 DwtTreeItem._processedMouseDown = false;
138 
139 // Public Methods
140 
141 DwtTreeItem.prototype.dispose =
142 function() {
143     DwtComposite.prototype.dispose.call(this);
144 	this._itemDiv = null;
145 	this._nodeCell = null;
146 	this._checkBoxCell = null;
147 	this._checkedImg = null;
148 	this._checkBox = null;
149 	this._imageCell = null;
150 	this._textCell = null;
151 	this._childDiv = null;
152 	this._initialized = false;
153 };
154 
155 /**
156  * override DwtControl.prototype.getData to take care of special case of KEY_OBJECT of type ZmOrganizer. See bug 82027
157  * @param key
158  * @return {*}
159  */
160 DwtTreeItem.prototype.getData =
161 function(key) {
162 	var obj = this._data[key];
163 	if (key !== Dwt.KEY_OBJECT || !obj || !obj.isZmOrganizer) {
164 		return obj;
165 	}
166 	//special case for ZmOrganizer instance of the Dwt.KEY_OBJECT attribute.
167 	//bug 82027 - the folder attributes such as name could be wrong after refresh block+ rename when new instance was created but not set to the item Dwt.KEY_OBJECT attribute.
168 	var cachedOrganizer = obj && appCtxt.cacheGet(obj.id);
169 	return cachedOrganizer || obj; //just in case somehow it's no longer cached. No idea if could happen.
170 };
171 
172 /**
173  * Checks if the item is checked.
174  * 
175  * @return	{boolean}	<code>true</code> if the item is checked
176  */
177 DwtTreeItem.prototype.getChecked =
178 function() {
179 	return this._itemChecked;
180 };
181 
182 /**
183  * Sets the checked flag.
184  * 
185  * @param	{boolean}	checked		if <code>true</code>, check the item
186  * @param	{boolean}	force		if <code>true</code>, force the setting
187  */
188 DwtTreeItem.prototype.setChecked =
189 function(checked, force) {
190 	if ((this._itemChecked != checked) || force) {
191 		this._itemChecked = checked;
192 		if (this._checkBox != null &&
193 			(this._checkBoxCell && Dwt.getVisible(this._checkBoxCell)))
194 		{
195 			Dwt.setVisible(this._checkedImg, checked);
196 		}
197 	}
198 };
199 
200 DwtTreeItem.prototype._handleCheckboxOnclick =
201 function(ev) {
202 	this.setChecked(!Dwt.getVisible(this._checkedImg));
203 
204 	ev = ev || window.event;
205 	ev.item = this;
206 	this._tree._itemChecked(this, ev);
207 };
208 
209 DwtTreeItem.prototype.getExpanded =
210 function() {
211 	return this._expanded;
212 };
213 
214 /**
215  * Expands or collapses this tree item.
216  *
217  * @param {boolean}	expanded		if <code>true</code>, expands this node; otherwise collapses it
218  * @param {boolean}	recurse		if <code>true</code>, expand children recursively (does not apply to collapsing)
219  * @param	{boolean}	skipNotify		if <code>true</code>, do not notify the listeners
220  */
221 DwtTreeItem.prototype.setExpanded =
222 function(expanded, recurse, skipNotify) {
223 	// Go up the chain, ensuring that parents are expanded/initialized
224 	if (expanded) {
225 		var p = this.parent;
226 		while (p instanceof DwtTreeItem && !p._expanded) {
227 			p.setExpanded(true);
228 			p = p.parent;
229 		}
230 		// Realize any deferred children
231 		this._realizeDeferredChildren();
232 	}
233 		
234 	// If we have children, then allow for expanding/collapsing
235 	if (this.getNumChildren()) {
236 		if (expanded && recurse) {
237 			if (!this._expanded) {
238 				this._expand(expanded, null, skipNotify);
239 			}
240 			var a = this.getChildren();
241 			for (var i = 0; i < a.length; i++) {
242 				if (a[i] instanceof DwtTreeItem) {
243 					a[i].setExpanded(expanded, recurse, skipNotify);
244 				}
245 			}
246 		} else if (this._expanded != expanded) {
247 			this._expand(expanded, null, skipNotify);
248 		}
249 	}
250 };
251 
252 /**
253  * Gets the child item count.
254  * 
255  * @return	{number}	the child item count
256  */
257 DwtTreeItem.prototype.getItemCount =
258 function() {
259 	return this._children.size();
260 };
261 
262 /**
263  * Gets the items.
264  * 
265  * @return	{array}	an array of child {@link DwtTreeItem} objects
266  */
267 DwtTreeItem.prototype.getItems =
268 function() {
269 	return this._children.getArray();
270 };
271 
272 DwtTreeItem.prototype.getChildIndex =
273 function(item) {
274 	return this._children.indexOf(item);
275 };
276 
277 /**
278  * Get the nesting level; the toplevel tree is zero, and each lower layer
279  * increases by one.
280  * 
281  * @return	{number}	the child item count
282  */
283 DwtTreeItem.prototype.getNestingLevel =
284 function() {
285 	return this.parent.getNestingLevel() + 1;
286 };
287 
288 /**
289  * Gets the image.
290  * 
291  * @return	{string}	the image
292  */
293 DwtTreeItem.prototype.getImage =
294 function() {
295 	return this._imageInfo;
296 };
297 
298 /**
299  * Sets the image.
300  * 
301  * @param	{string}	imageInfo		the image
302  */
303 DwtTreeItem.prototype.setImage =
304 function(imageInfo) {
305 	if (this._initialized) {
306 		if (this._imageCell) {
307 			AjxImg.setImage(this._imageCell, imageInfo);
308 		}
309 		this._imageInfo = imageInfo;
310 	} else {
311 		this._imageInfoParam = imageInfo;
312 	}	
313 };
314 
315 DwtTreeItem.prototype.setDndImage =
316 function(imageInfo) {
317 	this._dndImageInfo = imageInfo;
318 };
319 
320 DwtTreeItem.prototype.getSelected =
321 function() {
322 	return this._selected;
323 };
324 
325 DwtTreeItem.prototype.getActioned =
326 function() {
327 	return this._actioned;
328 };
329 
330 /**
331  * Gets the text.
332  * 
333  * @return	{string}	the text
334  */
335 DwtTreeItem.prototype.getText =
336 function() {
337 	return this._text;
338 };
339 
340 /**
341  * Sets the text.
342  * 
343  * @param	{string}	text		the text
344  */
345 DwtTreeItem.prototype.setText =
346 function(text) {
347 	if (this._initialized && this._textCell) {
348 		if (!text) text = "";
349 		this._text = this._textCell.innerHTML = text;
350 	} else {
351 		this._textParam = text;
352 	}
353 };
354 
355 /**
356  * Sets the drag-and-drop text.
357  * 
358  * @param	{string}	text		the text
359  */
360 DwtTreeItem.prototype.setDndText =
361 function(text) {
362 	this._dndText = text;
363 };
364 
365 /**
366  * Shows (or hides) the check box.
367  * 
368  * @param	{boolean}	show		if <code>true</code>, show the check box
369  */
370 DwtTreeItem.prototype.showCheckBox =
371 function(show) {
372 	this._checkBoxVisible = show;
373 	if (this._checkBoxCell) {
374 		Dwt.setVisible(this._checkBoxCell, show);
375 	}
376 };
377 
378 /**
379  * Shows (or hides) the expansion icon.
380  * 
381  * @param	{boolean}	show		if <code>true</code>, show the expansion icon
382  */
383 DwtTreeItem.prototype.showExpansionIcon =
384 function(show) {
385 	if (this._nodeCell) {
386 		Dwt.setVisible(this._nodeCell, show);
387 	}
388 };
389 
390 /**
391  * Enables (or disables) the selection.
392  * 
393  * @param	{boolean}	enable		if <code>true</code>, enable selection
394  */
395 DwtTreeItem.prototype.enableSelection =
396 function(enable) {
397 	this._selectionEnabled = enable;
398 	this._selectedClassName = enable
399 		? this._origClassName + "-" + DwtCssStyle.SELECTED
400 		: this._origClassName;
401 
402 };
403 
404 DwtTreeItem.prototype.isSelectionEnabled =
405 function() {
406 	return this._selectionEnabled;
407 };
408 
409 
410 DwtTreeItem.prototype.enableAction =
411 function(enable) {
412 	this._actionEnabled = enable;
413 };
414 
415 /**
416  * Adds a separator at the given index. If no index is provided, adds it at the
417  * end. A separator cannot currently be added as the first item (the child DIV will
418  * not have been created).
419  *
420  * @param {number}	index		the position at which to add the separator
421  */
422 DwtTreeItem.prototype.addSeparator =
423 function(index) {
424 	this._children.add((new DwtTreeItemSeparator(this)), index);
425 };
426 
427 /**
428  * Makes this tree item, or just part of it, visible or hidden.
429  *
430  * @param {boolean}	visible		if <code>true</code>, item (or part of it) becomes visible
431  * @param {boolean}	itemOnly		if <code>true</code>, apply to this item's DIV only; child items are unaffected
432  * @param {boolean}	childOnly		if <code>true</code>, apply to this item's child items only
433  */
434 DwtTreeItem.prototype.setVisible =
435 function(visible, itemOnly, childOnly) {
436 	if (itemOnly && !childOnly) {
437 		Dwt.setVisible(this._itemDiv, visible);
438 	} else if (childOnly && !itemOnly) {
439 		Dwt.setVisible(this._childDiv, visible);
440 	} else {
441 		DwtComposite.prototype.setVisible.call(this, visible);
442 	}
443 };
444 
445 DwtTreeItem.prototype.removeChild =
446 function(child) {
447 	if (child._initialized) {
448 		this._tree._deselect(child);
449 		if (this._childDiv) {
450 			this._childDiv.removeChild(child.getHtmlElement());
451 		}
452 	}
453 	this._children.remove(child);
454 
455 	// if we have no children and we are expanded, then mark us a collapsed.
456 	// Also if there are no deferred children, then make sure we remove the
457 	// expand/collapse icon and replace it with a blank16Icon.
458 	if (this._children.size() == 0) {
459 		if (this._expanded)
460 			this._expanded = false;
461 
462 		this._expandable = false;
463 		this.removeAttribute('aria-expanded')
464 		
465 		if (this._initialized && this._nodeCell) {
466 			AjxImg.setImage(this._nodeCell, "Blank_16");
467 			var imgEl = AjxImg.getImageElement(this._nodeCell);
468 			if (imgEl)
469 				Dwt.clearHandler(imgEl, DwtEvent.ONMOUSEDOWN);
470 		}
471 	}
472 };
473 
474 DwtTreeItem.prototype.getKeyMapName =
475 function() {
476 	return DwtKeyMap.MAP_TREE;
477 };
478 
479 DwtTreeItem.prototype.handleKeyAction =
480 function(actionCode, ev) {
481 
482 	switch (actionCode) {
483 		
484 		case DwtKeyMap.ENTER:
485 			this._tree.setEnterSelection(this, true);
486 			break;
487 
488 
489 		case DwtKeyMap.NEXT: {
490 			var ti = this._tree._getNextTreeItem(true);
491 			if (ti) {
492 				ti._tree.setSelection(ti, false, true);
493 			}
494 			break;
495 		}
496 
497 		case DwtKeyMap.PREV: {
498 			var ti = this._tree._getNextTreeItem(false);
499 			if (ti) {
500 				ti._tree.setSelection(ti, false, true);
501 			}
502 			break;
503 		}
504 
505 		case DwtKeyMap.SELECT_FIRST:
506 		case DwtKeyMap.SELECT_LAST: {
507 			var ti = (actionCode === DwtKeyMap.SELECT_FIRST) ?
508 				this._tree._getFirstTreeItem() : this._tree._getLastTreeItem();
509 			if (ti) {
510 				ti._tree.setSelection(ti, false, true);
511 			}
512 			break;
513 		}
514 
515 		case DwtKeyMap.EXPAND: {
516 			if (!this._expanded) {
517 				this.setExpanded(true, false, true);
518 			} else if (this._children.size() > 0) {
519 				// Select first child node
520 				var firstChild = this._children.get(0);
521 				this._tree.setSelection(firstChild, false, true);
522 			}
523 			break;
524 		}
525 
526 		case DwtKeyMap.COLLAPSE: {
527 			if (this._expanded) {
528 				this.setExpanded(false, false, true);
529 			} else if (this.parent.isDwtTreeItem) {
530 				// select parent
531 				this._tree.setSelection(this.parent, false, true);
532 			}
533 			break;
534 		}
535 
536 		case DwtKeyMap.SUBMENU: {
537 			var target = this.getHtmlElement();
538 			var p = Dwt.toWindow(target, 0, 0);
539 			var s = this.getSize();
540 			var docX = p.x + s.x / 4;
541 			var docY = p.y + s.y / 2;
542 			this._gotMouseDownRight = true;
543 			this._emulateSingleClick({dwtObj:this, target:target, button:DwtMouseEvent.RIGHT,
544 									  docX:docX, docY:docY, kbNavEvent:true});
545 			break;
546 		}
547 
548 		default:
549 			return false;
550 
551 	}
552 
553 	return true;
554 };
555 
556 DwtTreeItem.prototype.addNodeIconListeners =
557 function() {
558 	var imgEl = AjxImg.getImageElement(this._nodeCell);
559 	if (imgEl) {
560 		Dwt.setHandler(imgEl, DwtEvent.ONMOUSEDOWN, DwtTreeItem._nodeIconMouseDownHdlr);
561 		Dwt.setHandler(imgEl, DwtEvent.ONMOUSEUP, DwtTreeItem._nodeIconMouseUpHdlr);
562 	}
563 };
564 
565 DwtTreeItem.prototype._initialize =
566 function(index, realizeDeferred, forceNode) {
567 	this._checkState();
568 	if (AjxEnv.isIE) {
569 		this._setEventHdlrs([DwtEvent.ONMOUSEENTER, DwtEvent.ONMOUSELEAVE]);
570 	}
571 	if (AjxEnv.isSafari) {	// bug fix #25016
572 		this._setEventHdlrs([DwtEvent.ONCONTEXTMENU]);
573 	}
574 	var data = {
575 		id: this._htmlElId,
576 		divClassName: this._origClassName,
577 		isCheckedStyle: this._tree.isCheckedStyle,
578 		textClassName: this._textClassName
579 	};
580 
581 	this._createHtmlFromTemplate(this.TEMPLATE, data);
582 
583 	// add this object's HTML element to the DOM
584 	this.parent._addItem(this, index, realizeDeferred);
585 
586 	// cache DOM objects here
587 	this._itemDiv = document.getElementById(data.id + "_div");
588 	this._nodeCell = document.getElementById(data.id + "_nodeCell");
589 	this._checkBoxCell = document.getElementById(data.id + "_checkboxCell");
590 	this._checkBox = document.getElementById(data.id + "_checkbox");
591 	this._checkedImg = document.getElementById(data.id + "_checkboxImg");
592 	this._imageCell = document.getElementById(data.id + "_imageCell");
593 	this._textCell = document.getElementById(data.id + "_textCell");
594 	this._extraCell = document.getElementById(data.id + "_extraCell");
595 
596 	/* assign the ARIA level */
597 	this.setAttribute("aria-level", this.getNestingLevel());
598 
599 	/* add a label for screenreaders, so that they don't read the entire
600 	   element */
601 	if (this._textCell) {
602 		this.setAttribute("aria-labelledby", this._textCell.id);
603 	}
604 
605 	if (this._dynamicWidth){
606 		var tableNode = document.getElementById(data.id + "_table");
607 		if (tableNode) {
608 			tableNode.style.tableLayout = "auto";
609 		}
610 	}
611 
612 	this._expandable = false;
613 	this.removeAttribute('aria-expanded');
614 
615 	// If we have deferred children, then make sure we set up accordingly
616 	if (this._nodeCell) {
617 		this._nodeCell.style.minWidth = this._nodeCell.style.width = this._nodeCell.style.height = DwtTreeItem._NODECELL_DIM;
618 		if (this._children.size() > 0 || forceNode) {
619 			this._expandable = true;
620 			AjxImg.setImage(this._nodeCell, this._collapseNodeImage);
621 			this.addNodeIconListeners();
622 		}
623 	}
624 
625 	if (this._extraCell) {
626 		AjxImg.setImage(this._extraCell, (this._extraInfo ||  "Blank_16"));
627 		this._extraCell.className = this._treeItemExtraImgClass;
628 	}
629 
630 	// initialize checkbox
631 	if (this._tree.isCheckedStyle && this._checkBox) {
632 		this._checkBox.onclick = AjxCallback.simpleClosure(this._handleCheckboxOnclick, this);
633 		this.showCheckBox(this._checkBoxVisible);
634 		this.setChecked(this._tree.isCheckedByDefault, true);
635 	}
636 
637 	// initialize icon
638 	if (this._imageCell && this._imageInfoParam) {
639 		AjxImg.setImage(this._imageCell, this._imageInfoParam);
640 		this._imageInfo = this._imageInfoParam;
641 	}
642 
643 	// initialize text
644 	if (this._textCell && this._textParam) {
645 		this._textCell.innerHTML = this._text = this._textParam;
646 	}
647 	this._expanded = this._selected = this._actioned = false;
648 	this._gotMouseDownLeft = this._gotMouseDownRight = false;
649 	this._addMouseListeners();
650 
651 	this._initialized = true;
652 };
653 
654 /**
655  * Sets the tree item color.
656  * 
657  * @param	{string}	className		the class name
658  */
659 DwtTreeItem.prototype.setTreeItemColor = 
660 function(className) {
661 	var id = this._htmlElId +"_table";
662 	var treeItemTableEl = document.getElementById(id);
663 	var treeItemDivEl = document.getElementById(this._htmlElId + "_div");
664 	var treeItemEl = this.getHtmlElement();
665 
666 	var newClassName = this._origClassName + " " + className;
667 	if (treeItemDivEl) {
668 		treeItemDivEl.className = newClassName;
669 	} else if (treeItemEl) {
670 		treeItemEl.className =  className;
671 	}
672 };
673 
674 DwtTreeItem.prototype._addMouseListeners =
675 function() {
676 	var events = [DwtEvent.ONMOUSEDOWN, DwtEvent.ONMOUSEUP, DwtEvent.ONDBLCLICK];
677 	if (AjxEnv.isIE) {
678 		events.push(DwtEvent.ONMOUSEENTER, DwtEvent.ONMOUSELEAVE);
679 	} else {
680 		events.push(DwtEvent.ONMOUSEOVER, DwtEvent.ONMOUSEOUT);
681 	}
682 	if (AjxEnv.isSafari) {
683 		events.push(DwtEvent.ONCONTEXTMENU);
684 	}
685 	for (var i = 0; i < events.length; i++) {
686 		this.addListener(events[i], DwtTreeItem._listeners[events[i]]);
687 	}
688 };
689 
690 DwtTreeItem.prototype._addDeferredChild =
691 function(child, index) {
692 	// If we are initialized, then we need to add a expansion node
693 	if (this._initialized && this._children.size() == 0) {
694 		if (this._nodeCell) {
695 			AjxImg.setImage(this._nodeCell, this._collapseNodeImage);
696 			var imgEl = AjxImg.getImageElement(this._nodeCell);
697 			if (imgEl) {
698 				this._expandable = true;
699 				this.setAttribute('aria-expanded', this._expanded);
700 				Dwt.setHandler(imgEl, DwtEvent.ONMOUSEDOWN, DwtTreeItem._nodeIconMouseDownHdlr);
701 				Dwt.setHandler(imgEl, DwtEvent.ONMOUSEUP, DwtTreeItem._nodeIconMouseUpHdlr);
702 			}
703 		}
704 	}
705 	this._children.add(child, index);
706 };
707 
708 DwtTreeItem.prototype.addChild =
709 function(child) { /* do nothing since we add to the DOM our own way */ };
710 
711 DwtTreeItem.prototype._addItem =
712 function(item, index, realizeDeferred) {
713 	if (!this._children.contains(item)) {
714 		this._children.add(item, index);
715 	}
716 	this._expandable = true;
717 
718 	if (this._childDiv == null) {
719 		this._childDiv = document.createElement("div");
720 		this._childDiv.className = (this.parent != this._tree)
721 			? "DwtTreeItemChildDiv" : "DwtTreeItemLevel1ChildDiv";
722 		this._childDiv.setAttribute('role', 'group');
723 		this._childDiv.setAttribute('aria-labelledby', this._itemDiv.id);
724 		this._childDiv.setAttribute('aria-expanded', this._expanded);
725 		this.getHtmlElement().appendChild(this._childDiv);
726 		if (!this._expanded) {
727 			this._childDiv.style.display = "none";
728 		}
729 	}
730 
731 	if (realizeDeferred && this._nodeCell) {
732 		if (AjxImg.getImageClass(this._nodeCell) == AjxImg.getClassForImage("Blank_16")) {
733 			AjxImg.setImage(this._nodeCell, this._expanded ? this._expandNodeImage : this._collapseNodeImage);
734 			var imgEl = AjxImg.getImageElement(this._nodeCell);
735 			if (imgEl) {
736 				Dwt.setHandler(imgEl, DwtEvent.ONMOUSEDOWN, DwtTreeItem._nodeIconMouseDownHdlr);
737 			}
738 		}
739 	}
740 
741 	var childDiv = this._childDiv;
742 	var numChildren = childDiv.childNodes.length;
743 	if (index == null || index >= numChildren || numChildren == 0) {
744 		childDiv.appendChild(item.getHtmlElement());
745 	} else {
746 		childDiv.insertBefore(item.getHtmlElement(), childDiv.childNodes[index]);
747 	}
748 };
749 
750 DwtTreeItem.prototype.sort =
751 function(cmp) {
752 	this._children.sort(cmp);
753 	if (this._childDiv) {
754 		this._setChildElOrder();
755 	} else {
756 		this._needsSort = true;
757 	}
758 };
759 
760 DwtTreeItem.prototype._setChildElOrder =
761 function(cmp) {
762 	var df = document.createDocumentFragment();
763 	this._children.foreach(function(item, i) {
764 		df.appendChild(item.getHtmlElement());
765 		item._index = i;
766 	});
767 	this._childDiv.appendChild(df);
768 };
769 
770 DwtTreeItem.prototype._getDragProxy =
771 function() {
772 	var icon = document.createElement("div");
773 	Dwt.setPosition(icon, Dwt.ABSOLUTE_STYLE); 
774 	var table = document.createElement("table");
775 	icon.appendChild(table);
776 	table.cellSpacing = table.cellPadding = 0;
777 
778 	var row = table.insertRow(0);
779 	var i = 0;
780 
781 	var c = row.insertCell(i++);
782 	c.noWrap = true;
783 	if (this._dndImageInfo) {
784 		AjxImg.setImage(c, this._dndImageInfo);
785 	} else if (this._imageInfo) {
786 		AjxImg.setImage(c, this._imageInfo);
787 	}
788 
789 	c = row.insertCell(i);
790 	c.noWrap = true;
791 	c.className = this._origClassName;
792 	if (this._dndText) {
793 		c.innerHTML = this._dndText;
794 	} else if (this._text) {
795 		c.innerHTML = this._text;
796 	}
797 
798 	this.shell.getHtmlElement().appendChild(icon);
799 	Dwt.setZIndex(icon, Dwt.Z_DND);
800 	return icon;
801 };
802 
803 DwtTreeItem.prototype._dragEnter =
804 function() {
805 	this._preDragClassName = this._textCell.className;
806 	this._textCell.className = this._dragOverClassName;
807 	this._draghovering = true;
808 };
809 
810 DwtTreeItem.prototype._dragHover =
811 function() {
812 	if (this.getNumChildren() > 0 && !this.getExpanded()) {
813 		this.setExpanded(true);
814 	}
815 };
816 
817 DwtTreeItem.prototype._dragLeave =
818 function(ev) {
819 	if (this._preDragClassName) {
820 		this._textCell.className = this._preDragClassName;
821 	}
822 	this._draghovering = false;
823 };
824 
825 DwtTreeItem.prototype._drop =
826 function() {
827 	if (this._preDragClassName) {
828 		this._textCell.className = this._preDragClassName;
829 	}
830 	this._draghovering = false;
831 };
832 
833 /**
834  *   This is for bug 45129.
835  *   In the DwControl's focusByMouseDownEvent, it focuses the TreeItem 
836  *   And change TreeItem's color. But sometimes when mousedown and mouseup
837  *   haven't been matched on the one element. It will cause multiple selection. 
838  *   For in the mouseup handle function, we has done focus if we find both mouse 
839  *   down and up happened on the same element. So when the mouse is down, we just
840  *   do nothing.
841  */
842 DwtTreeItem.prototype._focusByMouseDownEvent =
843 function(ev) {
844 	
845 }
846 
847 DwtTreeItem._nodeIconMouseDownHdlr =
848 function(ev) {
849 	var obj = DwtControl.getTargetControl(ev);
850 	var mouseEv = DwtShell.mouseEvent;
851 	mouseEv.setFromDhtmlEvent(ev, obj);
852 	if (mouseEv.button == DwtMouseEvent.LEFT) {
853 		obj._expand(!obj._expanded, mouseEv);
854 	} else if (mouseEv.button == DwtMouseEvent.RIGHT) {
855 		mouseEv.dwtObj._tree._itemActioned(mouseEv.dwtObj, mouseEv);
856 	}
857 
858 	mouseEv._stopPropagation = true;
859 	mouseEv._returnValue = false;
860 	mouseEv.setToDhtmlEvent(ev);
861 	return false;
862 };
863 
864 DwtTreeItem._nodeIconMouseUpHdlr = 
865 function(ev) {
866 	var obj = DwtControl.getTargetControl(ev);
867 	var mouseEv = DwtShell.mouseEvent;
868 	mouseEv._stopPropagation = true;
869 	mouseEv._returnValue = false;
870 	mouseEv.setToDhtmlEvent(ev);
871 	return false;
872 };
873 
874 DwtTreeItem.prototype._expand =
875 function(expand, ev, skipNotify) {
876 	if (expand !== this._expanded) {
877 		if (!expand) {
878 			this._expanded = false;
879 			this._childDiv.style.display = "none";
880 			if (this._nodeCell) {
881 				AjxImg.setImage(this._nodeCell, this._collapseNodeImage);
882 			}
883 			this._tree._itemCollapsed(this, ev, skipNotify);
884 		} else {
885 			// The first thing we need to do is initialize any deferred children so that they
886 			// actually have content
887 			this._realizeDeferredChildren();
888 			this._expanded = true;
889 			if(this._childDiv && this._childDiv.style)
890 				this._childDiv.style.display = "block";
891 			if (this._nodeCell) {
892 				AjxImg.setImage(this._nodeCell, this._expandNodeImage);
893 			}
894 			this._tree._itemExpanded(this, ev, skipNotify);
895 		}	
896 
897 		this.setAttribute('aria-expanded', expand);
898 		this._childDiv.setAttribute('aria-expanded', expand);
899 		this._childDiv.setAttribute('aria-hidden', !expand);
900 	}
901 };
902 
903 DwtTreeItem.prototype._realizeDeferredChildren =
904 function() {
905 	var a = this._children.getArray();
906 	for (var i = 0; i < a.length; i++) {
907 		var treeItem = a[i];
908 		if (!treeItem._initialized) {
909 			treeItem._initialize(treeItem._index, true);
910 		} else if (treeItem._isSeparator && !treeItem.div && this._childDiv) {
911 			// Note: separators marked as initialized on construction
912 			var div = treeItem.div = document.createElement("div");
913 			div.className = "vSpace";
914 			this._childDiv.appendChild(div);
915 			treeItem._initialized = true;
916 		}
917 	}
918 	if (this._needsSort) {
919 		if (a.length) {
920 			this._setChildElOrder();
921 		}
922 		delete this.__needsSort;
923 	}
924 };
925 
926 DwtTreeItem.prototype._isChildOf =
927 function(item) {
928 	var test = this.parent;
929 	while (test && test != this._tree) {
930 		if (test == item)
931 			return true;
932 		test = test.parent;
933 	}
934 	return false;
935 };
936 
937 DwtTreeItem.prototype._setTreeElementStyles =
938 function(img, focused) {
939    if (this._arrowDisabled || this._draghovering) {
940         return;
941    }
942    var selected = focused ? "-focused" : "";
943    if (this._extraCell) {
944         AjxImg.setImage(this._extraCell, img);
945         this._extraCell.className = this._treeItemExtraImgClass + selected;
946    }
947    if (this._textCell)
948         this._textCell.className = this._treeItemTextClass + selected;
949 }
950 
951 DwtTreeItem.prototype._setSelected =
952 function(selected, noFocus) {
953 	if (this._selected != selected && !this._disposed) {
954 		this._selected = selected;
955 		if (!this._initialized) {
956 			this._initialize();
957 		}
958 		if (!this._itemDiv) { return; }
959 
960 		var didSelect;
961 
962 		if (selected && (this._selectionEnabled || this._forceNotifySelection) /*&& this._origClassName == "DwtTreeItem"*/) {
963 			this._itemDiv.className = this._selectedClassName;
964 			this._setTreeElementStyles("DownArrowSmall", true);
965 			this._tree.setAttribute('aria-activedescendant', this.getHTMLElId());
966             if (!noFocus) {
967 				this.focus();
968 			}
969 			didSelect = true;
970 		} else {
971 			this.blur();
972 			this._setTreeElementStyles("Blank_16", false);
973 			this._itemDiv.className = this._origClassName;;
974 			this._tree.removeAttribute('aria-activedescendant');
975 			didSelect = false;
976 		}
977 
978 		this.getHtmlElement().setAttribute('aria-selected', selected);
979 		/* TODO: disable on IE? screenreaders in IE may announce items twice if
980 		 * we do the below, which is not strictly necessary */
981 		var treeEl = this._tree.getHtmlElement();
982 
983 		if (selected) {
984 			treeEl.setAttribute('aria-activedescendant', this.getHTMLElId());
985 		} else {
986 			treeEl.removeAttribute('aria-activedescendant');
987 		}
988 
989 		return didSelect;
990 	}
991 };
992 
993 DwtTreeItem.prototype._setActioned =
994 function(actioned) {
995 	if (this._actioned != actioned) {
996 		this._actioned = actioned;
997 		if (!this._initialized) {
998 			this._initialize();
999 		}
1000 
1001 		if (!this._itemDiv) { return; }
1002 
1003 		if (actioned && (this._actionEnabled || this._forceNotifyAction) && !this._selected) {
1004 			this._itemDiv.className = this._actionedClassName;
1005 			return true;
1006 		}
1007 
1008 		if (!actioned) {
1009 			if (!this._selected) {
1010 				this._itemDiv.className = this._origClassName;
1011 			}
1012 			return false;
1013 		}
1014 	}
1015 };
1016 
1017 DwtTreeItem.prototype._focus =
1018 function() {
1019 	if (!this._itemDiv) { return; }
1020 	// focused tree item should always be selected as well
1021 	this._itemDiv.className = this._selectedFocusedClassName;
1022 	this._setTreeElementStyles("DownArrowSmall", true);
1023 };
1024 
1025 DwtTreeItem.prototype._blur =
1026 function() {
1027 	if (!this._itemDiv) { return; }
1028 	this._itemDiv.className = this._selected
1029 		? this._selectedClassName : this._origClassName;
1030 	this._setTreeElementStyles(this._selected ? "DownArrowSmall" : "Blank_16", this._selected);
1031 };
1032 
1033 DwtTreeItem._mouseDownListener =
1034 function(ev) {
1035 	var treeItem = ev.dwtObj;
1036 	if (!treeItem) { return false; }
1037 	if (ev.target == treeItem._childDiv) { return; }
1038 
1039 	if (ev.button == DwtMouseEvent.LEFT && (treeItem._selectionEnabled || treeItem._forceNotifySelection)) {
1040 		treeItem._gotMouseDownLeft = true;
1041 	} else if (ev.button == DwtMouseEvent.RIGHT && (treeItem._actionEnabled || treeItem._forceNotifyAction)) {
1042 		treeItem._gotMouseDownRight = true;
1043 	}
1044 
1045 };
1046 
1047 DwtTreeItem._mouseOutListener = 
1048 function(ev) {
1049 	var treeItem = ev.dwtObj;
1050 	if (!treeItem) { return false; }
1051 	if (ev.target == treeItem._childDiv) { return; }
1052 
1053 	treeItem._gotMouseDownLeft = false;
1054 	treeItem._gotMouseDownRight = false;
1055 	if (treeItem._singleClickAction && treeItem._textCell) {
1056 		treeItem._textCell.className = treeItem._textClassName;
1057 	}
1058     if(!treeItem._selected){
1059        treeItem._setTreeElementStyles("Blank_16", false);
1060     }
1061 
1062 };
1063 
1064 DwtTreeItem._mouseOverListener =
1065 function(ev) {
1066 	var treeItem = ev.dwtObj;
1067 	if (!treeItem) { return false; }
1068 	if (ev.target == treeItem._childDiv) { return; }
1069 
1070 	if (treeItem._singleClickAction && treeItem._textCell) {
1071 		treeItem._textCell.className = treeItem._hoverClassName;
1072 	}
1073     if(!treeItem._selected){
1074        treeItem._setTreeElementStyles("ColumnDownArrow", true);
1075     }
1076 };
1077 
1078 DwtTreeItem._mouseUpListener = function(ev) {
1079 
1080 	var treeItem = ev.dwtObj;
1081 	if (!treeItem) {
1082         return false;
1083     }
1084 
1085 	// Ignore any mouse events in the child div i.e. the div which 
1086 	// holds all the items children. In the case of IE, no clicks are
1087 	// reported when clicking in the padding area (note all children
1088 	// are indented using padding-left style); however, mozilla
1089 	// reports mouse events that happen in the padding area
1090 	if (ev.target === treeItem._childDiv) {
1091         return;
1092     }
1093 
1094 	//ignore the collapse/expand arrow. This is handled in DwtTreeItem._nodeIconMouseDownHdlr. It should only collapse/expand and never select this item, so no point in going on.
1095 	if (treeItem._expandable && ev.target === AjxImg.getImageElement(treeItem._nodeCell)) {
1096 		return;
1097 	}
1098 
1099     var targetElement = DwtUiEvent.getTargetWithProp(ev, "id"),
1100         isContextCmd = (treeItem._extraCell && targetElement && treeItem._extraCell.id === targetElement.id);
1101 
1102     if ((ev.button === DwtMouseEvent.RIGHT && treeItem._gotMouseDownRight) || isContextCmd) {
1103         treeItem._tree._itemActioned(treeItem, ev);
1104     }
1105     else if (ev.button === DwtMouseEvent.LEFT && treeItem._gotMouseDownLeft) {
1106 		treeItem._tree._itemClicked(treeItem, ev);
1107 	}
1108 };
1109 
1110 DwtTreeItem._doubleClickListener =
1111 function(ev) {
1112 	var treeItem = ev.dwtObj;
1113 	if (!treeItem) { return false; }
1114 	// See comment in DwtTreeItem.prototype._mouseDownListener
1115 	if (ev.target == treeItem._childDiv) { return; }
1116 
1117 	var obj = DwtControl.getTargetControl(ev);
1118 	var mouseEv = DwtShell.mouseEvent;
1119 	mouseEv.setFromDhtmlEvent(ev, obj);
1120 	if (mouseEv.button == DwtMouseEvent.LEFT || mouseEv.button == DwtMouseEvent.NONE) {	// NONE for IE bug
1121 		mouseEv.dwtObj._tree._itemDblClicked(mouseEv.dwtObj, mouseEv);
1122 	}
1123 };
1124 
1125 DwtTreeItem._contextListener =
1126 function(ev) {
1127 	// for Safari, we have to fake a right click
1128 	if (AjxEnv.isSafari) {
1129 		var obj = DwtControl.getTargetControl(ev);
1130 		var prevent = obj ? obj.preventContextMenu() : true;
1131 		if (prevent) {
1132 			obj.notifyListeners(DwtEvent.ONMOUSEDOWN, ev);
1133 			return obj.notifyListeners(DwtEvent.ONMOUSEUP, ev);
1134 		}
1135 	}
1136 };
1137 
1138 DwtTreeItem.prototype._emulateSingleClick =
1139 function(params) {
1140 	var mev = new DwtMouseEvent();
1141 	this._setMouseEvent(mev, params);
1142 	mev.kbNavEvent = params.kbNavEvent;
1143 	this.notifyListeners(DwtEvent.ONMOUSEUP, mev);
1144 };
1145 
1146 DwtTreeItem.prototype.getTooltipBase =
1147 function(hoverEv) {
1148 	return this._itemDiv;
1149 };
1150 
1151 DwtTreeItem._listeners = {};
1152 DwtTreeItem._listeners[DwtEvent.ONMOUSEDOWN] = new AjxListener(null, DwtTreeItem._mouseDownListener);
1153 DwtTreeItem._listeners[DwtEvent.ONMOUSEOUT] = new AjxListener(null, DwtTreeItem._mouseOutListener);
1154 DwtTreeItem._listeners[DwtEvent.ONMOUSELEAVE] = new AjxListener(null, DwtTreeItem._mouseOutListener);
1155 DwtTreeItem._listeners[DwtEvent.ONMOUSEENTER] = new AjxListener(null, DwtTreeItem._mouseOverListener);
1156 DwtTreeItem._listeners[DwtEvent.ONMOUSEOVER] = new AjxListener(null, DwtTreeItem._mouseOverListener);
1157 DwtTreeItem._listeners[DwtEvent.ONMOUSEUP] = new AjxListener(null, DwtTreeItem._mouseUpListener);
1158 DwtTreeItem._listeners[DwtEvent.ONDBLCLICK] = new AjxListener(null, DwtTreeItem._doubleClickListener);
1159 DwtTreeItem._listeners[DwtEvent.ONCONTEXTMENU] = new AjxListener(null, DwtTreeItem._contextListener);
1160 
1161 
1162 /**
1163  * Minimal class for a separator (some vertical space) between other tree items.
1164  * The functions it has are to handle a dispose() call when the containing tree
1165  * is disposed.
1166  * 
1167  * TODO: At some point we should just make this a DwtControl, or find some other
1168  * 		 way of keeping it minimal.
1169  * 
1170  * 
1171  * @private
1172  */
1173 DwtTreeItemSeparator = function(parent) {
1174 	this.parent = parent;
1175 	this._isSeparator = true;
1176 	this._initialized = true;
1177 };
1178 
1179 DwtTreeItemSeparator.prototype.dispose =
1180 function() {
1181 	DwtComposite.prototype.removeChild.call(this.parent, this);
1182 };
1183 
1184 DwtTreeItemSeparator.prototype.isInitialized =
1185 function() {
1186 	return this._initialized;
1187 };
1188 
1189 DwtTreeItemSeparator.prototype.getHtmlElement =
1190 function() {
1191 	return this.div;
1192 };
1193