]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/yui/build/treeview/treeview.js
Release 6.2.0beta4
[Github/sugarcrm.git] / include / javascript / yui / build / treeview / treeview.js
1 /*
2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.8.0r4
6 */
7 (function () {
8     var Dom = YAHOO.util.Dom,
9         Event = YAHOO.util.Event,
10         Lang = YAHOO.lang,
11         Widget = YAHOO.widget;
12         
13     
14
15 /**
16  * The treeview widget is a generic tree building tool.
17  * @module treeview
18  * @title TreeView Widget
19  * @requires yahoo, dom, event
20  * @optional animation, json, calendar
21  * @namespace YAHOO.widget
22  */
23
24 /**
25  * Contains the tree view state data and the root node.
26  *
27  * @class TreeView
28  * @uses YAHOO.util.EventProvider
29  * @constructor
30  * @param {string|HTMLElement} id The id of the element, or the element itself that the tree will be inserted into.  
31  *        Existing markup in this element, if valid, will be used to build the tree
32  * @param {Array|Object|String}  oConfig (optional)  If present, it will be used to build the tree via method <a href="#method_buildTreeFromObject">buildTreeFromObject</a>
33  * 
34  */
35 YAHOO.widget.TreeView = function(id, oConfig) {
36     if (id) { this.init(id); }
37     if (oConfig) {
38         this.buildTreeFromObject(oConfig);
39     } else if (Lang.trim(this._el.innerHTML)) {
40         this.buildTreeFromMarkup(id);
41     }
42 };
43
44 var TV = Widget.TreeView;
45
46 TV.prototype = {
47
48     /**
49      * The id of tree container element
50      * @property id
51      * @type String
52      */
53     id: null,
54
55     /**
56      * The host element for this tree
57      * @property _el
58      * @private
59      * @type HTMLelement
60      */
61     _el: null,
62
63      /**
64      * Flat collection of all nodes in this tree.  This is a sparse
65      * array, so the length property can't be relied upon for a
66      * node count for the tree.
67      * @property _nodes
68      * @type Node[]
69      * @private
70      */
71     _nodes: null,
72
73     /**
74      * We lock the tree control while waiting for the dynamic loader to return
75      * @property locked
76      * @type boolean
77      */
78     locked: false,
79
80     /**
81      * The animation to use for expanding children, if any
82      * @property _expandAnim
83      * @type string
84      * @private
85      */
86     _expandAnim: null,
87
88     /**
89      * The animation to use for collapsing children, if any
90      * @property _collapseAnim
91      * @type string
92      * @private
93      */
94     _collapseAnim: null,
95
96     /**
97      * The current number of animations that are executing
98      * @property _animCount
99      * @type int
100      * @private
101      */
102     _animCount: 0,
103
104     /**
105      * The maximum number of animations to run at one time.
106      * @property maxAnim
107      * @type int
108      */
109     maxAnim: 2,
110
111     /**
112      * Whether there is any subscriber to dblClickEvent
113      * @property _hasDblClickSubscriber
114      * @type boolean
115      * @private
116      */
117     _hasDblClickSubscriber: false,
118     
119     /**
120      * Stores the timer used to check for double clicks
121      * @property _dblClickTimer
122      * @type window.timer object
123      * @private
124      */
125     _dblClickTimer: null,
126
127   /**
128      * A reference to the Node currently having the focus or null if none.
129      * @property currentFocus
130      * @type YAHOO.widget.Node
131      */
132     currentFocus: null,
133     
134     /**
135     * If true, only one Node can be highlighted at a time
136     * @property singleNodeHighlight
137     * @type boolean
138     * @default false
139     */
140     
141     singleNodeHighlight: false,
142     
143     /**
144     * A reference to the Node that is currently highlighted.
145     * It is only meaningful if singleNodeHighlight is enabled
146     * @property _currentlyHighlighted
147     * @type YAHOO.widget.Node
148     * @default null
149     * @private
150     */
151     
152     _currentlyHighlighted: null,
153
154     /**
155      * Sets up the animation for expanding children
156      * @method setExpandAnim
157      * @param {string} type the type of animation (acceptable values defined 
158      * in YAHOO.widget.TVAnim)
159      */
160     setExpandAnim: function(type) {
161         this._expandAnim = (Widget.TVAnim.isValid(type)) ? type : null;
162     },
163
164     /**
165      * Sets up the animation for collapsing children
166      * @method setCollapseAnim
167      * @param {string} type of animation (acceptable values defined in 
168      * YAHOO.widget.TVAnim)
169      */
170     setCollapseAnim: function(type) {
171         this._collapseAnim = (Widget.TVAnim.isValid(type)) ? type : null;
172     },
173
174     /**
175      * Perform the expand animation if configured, or just show the
176      * element if not configured or too many animations are in progress
177      * @method animateExpand
178      * @param el {HTMLElement} the element to animate
179      * @param node {YAHOO.util.Node} the node that was expanded
180      * @return {boolean} true if animation could be invoked, false otherwise
181      */
182     animateExpand: function(el, node) {
183
184         if (this._expandAnim && this._animCount < this.maxAnim) {
185             // this.locked = true;
186             var tree = this;
187             var a = Widget.TVAnim.getAnim(this._expandAnim, el, 
188                             function() { tree.expandComplete(node); });
189             if (a) { 
190                 ++this._animCount;
191                 this.fireEvent("animStart", {
192                         "node": node, 
193                         "type": "expand"
194                     });
195                 a.animate();
196             }
197
198             return true;
199         }
200
201         return false;
202     },
203
204     /**
205      * Perform the collapse animation if configured, or just show the
206      * element if not configured or too many animations are in progress
207      * @method animateCollapse
208      * @param el {HTMLElement} the element to animate
209      * @param node {YAHOO.util.Node} the node that was expanded
210      * @return {boolean} true if animation could be invoked, false otherwise
211      */
212     animateCollapse: function(el, node) {
213
214         if (this._collapseAnim && this._animCount < this.maxAnim) {
215             // this.locked = true;
216             var tree = this;
217             var a = Widget.TVAnim.getAnim(this._collapseAnim, el, 
218                             function() { tree.collapseComplete(node); });
219             if (a) { 
220                 ++this._animCount;
221                 this.fireEvent("animStart", {
222                         "node": node, 
223                         "type": "collapse"
224                     });
225                 a.animate();
226             }
227
228             return true;
229         }
230
231         return false;
232     },
233
234     /**
235      * Function executed when the expand animation completes
236      * @method expandComplete
237      */
238     expandComplete: function(node) {
239         --this._animCount;
240         this.fireEvent("animComplete", {
241                 "node": node, 
242                 "type": "expand"
243             });
244         // this.locked = false;
245     },
246
247     /**
248      * Function executed when the collapse animation completes
249      * @method collapseComplete
250      */
251     collapseComplete: function(node) {
252         --this._animCount;
253         this.fireEvent("animComplete", {
254                 "node": node, 
255                 "type": "collapse"
256             });
257         // this.locked = false;
258     },
259
260     /**
261      * Initializes the tree
262      * @method init
263      * @parm {string|HTMLElement} id the id of the element that will hold the tree
264      * @private
265      */
266     init: function(id) {
267         this._el = Dom.get(id);
268         this.id = Dom.generateId(this._el,"yui-tv-auto-id-");
269
270     /**
271          * When animation is enabled, this event fires when the animation
272          * starts
273          * @event animStart
274          * @type CustomEvent
275          * @param {YAHOO.widget.Node} oArgs.node the node that is expanding/collapsing
276          * @param {String} oArgs.type the type of animation ("expand" or "collapse")
277          */
278         this.createEvent("animStart", this);
279
280         /**
281          * When animation is enabled, this event fires when the animation
282          * completes
283          * @event animComplete
284          * @type CustomEvent
285          * @param {YAHOO.widget.Node} oArgs.node the node that is expanding/collapsing
286          * @param {String} oArgs.type the type of animation ("expand" or "collapse")
287          */
288         this.createEvent("animComplete", this);
289
290         /**
291          * Fires when a node is going to be collapsed.  Return false to stop
292          * the collapse.
293          * @event collapse
294          * @type CustomEvent
295          * @param {YAHOO.widget.Node} node the node that is collapsing
296          */
297         this.createEvent("collapse", this);
298
299         /**
300          * Fires after a node is successfully collapsed.  This event will not fire
301          * if the "collapse" event was cancelled.
302          * @event collapseComplete
303          * @type CustomEvent
304          * @param {YAHOO.widget.Node} node the node that was collapsed
305          */
306         this.createEvent("collapseComplete", this);
307
308         /**
309          * Fires when a node is going to be expanded.  Return false to stop
310          * the collapse.
311          * @event expand
312          * @type CustomEvent
313          * @param {YAHOO.widget.Node} node the node that is expanding
314          */
315         this.createEvent("expand", this);
316
317         /**
318          * Fires after a node is successfully expanded.  This event will not fire
319          * if the "expand" event was cancelled.
320          * @event expandComplete
321          * @type CustomEvent
322          * @param {YAHOO.widget.Node} node the node that was expanded
323          */
324         this.createEvent("expandComplete", this);
325
326     /**
327          * Fires when the Enter key is pressed on a node that has the focus
328          * @event enterKeyPressed
329          * @type CustomEvent
330          * @param {YAHOO.widget.Node} node the node that has the focus
331          */
332         this.createEvent("enterKeyPressed", this);
333         
334     /**
335          * Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a Click.
336     * The listener may return false to cancel toggling and focusing on the node.
337          * @event clickEvent
338          * @type CustomEvent
339          * @param oArgs.event  {HTMLEvent} The event object
340          * @param oArgs.node {YAHOO.widget.Node} node the node that was clicked
341          */
342         this.createEvent("clickEvent", this);
343         
344     /**
345          * Fires when the focus receives the focus, when it changes from a Node 
346     * to another Node or when it is completely lost (blurred)
347          * @event focusChanged
348          * @type CustomEvent
349          * @param oArgs.oldNode  {YAHOO.widget.Node} Node that had the focus or null if none
350          * @param oArgs.newNode {YAHOO.widget.Node} Node that receives the focus or null if none
351          */
352         
353         this.createEvent('focusChanged',this);
354
355     /**
356          * Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a double Click
357          * @event dblClickEvent
358          * @type CustomEvent
359          * @param oArgs.event  {HTMLEvent} The event object
360          * @param oArgs.node {YAHOO.widget.Node} node the node that was clicked
361          */
362         var self = this;
363         this.createEvent("dblClickEvent", {
364             scope:this,
365             onSubscribeCallback: function() {
366                 self._hasDblClickSubscriber = true;
367             }
368         });
369         
370     /**
371          * Custom event that is fired when the text node label is clicked. 
372          *  The node clicked is  provided as an argument
373          *
374          * @event labelClick
375          * @type CustomEvent
376          * @param {YAHOO.widget.Node} node the node clicked
377     * @deprecated use clickEvent or dblClickEvent
378          */
379         this.createEvent("labelClick", this);
380         
381     /**
382      * Custom event fired when the highlight of a node changes.
383      * The node that triggered the change is provided as an argument:
384      * The status of the highlight can be checked in 
385      * <a href="YAHOO.widget.Node.html#property_highlightState">nodeRef.highlightState</a>.
386      * Depending on <a href="YAHOO.widget.Node.html#property_propagateHighlight">nodeRef.propagateHighlight</a>, other nodes might have changed
387      * @event highlightEvent
388      * @type CustomEvent
389      * @param node {YAHOO.widget.Node} the node that started the change in highlighting state
390     */
391         this.createEvent("highlightEvent",this);
392      
393
394
395         this._nodes = [];
396
397         // store a global reference
398         TV.trees[this.id] = this;
399
400         // Set up the root node
401         this.root = new Widget.RootNode(this);
402
403         var LW = Widget.LogWriter;
404
405
406                 
407                 if (this._initEditor) {
408                         this._initEditor();
409                 }
410         
411         // YAHOO.util.Event.onContentReady(this.id, this.handleAvailable, this, true);
412         // YAHOO.util.Event.on(this.id, "click", this.handleClick, this, true);
413     },
414
415     //handleAvailable: function() {
416         //var Event = YAHOO.util.Event;
417         //Event.on(this.id, 
418     //},
419  /**
420      * Builds the TreeView from an object.  
421      * This is the method called by the constructor to build the tree when it has a second argument.
422      *  A tree can be described by an array of objects, each object corresponding to a node.
423      *  Node descriptions may contain values for any property of a node plus the following extra properties: <ul>
424      * <li>type:  can be one of the following:<ul>
425      *    <li> A shortname for a node type (<code>'text','menu','html'</code>) </li>
426      *    <li>The name of a Node class under YAHOO.widget (<code>'TextNode', 'MenuNode', 'DateNode'</code>, etc) </li>
427      *    <li>a reference to an actual class: <code>YAHOO.widget.DateNode</code></li>
428          * </ul></li>
429      * <li>children: an array containing further node definitions</li></ul>
430          * A string instead of an object will produce a node of type 'text' with the given string as its label.
431      * @method buildTreeFromObject
432      * @param  oConfig {Array|Object|String}  array containing a full description of the tree.
433      *        An object or a string will be turned into an array with the given object or string as its only element.
434      * 
435      */
436     buildTreeFromObject: function (oConfig) {
437         var build = function (parent, oConfig) {
438             var i, item, node, children, type, NodeType, ThisType;
439             for (i = 0; i < oConfig.length; i++) {
440                 item = oConfig[i];
441                 if (Lang.isString(item)) {
442                     node = new Widget.TextNode(item, parent);
443                 } else if (Lang.isObject(item)) {
444                     children = item.children;
445                     delete item.children;
446                     type = item.type || 'text';
447                     delete item.type;
448                     switch (Lang.isString(type) && type.toLowerCase()) {
449                         case 'text':
450                             node = new Widget.TextNode(item, parent);
451                             break;
452                         case 'menu':
453                             node = new Widget.MenuNode(item, parent);
454                             break;
455                         case 'html':
456                             node = new Widget.HTMLNode(item, parent);
457                             break;
458                         default:
459                             if (Lang.isString(type)) {
460                                 NodeType = Widget[type];
461                             } else {
462                                 NodeType = type;
463                             }
464                             if (Lang.isObject(NodeType)) {
465                                 for (ThisType = NodeType; ThisType && ThisType !== Widget.Node; ThisType = ThisType.superclass.constructor) {}
466                                 if (ThisType) {
467                                     node = new NodeType(item, parent);
468                                 } else {
469                                 }
470                             } else {
471                             }
472                     }
473                     if (children) {
474                         build(node,children);
475                     }
476                 } else {
477                 }
478             }
479         };
480         if (!Lang.isArray(oConfig)) {
481             oConfig = [oConfig];
482         }
483
484                     
485         build(this.root,oConfig);
486     },
487 /**
488      * Builds the TreeView from existing markup.   Markup should consist of &lt;UL&gt; or &lt;OL&gt; elements containing &lt;LI&gt; elements.  
489      * Each &lt;LI&gt; can have one element used as label and a second optional element which is to be a &lt;UL&gt; or &lt;OL&gt;
490      * containing nested nodes.
491      * Depending on what the first element of the &lt;LI&gt; element is, the following Nodes will be created: <ul>
492      *           <li>plain text:  a regular TextNode</li>
493      *           <li>anchor &lt;A&gt;: a TextNode with its <code>href</code> and <code>target</code> taken from the anchor</li>
494      *           <li>anything else: an HTMLNode</li></ul>
495      * Only the first  outermost (un-)ordered list in the markup and its children will be parsed.
496      * Nodes will be collapsed unless  an  &lt;LI&gt;  tag has a className called 'expanded'.
497      * All other className attributes will be copied over to the Node className property.
498      * If the &lt;LI&gt; element contains an attribute called <code>yuiConfig</code>, its contents should be a JSON-encoded object
499      * as the one used in method <a href="#method_buildTreeFromObject">buildTreeFromObject</a>.
500      * @method buildTreeFromMarkup
501      * @param  id {string|HTMLElement} The id of the element that contains the markup or a reference to it.
502      */
503     buildTreeFromMarkup: function (id) {
504         var build = function (markup) {
505             var el, child, branch = [], config = {}, label, yuiConfig;
506             // Dom's getFirstChild and getNextSibling skip over text elements
507             for (el = Dom.getFirstChild(markup); el; el = Dom.getNextSibling(el)) {
508                 switch (el.tagName.toUpperCase()) {
509                     case 'LI':
510                         label = '';
511                         config = {
512                             expanded: Dom.hasClass(el,'expanded'),
513                             title: el.title || el.alt || null,
514                             className: Lang.trim(el.className.replace(/\bexpanded\b/,'')) || null
515                         };
516                         // I cannot skip over text elements here because I want them for labels
517                         child = el.firstChild;
518                         if (child.nodeType == 3) {
519                             // nodes with only whitespace, tabs and new lines don't count, they are probably just formatting.
520                             label = Lang.trim(child.nodeValue.replace(/[\n\t\r]*/g,''));
521                             if (label) {
522                                 config.type = 'text';
523                                 config.label = label;
524                             } else {
525                                 child = Dom.getNextSibling(child);
526                             }
527                         }
528                         if (!label) {
529                             if (child.tagName.toUpperCase() == 'A') {
530                                 config.type = 'text';
531                                 config.label = child.innerHTML;
532                                 config.href = child.href;
533                                 config.target = child.target;
534                                 config.title = child.title || child.alt || config.title;
535                             } else {
536                                 config.type = 'html';
537                                 var d = document.createElement('div');
538                                 d.appendChild(child.cloneNode(true));
539                                 config.html = d.innerHTML;
540                                 config.hasIcon = true;
541                             }
542                         }
543                         // see if after the label it has a further list which will become children of this node.
544                         child = Dom.getNextSibling(child);
545                         switch (child && child.tagName.toUpperCase()) {
546                             case 'UL':
547                             case 'OL':
548                                 config.children = build(child);
549                                 break;
550                         }
551                         // if there are further elements or text, it will be ignored.
552                         
553                         if (YAHOO.lang.JSON) {
554                             yuiConfig = el.getAttribute('yuiConfig');
555                             if (yuiConfig) {
556                                 yuiConfig = YAHOO.lang.JSON.parse(yuiConfig);
557                                 config = YAHOO.lang.merge(config,yuiConfig);
558                             }
559                         }
560                         
561                         branch.push(config);
562                         break;
563                     case 'UL':
564                     case 'OL':
565                         config = {
566                             type: 'text',
567                             label: '',
568                             children: build(child)
569                         };
570                         branch.push(config);
571                         break;
572                 }
573             }
574             return branch;
575         };
576
577         var markup = Dom.getChildrenBy(Dom.get(id),function (el) { 
578             var tag = el.tagName.toUpperCase();
579             return  tag == 'UL' || tag == 'OL';
580         });
581         if (markup.length) {
582             this.buildTreeFromObject(build(markup[0]));
583         } else {
584         }
585     },
586   /**
587      * Returns the TD element where the event has occurred
588      * @method _getEventTargetTdEl
589      * @private
590      */
591     _getEventTargetTdEl: function (ev) {
592         var target = Event.getTarget(ev); 
593         // go up looking for a TD with a className with a ygtv prefix
594         while (target && !(target.tagName.toUpperCase() == 'TD' && Dom.hasClass(target.parentNode,'ygtvrow'))) { 
595             target = Dom.getAncestorByTagName(target,'td'); 
596         }
597         if (Lang.isNull(target)) { return null; }
598         // If it is a spacer cell, do nothing
599         if (/\bygtv(blank)?depthcell/.test(target.className)) { return null;}
600         // If it has an id, search for the node number and see if it belongs to a node in this tree.
601         if (target.id) {
602             var m = target.id.match(/\bygtv([^\d]*)(.*)/);
603             if (m && m[2] && this._nodes[m[2]]) {
604                 return target;
605             }
606         }
607         return null;
608     },
609   /**
610      * Event listener for click events
611      * @method _onClickEvent
612      * @private
613      */
614     _onClickEvent: function (ev) {
615         var self = this,
616             td = this._getEventTargetTdEl(ev),
617             node,
618             target,
619             toggle = function (force) {
620                 node.focus();
621                                 if (force || !node.href) {
622                                         node.toggle();
623                                         try {
624                                                 Event.preventDefault(ev);
625                                         } catch (e) {
626                             // @TODO
627                             // For some reason IE8 is providing an event object with
628                             // most of the fields missing, but only when clicking on
629                             // the node's label, and only when working with inline
630                             // editing.  This generates a "Member not found" error
631                             // in that browser.  Determine if this is a browser
632                             // bug, or a problem with this code.  Already checked to
633                             // see if the problem has to do with access the event
634                             // in the outer scope, and that isn't the problem.
635                             // Maybe the markup for inline editing is broken.
636                                         }
637                 }
638             };
639
640         if (!td) {
641             return; 
642         }
643
644         node = this.getNodeByElement(td);
645         if (!node) { 
646             return; 
647         }
648         
649         // exception to handle deprecated event labelClick
650         // @TODO take another look at this deprecation.  It is common for people to
651         // only be interested in the label click, so why make them have to test
652         // the node type to figure out whether the click was on the label?
653         target = Event.getTarget(ev);
654         if (Dom.hasClass(target, node.labelStyle) || Dom.getAncestorByClassName(target,node.labelStyle)) {
655             this.fireEvent('labelClick',node);
656         }
657         
658         //  If it is a toggle cell, toggle
659         if (/\bygtv[tl][mp]h?h?/.test(td.className)) {
660             toggle(true);
661         } else {
662             if (this._dblClickTimer) {
663                 window.clearTimeout(this._dblClickTimer);
664                 this._dblClickTimer = null;
665             } else {
666                 if (this._hasDblClickSubscriber) {
667                     this._dblClickTimer = window.setTimeout(function () {
668                         self._dblClickTimer = null;
669                         if (self.fireEvent('clickEvent', {event:ev,node:node}) !== false) { 
670                             toggle();
671                         }
672                     }, 200);
673                 } else {
674                     if (self.fireEvent('clickEvent', {event:ev,node:node}) !== false) { 
675                         toggle();
676                     }
677                 }
678             }
679         }
680     },
681
682   /**
683      * Event listener for double-click events
684      * @method _onDblClickEvent
685      * @private
686      */
687     _onDblClickEvent: function (ev) {
688         if (!this._hasDblClickSubscriber) { return; }
689         var td = this._getEventTargetTdEl(ev);
690         if (!td) {return;}
691
692         if (!(/\bygtv[tl][mp]h?h?/.test(td.className))) {
693             this.fireEvent('dblClickEvent', {event:ev, node:this.getNodeByElement(td)}); 
694             if (this._dblClickTimer) {
695                 window.clearTimeout(this._dblClickTimer);
696                 this._dblClickTimer = null;
697             }
698         }
699     },
700   /**
701      * Event listener for mouse over events
702      * @method _onMouseOverEvent
703      * @private
704      */
705     _onMouseOverEvent:function (ev) {
706         var target;
707         if ((target = this._getEventTargetTdEl(ev)) && (target = this.getNodeByElement(target)) && (target = target.getToggleEl())) {
708             target.className = target.className.replace(/\bygtv([lt])([mp])\b/gi,'ygtv$1$2h');
709         }
710     },
711   /**
712      * Event listener for mouse out events
713      * @method _onMouseOutEvent
714      * @private
715      */
716     _onMouseOutEvent: function (ev) {
717         var target;
718         if ((target = this._getEventTargetTdEl(ev)) && (target = this.getNodeByElement(target)) && (target = target.getToggleEl())) {
719             target.className = target.className.replace(/\bygtv([lt])([mp])h\b/gi,'ygtv$1$2');
720         }
721     },
722   /**
723      * Event listener for key down events
724      * @method _onKeyDownEvent
725      * @private
726      */
727     _onKeyDownEvent: function (ev) {
728         var target = Event.getTarget(ev),
729             node = this.getNodeByElement(target),
730             newNode = node,
731             KEY = YAHOO.util.KeyListener.KEY;
732
733         switch(ev.keyCode) {
734             case KEY.UP:
735                 do {
736                     if (newNode.previousSibling) {
737                         newNode = newNode.previousSibling;
738                     } else {
739                         newNode = newNode.parent;
740                     }
741                 } while (newNode && !newNode._canHaveFocus());
742                 if (newNode) { newNode.focus(); }
743                 Event.preventDefault(ev);
744                 break;
745             case KEY.DOWN:
746                 do {
747                     if (newNode.nextSibling) {
748                         newNode = newNode.nextSibling;
749                     } else {
750                         newNode.expand();
751                         newNode = (newNode.children.length || null) && newNode.children[0];
752                     }
753                 } while (newNode && !newNode._canHaveFocus);
754                 if (newNode) { newNode.focus();}
755                 Event.preventDefault(ev);
756                 break;
757             case KEY.LEFT:
758                 do {
759                     if (newNode.parent) {
760                         newNode = newNode.parent;
761                     } else {
762                         newNode = newNode.previousSibling;
763                     }
764                 } while (newNode && !newNode._canHaveFocus());
765                 if (newNode) { newNode.focus();}
766                 Event.preventDefault(ev);
767                 break;
768                         case KEY.RIGHT:
769                                 var self = this,
770                                         moveFocusRight,
771                                         focusOnExpand = function (newNode) {
772                                                 self.unsubscribe('expandComplete',focusOnExpand);
773                                                 moveFocusRight(newNode);
774                                         };
775                                 moveFocusRight = function (newNode) {
776                                         do {
777                                                 if (newNode.isDynamic() && !newNode.childrenRendered) {
778                                                         self.subscribe('expandComplete',focusOnExpand);
779                                                         newNode.expand();
780                                                         newNode = null;
781                                                         break;
782                                                 } else {
783                                                         newNode.expand();
784                                                         if (newNode.children.length) {
785                                                                 newNode = newNode.children[0];
786                                                         } else {
787                                                                 newNode = newNode.nextSibling;
788                                                         }
789                                                 }
790                                         } while (newNode && !newNode._canHaveFocus());
791                                         if (newNode) { newNode.focus();}
792                                 };
793                                         
794                                 moveFocusRight(newNode);
795                                 Event.preventDefault(ev);
796                                 break;
797             case KEY.ENTER:
798                 if (node.href) {
799                     if (node.target) {
800                         window.open(node.href,node.target);
801                     } else {
802                         window.location(node.href);
803                     }
804                 } else {
805                     node.toggle();
806                 }
807                 this.fireEvent('enterKeyPressed',node);
808                 Event.preventDefault(ev);
809                 break;
810             case KEY.HOME:
811                 newNode = this.getRoot();
812                 if (newNode.children.length) {newNode = newNode.children[0];}
813                 if (newNode._canHaveFocus()) { newNode.focus(); }
814                 Event.preventDefault(ev);
815                 break;
816             case KEY.END:
817                 newNode = newNode.parent.children;
818                 newNode = newNode[newNode.length -1];
819                 if (newNode._canHaveFocus()) { newNode.focus(); }
820                 Event.preventDefault(ev);
821                 break;
822             // case KEY.PAGE_UP:
823                 // break;
824             // case KEY.PAGE_DOWN:
825                 // break;
826             case 107:  // plus key
827                 if (ev.shiftKey) {
828                     node.parent.expandAll();
829                 } else {
830                     node.expand();
831                 }
832                 break;
833             case 109: // minus key
834                 if (ev.shiftKey) {
835                     node.parent.collapseAll();
836                 } else {
837                     node.collapse();
838                 }
839                 break;
840             default:
841                 break;
842         }
843     },
844     /**
845      * Renders the tree boilerplate and visible nodes
846      * @method render
847      */
848     render: function() {
849         var html = this.root.getHtml(),
850             el = this.getEl();
851         el.innerHTML = html;
852         if (!this._hasEvents) {
853             Event.on(el, 'click', this._onClickEvent, this, true);
854             Event.on(el, 'dblclick', this._onDblClickEvent, this, true);
855             Event.on(el, 'mouseover', this._onMouseOverEvent, this, true);
856             Event.on(el, 'mouseout', this._onMouseOutEvent, this, true);
857             Event.on(el, 'keydown', this._onKeyDownEvent, this, true);
858         }
859         this._hasEvents = true;
860     },
861     
862   /**
863      * Returns the tree's host element
864      * @method getEl
865      * @return {HTMLElement} the host element
866      */
867     getEl: function() {
868         if (! this._el) {
869             this._el = Dom.get(this.id);
870         }
871         return this._el;
872     },
873
874     /**
875      * Nodes register themselves with the tree instance when they are created.
876      * @method regNode
877      * @param node {Node} the node to register
878      * @private
879      */
880     regNode: function(node) {
881         this._nodes[node.index] = node;
882     },
883
884     /**
885      * Returns the root node of this tree
886      * @method getRoot
887      * @return {Node} the root node
888      */
889     getRoot: function() {
890         return this.root;
891     },
892
893     /**
894      * Configures this tree to dynamically load all child data
895      * @method setDynamicLoad
896      * @param {function} fnDataLoader the function that will be called to get the data
897      * @param iconMode {int} configures the icon that is displayed when a dynamic
898      * load node is expanded the first time without children.  By default, the 
899      * "collapse" icon will be used.  If set to 1, the leaf node icon will be
900      * displayed.
901      */
902     setDynamicLoad: function(fnDataLoader, iconMode) { 
903         this.root.setDynamicLoad(fnDataLoader, iconMode);
904     },
905
906     /**
907      * Expands all child nodes.  Note: this conflicts with the "multiExpand"
908      * node property.  If expand all is called in a tree with nodes that
909      * do not allow multiple siblings to be displayed, only the last sibling
910      * will be expanded.
911      * @method expandAll
912      */
913     expandAll: function() { 
914         if (!this.locked) {
915             this.root.expandAll(); 
916         }
917     },
918
919     /**
920      * Collapses all expanded child nodes in the entire tree.
921      * @method collapseAll
922      */
923     collapseAll: function() { 
924         if (!this.locked) {
925             this.root.collapseAll(); 
926         }
927     },
928
929     /**
930      * Returns a node in the tree that has the specified index (this index
931      * is created internally, so this function probably will only be used
932      * in html generated for a given node.)
933      * @method getNodeByIndex
934      * @param {int} nodeIndex the index of the node wanted
935      * @return {Node} the node with index=nodeIndex, null if no match
936      */
937     getNodeByIndex: function(nodeIndex) {
938         var n = this._nodes[nodeIndex];
939         return (n) ? n : null;
940     },
941
942     /**
943      * Returns a node that has a matching property and value in the data
944      * object that was passed into its constructor.
945      * @method getNodeByProperty
946      * @param {object} property the property to search (usually a string)
947      * @param {object} value the value we want to find (usuall an int or string)
948      * @return {Node} the matching node, null if no match
949      */
950     getNodeByProperty: function(property, value) {
951         for (var i in this._nodes) {
952             if (this._nodes.hasOwnProperty(i)) {
953                 var n = this._nodes[i];
954                 if ((property in n && n[property] == value) || (n.data && value == n.data[property])) {
955                     return n;
956                 }
957             }
958         }
959
960         return null;
961     },
962
963     /**
964      * Returns a collection of nodes that have a matching property 
965      * and value in the data object that was passed into its constructor.  
966      * @method getNodesByProperty
967      * @param {object} property the property to search (usually a string)
968      * @param {object} value the value we want to find (usuall an int or string)
969      * @return {Array} the matching collection of nodes, null if no match
970      */
971     getNodesByProperty: function(property, value) {
972         var values = [];
973         for (var i in this._nodes) {
974             if (this._nodes.hasOwnProperty(i)) {
975                 var n = this._nodes[i];
976                 if ((property in n && n[property] == value) || (n.data && value == n.data[property])) {
977                     values.push(n);
978                 }
979             }
980         }
981
982         return (values.length) ? values : null;
983     },
984
985
986     /**
987      * Returns a collection of nodes that have passed the test function
988          * passed as its only argument.  
989          * The function will receive a reference to each node to be tested.  
990      * @method getNodesBy
991      * @param {function} a boolean function that receives a Node instance and returns true to add the node to the results list
992      * @return {Array} the matching collection of nodes, null if no match
993      */
994     getNodesBy: function(fn) {
995         var values = [];
996         for (var i in this._nodes) {
997             if (this._nodes.hasOwnProperty(i)) {
998                 var n = this._nodes[i];
999                 if (fn(n)) {
1000                     values.push(n);
1001                 }
1002             }
1003         }
1004         return (values.length) ? values : null;
1005     },
1006     /**
1007      * Returns the treeview node reference for an ancestor element
1008      * of the node, or null if it is not contained within any node
1009      * in this tree.
1010      * @method getNodeByElement
1011      * @param el {HTMLElement} the element to test
1012      * @return {YAHOO.widget.Node} a node reference or null
1013      */
1014     getNodeByElement: function(el) {
1015
1016         var p=el, m, re=/ygtv([^\d]*)(.*)/;
1017
1018         do {
1019
1020             if (p && p.id) {
1021                 m = p.id.match(re);
1022                 if (m && m[2]) {
1023                     return this.getNodeByIndex(m[2]);
1024                 }
1025             }
1026
1027             p = p.parentNode;
1028
1029             if (!p || !p.tagName) {
1030                 break;
1031             }
1032
1033         } 
1034         while (p.id !== this.id && p.tagName.toLowerCase() !== "body");
1035
1036         return null;
1037     },
1038         
1039     /**
1040      * When in singleNodeHighlight it returns the node highlighted
1041          * or null if none.  Returns null if singleNodeHighlight is false.
1042      * @method getHighlightedNode
1043      * @return {YAHOO.widget.Node} a node reference or null
1044      */
1045         getHighlightedNode: function() {
1046                 return this._currentlyHighlighted;
1047         },
1048
1049
1050     /**
1051      * Removes the node and its children, and optionally refreshes the 
1052      * branch of the tree that was affected.
1053      * @method removeNode
1054      * @param {Node} node to remove
1055      * @param {boolean} autoRefresh automatically refreshes branch if true
1056      * @return {boolean} False is there was a problem, true otherwise.
1057      */
1058     removeNode: function(node, autoRefresh) { 
1059
1060         // Don't delete the root node
1061         if (node.isRoot()) {
1062             return false;
1063         }
1064
1065         // Get the branch that we may need to refresh
1066         var p = node.parent;
1067         if (p.parent) {
1068             p = p.parent;
1069         }
1070
1071         // Delete the node and its children
1072         this._deleteNode(node);
1073
1074         // Refresh the parent of the parent
1075         if (autoRefresh && p && p.childrenRendered) {
1076             p.refresh();
1077         }
1078
1079         return true;
1080     },
1081
1082     /**
1083      * wait until the animation is complete before deleting 
1084      * to avoid javascript errors
1085      * @method _removeChildren_animComplete
1086      * @param o the custom event payload
1087      * @private
1088      */
1089     _removeChildren_animComplete: function(o) {
1090         this.unsubscribe(this._removeChildren_animComplete);
1091         this.removeChildren(o.node);
1092     },
1093
1094     /**
1095      * Deletes this nodes child collection, recursively.  Also collapses
1096      * the node, and resets the dynamic load flag.  The primary use for
1097      * this method is to purge a node and allow it to fetch its data
1098      * dynamically again.
1099      * @method removeChildren
1100      * @param {Node} node the node to purge
1101      */
1102     removeChildren: function(node) { 
1103
1104         if (node.expanded) {
1105             // wait until the animation is complete before deleting to
1106             // avoid javascript errors
1107             if (this._collapseAnim) {
1108                 this.subscribe("animComplete", 
1109                         this._removeChildren_animComplete, this, true);
1110                 Widget.Node.prototype.collapse.call(node);
1111                 return;
1112             }
1113
1114             node.collapse();
1115         }
1116
1117         while (node.children.length) {
1118             this._deleteNode(node.children[0]);
1119         }
1120
1121         if (node.isRoot()) {
1122             Widget.Node.prototype.expand.call(node);
1123         }
1124
1125         node.childrenRendered = false;
1126         node.dynamicLoadComplete = false;
1127
1128         node.updateIcon();
1129     },
1130
1131     /**
1132      * Deletes the node and recurses children
1133      * @method _deleteNode
1134      * @private
1135      */
1136     _deleteNode: function(node) { 
1137         // Remove all the child nodes first
1138         this.removeChildren(node);
1139
1140         // Remove the node from the tree
1141         this.popNode(node);
1142     },
1143
1144     /**
1145      * Removes the node from the tree, preserving the child collection 
1146      * to make it possible to insert the branch into another part of the 
1147      * tree, or another tree.
1148      * @method popNode
1149      * @param {Node} node to remove
1150      */
1151     popNode: function(node) { 
1152         var p = node.parent;
1153
1154         // Update the parent's collection of children
1155         var a = [];
1156
1157         for (var i=0, len=p.children.length;i<len;++i) {
1158             if (p.children[i] != node) {
1159                 a[a.length] = p.children[i];
1160             }
1161         }
1162
1163         p.children = a;
1164
1165         // reset the childrenRendered flag for the parent
1166         p.childrenRendered = false;
1167
1168          // Update the sibling relationship
1169         if (node.previousSibling) {
1170             node.previousSibling.nextSibling = node.nextSibling;
1171         }
1172
1173         if (node.nextSibling) {
1174             node.nextSibling.previousSibling = node.previousSibling;
1175         }
1176
1177                 if (this.currentFocus == node) {
1178                         this.currentFocus = null;
1179                 }
1180                 if (this._currentlyHighlighted == node) {
1181                         this._currentlyHighlighted = null;
1182                 }
1183
1184         node.parent = null;
1185         node.previousSibling = null;
1186         node.nextSibling = null;
1187         node.tree = null;
1188
1189         // Update the tree's node collection 
1190         delete this._nodes[node.index];
1191     },
1192
1193     /**
1194     * Nulls out the entire TreeView instance and related objects, removes attached
1195     * event listeners, and clears out DOM elements inside the container. After
1196     * calling this method, the instance reference should be expliclitly nulled by
1197     * implementer, as in myDataTable = null. Use with caution!
1198     *
1199     * @method destroy
1200     */
1201     destroy : function() {
1202         // Since the label editor can be separated from the main TreeView control
1203         // the destroy method for it might not be there.
1204         if (this._destroyEditor) { this._destroyEditor(); }
1205         var el = this.getEl();
1206         Event.removeListener(el,'click');
1207         Event.removeListener(el,'dblclick');
1208         Event.removeListener(el,'mouseover');
1209         Event.removeListener(el,'mouseout');
1210         Event.removeListener(el,'keydown');
1211         for (var i = 0 ; i < this._nodes.length; i++) {
1212             var node = this._nodes[i];
1213             if (node && node.destroy) {node.destroy(); }
1214         }
1215         el.innerHTML = '';
1216         this._hasEvents = false;
1217     },
1218         
1219             
1220
1221
1222     /**
1223      * TreeView instance toString
1224      * @method toString
1225      * @return {string} string representation of the tree
1226      */
1227     toString: function() {
1228         return "TreeView " + this.id;
1229     },
1230
1231     /**
1232      * Count of nodes in tree
1233      * @method getNodeCount
1234      * @return {int} number of nodes in the tree
1235      */
1236     getNodeCount: function() {
1237         return this.getRoot().getNodeCount();
1238     },
1239
1240     /**
1241      * Returns an object which could be used to rebuild the tree.
1242      * It can be passed to the tree constructor to reproduce the same tree.
1243      * It will return false if any node loads dynamically, regardless of whether it is loaded or not.
1244      * @method getTreeDefinition
1245      * @return {Object | false}  definition of the tree or false if any node is defined as dynamic
1246      */
1247     getTreeDefinition: function() {
1248         return this.getRoot().getNodeDefinition();
1249     },
1250
1251     /**
1252      * Abstract method that is executed when a node is expanded
1253      * @method onExpand
1254      * @param node {Node} the node that was expanded
1255      * @deprecated use treeobj.subscribe("expand") instead
1256      */
1257     onExpand: function(node) { },
1258
1259     /**
1260      * Abstract method that is executed when a node is collapsed.
1261      * @method onCollapse
1262      * @param node {Node} the node that was collapsed.
1263      * @deprecated use treeobj.subscribe("collapse") instead
1264      */
1265     onCollapse: function(node) { },
1266     
1267     /**
1268     * Sets the value of a property for all loaded nodes in the tree.
1269     * @method setNodesProperty
1270     * @param name {string} Name of the property to be set
1271     * @param value {any} value to be set
1272     * @param refresh {boolean} if present and true, it does a refresh
1273     */
1274     setNodesProperty: function(name, value, refresh) {
1275         this.root.setNodesProperty(name,value);
1276         if (refresh) {
1277             this.root.refresh();
1278         }
1279     },
1280     /**
1281     * Event listener to toggle node highlight.
1282     * Can be assigned as listener to clickEvent, dblClickEvent and enterKeyPressed.
1283     * It returns false to prevent the default action.
1284     * @method onEventToggleHighlight
1285     * @param oArgs {any} it takes the arguments of any of the events mentioned above
1286     * @return {false} Always cancels the default action for the event
1287     */
1288     onEventToggleHighlight: function (oArgs) {
1289         var node;
1290         if ('node' in oArgs && oArgs.node instanceof Widget.Node) {
1291             node = oArgs.node;
1292         } else if (oArgs instanceof Widget.Node) {
1293             node = oArgs;
1294         } else {
1295             return false;
1296         }
1297         node.toggleHighlight();
1298         return false;
1299     }
1300         
1301
1302 };
1303
1304 /* Backwards compatibility aliases */
1305 var PROT = TV.prototype;
1306  /**
1307      * Renders the tree boilerplate and visible nodes.
1308      *  Alias for render
1309      * @method draw
1310      * @deprecated Use render instead
1311      */
1312 PROT.draw = PROT.render;
1313
1314 /* end backwards compatibility aliases */
1315
1316 YAHOO.augment(TV, YAHOO.util.EventProvider);
1317
1318 /**
1319  * Running count of all nodes created in all trees.  This is 
1320  * used to provide unique identifies for all nodes.  Deleting
1321  * nodes does not change the nodeCount.
1322  * @property YAHOO.widget.TreeView.nodeCount
1323  * @type int
1324  * @static
1325  */
1326 TV.nodeCount = 0;
1327
1328 /**
1329  * Global cache of tree instances
1330  * @property YAHOO.widget.TreeView.trees
1331  * @type Array
1332  * @static
1333  * @private
1334  */
1335 TV.trees = [];
1336
1337 /**
1338  * Global method for getting a tree by its id.  Used in the generated
1339  * tree html.
1340  * @method YAHOO.widget.TreeView.getTree
1341  * @param treeId {String} the id of the tree instance
1342  * @return {TreeView} the tree instance requested, null if not found.
1343  * @static
1344  */
1345 TV.getTree = function(treeId) {
1346     var t = TV.trees[treeId];
1347     return (t) ? t : null;
1348 };
1349
1350
1351 /**
1352  * Global method for getting a node by its id.  Used in the generated
1353  * tree html.
1354  * @method YAHOO.widget.TreeView.getNode
1355  * @param treeId {String} the id of the tree instance
1356  * @param nodeIndex {String} the index of the node to return
1357  * @return {Node} the node instance requested, null if not found
1358  * @static
1359  */
1360 TV.getNode = function(treeId, nodeIndex) {
1361     var t = TV.getTree(treeId);
1362     return (t) ? t.getNodeByIndex(nodeIndex) : null;
1363 };
1364
1365
1366 /**
1367      * Class name assigned to elements that have the focus
1368      *
1369      * @property TreeView.FOCUS_CLASS_NAME
1370      * @type String
1371      * @static
1372      * @final
1373      * @default "ygtvfocus"
1374
1375     */ 
1376 TV.FOCUS_CLASS_NAME = 'ygtvfocus';
1377
1378
1379
1380 })();
1381
1382 (function () {
1383     var Dom = YAHOO.util.Dom,
1384         Lang = YAHOO.lang,
1385         Event = YAHOO.util.Event;
1386 /**
1387  * The base class for all tree nodes.  The node's presentation and behavior in
1388  * response to mouse events is handled in Node subclasses.
1389  * @namespace YAHOO.widget
1390  * @class Node
1391  * @uses YAHOO.util.EventProvider
1392  * @param oData {object} a string or object containing the data that will
1393  * be used to render this node, and any custom attributes that should be
1394  * stored with the node (which is available in noderef.data).
1395  * All values in oData will be used to set equally named properties in the node
1396  * as long as the node does have such properties, they are not undefined, private or functions,
1397  * the rest of the values will be stored in noderef.data
1398  * @param oParent {Node} this node's parent node
1399  * @param expanded {boolean} the initial expanded/collapsed state (deprecated, use oData.expanded)
1400  * @constructor
1401  */
1402 YAHOO.widget.Node = function(oData, oParent, expanded) {
1403     if (oData) { this.init(oData, oParent, expanded); }
1404 };
1405
1406 YAHOO.widget.Node.prototype = {
1407
1408     /**
1409      * The index for this instance obtained from global counter in YAHOO.widget.TreeView.
1410      * @property index
1411      * @type int
1412      */
1413     index: 0,
1414
1415     /**
1416      * This node's child node collection.
1417      * @property children
1418      * @type Node[] 
1419      */
1420     children: null,
1421
1422     /**
1423      * Tree instance this node is part of
1424      * @property tree
1425      * @type TreeView
1426      */
1427     tree: null,
1428
1429     /**
1430      * The data linked to this node.  This can be any object or primitive
1431      * value, and the data can be used in getNodeHtml().
1432      * @property data
1433      * @type object
1434      */
1435     data: null,
1436
1437     /**
1438      * Parent node
1439      * @property parent
1440      * @type Node
1441      */
1442     parent: null,
1443
1444     /**
1445      * The depth of this node.  We start at -1 for the root node.
1446      * @property depth
1447      * @type int
1448      */
1449     depth: -1,
1450
1451     /**
1452      * The node's expanded/collapsed state
1453      * @property expanded
1454      * @type boolean
1455      */
1456     expanded: false,
1457
1458     /**
1459      * Can multiple children be expanded at once?
1460      * @property multiExpand
1461      * @type boolean
1462      */
1463     multiExpand: true,
1464
1465     /**
1466      * Should we render children for a collapsed node?  It is possible that the
1467      * implementer will want to render the hidden data...  @todo verify that we 
1468      * need this, and implement it if we do.
1469      * @property renderHidden
1470      * @type boolean
1471      */
1472     renderHidden: false,
1473
1474     /**
1475      * This flag is set to true when the html is generated for this node's
1476      * children, and set to false when new children are added.
1477      * @property childrenRendered
1478      * @type boolean
1479      */
1480     childrenRendered: false,
1481
1482     /**
1483      * Dynamically loaded nodes only fetch the data the first time they are
1484      * expanded.  This flag is set to true once the data has been fetched.
1485      * @property dynamicLoadComplete
1486      * @type boolean
1487      */
1488     dynamicLoadComplete: false,
1489
1490     /**
1491      * This node's previous sibling
1492      * @property previousSibling
1493      * @type Node
1494      */
1495     previousSibling: null,
1496
1497     /**
1498      * This node's next sibling
1499      * @property nextSibling
1500      * @type Node
1501      */
1502     nextSibling: null,
1503
1504     /**
1505      * We can set the node up to call an external method to get the child
1506      * data dynamically.
1507      * @property _dynLoad
1508      * @type boolean
1509      * @private
1510      */
1511     _dynLoad: false,
1512
1513     /**
1514      * Function to execute when we need to get this node's child data.
1515      * @property dataLoader
1516      * @type function
1517      */
1518     dataLoader: null,
1519
1520     /**
1521      * This is true for dynamically loading nodes while waiting for the
1522      * callback to return.
1523      * @property isLoading
1524      * @type boolean
1525      */
1526     isLoading: false,
1527
1528     /**
1529      * The toggle/branch icon will not show if this is set to false.  This
1530      * could be useful if the implementer wants to have the child contain
1531      * extra info about the parent, rather than an actual node.
1532      * @property hasIcon
1533      * @type boolean
1534      */
1535     hasIcon: true,
1536
1537     /**
1538      * Used to configure what happens when a dynamic load node is expanded
1539      * and we discover that it does not have children.  By default, it is
1540      * treated as if it still could have children (plus/minus icon).  Set
1541      * iconMode to have it display like a leaf node instead.
1542      * @property iconMode
1543      * @type int
1544      */
1545     iconMode: 0,
1546
1547     /**
1548      * Specifies whether or not the content area of the node should be allowed
1549      * to wrap.
1550      * @property nowrap
1551      * @type boolean
1552      * @default false
1553      */
1554     nowrap: false,
1555
1556  /**
1557      * If true, the node will alway be rendered as a leaf node.  This can be
1558      * used to override the presentation when dynamically loading the entire
1559      * tree.  Setting this to true also disables the dynamic load call for the
1560      * node.
1561      * @property isLeaf
1562      * @type boolean
1563      * @default false
1564      */
1565     isLeaf: false,
1566
1567 /**
1568      * The CSS class for the html content container.  Defaults to ygtvhtml, but 
1569      * can be overridden to provide a custom presentation for a specific node.
1570      * @property contentStyle
1571      * @type string
1572      */
1573     contentStyle: "",
1574
1575
1576     /**
1577      * The generated id that will contain the data passed in by the implementer.
1578      * @property contentElId
1579      * @type string
1580      */
1581     contentElId: null,
1582     
1583 /** 
1584  * Enables node highlighting.  If true, the node can be highlighted and/or propagate highlighting
1585  * @property enableHighlight
1586  * @type boolean
1587  * @default true
1588  */
1589     enableHighlight: true,
1590     
1591 /** 
1592  * Stores the highlight state.  Can be any of:
1593  * <ul>
1594  * <li>0 - not highlighted</li>
1595  * <li>1 - highlighted</li>
1596  * <li>2 - some children highlighted</li>
1597  * </ul>
1598  * @property highlightState
1599  * @type integer
1600  * @default 0
1601  */
1602  
1603  highlightState: 0,
1604  
1605  /**
1606  * Tells whether highlighting will be propagated up to the parents of the clicked node
1607  * @property propagateHighlightUp
1608  * @type boolean
1609  * @default false
1610  */
1611  
1612  propagateHighlightUp: false,
1613  
1614  /**
1615  * Tells whether highlighting will be propagated down to the children of the clicked node
1616  * @property propagateHighlightDown
1617  * @type boolean
1618  * @default false
1619  */
1620  
1621  propagateHighlightDown: false,
1622  
1623  /**
1624   * User-defined className to be added to the Node
1625   * @property className
1626   * @type string
1627   * @default null
1628   */
1629  
1630  className: null,
1631  
1632  /**
1633      * The node type
1634      * @property _type
1635      * @private
1636      * @type string
1637      * @default "Node"
1638 */
1639     _type: "Node",
1640
1641     /*
1642     spacerPath: "http://l.yimg.com/a/i/space.gif",
1643     expandedText: "Expanded",
1644     collapsedText: "Collapsed",
1645     loadingText: "Loading",
1646     */
1647
1648     /**
1649      * Initializes this node, gets some of the properties from the parent
1650      * @method init
1651      * @param oData {object} a string or object containing the data that will
1652      * be used to render this node
1653      * @param oParent {Node} this node's parent node
1654      * @param expanded {boolean} the initial expanded/collapsed state
1655      */
1656     init: function(oData, oParent, expanded) {
1657
1658         this.data = {};
1659         this.children   = [];
1660         this.index      = YAHOO.widget.TreeView.nodeCount;
1661         ++YAHOO.widget.TreeView.nodeCount;
1662         this.contentElId = "ygtvcontentel" + this.index;
1663         
1664         if (Lang.isObject(oData)) {
1665             for (var property in oData) {
1666                 if (oData.hasOwnProperty(property)) {
1667                     if (property.charAt(0) != '_'  && !Lang.isUndefined(this[property]) && !Lang.isFunction(this[property]) ) {
1668                         this[property] = oData[property];
1669                     } else {
1670                         this.data[property] = oData[property];
1671                     }
1672                 }
1673             }
1674         }
1675         if (!Lang.isUndefined(expanded) ) { this.expanded  = expanded;  }
1676         
1677
1678         /**
1679          * The parentChange event is fired when a parent element is applied
1680          * to the node.  This is useful if you need to apply tree-level
1681          * properties to a tree that need to happen if a node is moved from
1682          * one tree to another.
1683          *
1684          * @event parentChange
1685          * @type CustomEvent
1686          */
1687         this.createEvent("parentChange", this);
1688
1689         // oParent should never be null except when we create the root node.
1690         if (oParent) {
1691             oParent.appendChild(this);
1692         }
1693     },
1694
1695     /**
1696      * Certain properties for the node cannot be set until the parent
1697      * is known. This is called after the node is inserted into a tree.
1698      * the parent is also applied to this node's children in order to
1699      * make it possible to move a branch from one tree to another.
1700      * @method applyParent
1701      * @param {Node} parentNode this node's parent node
1702      * @return {boolean} true if the application was successful
1703      */
1704     applyParent: function(parentNode) {
1705         if (!parentNode) {
1706             return false;
1707         }
1708
1709         this.tree   = parentNode.tree;
1710         this.parent = parentNode;
1711         this.depth  = parentNode.depth + 1;
1712
1713         // @todo why was this put here.  This causes new nodes added at the
1714         // root level to lose the menu behavior.
1715         // if (! this.multiExpand) {
1716             // this.multiExpand = parentNode.multiExpand;
1717         // }
1718
1719         this.tree.regNode(this);
1720         parentNode.childrenRendered = false;
1721
1722         // cascade update existing children
1723         for (var i=0, len=this.children.length;i<len;++i) {
1724             this.children[i].applyParent(this);
1725         }
1726
1727         this.fireEvent("parentChange");
1728
1729         return true;
1730     },
1731
1732     /**
1733      * Appends a node to the child collection.
1734      * @method appendChild
1735      * @param childNode {Node} the new node
1736      * @return {Node} the child node
1737      * @private
1738      */
1739     appendChild: function(childNode) {
1740         if (this.hasChildren()) {
1741             var sib = this.children[this.children.length - 1];
1742             sib.nextSibling = childNode;
1743             childNode.previousSibling = sib;
1744         }
1745         this.children[this.children.length] = childNode;
1746         childNode.applyParent(this);
1747
1748         // part of the IE display issue workaround. If child nodes
1749         // are added after the initial render, and the node was
1750         // instantiated with expanded = true, we need to show the
1751         // children div now that the node has a child.
1752         if (this.childrenRendered && this.expanded) {
1753             this.getChildrenEl().style.display = "";
1754         }
1755
1756         return childNode;
1757     },
1758
1759     /**
1760      * Appends this node to the supplied node's child collection
1761      * @method appendTo
1762      * @param parentNode {Node} the node to append to.
1763      * @return {Node} The appended node
1764      */
1765     appendTo: function(parentNode) {
1766         return parentNode.appendChild(this);
1767     },
1768
1769     /**
1770     * Inserts this node before this supplied node
1771     * @method insertBefore
1772     * @param node {Node} the node to insert this node before
1773     * @return {Node} the inserted node
1774     */
1775     insertBefore: function(node) {
1776         var p = node.parent;
1777         if (p) {
1778
1779             if (this.tree) {
1780                 this.tree.popNode(this);
1781             }
1782
1783             var refIndex = node.isChildOf(p);
1784             p.children.splice(refIndex, 0, this);
1785             if (node.previousSibling) {
1786                 node.previousSibling.nextSibling = this;
1787             }
1788             this.previousSibling = node.previousSibling;
1789             this.nextSibling = node;
1790             node.previousSibling = this;
1791
1792             this.applyParent(p);
1793         }
1794
1795         return this;
1796     },
1797  
1798     /**
1799     * Inserts this node after the supplied node
1800     * @method insertAfter
1801     * @param node {Node} the node to insert after
1802     * @return {Node} the inserted node
1803     */
1804     insertAfter: function(node) {
1805         var p = node.parent;
1806         if (p) {
1807
1808             if (this.tree) {
1809                 this.tree.popNode(this);
1810             }
1811
1812             var refIndex = node.isChildOf(p);
1813
1814             if (!node.nextSibling) {
1815                 this.nextSibling = null;
1816                 return this.appendTo(p);
1817             }
1818
1819             p.children.splice(refIndex + 1, 0, this);
1820
1821             node.nextSibling.previousSibling = this;
1822             this.previousSibling = node;
1823             this.nextSibling = node.nextSibling;
1824             node.nextSibling = this;
1825
1826             this.applyParent(p);
1827         }
1828
1829         return this;
1830     },
1831
1832     /**
1833     * Returns true if the Node is a child of supplied Node
1834     * @method isChildOf
1835     * @param parentNode {Node} the Node to check
1836     * @return {boolean} The node index if this Node is a child of 
1837     *                   supplied Node, else -1.
1838     * @private
1839     */
1840     isChildOf: function(parentNode) {
1841         if (parentNode && parentNode.children) {
1842             for (var i=0, len=parentNode.children.length; i<len ; ++i) {
1843                 if (parentNode.children[i] === this) {
1844                     return i;
1845                 }
1846             }
1847         }
1848
1849         return -1;
1850     },
1851
1852     /**
1853      * Returns a node array of this node's siblings, null if none.
1854      * @method getSiblings
1855      * @return Node[]
1856      */
1857     getSiblings: function() {
1858         var sib =  this.parent.children.slice(0);
1859         for (var i=0;i < sib.length && sib[i] != this;i++) {}
1860         sib.splice(i,1);
1861         if (sib.length) { return sib; }
1862         return null;
1863     },
1864
1865     /**
1866      * Shows this node's children
1867      * @method showChildren
1868      */
1869     showChildren: function() {
1870         if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
1871             if (this.hasChildren()) {
1872                 this.getChildrenEl().style.display = "";
1873             }
1874         }
1875     },
1876
1877     /**
1878      * Hides this node's children
1879      * @method hideChildren
1880      */
1881     hideChildren: function() {
1882
1883         if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
1884             this.getChildrenEl().style.display = "none";
1885         }
1886     },
1887
1888     /**
1889      * Returns the id for this node's container div
1890      * @method getElId
1891      * @return {string} the element id
1892      */
1893     getElId: function() {
1894         return "ygtv" + this.index;
1895     },
1896
1897     /**
1898      * Returns the id for this node's children div
1899      * @method getChildrenElId
1900      * @return {string} the element id for this node's children div
1901      */
1902     getChildrenElId: function() {
1903         return "ygtvc" + this.index;
1904     },
1905
1906     /**
1907      * Returns the id for this node's toggle element
1908      * @method getToggleElId
1909      * @return {string} the toggel element id
1910      */
1911     getToggleElId: function() {
1912         return "ygtvt" + this.index;
1913     },
1914
1915
1916     /*
1917      * Returns the id for this node's spacer image.  The spacer is positioned
1918      * over the toggle and provides feedback for screen readers.
1919      * @method getSpacerId
1920      * @return {string} the id for the spacer image
1921      */
1922     /*
1923     getSpacerId: function() {
1924         return "ygtvspacer" + this.index;
1925     }, 
1926     */
1927
1928     /**
1929      * Returns this node's container html element
1930      * @method getEl
1931      * @return {HTMLElement} the container html element
1932      */
1933     getEl: function() {
1934         return Dom.get(this.getElId());
1935     },
1936
1937     /**
1938      * Returns the div that was generated for this node's children
1939      * @method getChildrenEl
1940      * @return {HTMLElement} this node's children div
1941      */
1942     getChildrenEl: function() {
1943         return Dom.get(this.getChildrenElId());
1944     },
1945
1946     /**
1947      * Returns the element that is being used for this node's toggle.
1948      * @method getToggleEl
1949      * @return {HTMLElement} this node's toggle html element
1950      */
1951     getToggleEl: function() {
1952         return Dom.get(this.getToggleElId());
1953     },
1954     /**
1955     * Returns the outer html element for this node's content
1956     * @method getContentEl
1957     * @return {HTMLElement} the element
1958     */
1959     getContentEl: function() { 
1960         return Dom.get(this.contentElId);
1961     },
1962
1963
1964     /*
1965      * Returns the element that is being used for this node's spacer.
1966      * @method getSpacer
1967      * @return {HTMLElement} this node's spacer html element
1968      */
1969     /*
1970     getSpacer: function() {
1971         return document.getElementById( this.getSpacerId() ) || {};
1972     },
1973     */
1974
1975     /*
1976     getStateText: function() {
1977         if (this.isLoading) {
1978             return this.loadingText;
1979         } else if (this.hasChildren(true)) {
1980             if (this.expanded) {
1981                 return this.expandedText;
1982             } else {
1983                 return this.collapsedText;
1984             }
1985         } else {
1986             return "";
1987         }
1988     },
1989     */
1990
1991   /**
1992      * Hides this nodes children (creating them if necessary), changes the toggle style.
1993      * @method collapse
1994      */
1995     collapse: function() {
1996         // Only collapse if currently expanded
1997         if (!this.expanded) { return; }
1998
1999         // fire the collapse event handler
2000         var ret = this.tree.onCollapse(this);
2001
2002         if (false === ret) {
2003             return;
2004         }
2005
2006         ret = this.tree.fireEvent("collapse", this);
2007
2008         if (false === ret) {
2009             return;
2010         }
2011
2012
2013         if (!this.getEl()) {
2014             this.expanded = false;
2015         } else {
2016             // hide the child div
2017             this.hideChildren();
2018             this.expanded = false;
2019
2020             this.updateIcon();
2021         }
2022
2023         // this.getSpacer().title = this.getStateText();
2024
2025         ret = this.tree.fireEvent("collapseComplete", this);
2026
2027     },
2028
2029     /**
2030      * Shows this nodes children (creating them if necessary), changes the
2031      * toggle style, and collapses its siblings if multiExpand is not set.
2032      * @method expand
2033      */
2034     expand: function(lazySource) {
2035         // Only expand if currently collapsed.
2036         if (this.isLoading || (this.expanded && !lazySource)) { 
2037             return; 
2038         }
2039
2040         var ret = true;
2041
2042         // When returning from the lazy load handler, expand is called again
2043         // in order to render the new children.  The "expand" event already
2044         // fired before fething the new data, so we need to skip it now.
2045         if (!lazySource) {
2046             // fire the expand event handler
2047             ret = this.tree.onExpand(this);
2048
2049             if (false === ret) {
2050                 return;
2051             }
2052             
2053             ret = this.tree.fireEvent("expand", this);
2054         }
2055
2056         if (false === ret) {
2057             return;
2058         }
2059
2060         if (!this.getEl()) {
2061             this.expanded = true;
2062             return;
2063         }
2064
2065         if (!this.childrenRendered) {
2066             this.getChildrenEl().innerHTML = this.renderChildren();
2067         } else {
2068         }
2069
2070         this.expanded = true;
2071
2072         this.updateIcon();
2073
2074         // this.getSpacer().title = this.getStateText();
2075
2076         // We do an extra check for children here because the lazy
2077         // load feature can expose nodes that have no children.
2078
2079         // if (!this.hasChildren()) {
2080         if (this.isLoading) {
2081             this.expanded = false;
2082             return;
2083         }
2084
2085         if (! this.multiExpand) {
2086             var sibs = this.getSiblings();
2087             for (var i=0; sibs && i<sibs.length; ++i) {
2088                 if (sibs[i] != this && sibs[i].expanded) { 
2089                     sibs[i].collapse(); 
2090                 }
2091             }
2092         }
2093
2094         this.showChildren();
2095
2096         ret = this.tree.fireEvent("expandComplete", this);
2097     },
2098
2099     updateIcon: function() {
2100         if (this.hasIcon) {
2101             var el = this.getToggleEl();
2102             if (el) {
2103                 el.className = el.className.replace(/\bygtv(([tl][pmn]h?)|(loading))\b/gi,this.getStyle());
2104             }
2105         }
2106     },
2107
2108     /**
2109      * Returns the css style name for the toggle
2110      * @method getStyle
2111      * @return {string} the css class for this node's toggle
2112      */
2113     getStyle: function() {
2114         if (this.isLoading) {
2115             return "ygtvloading";
2116         } else {
2117             // location top or bottom, middle nodes also get the top style
2118             var loc = (this.nextSibling) ? "t" : "l";
2119
2120             // type p=plus(expand), m=minus(collapase), n=none(no children)
2121             var type = "n";
2122             if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) {
2123             // if (this.hasChildren(true)) {
2124                 type = (this.expanded) ? "m" : "p";
2125             }
2126
2127             return "ygtv" + loc + type;
2128         }
2129     },
2130
2131     /**
2132      * Returns the hover style for the icon
2133      * @return {string} the css class hover state
2134      * @method getHoverStyle
2135      */
2136     getHoverStyle: function() { 
2137         var s = this.getStyle();
2138         if (this.hasChildren(true) && !this.isLoading) { 
2139             s += "h"; 
2140         }
2141         return s;
2142     },
2143
2144     /**
2145      * Recursively expands all of this node's children.
2146      * @method expandAll
2147      */
2148     expandAll: function() { 
2149         var l = this.children.length;
2150         for (var i=0;i<l;++i) {
2151             var c = this.children[i];
2152             if (c.isDynamic()) {
2153                 break;
2154             } else if (! c.multiExpand) {
2155                 break;
2156             } else {
2157                 c.expand();
2158                 c.expandAll();
2159             }
2160         }
2161     },
2162
2163     /**
2164      * Recursively collapses all of this node's children.
2165      * @method collapseAll
2166      */
2167     collapseAll: function() { 
2168         for (var i=0;i<this.children.length;++i) {
2169             this.children[i].collapse();
2170             this.children[i].collapseAll();
2171         }
2172     },
2173
2174     /**
2175      * Configures this node for dynamically obtaining the child data
2176      * when the node is first expanded.  Calling it without the callback
2177      * will turn off dynamic load for the node.
2178      * @method setDynamicLoad
2179      * @param fmDataLoader {function} the function that will be used to get the data.
2180      * @param iconMode {int} configures the icon that is displayed when a dynamic
2181      * load node is expanded the first time without children.  By default, the 
2182      * "collapse" icon will be used.  If set to 1, the leaf node icon will be
2183      * displayed.
2184      */
2185     setDynamicLoad: function(fnDataLoader, iconMode) { 
2186         if (fnDataLoader) {
2187             this.dataLoader = fnDataLoader;
2188             this._dynLoad = true;
2189         } else {
2190             this.dataLoader = null;
2191             this._dynLoad = false;
2192         }
2193
2194         if (iconMode) {
2195             this.iconMode = iconMode;
2196         }
2197     },
2198
2199     /**
2200      * Evaluates if this node is the root node of the tree
2201      * @method isRoot
2202      * @return {boolean} true if this is the root node
2203      */
2204     isRoot: function() { 
2205         return (this == this.tree.root);
2206     },
2207
2208     /**
2209      * Evaluates if this node's children should be loaded dynamically.  Looks for
2210      * the property both in this instance and the root node.  If the tree is
2211      * defined to load all children dynamically, the data callback function is
2212      * defined in the root node
2213      * @method isDynamic
2214      * @return {boolean} true if this node's children are to be loaded dynamically
2215      */
2216     isDynamic: function() { 
2217         if (this.isLeaf) {
2218             return false;
2219         } else {
2220             return (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
2221             // return lazy;
2222         }
2223     },
2224
2225     /**
2226      * Returns the current icon mode.  This refers to the way childless dynamic
2227      * load nodes appear (this comes into play only after the initial dynamic
2228      * load request produced no children).
2229      * @method getIconMode
2230      * @return {int} 0 for collapse style, 1 for leaf node style
2231      */
2232     getIconMode: function() {
2233         return (this.iconMode || this.tree.root.iconMode);
2234     },
2235
2236     /**
2237      * Checks if this node has children.  If this node is lazy-loading and the
2238      * children have not been rendered, we do not know whether or not there
2239      * are actual children.  In most cases, we need to assume that there are
2240      * children (for instance, the toggle needs to show the expandable 
2241      * presentation state).  In other times we want to know if there are rendered
2242      * children.  For the latter, "checkForLazyLoad" should be false.
2243      * @method hasChildren
2244      * @param checkForLazyLoad {boolean} should we check for unloaded children?
2245      * @return {boolean} true if this has children or if it might and we are
2246      * checking for this condition.
2247      */
2248     hasChildren: function(checkForLazyLoad) { 
2249         if (this.isLeaf) {
2250             return false;
2251         } else {
2252             return ( this.children.length > 0 || 
2253                                 (checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete) 
2254                         );
2255         }
2256     },
2257
2258     /**
2259      * Expands if node is collapsed, collapses otherwise.
2260      * @method toggle
2261      */
2262     toggle: function() {
2263         if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
2264             if (this.expanded) { this.collapse(); } else { this.expand(); }
2265         }
2266     },
2267
2268     /**
2269      * Returns the markup for this node and its children.
2270      * @method getHtml
2271      * @return {string} the markup for this node and its expanded children.
2272      */
2273     getHtml: function() {
2274
2275         this.childrenRendered = false;
2276
2277         return ['<div class="ygtvitem" id="' , this.getElId() , '">' ,this.getNodeHtml() , this.getChildrenHtml() ,'</div>'].join("");
2278     },
2279
2280     /**
2281      * Called when first rendering the tree.  We always build the div that will
2282      * contain this nodes children, but we don't render the children themselves
2283      * unless this node is expanded.
2284      * @method getChildrenHtml
2285      * @return {string} the children container div html and any expanded children
2286      * @private
2287      */
2288     getChildrenHtml: function() {
2289
2290
2291         var sb = [];
2292         sb[sb.length] = '<div class="ygtvchildren" id="' + this.getChildrenElId() + '"';
2293
2294         // This is a workaround for an IE rendering issue, the child div has layout
2295         // in IE, creating extra space if a leaf node is created with the expanded
2296         // property set to true.
2297         if (!this.expanded || !this.hasChildren()) {
2298             sb[sb.length] = ' style="display:none;"';
2299         }
2300         sb[sb.length] = '>';
2301
2302
2303         // Don't render the actual child node HTML unless this node is expanded.
2304         if ( (this.hasChildren(true) && this.expanded) ||
2305                 (this.renderHidden && !this.isDynamic()) ) {
2306             sb[sb.length] = this.renderChildren();
2307         }
2308
2309         sb[sb.length] = '</div>';
2310
2311         return sb.join("");
2312     },
2313
2314     /**
2315      * Generates the markup for the child nodes.  This is not done until the node
2316      * is expanded.
2317      * @method renderChildren
2318      * @return {string} the html for this node's children
2319      * @private
2320      */
2321     renderChildren: function() {
2322
2323
2324         var node = this;
2325
2326         if (this.isDynamic() && !this.dynamicLoadComplete) {
2327             this.isLoading = true;
2328             this.tree.locked = true;
2329
2330             if (this.dataLoader) {
2331
2332                 setTimeout( 
2333                     function() {
2334                         node.dataLoader(node, 
2335                             function() { 
2336                                 node.loadComplete(); 
2337                             });
2338                     }, 10);
2339                 
2340             } else if (this.tree.root.dataLoader) {
2341
2342                 setTimeout( 
2343                     function() {
2344                         node.tree.root.dataLoader(node, 
2345                             function() { 
2346                                 node.loadComplete(); 
2347                             });
2348                     }, 10);
2349
2350             } else {
2351                 return "Error: data loader not found or not specified.";
2352             }
2353
2354             return "";
2355
2356         } else {
2357             return this.completeRender();
2358         }
2359     },
2360
2361     /**
2362      * Called when we know we have all the child data.
2363      * @method completeRender
2364      * @return {string} children html
2365      */
2366     completeRender: function() {
2367         var sb = [];
2368
2369         for (var i=0; i < this.children.length; ++i) {
2370             // this.children[i].childrenRendered = false;
2371             sb[sb.length] = this.children[i].getHtml();
2372         }
2373         
2374         this.childrenRendered = true;
2375
2376         return sb.join("");
2377     },
2378
2379     /**
2380      * Load complete is the callback function we pass to the data provider
2381      * in dynamic load situations.
2382      * @method loadComplete
2383      */
2384     loadComplete: function() {
2385         this.getChildrenEl().innerHTML = this.completeRender();
2386                 if (this.propagateHighlightDown) {
2387                         if (this.highlightState === 1 && !this.tree.singleNodeHighlight) {
2388                                 for (var i = 0; i < this.children.length; i++) {
2389                                 this.children[i].highlight(true);
2390                         }
2391                         } else if (this.highlightState === 0 || this.tree.singleNodeHighlight) {
2392                                 for (i = 0; i < this.children.length; i++) {
2393                                         this.children[i].unhighlight(true);
2394                                 }
2395                         } // if (highlighState == 2) leave child nodes with whichever highlight state they are set
2396                 }
2397                                 
2398         this.dynamicLoadComplete = true;
2399         this.isLoading = false;
2400         this.expand(true);
2401         this.tree.locked = false;
2402     },
2403
2404     /**
2405      * Returns this node's ancestor at the specified depth.
2406      * @method getAncestor
2407      * @param {int} depth the depth of the ancestor.
2408      * @return {Node} the ancestor
2409      */
2410     getAncestor: function(depth) {
2411         if (depth >= this.depth || depth < 0)  {
2412             return null;
2413         }
2414
2415         var p = this.parent;
2416         
2417         while (p.depth > depth) {
2418             p = p.parent;
2419         }
2420
2421         return p;
2422     },
2423
2424     /**
2425      * Returns the css class for the spacer at the specified depth for
2426      * this node.  If this node's ancestor at the specified depth
2427      * has a next sibling the presentation is different than if it
2428      * does not have a next sibling
2429      * @method getDepthStyle
2430      * @param {int} depth the depth of the ancestor.
2431      * @return {string} the css class for the spacer
2432      */
2433     getDepthStyle: function(depth) {
2434         return (this.getAncestor(depth).nextSibling) ? 
2435             "ygtvdepthcell" : "ygtvblankdepthcell";
2436     },
2437
2438     /**
2439      * Get the markup for the node.  This may be overrided so that we can
2440      * support different types of nodes.
2441      * @method getNodeHtml
2442      * @return {string} The HTML that will render this node.
2443      */
2444     getNodeHtml: function() { 
2445         var sb = [];
2446
2447         sb[sb.length] = '<table id="ygtvtableel' + this.index + '" border="0" cellpadding="0" cellspacing="0" class="ygtvtable ygtvdepth' + this.depth;
2448         if (this.enableHighlight) {
2449             sb[sb.length] = ' ygtv-highlight' + this.highlightState;
2450         }
2451         if (this.className) {
2452             sb[sb.length] = ' ' + this.className;
2453         }           
2454         sb[sb.length] = '"><tr class="ygtvrow">';
2455         
2456         for (var i=0;i<this.depth;++i) {
2457             sb[sb.length] = '<td class="ygtvcell ' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
2458         }
2459
2460         if (this.hasIcon) {
2461             sb[sb.length] = '<td id="' + this.getToggleElId();
2462             sb[sb.length] = '" class="ygtvcell ';
2463             sb[sb.length] = this.getStyle() ;
2464             sb[sb.length] = '"><a href="#" class="ygtvspacer">&#160;</a></td>';
2465         }
2466
2467         sb[sb.length] = '<td id="' + this.contentElId; 
2468         sb[sb.length] = '" class="ygtvcell ';
2469         sb[sb.length] = this.contentStyle  + ' ygtvcontent" ';
2470         sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
2471         sb[sb.length] = ' >';
2472         sb[sb.length] = this.getContentHtml();
2473         sb[sb.length] = '</td></tr></table>';
2474
2475         return sb.join("");
2476
2477     },
2478     /**
2479      * Get the markup for the contents of the node.  This is designed to be overrided so that we can
2480      * support different types of nodes.
2481      * @method getContentHtml
2482      * @return {string} The HTML that will render the content of this node.
2483      */
2484     getContentHtml: function () {
2485         return "";
2486     },
2487
2488     /**
2489      * Regenerates the html for this node and its children.  To be used when the
2490      * node is expanded and new children have been added.
2491      * @method refresh
2492      */
2493     refresh: function() {
2494         // this.loadComplete();
2495         this.getChildrenEl().innerHTML = this.completeRender();
2496
2497         if (this.hasIcon) {
2498             var el = this.getToggleEl();
2499             if (el) {
2500                 el.className = el.className.replace(/\bygtv[lt][nmp]h*\b/gi,this.getStyle());
2501             }
2502         }
2503     },
2504
2505     /**
2506      * Node toString
2507      * @method toString
2508      * @return {string} string representation of the node
2509      */
2510     toString: function() {
2511         return this._type + " (" + this.index + ")";
2512     },
2513     /**
2514     * array of items that had the focus set on them
2515     * so that they can be cleaned when focus is lost
2516     * @property _focusHighlightedItems
2517     * @type Array of DOM elements
2518     * @private
2519     */
2520     _focusHighlightedItems: [],
2521     /**
2522     * DOM element that actually got the browser focus
2523     * @property _focusedItem
2524     * @type DOM element
2525     * @private
2526     */
2527     _focusedItem: null,
2528     
2529     /**
2530     * Returns true if there are any elements in the node that can 
2531     * accept the real actual browser focus
2532     * @method _canHaveFocus
2533     * @return {boolean} success
2534     * @private
2535     */
2536     _canHaveFocus: function() {
2537         return this.getEl().getElementsByTagName('a').length > 0;
2538     },
2539     /**
2540     * Removes the focus of previously selected Node
2541     * @method _removeFocus
2542     * @private
2543     */
2544     _removeFocus:function () {
2545         if (this._focusedItem) {
2546             Event.removeListener(this._focusedItem,'blur');
2547             this._focusedItem = null;
2548         }
2549         var el;
2550         while ((el = this._focusHighlightedItems.shift())) {  // yes, it is meant as an assignment, really
2551             Dom.removeClass(el,YAHOO.widget.TreeView.FOCUS_CLASS_NAME );
2552         }
2553     },
2554     /**
2555     * Sets the focus on the node element.
2556     * It will only be able to set the focus on nodes that have anchor elements in it.  
2557     * Toggle or branch icons have anchors and can be focused on.  
2558     * If will fail in nodes that have no anchor
2559     * @method focus
2560     * @return {boolean} success
2561     */
2562     focus: function () {
2563         var focused = false, self = this;
2564
2565         if (this.tree.currentFocus) {
2566             this.tree.currentFocus._removeFocus();
2567         }
2568     
2569         var  expandParent = function (node) {
2570             if (node.parent) {
2571                 expandParent(node.parent);
2572                 node.parent.expand();
2573             } 
2574         };
2575         expandParent(this);
2576
2577         Dom.getElementsBy  ( 
2578             function (el) {
2579                 return (/ygtv(([tl][pmn]h?)|(content))/).test(el.className);
2580             } ,
2581             'td' , 
2582             self.getEl().firstChild , 
2583             function (el) {
2584                 Dom.addClass(el, YAHOO.widget.TreeView.FOCUS_CLASS_NAME );
2585                 if (!focused) { 
2586                     var aEl = el.getElementsByTagName('a');
2587                     if (aEl.length) {
2588                         aEl = aEl[0];
2589                         aEl.focus();
2590                         self._focusedItem = aEl;
2591                         Event.on(aEl,'blur',function () {
2592                             //console.log('f1');
2593                             self.tree.fireEvent('focusChanged',{oldNode:self.tree.currentFocus,newNode:null});
2594                             self.tree.currentFocus = null;
2595                             self._removeFocus();
2596                         });
2597                         focused = true;
2598                     }
2599                 }
2600                 self._focusHighlightedItems.push(el);
2601             }
2602         );
2603         if (focused) { 
2604                             //console.log('f2');
2605             this.tree.fireEvent('focusChanged',{oldNode:this.tree.currentFocus,newNode:this});
2606             this.tree.currentFocus = this;
2607         } else {
2608                             //console.log('f3');
2609             this.tree.fireEvent('focusChanged',{oldNode:self.tree.currentFocus,newNode:null});
2610             this.tree.currentFocus = null;
2611             this._removeFocus(); 
2612         }
2613         return focused;
2614     },
2615
2616   /**
2617      * Count of nodes in a branch
2618      * @method getNodeCount
2619      * @return {int} number of nodes in the branch
2620      */
2621     getNodeCount: function() {
2622         for (var i = 0, count = 0;i< this.children.length;i++) {
2623             count += this.children[i].getNodeCount();
2624         }
2625         return count + 1;
2626     },
2627     
2628       /**
2629      * Returns an object which could be used to build a tree out of this node and its children.
2630      * It can be passed to the tree constructor to reproduce this node as a tree.
2631      * It will return false if the node or any children loads dynamically, regardless of whether it is loaded or not.
2632      * @method getNodeDefinition
2633      * @return {Object | false}  definition of the tree or false if the node or any children is defined as dynamic
2634      */
2635     getNodeDefinition: function() {
2636     
2637         if (this.isDynamic()) { return false; }
2638         
2639         var def, defs = Lang.merge(this.data), children = []; 
2640         
2641         
2642
2643         if (this.expanded) {defs.expanded = this.expanded; }
2644         if (!this.multiExpand) { defs.multiExpand = this.multiExpand; }
2645         if (!this.renderHidden) { defs.renderHidden = this.renderHidden; }
2646         if (!this.hasIcon) { defs.hasIcon = this.hasIcon; }
2647         if (this.nowrap) { defs.nowrap = this.nowrap; }
2648         if (this.className) { defs.className = this.className; }
2649         if (this.editable) { defs.editable = this.editable; }
2650         if (this.enableHighlight) { defs.enableHighlight = this.enableHighlight; }
2651         if (this.highlightState) { defs.highlightState = this.highlightState; }
2652         if (this.propagateHighlightUp) { defs.propagateHighlightUp = this.propagateHighlightUp; }
2653         if (this.propagateHighlightDown) { defs.propagateHighlightDown = this.propagateHighlightDown; }
2654         defs.type = this._type;
2655         
2656         
2657         
2658         for (var i = 0; i < this.children.length;i++) {
2659             def = this.children[i].getNodeDefinition();
2660             if (def === false) { return false;}
2661             children.push(def);
2662         }
2663         if (children.length) { defs.children = children; }
2664         return defs;
2665     },
2666
2667
2668     /**
2669      * Generates the link that will invoke this node's toggle method
2670      * @method getToggleLink
2671      * @return {string} the javascript url for toggling this node
2672      */
2673     getToggleLink: function() {
2674         return 'return false;';
2675     },
2676     
2677     /**
2678     * Sets the value of property for this node and all loaded descendants.  
2679     * Only public and defined properties can be set, not methods.  
2680     * Values for unknown properties will be assigned to the refNode.data object
2681     * @method setNodesProperty
2682     * @param name {string} Name of the property to be set
2683     * @param value {any} value to be set
2684     * @param refresh {boolean} if present and true, it does a refresh
2685     */
2686     setNodesProperty: function(name, value, refresh) {
2687         if (name.charAt(0) != '_'  && !Lang.isUndefined(this[name]) && !Lang.isFunction(this[name]) ) {
2688             this[name] = value;
2689         } else {
2690             this.data[name] = value;
2691         }
2692         for (var i = 0; i < this.children.length;i++) {
2693             this.children[i].setNodesProperty(name,value);
2694         }
2695         if (refresh) {
2696             this.refresh();
2697         }
2698     },
2699     /**
2700     * Toggles the highlighted state of a Node
2701     * @method toggleHighlight
2702     */
2703     toggleHighlight: function() {
2704         if (this.enableHighlight) {
2705             // unhighlights only if fully highligthed.  For not or partially highlighted it will highlight
2706             if (this.highlightState == 1) {
2707                 this.unhighlight();
2708             } else {
2709                 this.highlight();
2710             }
2711         }
2712     },
2713     
2714     /**
2715     * Turns highlighting on node.  
2716     * @method highlight
2717     * @param _silent {boolean} optional, don't fire the highlightEvent
2718     */
2719     highlight: function(_silent) {
2720         if (this.enableHighlight) {
2721             if (this.tree.singleNodeHighlight) {
2722                 if (this.tree._currentlyHighlighted) {
2723                     this.tree._currentlyHighlighted.unhighlight(_silent);
2724                 }
2725                 this.tree._currentlyHighlighted = this;
2726             }
2727             this.highlightState = 1;
2728             this._setHighlightClassName();
2729             if (!this.tree.singleNodeHighlight) {
2730                                 if (this.propagateHighlightDown) {
2731                                         for (var i = 0;i < this.children.length;i++) {
2732                                                 this.children[i].highlight(true);
2733                                         }
2734                                 }
2735                                 if (this.propagateHighlightUp) {
2736                                         if (this.parent) {
2737                                                 this.parent._childrenHighlighted();
2738                                         }
2739                                 }
2740                         }
2741             if (!_silent) {
2742                 this.tree.fireEvent('highlightEvent',this);
2743             }
2744         }
2745     },
2746     /**
2747     * Turns highlighting off a node.  
2748     * @method unhighlight
2749     * @param _silent {boolean} optional, don't fire the highlightEvent
2750     */
2751     unhighlight: function(_silent) {
2752         if (this.enableHighlight) {
2753                         // might have checked singleNodeHighlight but it wouldn't really matter either way
2754             this.tree._currentlyHighlighted = null;
2755             this.highlightState = 0;
2756             this._setHighlightClassName();
2757             if (!this.tree.singleNodeHighlight) {
2758                                 if (this.propagateHighlightDown) {
2759                                         for (var i = 0;i < this.children.length;i++) {
2760                                                 this.children[i].unhighlight(true);
2761                                         }
2762                                 }
2763                                 if (this.propagateHighlightUp) {
2764                                         if (this.parent) {
2765                                                 this.parent._childrenHighlighted();
2766                                         }
2767                                 }
2768                         }
2769             if (!_silent) {
2770                 this.tree.fireEvent('highlightEvent',this);
2771             }
2772         }
2773     },
2774     /** 
2775     * Checks whether all or part of the children of a node are highlighted and
2776     * sets the node highlight to full, none or partial highlight.
2777     * If set to propagate it will further call the parent
2778     * @method _childrenHighlighted
2779     * @private
2780     */
2781     _childrenHighlighted: function() {
2782         var yes = false, no = false;
2783         if (this.enableHighlight) {
2784             for (var i = 0;i < this.children.length;i++) {
2785                 switch(this.children[i].highlightState) {
2786                     case 0:
2787                         no = true;
2788                         break;
2789                     case 1:
2790                         yes = true;
2791                         break;
2792                     case 2:
2793                         yes = no = true;
2794                         break;
2795                 }
2796             }
2797             if (yes && no) {
2798                 this.highlightState = 2;
2799             } else if (yes) {
2800                 this.highlightState = 1;
2801             } else {
2802                 this.highlightState = 0;
2803             }
2804             this._setHighlightClassName();
2805             if (this.propagateHighlightUp) {
2806                 if (this.parent) {
2807                     this.parent._childrenHighlighted();
2808                 }
2809             }
2810         }
2811     },
2812     
2813     /**
2814     * Changes the classNames on the toggle and content containers to reflect the current highlighting
2815     * @method _setHighlightClassName
2816     * @private
2817     */
2818     _setHighlightClassName: function() {
2819         var el = Dom.get('ygtvtableel' + this.index);
2820         if (el) {
2821             el.className = el.className.replace(/\bygtv-highlight\d\b/gi,'ygtv-highlight' + this.highlightState);
2822         }
2823     }
2824     
2825 };
2826
2827 YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
2828 })();
2829
2830 /**
2831  * A custom YAHOO.widget.Node that handles the unique nature of 
2832  * the virtual, presentationless root node.
2833  * @namespace YAHOO.widget
2834  * @class RootNode
2835  * @extends YAHOO.widget.Node
2836  * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
2837  * @constructor
2838  */
2839 YAHOO.widget.RootNode = function(oTree) {
2840     // Initialize the node with null params.  The root node is a
2841     // special case where the node has no presentation.  So we have
2842     // to alter the standard properties a bit.
2843     this.init(null, null, true);
2844     
2845     /*
2846      * For the root node, we get the tree reference from as a param
2847      * to the constructor instead of from the parent element.
2848      */
2849     this.tree = oTree;
2850 };
2851
2852 YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
2853     
2854    /**
2855      * The node type
2856      * @property _type
2857       * @type string
2858      * @private
2859      * @default "RootNode"
2860      */
2861     _type: "RootNode",
2862     
2863     // overrides YAHOO.widget.Node
2864     getNodeHtml: function() { 
2865         return ""; 
2866     },
2867
2868     toString: function() { 
2869         return this._type;
2870     },
2871
2872     loadComplete: function() { 
2873         this.tree.draw();
2874     },
2875     
2876    /**
2877      * Count of nodes in tree.  
2878     * It overrides Nodes.getNodeCount because the root node should not be counted.
2879      * @method getNodeCount
2880      * @return {int} number of nodes in the tree
2881      */
2882     getNodeCount: function() {
2883         for (var i = 0, count = 0;i< this.children.length;i++) {
2884             count += this.children[i].getNodeCount();
2885         }
2886         return count;
2887     },
2888
2889   /**
2890      * Returns an object which could be used to build a tree out of this node and its children.
2891      * It can be passed to the tree constructor to reproduce this node as a tree.
2892      * Since the RootNode is automatically created by treeView, 
2893      * its own definition is excluded from the returned node definition
2894      * which only contains its children.
2895      * @method getNodeDefinition
2896      * @return {Object | false}  definition of the tree or false if any child node is defined as dynamic
2897      */
2898     getNodeDefinition: function() {
2899         
2900         for (var def, defs = [], i = 0; i < this.children.length;i++) {
2901             def = this.children[i].getNodeDefinition();
2902             if (def === false) { return false;}
2903             defs.push(def);
2904         }
2905         return defs;
2906     },
2907
2908     collapse: function() {},
2909     expand: function() {},
2910     getSiblings: function() { return null; },
2911     focus: function () {}
2912
2913 });
2914
2915 (function () {
2916     var Dom = YAHOO.util.Dom,
2917         Lang = YAHOO.lang,
2918         Event = YAHOO.util.Event;
2919 /**
2920  * The default node presentation.  The first parameter should be
2921  * either a string that will be used as the node's label, or an object
2922  * that has at least a string property called label.  By default,  clicking the
2923  * label will toggle the expanded/collapsed state of the node.  By
2924  * setting the href property of the instance, this behavior can be
2925  * changed so that the label will go to the specified href.
2926  * @namespace YAHOO.widget
2927  * @class TextNode
2928  * @extends YAHOO.widget.Node
2929  * @constructor
2930  * @param oData {object} a string or object containing the data that will
2931  * be used to render this node.
2932  * Providing a string is the same as providing an object with a single property named label.
2933  * All values in the oData will be used to set equally named properties in the node
2934  * as long as the node does have such properties, they are not undefined, private or functions.
2935  * All attributes are made available in noderef.data, which
2936  * can be used to store custom attributes.  TreeView.getNode(s)ByProperty
2937  * can be used to retrieve a node by one of the attributes.
2938  * @param oParent {YAHOO.widget.Node} this node's parent node
2939  * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded) 
2940  */
2941 YAHOO.widget.TextNode = function(oData, oParent, expanded) {
2942
2943     if (oData) { 
2944         if (Lang.isString(oData)) {
2945             oData = { label: oData };
2946         }
2947         this.init(oData, oParent, expanded);
2948         this.setUpLabel(oData);
2949     }
2950
2951 };
2952
2953 YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
2954     
2955     /**
2956      * The CSS class for the label href.  Defaults to ygtvlabel, but can be
2957      * overridden to provide a custom presentation for a specific node.
2958      * @property labelStyle
2959      * @type string
2960      */
2961     labelStyle: "ygtvlabel",
2962
2963     /**
2964      * The derived element id of the label for this node
2965      * @property labelElId
2966      * @type string
2967      */
2968     labelElId: null,
2969
2970     /**
2971      * The text for the label.  It is assumed that the oData parameter will
2972      * either be a string that will be used as the label, or an object that
2973      * has a property called "label" that we will use.
2974      * @property label
2975      * @type string
2976      */
2977     label: null,
2978
2979     /**
2980      * The text for the title (tooltip) for the label element
2981      * @property title
2982      * @type string
2983      */
2984     title: null,
2985     
2986     /**
2987      * The href for the node's label.  If one is not specified, the href will
2988      * be set so that it toggles the node.
2989      * @property href
2990      * @type string
2991      */
2992     href: null,
2993
2994     /**
2995      * The label href target, defaults to current window
2996      * @property target
2997      * @type string
2998      */
2999     target: "_self",
3000     
3001     /**
3002      * The node type
3003      * @property _type
3004      * @private
3005      * @type string
3006      * @default "TextNode"
3007      */
3008     _type: "TextNode",
3009
3010
3011     /**
3012      * Sets up the node label
3013      * @method setUpLabel
3014      * @param oData string containing the label, or an object with a label property
3015      */
3016     setUpLabel: function(oData) { 
3017         
3018         if (Lang.isString(oData)) {
3019             oData = { 
3020                 label: oData 
3021             };
3022         } else {
3023             if (oData.style) {
3024                 this.labelStyle = oData.style;
3025             }
3026         }
3027
3028         this.label = oData.label;
3029
3030         this.labelElId = "ygtvlabelel" + this.index;
3031         
3032     },
3033
3034     /**
3035      * Returns the label element
3036      * @for YAHOO.widget.TextNode
3037      * @method getLabelEl
3038      * @return {object} the element
3039      */
3040     getLabelEl: function() { 
3041         return Dom.get(this.labelElId);
3042     },
3043
3044     // overrides YAHOO.widget.Node
3045     getContentHtml: function() { 
3046         var sb = [];
3047         sb[sb.length] = this.href?'<a':'<span';
3048         sb[sb.length] = ' id="' + this.labelElId + '"';
3049         sb[sb.length] = ' class="' + this.labelStyle  + '"';
3050         if (this.href) {
3051             sb[sb.length] = ' href="' + this.href + '"';
3052             sb[sb.length] = ' target="' + this.target + '"';
3053         } 
3054         if (this.title) {
3055             sb[sb.length] = ' title="' + this.title + '"';
3056         }
3057         sb[sb.length] = ' >';
3058         sb[sb.length] = this.label;
3059         sb[sb.length] = this.href?'</a>':'</span>';
3060         return sb.join("");
3061     },
3062
3063
3064
3065   /**
3066      * Returns an object which could be used to build a tree out of this node and its children.
3067      * It can be passed to the tree constructor to reproduce this node as a tree.
3068      * It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not.
3069      * @method getNodeDefinition
3070      * @return {Object | false}  definition of the tree or false if this node or any descendant is defined as dynamic
3071      */
3072     getNodeDefinition: function() {
3073         var def = YAHOO.widget.TextNode.superclass.getNodeDefinition.call(this);
3074         if (def === false) { return false; }
3075
3076         // Node specific properties
3077         def.label = this.label;
3078         if (this.labelStyle != 'ygtvlabel') { def.style = this.labelStyle; }
3079         if (this.title) { def.title = this.title; }
3080         if (this.href) { def.href = this.href; }
3081         if (this.target != '_self') { def.target = this.target; }       
3082
3083         return def;
3084     
3085     },
3086
3087     toString: function() { 
3088         return YAHOO.widget.TextNode.superclass.toString.call(this) + ": " + this.label;
3089     },
3090
3091     // deprecated
3092     onLabelClick: function() {
3093         return false;
3094     },
3095     refresh: function() {
3096         YAHOO.widget.TextNode.superclass.refresh.call(this);
3097         var label = this.getLabelEl();
3098         label.innerHTML = this.label;
3099         if (label.tagName.toUpperCase() == 'A') {
3100             label.href = this.href;
3101             label.target = this.target;
3102         }
3103     }
3104         
3105     
3106
3107     
3108 });
3109 })();
3110
3111 /**
3112  * A menu-specific implementation that differs from TextNode in that only 
3113  * one sibling can be expanded at a time.
3114  * @namespace YAHOO.widget
3115  * @class MenuNode
3116  * @extends YAHOO.widget.TextNode
3117  * @param oData {object} a string or object containing the data that will
3118  * be used to render this node.
3119  * Providing a string is the same as providing an object with a single property named label.
3120  * All values in the oData will be used to set equally named properties in the node
3121  * as long as the node does have such properties, they are not undefined, private or functions.
3122  * All attributes are made available in noderef.data, which
3123  * can be used to store custom attributes.  TreeView.getNode(s)ByProperty
3124  * can be used to retrieve a node by one of the attributes.
3125  * @param oParent {YAHOO.widget.Node} this node's parent node
3126  * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded) 
3127  * @constructor
3128  */
3129 YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
3130     YAHOO.widget.MenuNode.superclass.constructor.call(this,oData,oParent,expanded);
3131
3132    /*
3133      * Menus usually allow only one branch to be open at a time.
3134      */
3135     this.multiExpand = false;
3136
3137 };
3138
3139 YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
3140
3141     /**
3142      * The node type
3143      * @property _type
3144      * @private
3145     * @default "MenuNode"
3146      */
3147     _type: "MenuNode"
3148
3149 });
3150
3151 (function () {
3152     var Dom = YAHOO.util.Dom,
3153         Lang = YAHOO.lang,
3154         Event = YAHOO.util.Event;
3155
3156 /**
3157  * This implementation takes either a string or object for the
3158  * oData argument.  If is it a string, it will use it for the display
3159  * of this node (and it can contain any html code).  If the parameter
3160  * is an object,it looks for a parameter called "html" that will be
3161  * used for this node's display.
3162  * @namespace YAHOO.widget
3163  * @class HTMLNode
3164  * @extends YAHOO.widget.Node
3165  * @constructor
3166  * @param oData {object} a string or object containing the data that will
3167  * be used to render this node.  
3168  * Providing a string is the same as providing an object with a single property named html.
3169  * All values in the oData will be used to set equally named properties in the node
3170  * as long as the node does have such properties, they are not undefined, private or functions.
3171  * All other attributes are made available in noderef.data, which
3172  * can be used to store custom attributes.  TreeView.getNode(s)ByProperty
3173  * can be used to retrieve a node by one of the attributes.
3174  * @param oParent {YAHOO.widget.Node} this node's parent node
3175  * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded) 
3176  * @param hasIcon {boolean} specifies whether or not leaf nodes should
3177  * be rendered with or without a horizontal line line and/or toggle icon. If the icon
3178  * is not displayed, the content fills the space it would have occupied.
3179  * This option operates independently of the leaf node presentation logic
3180  * for dynamic nodes.
3181  * (deprecated; use oData.hasIcon) 
3182  */
3183 YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
3184     if (oData) { 
3185         this.init(oData, oParent, expanded);
3186         this.initContent(oData, hasIcon);
3187     }
3188 };
3189
3190 YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, {
3191
3192     /**
3193      * The CSS class for the html content container.  Defaults to ygtvhtml, but 
3194      * can be overridden to provide a custom presentation for a specific node.
3195      * @property contentStyle
3196      * @type string
3197      */
3198     contentStyle: "ygtvhtml",
3199
3200
3201     /**
3202      * The HTML content to use for this node's display
3203      * @property html
3204      * @type string
3205      */
3206     html: null,
3207     
3208 /**
3209      * The node type
3210      * @property _type
3211      * @private
3212      * @type string
3213      * @default "HTMLNode"
3214      */
3215     _type: "HTMLNode",
3216
3217     /**
3218      * Sets up the node label
3219      * @property initContent
3220      * @param oData {object} An html string or object containing an html property
3221      * @param hasIcon {boolean} determines if the node will be rendered with an
3222      * icon or not
3223      */
3224     initContent: function(oData, hasIcon) { 
3225         this.setHtml(oData);
3226         this.contentElId = "ygtvcontentel" + this.index;
3227         if (!Lang.isUndefined(hasIcon)) { this.hasIcon  = hasIcon; }
3228         
3229     },
3230
3231     /**
3232      * Synchronizes the node.html, and the node's content
3233      * @property setHtml
3234      * @param o {object} An html string or object containing an html property
3235      */
3236     setHtml: function(o) {
3237
3238         this.html = (typeof o === "string") ? o : o.html;
3239
3240         var el = this.getContentEl();
3241         if (el) {
3242             el.innerHTML = this.html;
3243         }
3244
3245     },
3246
3247     // overrides YAHOO.widget.Node
3248     getContentHtml: function() { 
3249         return this.html;
3250     },
3251     
3252       /**
3253      * Returns an object which could be used to build a tree out of this node and its children.
3254      * It can be passed to the tree constructor to reproduce this node as a tree.
3255      * It will return false if any node loads dynamically, regardless of whether it is loaded or not.
3256      * @method getNodeDefinition
3257      * @return {Object | false}  definition of the tree or false if any node is defined as dynamic
3258      */
3259     getNodeDefinition: function() {
3260         var def = YAHOO.widget.HTMLNode.superclass.getNodeDefinition.call(this);
3261         if (def === false) { return false; }
3262         def.html = this.html;
3263         return def;
3264     
3265     }
3266 });
3267 })();
3268
3269 (function () {
3270     var Dom = YAHOO.util.Dom,
3271         Lang = YAHOO.lang,
3272         Event = YAHOO.util.Event,
3273         Calendar = YAHOO.widget.Calendar;
3274         
3275 /**
3276  * A Date-specific implementation that differs from TextNode in that it uses 
3277  * YAHOO.widget.Calendar as an in-line editor, if available
3278  * If Calendar is not available, it behaves as a plain TextNode.
3279  * @namespace YAHOO.widget
3280  * @class DateNode
3281  * @extends YAHOO.widget.TextNode
3282  * @param oData {object} a string or object containing the data that will
3283  * be used to render this node.
3284  * Providing a string is the same as providing an object with a single property named label.
3285  * All values in the oData will be used to set equally named properties in the node
3286  * as long as the node does have such properties, they are not undefined, private nor functions.
3287  * All attributes are made available in noderef.data, which
3288  * can be used to store custom attributes.  TreeView.getNode(s)ByProperty
3289  * can be used to retrieve a node by one of the attributes.
3290  * @param oParent {YAHOO.widget.Node} this node's parent node
3291  * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded) 
3292  * @constructor
3293  */
3294 YAHOO.widget.DateNode = function(oData, oParent, expanded) {
3295     YAHOO.widget.DateNode.superclass.constructor.call(this,oData, oParent, expanded);
3296 };
3297
3298 YAHOO.extend(YAHOO.widget.DateNode, YAHOO.widget.TextNode, {
3299
3300     /**
3301      * The node type
3302      * @property _type
3303      * @type string
3304      * @private
3305      * @default  "DateNode"
3306      */
3307     _type: "DateNode",
3308     
3309     /**
3310     * Configuration object for the Calendar editor, if used.
3311     * See <a href="http://developer.yahoo.com/yui/calendar/#internationalization">http://developer.yahoo.com/yui/calendar/#internationalization</a>
3312     * @property calendarConfig
3313     */
3314     calendarConfig: null,
3315     
3316     
3317     
3318     /** 
3319      *  If YAHOO.widget.Calendar is available, it will pop up a Calendar to enter a new date.  Otherwise, it falls back to a plain &lt;input&gt;  textbox
3320      * @method fillEditorContainer
3321      * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
3322      * @return void
3323      */
3324     fillEditorContainer: function (editorData) {
3325     
3326         var cal, container = editorData.inputContainer;
3327         
3328         if (Lang.isUndefined(Calendar)) {
3329             Dom.replaceClass(editorData.editorPanel,'ygtv-edit-DateNode','ygtv-edit-TextNode');
3330             YAHOO.widget.DateNode.superclass.fillEditorContainer.call(this, editorData);
3331             return;
3332         }
3333             
3334         if (editorData.nodeType != this._type) {
3335             editorData.nodeType = this._type;
3336             editorData.saveOnEnter = false;
3337             
3338             editorData.node.destroyEditorContents(editorData);
3339
3340             editorData.inputObject = cal = new Calendar(container.appendChild(document.createElement('div')));
3341             if (this.calendarConfig) { 
3342                 cal.cfg.applyConfig(this.calendarConfig,true); 
3343                 cal.cfg.fireQueue();
3344             }
3345             cal.selectEvent.subscribe(function () {
3346                 this.tree._closeEditor(true);
3347             },this,true);
3348         } else {
3349             cal = editorData.inputObject;
3350         }
3351
3352                 editorData.oldValue = this.label;
3353         cal.cfg.setProperty("selected",this.label, false); 
3354
3355         var delim = cal.cfg.getProperty('DATE_FIELD_DELIMITER');
3356         var pageDate = this.label.split(delim);
3357         cal.cfg.setProperty('pagedate',pageDate[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] + delim + pageDate[cal.cfg.getProperty('MDY_YEAR_POSITION') -1]);
3358         cal.cfg.fireQueue();
3359
3360         cal.render();
3361         cal.oDomContainer.focus();
3362     },
3363      /**
3364     * Returns the value from the input element.
3365     * Overrides Node.getEditorValue.
3366     * @method getEditorValue
3367      * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
3368      * @return {string} date entered
3369      */
3370
3371         getEditorValue: function (editorData) {
3372         if (Lang.isUndefined(Calendar)) {
3373             return editorData.inputElement.value;
3374         } else {
3375             var cal = editorData.inputObject,
3376                 date = cal.getSelectedDates()[0],
3377                 dd = [];
3378                 
3379             dd[cal.cfg.getProperty('MDY_DAY_POSITION') -1] = date.getDate();
3380             dd[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] = date.getMonth() + 1;
3381             dd[cal.cfg.getProperty('MDY_YEAR_POSITION') -1] = date.getFullYear();
3382             return dd.join(cal.cfg.getProperty('DATE_FIELD_DELIMITER'));
3383         }
3384         },
3385
3386         /**
3387     * Finally displays the newly entered date in the tree.
3388     * Overrides Node.displayEditedValue.
3389     * @method displayEditedValue
3390      *  @param value {string} date to be displayed and stored in the node
3391      * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
3392      */
3393         displayEditedValue: function (value,editorData) {
3394                 var node = editorData.node;
3395                 node.label = value;
3396                 node.getLabelEl().innerHTML = value;
3397         },
3398   /**
3399      * Returns an object which could be used to build a tree out of this node and its children.
3400      * It can be passed to the tree constructor to reproduce this node as a tree.
3401      * It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not.
3402      * @method getNodeDefinition
3403      * @return {Object | false}  definition of the node or false if this node or any descendant is defined as dynamic
3404      */ 
3405     getNodeDefinition: function() {
3406         var def = YAHOO.widget.DateNode.superclass.getNodeDefinition.call(this);
3407         if (def === false) { return false; }
3408         if (this.calendarConfig) { def.calendarConfig = this.calendarConfig; }
3409         return def;
3410     }
3411
3412
3413 });
3414 })();
3415
3416 (function () {
3417     var Dom = YAHOO.util.Dom,
3418         Lang = YAHOO.lang, 
3419         Event = YAHOO.util.Event,
3420         TV = YAHOO.widget.TreeView,
3421         TVproto = TV.prototype;
3422
3423     /**
3424      * An object to store information used for in-line editing
3425      * for all Nodes of all TreeViews. It contains:
3426      * <ul>
3427     * <li>active {boolean}, whether there is an active cell editor </li>
3428     * <li>whoHasIt {YAHOO.widget.TreeView} TreeView instance that is currently using the editor</li>
3429     * <li>nodeType {string} value of static Node._type property, allows reuse of input element if node is of the same type.</li>
3430     * <li>editorPanel {HTMLelement (&lt;div&gt;)} element holding the in-line editor</li>
3431     * <li>inputContainer {HTMLelement (&lt;div&gt;)} element which will hold the type-specific input element(s) to be filled by the fillEditorContainer method</li>
3432     * <li>buttonsContainer {HTMLelement (&lt;div&gt;)} element which holds the &lt;button&gt; elements for Ok/Cancel.  If you don't want any of the buttons, hide it via CSS styles, don't destroy it</li>
3433     * <li>node {YAHOO.widget.Node} reference to the Node being edited</li>
3434     * <li>saveOnEnter {boolean}, whether the Enter key should be accepted as a Save command (Esc. is always taken as Cancel), disable for multi-line input elements </li>
3435     * <li>oldValue {any}  value before editing</li>
3436     * </ul>
3437     *  Editors are free to use this object to store additional data.
3438      * @property editorData
3439      * @static
3440      * @for YAHOO.widget.TreeView
3441      */
3442     TV.editorData = {
3443         active:false,
3444         whoHasIt:null, // which TreeView has it
3445         nodeType:null,
3446         editorPanel:null,
3447         inputContainer:null,
3448         buttonsContainer:null,
3449         node:null, // which Node is being edited
3450         saveOnEnter:true,
3451                 oldValue:undefined
3452         // Each node type is free to add its own properties to this as it sees fit.
3453     };
3454     
3455     /**
3456     * Validator function for edited data, called from the TreeView instance scope, 
3457     * receives the arguments (newValue, oldValue, nodeInstance) 
3458     * and returns either the validated (or type-converted) value or undefined. 
3459     * An undefined return will prevent the editor from closing
3460     * @property validator
3461     * @type function
3462     * @default null
3463      * @for YAHOO.widget.TreeView
3464      */
3465     TVproto.validator = null;
3466     
3467     /**
3468     * Entry point for initializing the editing plug-in.  
3469     * TreeView will call this method on initializing if it exists
3470     * @method _initEditor
3471      * @for YAHOO.widget.TreeView
3472      * @private
3473     */
3474
3475         TVproto._initEditor = function () {
3476                 /** 
3477                 * Fires when the user clicks on the ok button of a node editor
3478                 * @event editorSaveEvent 
3479                 * @type CustomEvent 
3480                 * @param oArgs.newValue {mixed} the new value just entered 
3481                 * @param oArgs.oldValue {mixed} the value originally in the tree 
3482                 * @param oArgs.node {YAHOO.widget.Node} the node that has the focus 
3483                 * @for YAHOO.widget.TreeView
3484                 */ 
3485                 this.createEvent("editorSaveEvent", this); 
3486                 
3487                 /** 
3488                 * Fires when the user clicks on the cancel button of a node editor
3489                 * @event editorCancelEvent 
3490                 * @type CustomEvent 
3491                 * @param {YAHOO.widget.Node} node the node that has the focus 
3492                 * @for YAHOO.widget.TreeView
3493                 */ 
3494                 this.createEvent("editorCancelEvent", this); 
3495
3496         };
3497
3498     /**
3499     * Entry point of the editing plug-in.  
3500     * TreeView will call this method if it exists when a node label is clicked
3501     * @method _nodeEditing
3502     * @param node {YAHOO.widget.Node} the node to be edited
3503     * @return {Boolean} true to indicate that the node is editable and prevent any further bubbling of the click.
3504      * @for YAHOO.widget.TreeView
3505      * @private
3506     */
3507         
3508     
3509     
3510     TVproto._nodeEditing = function (node) {
3511         if (node.fillEditorContainer && node.editable) {
3512             var ed, topLeft, buttons, button, editorData = TV.editorData;
3513             editorData.active = true;
3514             editorData.whoHasIt = this;
3515             if (!editorData.nodeType) {
3516                 editorData.editorPanel = ed = document.body.appendChild(document.createElement('div'));
3517                 Dom.addClass(ed,'ygtv-label-editor');
3518
3519                 buttons = editorData.buttonsContainer = ed.appendChild(document.createElement('div'));
3520                 Dom.addClass(buttons,'ygtv-button-container');
3521                 button = buttons.appendChild(document.createElement('button'));
3522                 Dom.addClass(button,'ygtvok');
3523                 button.innerHTML = ' ';
3524                 button = buttons.appendChild(document.createElement('button'));
3525                 Dom.addClass(button,'ygtvcancel');
3526                 button.innerHTML = ' ';
3527                 Event.on(buttons, 'click', function (ev) {
3528                     var target = Event.getTarget(ev);
3529                     var node = TV.editorData.node;
3530                     if (Dom.hasClass(target,'ygtvok')) {
3531                         Event.stopEvent(ev);
3532                         this._closeEditor(true);
3533                     }
3534                     if (Dom.hasClass(target,'ygtvcancel')) {
3535                         Event.stopEvent(ev);
3536                         this._closeEditor(false);
3537                     }
3538                 }, this, true);
3539
3540                 editorData.inputContainer = ed.appendChild(document.createElement('div'));
3541                 Dom.addClass(editorData.inputContainer,'ygtv-input');
3542                 
3543                 Event.on(ed,'keydown',function (ev) {
3544                     var editorData = TV.editorData,
3545                         KEY = YAHOO.util.KeyListener.KEY;
3546                     switch (ev.keyCode) {
3547                         case KEY.ENTER:
3548                             Event.stopEvent(ev);
3549                             if (editorData.saveOnEnter) { 
3550                                 this._closeEditor(true);
3551                             }
3552                             break;
3553                         case KEY.ESCAPE:
3554                             Event.stopEvent(ev);
3555                             this._closeEditor(false);
3556                             break;
3557                     }
3558                 },this,true);
3559
3560
3561                 
3562             } else {
3563                 ed = editorData.editorPanel;
3564             }
3565             editorData.node = node;
3566             if (editorData.nodeType) {
3567                 Dom.removeClass(ed,'ygtv-edit-' + editorData.nodeType);
3568             }
3569             Dom.addClass(ed,' ygtv-edit-' + node._type);
3570             topLeft = Dom.getXY(node.getContentEl());
3571             Dom.setStyle(ed,'left',topLeft[0] + 'px');
3572             Dom.setStyle(ed,'top',topLeft[1] + 'px');
3573             Dom.setStyle(ed,'display','block');
3574             ed.focus();
3575             node.fillEditorContainer(editorData);
3576
3577             return true;  // If inline editor available, don't do anything else.
3578         }
3579     };
3580     
3581     /**
3582     * Method to be associated with an event (clickEvent, dblClickEvent or enterKeyPressed) to pop up the contents editor
3583     *  It calls the corresponding node editNode method.
3584     * @method onEventEditNode
3585     * @param oArgs {object} Object passed as arguments to TreeView event listeners
3586      * @for YAHOO.widget.TreeView
3587     */
3588
3589     TVproto.onEventEditNode = function (oArgs) {
3590         if (oArgs instanceof YAHOO.widget.Node) {
3591             oArgs.editNode();
3592         } else if (oArgs.node instanceof YAHOO.widget.Node) {
3593             oArgs.node.editNode();
3594         }
3595     };
3596     
3597     /**
3598     * Method to be called when the inline editing is finished and the editor is to be closed
3599     * @method _closeEditor
3600     * @param save {Boolean} true if the edited value is to be saved, false if discarded
3601     * @private
3602      * @for YAHOO.widget.TreeView
3603     */
3604     
3605     TVproto._closeEditor = function (save) {
3606         var ed = TV.editorData, 
3607             node = ed.node,
3608             close = true;
3609         if (save) { 
3610             close = ed.node.saveEditorValue(ed) !== false; 
3611         } else {
3612                         this.fireEvent( 'editorCancelEvent', node); 
3613                 }
3614                         
3615         if (close) {
3616             Dom.setStyle(ed.editorPanel,'display','none');  
3617             ed.active = false;
3618             node.focus();
3619         }
3620     };
3621     
3622     /**
3623     *  Entry point for TreeView's destroy method to destroy whatever the editing plug-in has created
3624     * @method _destroyEditor
3625     * @private
3626      * @for YAHOO.widget.TreeView
3627     */
3628     TVproto._destroyEditor = function() {
3629         var ed = TV.editorData;
3630         if (ed && ed.nodeType && (!ed.active || ed.whoHasIt === this)) {
3631             Event.removeListener(ed.editorPanel,'keydown');
3632             Event.removeListener(ed.buttonContainer,'click');
3633             ed.node.destroyEditorContents(ed);
3634             document.body.removeChild(ed.editorPanel);
3635             ed.nodeType = ed.editorPanel = ed.inputContainer = ed.buttonsContainer = ed.whoHasIt = ed.node = null;
3636             ed.active = false;
3637         }
3638     };
3639     
3640     var Nproto = YAHOO.widget.Node.prototype;
3641     
3642     /**
3643     * Signals if the label is editable.  (Ignored on TextNodes with href set.)
3644     * @property editable
3645     * @type boolean
3646          * @for YAHOO.widget.Node
3647     */
3648     Nproto.editable = false;
3649     
3650     /**
3651     * pops up the contents editor, if there is one and the node is declared editable
3652     * @method editNode
3653      * @for YAHOO.widget.Node
3654     */
3655     
3656     Nproto.editNode = function () {
3657         this.tree._nodeEditing(this);
3658     };
3659     
3660     
3661
3662
3663     /** Placeholder for a function that should provide the inline node label editor.
3664      *   Leaving it set to null will indicate that this node type is not editable.
3665      * It should be overridden by nodes that provide inline editing.
3666      *  The Node-specific editing element (input box, textarea or whatever) should be inserted into editorData.inputContainer.
3667      * @method fillEditorContainer
3668      * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
3669      * @return void
3670      * @for YAHOO.widget.Node
3671      */
3672     Nproto.fillEditorContainer = null;
3673
3674     
3675     /**
3676     * Node-specific destroy function to empty the contents of the inline editor panel.
3677     * This function is the worst case alternative that will purge all possible events and remove the editor contents.
3678     * Method Event.purgeElement is somewhat costly so if it can be replaced by specifc Event.removeListeners, it is better to do so.
3679     * @method destroyEditorContents
3680      * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
3681      * @for YAHOO.widget.Node
3682      */
3683     Nproto.destroyEditorContents = function (editorData) {
3684         // In the worst case, if the input editor (such as the Calendar) has no destroy method
3685         // we can only try to remove all possible events on it.
3686         Event.purgeElement(editorData.inputContainer,true);
3687         editorData.inputContainer.innerHTML = '';
3688     };
3689
3690     /**
3691     * Saves the value entered into the editor.
3692     * @method saveEditorValue
3693      * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
3694      * @return {false or none} a return of exactly false will prevent the editor from closing
3695      * @for YAHOO.widget.Node
3696      */
3697     Nproto.saveEditorValue = function (editorData) {
3698         var node = editorData.node, 
3699                         value,
3700             validator = node.tree.validator;
3701                         
3702                 value = this.getEditorValue(editorData);
3703         
3704         if (Lang.isFunction(validator)) {
3705             value = validator(value,editorData.oldValue,node);
3706             if (Lang.isUndefined(value)) { 
3707                                 return false; 
3708                         }
3709         }
3710
3711                 if (this.tree.fireEvent( 'editorSaveEvent', {
3712                         newValue:value,
3713                         oldValue:editorData.oldValue,
3714                         node:node
3715                 }) !== false) {
3716                         this.displayEditedValue(value,editorData);
3717                 }
3718         };
3719         
3720         
3721     /**
3722     * Returns the value(s) from the input element(s) .
3723     * Should be overridden by each node type.
3724     * @method getEditorValue
3725      * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
3726      * @return {any} value entered
3727      * @for YAHOO.widget.Node
3728      */
3729
3730          Nproto.getEditorValue = function (editorData) {
3731         };
3732
3733         /**
3734     * Finally displays the newly edited value(s) in the tree.
3735     * Should be overridden by each node type.
3736     * @method displayEditedValue
3737      *  @param value {any} value to be displayed and stored in the node
3738      * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
3739      * @for YAHOO.widget.Node
3740      */
3741         Nproto.displayEditedValue = function (value,editorData) {
3742         };
3743     
3744     var TNproto = YAHOO.widget.TextNode.prototype;
3745     
3746
3747
3748     /** 
3749      *  Places an &lt;input&gt;  textbox in the input container and loads the label text into it.
3750      * @method fillEditorContainer
3751      * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
3752      * @return void
3753      * @for YAHOO.widget.TextNode
3754      */
3755     TNproto.fillEditorContainer = function (editorData) {
3756     
3757         var input;
3758         // If last node edited is not of the same type as this one, delete it and fill it with our editor
3759         if (editorData.nodeType != this._type) {
3760             editorData.nodeType = this._type;
3761             editorData.saveOnEnter = true;
3762             editorData.node.destroyEditorContents(editorData);
3763
3764             editorData.inputElement = input = editorData.inputContainer.appendChild(document.createElement('input'));
3765             
3766         } else {
3767             // if the last node edited was of the same time, reuse the input element.
3768             input = editorData.inputElement;
3769         }
3770                 editorData.oldValue = this.label;
3771         input.value = this.label;
3772         input.focus();
3773         input.select();
3774     };
3775     
3776     /**
3777     * Returns the value from the input element.
3778     * Overrides Node.getEditorValue.
3779     * @method getEditorValue
3780      * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
3781      * @return {string} value entered
3782      * @for YAHOO.widget.TextNode
3783      */
3784
3785     TNproto.getEditorValue = function (editorData) {
3786         return editorData.inputElement.value;
3787         };
3788
3789         /**
3790     * Finally displays the newly edited value in the tree.
3791     * Overrides Node.displayEditedValue.
3792     * @method displayEditedValue
3793      *  @param value {string} value to be displayed and stored in the node
3794      * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
3795      * @for YAHOO.widget.TextNode
3796      */
3797         TNproto.displayEditedValue = function (value,editorData) {
3798                 var node = editorData.node;
3799                 node.label = value;
3800                 node.getLabelEl().innerHTML = value;
3801         };
3802
3803     /**
3804     * Destroys the contents of the inline editor panel.
3805     * Overrides Node.destroyEditorContent.
3806     * Since we didn't set any event listeners on this inline editor, it is more efficient to avoid the generic method in Node.
3807     * @method destroyEditorContents
3808      * @param editorData {YAHOO.widget.TreeView.editorData}  a shortcut to the static object holding editing information
3809      * @for YAHOO.widget.TextNode
3810      */
3811     TNproto.destroyEditorContents = function (editorData) {
3812         editorData.inputContainer.innerHTML = '';
3813     };
3814 })();
3815
3816 /**
3817  * A static factory class for tree view expand/collapse animations
3818  * @class TVAnim
3819  * @static
3820  */
3821 YAHOO.widget.TVAnim = function() {
3822     return {
3823         /**
3824          * Constant for the fade in animation
3825          * @property FADE_IN
3826          * @type string
3827          * @static
3828          */
3829         FADE_IN: "TVFadeIn",
3830
3831         /**
3832          * Constant for the fade out animation
3833          * @property FADE_OUT
3834          * @type string
3835          * @static
3836          */
3837         FADE_OUT: "TVFadeOut",
3838
3839         /**
3840          * Returns a ygAnim instance of the given type
3841          * @method getAnim
3842          * @param type {string} the type of animation
3843          * @param el {HTMLElement} the element to element (probably the children div)
3844          * @param callback {function} function to invoke when the animation is done.
3845          * @return {YAHOO.util.Animation} the animation instance
3846          * @static
3847          */
3848         getAnim: function(type, el, callback) {
3849             if (YAHOO.widget[type]) {
3850                 return new YAHOO.widget[type](el, callback);
3851             } else {
3852                 return null;
3853             }
3854         },
3855
3856         /**
3857          * Returns true if the specified animation class is available
3858          * @method isValid
3859          * @param type {string} the type of animation
3860          * @return {boolean} true if valid, false if not
3861          * @static
3862          */
3863         isValid: function(type) {
3864             return (YAHOO.widget[type]);
3865         }
3866     };
3867 } ();
3868
3869 /**
3870  * A 1/2 second fade-in animation.
3871  * @class TVFadeIn
3872  * @constructor
3873  * @param el {HTMLElement} the element to animate
3874  * @param callback {function} function to invoke when the animation is finished
3875  */
3876 YAHOO.widget.TVFadeIn = function(el, callback) {
3877     /**
3878      * The element to animate
3879      * @property el
3880      * @type HTMLElement
3881      */
3882     this.el = el;
3883
3884     /**
3885      * the callback to invoke when the animation is complete
3886      * @property callback
3887      * @type function
3888      */
3889     this.callback = callback;
3890
3891 };
3892
3893 YAHOO.widget.TVFadeIn.prototype = {
3894     /**
3895      * Performs the animation
3896      * @method animate
3897      */
3898     animate: function() {
3899         var tvanim = this;
3900
3901         var s = this.el.style;
3902         s.opacity = 0.1;
3903         s.filter = "alpha(opacity=10)";
3904         s.display = "";
3905
3906         var dur = 0.4; 
3907         var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur);
3908         a.onComplete.subscribe( function() { tvanim.onComplete(); } );
3909         a.animate();
3910     },
3911
3912     /**
3913      * Clean up and invoke callback
3914      * @method onComplete
3915      */
3916     onComplete: function() {
3917         this.callback();
3918     },
3919
3920     /**
3921      * toString
3922      * @method toString
3923      * @return {string} the string representation of the instance
3924      */
3925     toString: function() {
3926         return "TVFadeIn";
3927     }
3928 };
3929
3930 /**
3931  * A 1/2 second fade out animation.
3932  * @class TVFadeOut
3933  * @constructor
3934  * @param el {HTMLElement} the element to animate
3935  * @param callback {Function} function to invoke when the animation is finished
3936  */
3937 YAHOO.widget.TVFadeOut = function(el, callback) {
3938     /**
3939      * The element to animate
3940      * @property el
3941      * @type HTMLElement
3942      */
3943     this.el = el;
3944
3945     /**
3946      * the callback to invoke when the animation is complete
3947      * @property callback
3948      * @type function
3949      */
3950     this.callback = callback;
3951
3952 };
3953
3954 YAHOO.widget.TVFadeOut.prototype = {
3955     /**
3956      * Performs the animation
3957      * @method animate
3958      */
3959     animate: function() {
3960         var tvanim = this;
3961         var dur = 0.4;
3962         var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
3963         a.onComplete.subscribe( function() { tvanim.onComplete(); } );
3964         a.animate();
3965     },
3966
3967     /**
3968      * Clean up and invoke callback
3969      * @method onComplete
3970      */
3971     onComplete: function() {
3972         var s = this.el.style;
3973         s.display = "none";
3974         s.opacity = 1;
3975         s.filter = "alpha(opacity=100)";
3976         this.callback();
3977     },
3978
3979     /**
3980      * toString
3981      * @method toString
3982      * @return {string} the string representation of the instance
3983      */
3984     toString: function() {
3985         return "TVFadeOut";
3986     }
3987 };
3988
3989 YAHOO.register("treeview", YAHOO.widget.TreeView, {version: "2.8.0r4", build: "2449"});