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