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