Automatic installer.php lang files by installer_builder (20070726)
[moodle-linuxchix.git] / lib / yui / treeview / treeview.js
blobc8e82a8fbefce6112a879c3318ff905520693807
1 /*
2 Copyright (c) 2006, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 0.12.2
6 */
7 /**
8  * The treeview widget is a generic tree building tool.
9  * @module treeview
10  * @title TreeView Widget
11  * @requires yahoo, event
12  * @optional animation
13  * @namespace YAHOO.widget
14  */
16 /**
17  * Contains the tree view state data and the root node.
18  *
19  * @class TreeView
20  * @uses YAHOO.util.EventProvider
21  * @constructor
22  * @param {string|HTMLElement} id The id of the element, or the element
23  * itself that the tree will be inserted into.
24  */
25 YAHOO.widget.TreeView = function(id) {
26     if (id) { this.init(id); }
29 YAHOO.widget.TreeView.prototype = {
31     /**
32      * The id of tree container element
33      * @property id
34      * @type String
35      */
36     id: null,
38     /**
39      * The host element for this tree
40      * @property _el
41      * @private
42      */
43     _el: null,
45      /**
46      * Flat collection of all nodes in this tree
47      * @property _nodes
48      * @type Node[]
49      * @private
50      */
51     _nodes: null,
53     /**
54      * We lock the tree control while waiting for the dynamic loader to return
55      * @property locked
56      * @type boolean
57      */
58     locked: false,
60     /**
61      * The animation to use for expanding children, if any
62      * @property _expandAnim
63      * @type string
64      * @private
65      */
66     _expandAnim: null,
68     /**
69      * The animation to use for collapsing children, if any
70      * @property _collapseAnim
71      * @type string
72      * @private
73      */
74     _collapseAnim: null,
76     /**
77      * The current number of animations that are executing
78      * @property _animCount
79      * @type int
80      * @private
81      */
82     _animCount: 0,
84     /**
85      * The maximum number of animations to run at one time.
86      * @property maxAnim
87      * @type int
88      */
89     maxAnim: 2,
91     /**
92      * Sets up the animation for expanding children
93      * @method setExpandAnim
94      * @param {string} type the type of animation (acceptable values defined 
95      * in YAHOO.widget.TVAnim)
96      */
97     setExpandAnim: function(type) {
98         if (YAHOO.widget.TVAnim.isValid(type)) {
99             this._expandAnim = type;
100         }
101     },
103     /**
104      * Sets up the animation for collapsing children
105      * @method setCollapseAnim
106      * @param {string} the type of animation (acceptable values defined in 
107      * YAHOO.widget.TVAnim)
108      */
109     setCollapseAnim: function(type) {
110         if (YAHOO.widget.TVAnim.isValid(type)) {
111             this._collapseAnim = type;
112         }
113     },
115     /**
116      * Perform the expand animation if configured, or just show the
117      * element if not configured or too many animations are in progress
118      * @method animateExpand
119      * @param el {HTMLElement} the element to animate
120      * @param node {YAHOO.util.Node} the node that was expanded
121      * @return {boolean} true if animation could be invoked, false otherwise
122      */
123     animateExpand: function(el, node) {
125         if (this._expandAnim && this._animCount < this.maxAnim) {
126             // this.locked = true;
127             var tree = this;
128             var a = YAHOO.widget.TVAnim.getAnim(this._expandAnim, el, 
129                             function() { tree.expandComplete(node); });
130             if (a) { 
131                 ++this._animCount;
132                 this.fireEvent("animStart", {
133                         "node": node, 
134                         "type": "expand"
135                     });
136                 a.animate();
137             }
139             return true;
140         }
142         return false;
143     },
145     /**
146      * Perform the collapse animation if configured, or just show the
147      * element if not configured or too many animations are in progress
148      * @method animateCollapse
149      * @param el {HTMLElement} the element to animate
150      * @param node {YAHOO.util.Node} the node that was expanded
151      * @return {boolean} true if animation could be invoked, false otherwise
152      */
153     animateCollapse: function(el, node) {
155         if (this._collapseAnim && this._animCount < this.maxAnim) {
156             // this.locked = true;
157             var tree = this;
158             var a = YAHOO.widget.TVAnim.getAnim(this._collapseAnim, el, 
159                             function() { tree.collapseComplete(node); });
160             if (a) { 
161                 ++this._animCount;
162                 this.fireEvent("animStart", {
163                         "node": node, 
164                         "type": "collapse"
165                     });
166                 a.animate();
167             }
169             return true;
170         }
172         return false;
173     },
175     /**
176      * Function executed when the expand animation completes
177      * @method expandComplete
178      */
179     expandComplete: function(node) {
180         --this._animCount;
181         this.fireEvent("animComplete", {
182                 "node": node, 
183                 "type": "expand"
184             });
185         // this.locked = false;
186     },
188     /**
189      * Function executed when the collapse animation completes
190      * @method collapseComplete
191      */
192     collapseComplete: function(node) {
193         --this._animCount;
194         this.fireEvent("animComplete", {
195                 "node": node, 
196                 "type": "collapse"
197             });
198         // this.locked = false;
199     },
201     /**
202      * Initializes the tree
203      * @method init
204      * @parm {string|HTMLElement} id the id of the element that will hold the tree
205      * @private
206      */
207     init: function(id) {
209         this.id = id;
211         if ("string" !== typeof id) {
212             this._el = id;
213             this.id = this.generateId(id);
214         }
216         /**
217          * When animation is enabled, this event fires when the animation
218          * starts
219          * @event animStart
220          * @type CustomEvent
221          * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
222          * @parm {String} type the type of animation ("expand" or "collapse")
223          */
224         this.createEvent("animStart", this);
226         /**
227          * When animation is enabled, this event fires when the animation
228          * completes
229          * @event animComplete
230          * @type CustomEvent
231          * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
232          * @parm {String} type the type of animation ("expand" or "collapse")
233          */
234         this.createEvent("animComplete", this);
236         /**
237          * Fires when a node is going to be collapsed.  Return false to stop
238          * the collapse.
239          * @event collapse
240          * @type CustomEvent
241          * @param {YAHOO.widget.Node} node the node that is collapsing
242          */
243         this.createEvent("collapse", this);
245         /**
246          * Fires after a node is successfully collapsed.  This event will not fire
247          * if the "collapse" event was cancelled.
248          * @event collapseComplete
249          * @type CustomEvent
250          * @param {YAHOO.widget.Node} node the node that was collapsed
251          */
252         this.createEvent("collapseComplete", this);
254         /**
255          * Fires when a node is going to be expanded.  Return false to stop
256          * the collapse.
257          * @event expand
258          * @type CustomEvent
259          * @param {YAHOO.widget.Node} node the node that is expanding
260          */
261         this.createEvent("expand", this);
263         /**
264          * Fires after a node is successfully expanded.  This event will not fire
265          * if the "expand" event was cancelled.
266          * @event expandComplete
267          * @type CustomEvent
268          * @param {YAHOO.widget.Node} node the node that was expanded
269          */
270         this.createEvent("expandComplete", this);
272         this._nodes = [];
274         // store a global reference
275         YAHOO.widget.TreeView.trees[this.id] = this;
277         // Set up the root node
278         this.root = new YAHOO.widget.RootNode(this);
282         //YAHOO.util.Event.onContentReady(this.id, this.handleAvailable, this, true);
283         YAHOO.util.Event.on(this.id, "click", this.handleClick, this, true);
284     },
286     //handleAvailable: function() {
287         //var Event = YAHOO.util.Event;
288         //Event.on(this.id, 
289     //},
291     /**
292      * Renders the tree boilerplate and visible nodes
293      * @method draw
294      */
295     draw: function() {
296         var html = this.root.getHtml();
297         this.getEl().innerHTML = html;
298         this.firstDraw = false;
299     },
301     /**
302      * Returns the tree's host element
303      * @method getEl
304      * @return {HTMLElement} the host element
305      */
306     getEl: function() {
307         if (! this._el) {
308             this._el = document.getElementById(this.id);
309         }
310         return this._el;
311     },
313     /**
314      * Nodes register themselves with the tree instance when they are created.
315      * @method regNode
316      * @param node {Node} the node to register
317      * @private
318      */
319     regNode: function(node) {
320         this._nodes[node.index] = node;
321     },
323     /**
324      * Returns the root node of this tree
325      * @method getRoot
326      * @return {Node} the root node
327      */
328     getRoot: function() {
329         return this.root;
330     },
332     /**
333      * Configures this tree to dynamically load all child data
334      * @method setDynamicLoad
335      * @param {function} fnDataLoader the function that will be called to get the data
336      * @param iconMode {int} configures the icon that is displayed when a dynamic
337      * load node is expanded the first time without children.  By default, the 
338      * "collapse" icon will be used.  If set to 1, the leaf node icon will be
339      * displayed.
340      */
341     setDynamicLoad: function(fnDataLoader, iconMode) { 
342         this.root.setDynamicLoad(fnDataLoader, iconMode);
343     },
345     /**
346      * Expands all child nodes.  Note: this conflicts with the "multiExpand"
347      * node property.  If expand all is called in a tree with nodes that
348      * do not allow multiple siblings to be displayed, only the last sibling
349      * will be expanded.
350      * @method expandAll
351      */
352     expandAll: function() { 
353         if (!this.locked) {
354             this.root.expandAll(); 
355         }
356     },
358     /**
359      * Collapses all expanded child nodes in the entire tree.
360      * @method collapseAll
361      */
362     collapseAll: function() { 
363         if (!this.locked) {
364             this.root.collapseAll(); 
365         }
366     },
368     /**
369      * Returns a node in the tree that has the specified index (this index
370      * is created internally, so this function probably will only be used
371      * in html generated for a given node.)
372      * @method getNodeByIndex
373      * @param {int} nodeIndex the index of the node wanted
374      * @return {Node} the node with index=nodeIndex, null if no match
375      */
376     getNodeByIndex: function(nodeIndex) {
377         var n = this._nodes[nodeIndex];
378         return (n) ? n : null;
379     },
381     /**
382      * Returns a node that has a matching property and value in the data
383      * object that was passed into its constructor.
384      * @method getNodeByProperty
385      * @param {object} property the property to search (usually a string)
386      * @param {object} value the value we want to find (usuall an int or string)
387      * @return {Node} the matching node, null if no match
388      */
389     getNodeByProperty: function(property, value) {
390         for (var i in this._nodes) {
391             var n = this._nodes[i];
392             if (n.data && value == n.data[property]) {
393                 return n;
394             }
395         }
397         return null;
398     },
400     /**
401      * Returns a collection of nodes that have a matching property 
402      * and value in the data object that was passed into its constructor.  
403      * @method getNodesByProperty
404      * @param {object} property the property to search (usually a string)
405      * @param {object} value the value we want to find (usuall an int or string)
406      * @return {Array} the matching collection of nodes, null if no match
407      */
408     getNodesByProperty: function(property, value) {
409         var values = [];
410         for (var i in this._nodes) {
411             var n = this._nodes[i];
412             if (n.data && value == n.data[property]) {
413                 values.push(n);
414             }
415         }
417         return (values.length) ? values : null;
418     },
420     /**
421      * Removes the node and its children, and optionally refreshes the 
422      * branch of the tree that was affected.
423      * @method removeNode
424      * @param {Node} The node to remove
425      * @param {boolean} autoRefresh automatically refreshes branch if true
426      * @return {boolean} False is there was a problem, true otherwise.
427      */
428     removeNode: function(node, autoRefresh) { 
430         // Don't delete the root node
431         if (node.isRoot()) {
432             return false;
433         }
435         // Get the branch that we may need to refresh
436         var p = node.parent;
437         if (p.parent) {
438             p = p.parent;
439         }
441         // Delete the node and its children
442         this._deleteNode(node);
444         // Refresh the parent of the parent
445         if (autoRefresh && p && p.childrenRendered) {
446             p.refresh();
447         }
449         return true;
450     },
452     /**
453      * Deletes this nodes child collection, recursively.  Also collapses
454      * the node, and resets the dynamic load flag.  The primary use for
455      * this method is to purge a node and allow it to fetch its data
456      * dynamically again.
457      * @method removeChildren
458      * @param {Node} node the node to purge
459      */
460     removeChildren: function(node) { 
461         while (node.children.length) {
462             this._deleteNode(node.children[0]);
463         }
465         node.childrenRendered = false;
466         node.dynamicLoadComplete = false;
467         if (node.expanded) {
468             node.collapse();
469         } else {
470             node.updateIcon();
471         }
472     },
474     /**
475      * Deletes the node and recurses children
476      * @method _deleteNode
477      * @private
478      */
479     _deleteNode: function(node) { 
480         // Remove all the child nodes first
481         this.removeChildren(node);
483         // Remove the node from the tree
484         this.popNode(node);
485     },
487     /**
488      * Removes the node from the tree, preserving the child collection 
489      * to make it possible to insert the branch into another part of the 
490      * tree, or another tree.
491      * @method popNode
492      * @param {Node} the node to remove
493      */
494     popNode: function(node) { 
495         var p = node.parent;
497         // Update the parent's collection of children
498         var a = [];
500         for (var i=0, len=p.children.length;i<len;++i) {
501             if (p.children[i] != node) {
502                 a[a.length] = p.children[i];
503             }
504         }
506         p.children = a;
508         // reset the childrenRendered flag for the parent
509         p.childrenRendered = false;
511          // Update the sibling relationship
512         if (node.previousSibling) {
513             node.previousSibling.nextSibling = node.nextSibling;
514         }
516         if (node.nextSibling) {
517             node.nextSibling.previousSibling = node.previousSibling;
518         }
520         node.parent = null;
521         node.previousSibling = null;
522         node.nextSibling = null;
523         node.tree = null;
525         // Update the tree's node collection 
526         delete this._nodes[node.index];
527     },
529     /**
530      * TreeView instance toString
531      * @method toString
532      * @return {string} string representation of the tree
533      */
534     toString: function() {
535         return "TreeView " + this.id;
536     },
538     /**
539      * Generates an unique id for an element if it doesn't yet have one
540      * @method generateId
541      * @private
542      */
543     generateId: function(el) {
544         var id = el.id;
546         if (!id) {
547             id = "yui-tv-auto-id-" + YAHOO.widget.TreeView.counter;
548             ++YAHOO.widget.TreeView.counter;
549         }
551         return id;
552     },
554     /**
555      * Abstract method that is executed when a node is expanded
556      * @method onExpand
557      * @param node {Node} the node that was expanded
558      * @deprecated use treeobj.subscribe("expand") instead
559      */
560     onExpand: function(node) { },
562     /**
563      * Abstract method that is executed when a node is collapsed.
564      * @method onCollapse
565      * @param node {Node} the node that was collapsed.
566      * @deprecated use treeobj.subscribe("collapse") instead
567      */
568     onCollapse: function(node) { }
572 YAHOO.augment(YAHOO.widget.TreeView, YAHOO.util.EventProvider);
575  * Count of all nodes in all trees
576  * @property YAHOO.widget.TreeView.nodeCount
577  * @type int
578  * @static
579  */
580 YAHOO.widget.TreeView.nodeCount = 0;
583  * Global cache of tree instances
584  * @property YAHOO.widget.TreeView.trees
585  * @type Array
586  * @static
587  * @private
588  */
589 YAHOO.widget.TreeView.trees = [];
592  * Counter for generating a new unique element id
593  * @property YAHOO.widget.TreeView.counter
594  * @static
595  * @private
596  */
597 YAHOO.widget.TreeView.counter = 0;
600  * Global method for getting a tree by its id.  Used in the generated
601  * tree html.
602  * @method YAHOO.widget.TreeView.getTree
603  * @param treeId {String} the id of the tree instance
604  * @return {TreeView} the tree instance requested, null if not found.
605  * @static
606  */
607 YAHOO.widget.TreeView.getTree = function(treeId) {
608     var t = YAHOO.widget.TreeView.trees[treeId];
609     return (t) ? t : null;
613  * Global method for getting a node by its id.  Used in the generated
614  * tree html.
615  * @method YAHOO.widget.TreeView.getNode
616  * @param treeId {String} the id of the tree instance
617  * @param nodeIndex {String} the index of the node to return
618  * @return {Node} the node instance requested, null if not found
619  * @static
620  */
621 YAHOO.widget.TreeView.getNode = function(treeId, nodeIndex) {
622     var t = YAHOO.widget.TreeView.getTree(treeId);
623     return (t) ? t.getNodeByIndex(nodeIndex) : null;
627  * Add a DOM event
628  * @method YAHOO.widget.TreeView.addHandler
629  * @param el the elment to bind the handler to
630  * @param {string} sType the type of event handler
631  * @param {function} fn the callback to invoke
632  * @static
633  */
634 YAHOO.widget.TreeView.addHandler = function (el, sType, fn) {
635     if (el.addEventListener) {
636         el.addEventListener(sType, fn, false);
637     } else if (el.attachEvent) {
638         el.attachEvent("on" + sType, fn);
639     }
643  * Remove a DOM event
644  * @method YAHOO.widget.TreeView.removeHandler
645  * @param el the elment to bind the handler to
646  * @param {string} sType the type of event handler
647  * @param {function} fn the callback to invoke
648  * @static
649  */
651 YAHOO.widget.TreeView.removeHandler = function (el, sType, fn) {
652     if (el.removeEventListener) {
653         el.removeEventListener(sType, fn, false);
654     } else if (el.detachEvent) {
655         el.detachEvent("on" + sType, fn);
656     }
660  * Attempts to preload the images defined in the styles used to draw the tree by
661  * rendering off-screen elements that use the styles.
662  * @method YAHOO.widget.TreeView.preload
663  * @param {string} prefix the prefix to use to generate the names of the
664  * images to preload, default is ygtv
665  * @static
666  */
667 YAHOO.widget.TreeView.preload = function(prefix) {
668     prefix = prefix || "ygtv";
669     var styles = ["tn","tm","tmh","tp","tph","ln","lm","lmh","lp","lph","loading"];
671     var sb = [];
672     
673     for (var i = 0; i < styles.length; ++i) { 
674         sb[sb.length] = '<span class="' + prefix + styles[i] + '">&#160;</span>';
675     }
677     var f = document.createElement("div");
678     var s = f.style;
679     s.position = "absolute";
680     s.top = "-1000px";
681     s.left = "-1000px";
682     f.innerHTML = sb.join("");
684     document.body.appendChild(f);
686     YAHOO.widget.TreeView.removeHandler(window, 
687                 "load", YAHOO.widget.TreeView.preload);
691 YAHOO.widget.TreeView.addHandler(window, 
692                 "load", YAHOO.widget.TreeView.preload);
695  * The base class for all tree nodes.  The node's presentation and behavior in
696  * response to mouse events is handled in Node subclasses.
697  * @namespace YAHOO.widget
698  * @class Node
699  * @uses YAHOO.util.EventProvider
700  * @param oData {object} a string or object containing the data that will
701  * be used to render this node
702  * @param oParent {Node} this node's parent node
703  * @param expanded {boolean} the initial expanded/collapsed state
704  * @constructor
705  */
706 YAHOO.widget.Node = function(oData, oParent, expanded) {
707     if (oData) { this.init(oData, oParent, expanded); }
710 YAHOO.widget.Node.prototype = {
712     /**
713      * The index for this instance obtained from global counter in YAHOO.widget.TreeView.
714      * @property index
715      * @type int
716      */
717     index: 0,
719     /**
720      * This node's child node collection.
721      * @property children
722      * @type Node[] 
723      */
724     children: null,
726     /**
727      * Tree instance this node is part of
728      * @property tree
729      * @type TreeView
730      */
731     tree: null,
733     /**
734      * The data linked to this node.  This can be any object or primitive
735      * value, and the data can be used in getNodeHtml().
736      * @property data
737      * @type object
738      */
739     data: null,
741     /**
742      * Parent node
743      * @property parent
744      * @type Node
745      */
746     parent: null,
748     /**
749      * The depth of this node.  We start at -1 for the root node.
750      * @property depth
751      * @type int
752      */
753     depth: -1,
755     /**
756      * The href for the node's label.  If one is not specified, the href will
757      * be set so that it toggles the node.
758      * @property href
759      * @type string
760      */
761     href: null,
763     /**
764      * The label href target, defaults to current window
765      * @property target
766      * @type string
767      */
768     target: "_self",
770     /**
771      * The node's expanded/collapsed state
772      * @property expanded
773      * @type boolean
774      */
775     expanded: false,
777     /**
778      * Can multiple children be expanded at once?
779      * @property multiExpand
780      * @type boolean
781      */
782     multiExpand: true,
784     /**
785      * Should we render children for a collapsed node?  It is possible that the
786      * implementer will want to render the hidden data...  @todo verify that we 
787      * need this, and implement it if we do.
788      * @property renderHidden
789      * @type boolean
790      */
791     renderHidden: false,
793     /**
794      * This flag is set to true when the html is generated for this node's
795      * children, and set to false when new children are added.
796      * @property childrenRendered
797      * @type boolean
798      */
799     childrenRendered: false,
801     /**
802      * Dynamically loaded nodes only fetch the data the first time they are
803      * expanded.  This flag is set to true once the data has been fetched.
804      * @property dynamicLoadComplete
805      * @type boolean
806      */
807     dynamicLoadComplete: false,
809     /**
810      * This node's previous sibling
811      * @property previousSibling
812      * @type Node
813      */
814     previousSibling: null,
816     /**
817      * This node's next sibling
818      * @property nextSibling
819      * @type Node
820      */
821     nextSibling: null,
823     /**
824      * We can set the node up to call an external method to get the child
825      * data dynamically.
826      * @property _dynLoad
827      * @type boolean
828      * @private
829      */
830     _dynLoad: false,
832     /**
833      * Function to execute when we need to get this node's child data.
834      * @property dataLoader
835      * @type function
836      */
837     dataLoader: null,
839     /**
840      * This is true for dynamically loading nodes while waiting for the
841      * callback to return.
842      * @property isLoading
843      * @type boolean
844      */
845     isLoading: false,
847     /**
848      * The toggle/branch icon will not show if this is set to false.  This
849      * could be useful if the implementer wants to have the child contain
850      * extra info about the parent, rather than an actual node.
851      * @property hasIcon
852      * @type boolean
853      */
854     hasIcon: true,
856     /**
857      * Used to configure what happens when a dynamic load node is expanded
858      * and we discover that it does not have children.  By default, it is
859      * treated as if it still could have children (plus/minus icon).  Set
860      * iconMode to have it display like a leaf node instead.
861      * @property iconMode
862      * @type int
863      */
864     iconMode: 0,
866     /**
867      * Specifies whether or not the content area of the node should be allowed
868      * to wrap.
869      * @property nowrap
870      * @type boolean
871      * @default true
872      */
873     nowrap: false,
875     /**
876      * The node type
877      * @property _type
878      * @private
879      */
880     _type: "Node",
882     /*
883     spacerPath: "http://us.i1.yimg.com/us.yimg.com/i/space.gif",
884     expandedText: "Expanded",
885     collapsedText: "Collapsed",
886     loadingText: "Loading",
887     */
889     /**
890      * Initializes this node, gets some of the properties from the parent
891      * @method init
892      * @param oData {object} a string or object containing the data that will
893      * be used to render this node
894      * @param oParent {Node} this node's parent node
895      * @param expanded {boolean} the initial expanded/collapsed state
896      */
897     init: function(oData, oParent, expanded) {
899         this.data       = oData;
900         this.children   = [];
901         this.index      = YAHOO.widget.TreeView.nodeCount;
902         ++YAHOO.widget.TreeView.nodeCount;
903         this.expanded   = expanded;
905         /**
906          * The parentChange event is fired when a parent element is applied
907          * to the node.  This is useful if you need to apply tree-level
908          * properties to a tree that need to happen if a node is moved from
909          * one tree to another.
910          *
911          * @event parentChange
912          * @type CustomEvent
913          */
914         this.createEvent("parentChange", this);
916         // oParent should never be null except when we create the root node.
917         if (oParent) {
918             oParent.appendChild(this);
919         }
920     },
922     /**
923      * Certain properties for the node cannot be set until the parent
924      * is known. This is called after the node is inserted into a tree.
925      * the parent is also applied to this node's children in order to
926      * make it possible to move a branch from one tree to another.
927      * @method applyParent
928      * @param {Node} parentNode this node's parent node
929      * @return {boolean} true if the application was successful
930      */
931     applyParent: function(parentNode) {
932         if (!parentNode) {
933             return false;
934         }
936         this.tree   = parentNode.tree;
937         this.parent = parentNode;
938         this.depth  = parentNode.depth + 1;
940         if (!this.href) {
941             this.href = "javascript:" + this.getToggleLink();
942         }
944         if (! this.multiExpand) {
945             this.multiExpand = parentNode.multiExpand;
946         }
948         this.tree.regNode(this);
949         parentNode.childrenRendered = false;
951         // cascade update existing children
952         for (var i=0, len=this.children.length;i<len;++i) {
953             this.children[i].applyParent(this);
954         }
956         this.fireEvent("parentChange");
958         return true;
959     },
961     /**
962      * Appends a node to the child collection.
963      * @method appendChild
964      * @param childNode {Node} the new node
965      * @return {Node} the child node
966      * @private
967      */
968     appendChild: function(childNode) {
969         if (this.hasChildren()) {
970             var sib = this.children[this.children.length - 1];
971             sib.nextSibling = childNode;
972             childNode.previousSibling = sib;
973         }
974         this.children[this.children.length] = childNode;
975         childNode.applyParent(this);
977         return childNode;
978     },
980     /**
981      * Appends this node to the supplied node's child collection
982      * @method appendTo
983      * @param parentNode {Node} the node to append to.
984      * @return {Node} The appended node
985      */
986     appendTo: function(parentNode) {
987         return parentNode.appendChild(this);
988     },
990     /**
991     * Inserts this node before this supplied node
992     * @method insertBefore
993     * @param node {Node} the node to insert this node before
994     * @return {Node} the inserted node
995     */
996     insertBefore: function(node) {
997         var p = node.parent;
998         if (p) {
1000             if (this.tree) {
1001                 this.tree.popNode(this);
1002             }
1004             var refIndex = node.isChildOf(p);
1005             p.children.splice(refIndex, 0, this);
1006             if (node.previousSibling) {
1007                 node.previousSibling.nextSibling = this;
1008             }
1009             this.previousSibling = node.previousSibling;
1010             this.nextSibling = node;
1011             node.previousSibling = this;
1013             this.applyParent(p);
1014         }
1016         return this;
1017     },
1019     /**
1020     * Inserts this node after the supplied node
1021     * @method insertAfter
1022     * @param node {Node} the node to insert after
1023     * @return {Node} the inserted node
1024     */
1025     insertAfter: function(node) {
1026         var p = node.parent;
1027         if (p) {
1029             if (this.tree) {
1030                 this.tree.popNode(this);
1031             }
1033             var refIndex = node.isChildOf(p);
1035             if (!node.nextSibling) {
1036                 return this.appendTo(p);
1037             }
1039             p.children.splice(refIndex + 1, 0, this);
1041             node.nextSibling.previousSibling = this;
1042             this.previousSibling = node;
1043             this.nextSibling = node.nextSibling;
1044             node.nextSibling = this;
1046             this.applyParent(p);
1047         }
1049         return this;
1050     },
1052     /**
1053     * Returns true if the Node is a child of supplied Node
1054     * @method isChildOf
1055     * @param parentNode {Node} the Node to check
1056     * @return {boolean} The node index if this Node is a child of 
1057     *                   supplied Node, else -1.
1058     * @private
1059     */
1060     isChildOf: function(parentNode) {
1061         if (parentNode && parentNode.children) {
1062             for (var i=0, len=parentNode.children.length; i<len ; ++i) {
1063                 if (parentNode.children[i] === this) {
1064                     return i;
1065                 }
1066             }
1067         }
1069         return -1;
1070     },
1072     /**
1073      * Returns a node array of this node's siblings, null if none.
1074      * @method getSiblings
1075      * @return Node[]
1076      */
1077     getSiblings: function() {
1078         return this.parent.children;
1079     },
1081     /**
1082      * Shows this node's children
1083      * @method showChildren
1084      */
1085     showChildren: function() {
1086         if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
1087             if (this.hasChildren()) {
1088                 this.getChildrenEl().style.display = "";
1089             }
1090         }
1091     },
1093     /**
1094      * Hides this node's children
1095      * @method hideChildren
1096      */
1097     hideChildren: function() {
1099         if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
1100             this.getChildrenEl().style.display = "none";
1101         }
1102     },
1104     /**
1105      * Returns the id for this node's container div
1106      * @method getElId
1107      * @return {string} the element id
1108      */
1109     getElId: function() {
1110         return "ygtv" + this.index;
1111     },
1113     /**
1114      * Returns the id for this node's children div
1115      * @method getChildrenElId
1116      * @return {string} the element id for this node's children div
1117      */
1118     getChildrenElId: function() {
1119         return "ygtvc" + this.index;
1120     },
1122     /**
1123      * Returns the id for this node's toggle element
1124      * @method getToggleElId
1125      * @return {string} the toggel element id
1126      */
1127     getToggleElId: function() {
1128         return "ygtvt" + this.index;
1129     },
1131     /*
1132      * Returns the id for this node's spacer image.  The spacer is positioned
1133      * over the toggle and provides feedback for screen readers.
1134      * @method getSpacerId
1135      * @return {string} the id for the spacer image
1136      */
1137     /*
1138     getSpacerId: function() {
1139         return "ygtvspacer" + this.index;
1140     }, 
1141     */
1143     /**
1144      * Returns this node's container html element
1145      * @method getEl
1146      * @return {HTMLElement} the container html element
1147      */
1148     getEl: function() {
1149         return document.getElementById(this.getElId());
1150     },
1152     /**
1153      * Returns the div that was generated for this node's children
1154      * @method getChildrenEl
1155      * @return {HTMLElement} this node's children div
1156      */
1157     getChildrenEl: function() {
1158         return document.getElementById(this.getChildrenElId());
1159     },
1161     /**
1162      * Returns the element that is being used for this node's toggle.
1163      * @method getToggleEl
1164      * @return {HTMLElement} this node's toggle html element
1165      */
1166     getToggleEl: function() {
1167         return document.getElementById(this.getToggleElId());
1168     },
1170     /*
1171      * Returns the element that is being used for this node's spacer.
1172      * @method getSpacer
1173      * @return {HTMLElement} this node's spacer html element
1174      */
1175     /*
1176     getSpacer: function() {
1177         return document.getElementById( this.getSpacerId() ) || {};
1178     },
1179     */
1181     /*
1182     getStateText: function() {
1183         if (this.isLoading) {
1184             return this.loadingText;
1185         } else if (this.hasChildren(true)) {
1186             if (this.expanded) {
1187                 return this.expandedText;
1188             } else {
1189                 return this.collapsedText;
1190             }
1191         } else {
1192             return "";
1193         }
1194     },
1195     */
1197     /**
1198      * Generates the link that will invoke this node's toggle method
1199      * @method getToggleLink
1200      * @return {string} the javascript url for toggling this node
1201      */
1202     getToggleLink: function() {
1203         return "YAHOO.widget.TreeView.getNode(\'" + this.tree.id + "\'," + 
1204             this.index + ").toggle()";
1205     },
1207     /**
1208      * Hides this nodes children (creating them if necessary), changes the
1209      * @method collapse
1210      * toggle style.
1211      */
1212     collapse: function() {
1213         // Only collapse if currently expanded
1214         if (!this.expanded) { return; }
1216         // fire the collapse event handler
1217         var ret = this.tree.onCollapse(this);
1219         if (false === ret) {
1220             return;
1221         }
1223         ret = this.tree.fireEvent("collapse", this);
1225         if (false === ret) {
1226             return;
1227         }
1229         if (!this.getEl()) {
1230             this.expanded = false;
1231         } else {
1232             // hide the child div
1233             this.hideChildren();
1234             this.expanded = false;
1236             this.updateIcon();
1237         }
1239         // this.getSpacer().title = this.getStateText();
1241         ret = this.tree.fireEvent("collapseComplete", this);
1243     },
1245     /**
1246      * Shows this nodes children (creating them if necessary), changes the
1247      * toggle style, and collapses its siblings if multiExpand is not set.
1248      * @method expand
1249      */
1250     expand: function() {
1251         // Only expand if currently collapsed.
1252         if (this.expanded) { return; }
1254         // fire the expand event handler
1255         var ret = this.tree.onExpand(this);
1257         if (false === ret) {
1258             return;
1259         }
1260         
1261         ret = this.tree.fireEvent("expand", this);
1263         if (false === ret) {
1264             return;
1265         }
1267         if (!this.getEl()) {
1268             this.expanded = true;
1269             return;
1270         }
1272         if (! this.childrenRendered) {
1273             this.getChildrenEl().innerHTML = this.renderChildren();
1274         } else {
1275         }
1277         this.expanded = true;
1279         this.updateIcon();
1281         // this.getSpacer().title = this.getStateText();
1283         // We do an extra check for children here because the lazy
1284         // load feature can expose nodes that have no children.
1286         // if (!this.hasChildren()) {
1287         if (this.isLoading) {
1288             this.expanded = false;
1289             return;
1290         }
1292         if (! this.multiExpand) {
1293             var sibs = this.getSiblings();
1294             for (var i=0; i<sibs.length; ++i) {
1295                 if (sibs[i] != this && sibs[i].expanded) { 
1296                     sibs[i].collapse(); 
1297                 }
1298             }
1299         }
1301         this.showChildren();
1303         ret = this.tree.fireEvent("expandComplete", this);
1304     },
1306     updateIcon: function() {
1307         if (this.hasIcon) {
1308             var el = this.getToggleEl();
1309             if (el) {
1310                 el.className = this.getStyle();
1311             }
1312         }
1313     },
1315     /**
1316      * Returns the css style name for the toggle
1317      * @method getStyle
1318      * @return {string} the css class for this node's toggle
1319      */
1320     getStyle: function() {
1321         if (this.isLoading) {
1322             return "ygtvloading";
1323         } else {
1324             // location top or bottom, middle nodes also get the top style
1325             var loc = (this.nextSibling) ? "t" : "l";
1327             // type p=plus(expand), m=minus(collapase), n=none(no children)
1328             var type = "n";
1329             if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) {
1330             // if (this.hasChildren(true)) {
1331                 type = (this.expanded) ? "m" : "p";
1332             }
1334             return "ygtv" + loc + type;
1335         }
1336     },
1338     /**
1339      * Returns the hover style for the icon
1340      * @return {string} the css class hover state
1341      * @method getHoverStyle
1342      */
1343     getHoverStyle: function() { 
1344         var s = this.getStyle();
1345         if (this.hasChildren(true) && !this.isLoading) { 
1346             s += "h"; 
1347         }
1348         return s;
1349     },
1351     /**
1352      * Recursively expands all of this node's children.
1353      * @method expandAll
1354      */
1355     expandAll: function() { 
1356         for (var i=0;i<this.children.length;++i) {
1357             var c = this.children[i];
1358             if (c.isDynamic()) {
1359                 alert("Not supported (lazy load + expand all)");
1360                 break;
1361             } else if (! c.multiExpand) {
1362                 alert("Not supported (no multi-expand + expand all)");
1363                 break;
1364             } else {
1365                 c.expand();
1366                 c.expandAll();
1367             }
1368         }
1369     },
1371     /**
1372      * Recursively collapses all of this node's children.
1373      * @method collapseAll
1374      */
1375     collapseAll: function() { 
1376         for (var i=0;i<this.children.length;++i) {
1377             this.children[i].collapse();
1378             this.children[i].collapseAll();
1379         }
1380     },
1382     /**
1383      * Configures this node for dynamically obtaining the child data
1384      * when the node is first expanded.  Calling it without the callback
1385      * will turn off dynamic load for the node.
1386      * @method setDynamicLoad
1387      * @param fmDataLoader {function} the function that will be used to get the data.
1388      * @param iconMode {int} configures the icon that is displayed when a dynamic
1389      * load node is expanded the first time without children.  By default, the 
1390      * "collapse" icon will be used.  If set to 1, the leaf node icon will be
1391      * displayed.
1392      */
1393     setDynamicLoad: function(fnDataLoader, iconMode) { 
1394         if (fnDataLoader) {
1395             this.dataLoader = fnDataLoader;
1396             this._dynLoad = true;
1397         } else {
1398             this.dataLoader = null;
1399             this._dynLoad = false;
1400         }
1402         if (iconMode) {
1403             this.iconMode = iconMode;
1404         }
1405     },
1407     /**
1408      * Evaluates if this node is the root node of the tree
1409      * @method isRoot
1410      * @return {boolean} true if this is the root node
1411      */
1412     isRoot: function() { 
1413         return (this == this.tree.root);
1414     },
1416     /**
1417      * Evaluates if this node's children should be loaded dynamically.  Looks for
1418      * the property both in this instance and the root node.  If the tree is
1419      * defined to load all children dynamically, the data callback function is
1420      * defined in the root node
1421      * @method isDynamic
1422      * @return {boolean} true if this node's children are to be loaded dynamically
1423      */
1424     isDynamic: function() { 
1425         var lazy = (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
1426         return lazy;
1427     },
1429     /**
1430      * Returns the current icon mode.  This refers to the way childless dynamic
1431      * load nodes appear.
1432      * @method getIconMode
1433      * @return {int} 0 for collapse style, 1 for leaf node style
1434      */
1435     getIconMode: function() {
1436         return (this.iconMode || this.tree.root.iconMode);
1437     },
1439     /**
1440      * Checks if this node has children.  If this node is lazy-loading and the
1441      * children have not been rendered, we do not know whether or not there
1442      * are actual children.  In most cases, we need to assume that there are
1443      * children (for instance, the toggle needs to show the expandable 
1444      * presentation state).  In other times we want to know if there are rendered
1445      * children.  For the latter, "checkForLazyLoad" should be false.
1446      * @method hasChildren
1447      * @param checkForLazyLoad {boolean} should we check for unloaded children?
1448      * @return {boolean} true if this has children or if it might and we are
1449      * checking for this condition.
1450      */
1451     hasChildren: function(checkForLazyLoad) { 
1452         return ( this.children.length > 0 || 
1453                 (checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete) );
1454     },
1456     /**
1457      * Expands if node is collapsed, collapses otherwise.
1458      * @method toggle
1459      */
1460     toggle: function() {
1461         if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
1462             if (this.expanded) { this.collapse(); } else { this.expand(); }
1463         }
1464     },
1466     /**
1467      * Returns the markup for this node and its children.
1468      * @method getHtml
1469      * @return {string} the markup for this node and its expanded children.
1470      */
1471     getHtml: function() {
1473         this.childrenRendered = false;
1475         var sb = [];
1476         sb[sb.length] = '<div class="ygtvitem" id="' + this.getElId() + '">';
1477         sb[sb.length] = this.getNodeHtml();
1478         sb[sb.length] = this.getChildrenHtml();
1479         sb[sb.length] = '</div>';
1480         return sb.join("");
1481     },
1483     /**
1484      * Called when first rendering the tree.  We always build the div that will
1485      * contain this nodes children, but we don't render the children themselves
1486      * unless this node is expanded.
1487      * @method getChildrenHtml
1488      * @return {string} the children container div html and any expanded children
1489      * @private
1490      */
1491     getChildrenHtml: function() {
1493         var sb = [];
1494         sb[sb.length] = '<div class="ygtvchildren"';
1495         sb[sb.length] = ' id="' + this.getChildrenElId() + '"';
1496         if (!this.expanded) {
1497             sb[sb.length] = ' style="display:none;"';
1498         }
1499         sb[sb.length] = '>';
1501         // Don't render the actual child node HTML unless this node is expanded.
1502         if ( (this.hasChildren(true) && this.expanded) ||
1503                 (this.renderHidden && !this.isDynamic()) ) {
1504             sb[sb.length] = this.renderChildren();
1505         }
1507         sb[sb.length] = '</div>';
1509         return sb.join("");
1510     },
1512     /**
1513      * Generates the markup for the child nodes.  This is not done until the node
1514      * is expanded.
1515      * @method renderChildren
1516      * @return {string} the html for this node's children
1517      * @private
1518      */
1519     renderChildren: function() {
1522         var node = this;
1524         if (this.isDynamic() && !this.dynamicLoadComplete) {
1525             this.isLoading = true;
1526             this.tree.locked = true;
1528             if (this.dataLoader) {
1530                 setTimeout( 
1531                     function() {
1532                         node.dataLoader(node, 
1533                             function() { 
1534                                 node.loadComplete(); 
1535                             });
1536                     }, 10);
1537                 
1538             } else if (this.tree.root.dataLoader) {
1540                 setTimeout( 
1541                     function() {
1542                         node.tree.root.dataLoader(node, 
1543                             function() { 
1544                                 node.loadComplete(); 
1545                             });
1546                     }, 10);
1548             } else {
1549                 return "Error: data loader not found or not specified.";
1550             }
1552             return "";
1554         } else {
1555             return this.completeRender();
1556         }
1557     },
1559     /**
1560      * Called when we know we have all the child data.
1561      * @method completeRender
1562      * @return {string} children html
1563      */
1564     completeRender: function() {
1565         var sb = [];
1567         for (var i=0; i < this.children.length; ++i) {
1568             // this.children[i].childrenRendered = false;
1569             sb[sb.length] = this.children[i].getHtml();
1570         }
1571         
1572         this.childrenRendered = true;
1574         return sb.join("");
1575     },
1577     /**
1578      * Load complete is the callback function we pass to the data provider
1579      * in dynamic load situations.
1580      * @method loadComplete
1581      */
1582     loadComplete: function() {
1583         this.getChildrenEl().innerHTML = this.completeRender();
1584         this.dynamicLoadComplete = true;
1585         this.isLoading = false;
1586         this.expand();
1587         this.tree.locked = false;
1588     },
1590     /**
1591      * Returns this node's ancestor at the specified depth.
1592      * @method getAncestor
1593      * @param {int} depth the depth of the ancestor.
1594      * @return {Node} the ancestor
1595      */
1596     getAncestor: function(depth) {
1597         if (depth >= this.depth || depth < 0)  {
1598             return null;
1599         }
1601         var p = this.parent;
1602         
1603         while (p.depth > depth) {
1604             p = p.parent;
1605         }
1607         return p;
1608     },
1610     /**
1611      * Returns the css class for the spacer at the specified depth for
1612      * this node.  If this node's ancestor at the specified depth
1613      * has a next sibling the presentation is different than if it
1614      * does not have a next sibling
1615      * @method getDepthStyle
1616      * @param {int} depth the depth of the ancestor.
1617      * @return {string} the css class for the spacer
1618      */
1619     getDepthStyle: function(depth) {
1620         return (this.getAncestor(depth).nextSibling) ? 
1621             "ygtvdepthcell" : "ygtvblankdepthcell";
1622     },
1624     /**
1625      * Get the markup for the node.  This is designed to be overrided so that we can
1626      * support different types of nodes.
1627      * @method getNodeHtml
1628      * @return {string} The HTML that will render this node.
1629      */
1630     getNodeHtml: function() { 
1631         return ""; 
1632     },
1634     /**
1635      * Regenerates the html for this node and its children.  To be used when the
1636      * node is expanded and new children have been added.
1637      * @method refresh
1638      */
1639     refresh: function() {
1640         // this.loadComplete();
1641         this.getChildrenEl().innerHTML = this.completeRender();
1643         if (this.hasIcon) {
1644             var el = this.getToggleEl();
1645             if (el) {
1646                 el.className = this.getStyle();
1647             }
1648         }
1649     },
1651     /**
1652      * Node toString
1653      * @method toString
1654      * @return {string} string representation of the node
1655      */
1656     toString: function() {
1657         return "Node (" + this.index + ")";
1658     }
1662 YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
1665  * A custom YAHOO.widget.Node that handles the unique nature of 
1666  * the virtual, presentationless root node.
1667  * @namespace YAHOO.widget
1668  * @class RootNode
1669  * @extends YAHOO.widget.Node
1670  * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
1671  * @constructor
1672  */
1673 YAHOO.widget.RootNode = function(oTree) {
1674         // Initialize the node with null params.  The root node is a
1675         // special case where the node has no presentation.  So we have
1676         // to alter the standard properties a bit.
1677         this.init(null, null, true);
1678         
1679         /*
1680          * For the root node, we get the tree reference from as a param
1681          * to the constructor instead of from the parent element.
1682          */
1683         this.tree = oTree;
1686 YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
1687     
1688     // overrides YAHOO.widget.Node
1689     getNodeHtml: function() { 
1690         return ""; 
1691     },
1693     toString: function() { 
1694         return "RootNode";
1695     },
1697     loadComplete: function() { 
1698         this.tree.draw();
1699     },
1701     collapse: function() {},
1702     expand: function() {}
1706  * The default node presentation.  The first parameter should be
1707  * either a string that will be used as the node's label, or an object
1708  * that has a string propery called label.  By default, the clicking the
1709  * label will toggle the expanded/collapsed state of the node.  By
1710  * changing the href property of the instance, this behavior can be
1711  * changed so that the label will go to the specified href.
1712  * @namespace YAHOO.widget
1713  * @class TextNode
1714  * @extends YAHOO.widget.Node
1715  * @constructor
1716  * @param oData {object} a string or object containing the data that will
1717  * be used to render this node
1718  * @param oParent {YAHOO.widget.Node} this node's parent node
1719  * @param expanded {boolean} the initial expanded/collapsed state
1720  */
1721 YAHOO.widget.TextNode = function(oData, oParent, expanded) {
1723     if (oData) { 
1724         this.init(oData, oParent, expanded);
1725         this.setUpLabel(oData);
1726     }
1730 YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
1731     
1732     /**
1733      * The CSS class for the label href.  Defaults to ygtvlabel, but can be
1734      * overridden to provide a custom presentation for a specific node.
1735      * @property labelStyle
1736      * @type string
1737      */
1738     labelStyle: "ygtvlabel",
1740     /**
1741      * The derived element id of the label for this node
1742      * @property labelElId
1743      * @type string
1744      */
1745     labelElId: null,
1747     /**
1748      * The text for the label.  It is assumed that the oData parameter will
1749      * either be a string that will be used as the label, or an object that
1750      * has a property called "label" that we will use.
1751      * @property label
1752      * @type string
1753      */
1754     label: null,
1756     textNodeParentChange: function() {
1758         /**
1759          * Custom event that is fired when the text node label is clicked.  The
1760          * custom event is defined on the tree instance, so there is a single
1761          * event that handles all nodes in the tree.  The node clicked is 
1762          * provided as an argument
1763          *
1764          * @event labelClick
1765          * @for YAHOO.widget.TreeView
1766          * @param {YAHOO.widget.Node} node the node clicked
1767          */
1768         if (this.tree && !this.tree.hasEvent("labelClick")) {
1769             this.tree.createEvent("labelClick", this.tree);
1770         }
1771        
1772     },
1774     /**
1775      * Sets up the node label
1776      * @method setUpLabel
1777      * @param oData string containing the label, or an object with a label property
1778      */
1779     setUpLabel: function(oData) { 
1780         
1781         // set up the custom event on the tree
1782         this.textNodeParentChange();
1783         this.subscribe("parentChange", this.textNodeParentChange);
1785         if (typeof oData == "string") {
1786             oData = { label: oData };
1787         }
1788         this.label = oData.label;
1789         
1790         // update the link
1791         if (oData.href) {
1792             this.href = oData.href;
1793         }
1795         // set the target
1796         if (oData.target) {
1797             this.target = oData.target;
1798         }
1800         if (oData.style) {
1801             this.labelStyle = oData.style;
1802         }
1804         this.labelElId = "ygtvlabelel" + this.index;
1805     },
1807     /**
1808      * Returns the label element
1809      * @for YAHOO.widget.TextNode
1810      * @method getLabelEl
1811      * @return {object} the element
1812      */
1813     getLabelEl: function() { 
1814         return document.getElementById(this.labelElId);
1815     },
1817     // overrides YAHOO.widget.Node
1818     getNodeHtml: function() { 
1819         var sb = [];
1821         sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
1822         sb[sb.length] = '<tr>';
1823         
1824         for (var i=0;i<this.depth;++i) {
1825             //sb[sb.length] = '<td><div class="' + this.getDepthStyle(i) + '">&#160;</div></td>';
1826             //sb[sb.length] = '<td><div class="' + this.getDepthStyle(i) + '"></div></td>';
1827             sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
1828         }
1830         var getNode = 'YAHOO.widget.TreeView.getNode(\'' +
1831                         this.tree.id + '\',' + this.index + ')';
1833         sb[sb.length] = '<td';
1834         // sb[sb.length] = ' onselectstart="return false"';
1835         sb[sb.length] = ' id="' + this.getToggleElId() + '"';
1836         sb[sb.length] = ' class="' + this.getStyle() + '"';
1837         if (this.hasChildren(true)) {
1838             sb[sb.length] = ' onmouseover="this.className=';
1839             sb[sb.length] = getNode + '.getHoverStyle()"';
1840             sb[sb.length] = ' onmouseout="this.className=';
1841             sb[sb.length] = getNode + '.getStyle()"';
1842         }
1843         sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '">';
1845         sb[sb.length] = '<div class="ygtvspacer">';
1847         /*
1848         sb[sb.length] = '<img id="' + this.getSpacerId() + '"';
1849         sb[sb.length] = ' alt=""';
1850         sb[sb.length] = ' tabindex=0';
1851         sb[sb.length] = ' src="' + this.spacerPath + '"';
1852         sb[sb.length] = ' title="' + this.getStateText() + '"';
1853         sb[sb.length] = ' class="ygtvspacer"';
1854         // sb[sb.length] = ' onkeypress="return ' + getNode + '".onKeyPress()"';
1855         sb[sb.length] = ' />';
1856         */
1858         //sb[sb.length] = '&#160;';
1860         sb[sb.length] = '</div>';
1861         sb[sb.length] = '</td>';
1862         sb[sb.length] = '<td ';
1863         sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
1864         sb[sb.length] = ' >';
1865         sb[sb.length] = '<a';
1866         sb[sb.length] = ' id="' + this.labelElId + '"';
1867         sb[sb.length] = ' class="' + this.labelStyle + '"';
1868         sb[sb.length] = ' href="' + this.href + '"';
1869         sb[sb.length] = ' target="' + this.target + '"';
1870         sb[sb.length] = ' onclick="return ' + getNode + '.onLabelClick(' + getNode +')"';
1871         if (this.hasChildren(true)) {
1872             sb[sb.length] = ' onmouseover="document.getElementById(\'';
1873             sb[sb.length] = this.getToggleElId() + '\').className=';
1874             sb[sb.length] = getNode + '.getHoverStyle()"';
1875             sb[sb.length] = ' onmouseout="document.getElementById(\'';
1876             sb[sb.length] = this.getToggleElId() + '\').className=';
1877             sb[sb.length] = getNode + '.getStyle()"';
1878         }
1879         sb[sb.length] = ' >';
1880         sb[sb.length] = this.label;
1881         sb[sb.length] = '</a>';
1882         sb[sb.length] = '</td>';
1883         sb[sb.length] = '</tr>';
1884         sb[sb.length] = '</table>';
1886         return sb.join("");
1887     },
1889     /**
1890      * Executed when the label is clicked.  Fires the labelClick custom event.
1891      * @method onLabelClick
1892      * @param me {Node} this node
1893      * @scope the anchor tag clicked
1894      * @return false to cancel the anchor click
1895      */
1896     onLabelClick: function(me) { 
1897         return me.tree.fireEvent("labelClick", me);
1898         //return true;
1899     },
1901     toString: function() { 
1902         return "TextNode (" + this.index + ") " + this.label;
1903     }
1907  * A menu-specific implementation that differs from TextNode in that only 
1908  * one sibling can be expanded at a time.
1909  * @namespace YAHOO.widget
1910  * @class MenuNode
1911  * @extends YAHOO.widget.TextNode
1912  * @param oData {object} a string or object containing the data that will
1913  * be used to render this node
1914  * @param oParent {YAHOO.widget.Node} this node's parent node
1915  * @param expanded {boolean} the initial expanded/collapsed state
1916  * @constructor
1917  */
1918 YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
1919         if (oData) { 
1920                 this.init(oData, oParent, expanded);
1921                 this.setUpLabel(oData);
1922         }
1924     /*
1925      * Menus usually allow only one branch to be open at a time.
1926      */
1927         this.multiExpand = false;
1932 YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
1934     toString: function() { 
1935         return "MenuNode (" + this.index + ") " + this.label;
1936     }
1940  * This implementation takes either a string or object for the
1941  * oData argument.  If is it a string, we will use it for the display
1942  * of this node (and it can contain any html code).  If the parameter
1943  * is an object, we look for a parameter called "html" that will be
1944  * used for this node's display.
1945  * @namespace YAHOO.widget
1946  * @class HTMLNode
1947  * @extends YAHOO.widget.Node
1948  * @constructor
1949  * @param oData {object} a string or object containing the data that will
1950  * be used to render this node
1951  * @param oParent {YAHOO.widget.Node} this node's parent node
1952  * @param expanded {boolean} the initial expanded/collapsed state
1953  * @param hasIcon {boolean} specifies whether or not leaf nodes should
1954  * have an icon
1955  */
1956 YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
1957     if (oData) { 
1958         this.init(oData, oParent, expanded);
1959         this.initContent(oData, hasIcon);
1960     }
1963 YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, {
1965     /**
1966      * The CSS class for the html content container.  Defaults to ygtvhtml, but 
1967      * can be overridden to provide a custom presentation for a specific node.
1968      * @property contentStyle
1969      * @type string
1970      */
1971     contentStyle: "ygtvhtml",
1973     /**
1974      * The generated id that will contain the data passed in by the implementer.
1975      * @property contentElId
1976      * @type string
1977      */
1978     contentElId: null,
1980     /**
1981      * The HTML content to use for this node's display
1982      * @property content
1983      * @type string
1984      */
1985     content: null,
1987     /**
1988      * Sets up the node label
1989      * @property initContent
1990      * @param {object} An html string or object containing an html property
1991      * @param {boolean} hasIcon determines if the node will be rendered with an
1992      * icon or not
1993      */
1994     initContent: function(oData, hasIcon) { 
1995         if (typeof oData == "string") {
1996             oData = { html: oData };
1997         }
1999         this.html = oData.html;
2000         this.contentElId = "ygtvcontentel" + this.index;
2001         this.hasIcon = hasIcon;
2003     },
2005     /**
2006      * Returns the outer html element for this node's content
2007      * @method getContentEl
2008      * @return {HTMLElement} the element
2009      */
2010     getContentEl: function() { 
2011         return document.getElementById(this.contentElId);
2012     },
2014     // overrides YAHOO.widget.Node
2015     getNodeHtml: function() { 
2016         var sb = [];
2018         sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
2019         sb[sb.length] = '<tr>';
2020         
2021         for (var i=0;i<this.depth;++i) {
2022             //sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '">&#160;</td>';
2023             sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
2024         }
2026         if (this.hasIcon) {
2027             sb[sb.length] = '<td';
2028             sb[sb.length] = ' id="' + this.getToggleElId() + '"';
2029             sb[sb.length] = ' class="' + this.getStyle() + '"';
2030             sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '"';
2031             if (this.hasChildren(true)) {
2032                 sb[sb.length] = ' onmouseover="this.className=';
2033                 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
2034                 sb[sb.length] = this.tree.id + '\',' + this.index +  ').getHoverStyle()"';
2035                 sb[sb.length] = ' onmouseout="this.className=';
2036                 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
2037                 sb[sb.length] = this.tree.id + '\',' + this.index +  ').getStyle()"';
2038             }
2039             //sb[sb.length] = '>&#160;</td>';
2040             sb[sb.length] = '><div class="ygtvspacer"></div></td>';
2041         }
2043         sb[sb.length] = '<td';
2044         sb[sb.length] = ' id="' + this.contentElId + '"';
2045         sb[sb.length] = ' class="' + this.contentStyle + '"';
2046         sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
2047         sb[sb.length] = ' >';
2048         sb[sb.length] = this.html;
2049         sb[sb.length] = '</td>';
2050         sb[sb.length] = '</tr>';
2051         sb[sb.length] = '</table>';
2053         return sb.join("");
2054     },
2056     toString: function() { 
2057         return "HTMLNode (" + this.index + ")";
2058     }
2062  * A static factory class for tree view expand/collapse animations
2063  * @class TVAnim
2064  * @static
2065  */
2066 YAHOO.widget.TVAnim = function() {
2067     return {
2068         /**
2069          * Constant for the fade in animation
2070          * @property FADE_IN
2071          * @type string
2072          * @static
2073          */
2074         FADE_IN: "TVFadeIn",
2076         /**
2077          * Constant for the fade out animation
2078          * @property FADE_OUT
2079          * @type string
2080          * @static
2081          */
2082         FADE_OUT: "TVFadeOut",
2084         /**
2085          * Returns a ygAnim instance of the given type
2086          * @method getAnim
2087          * @param type {string} the type of animation
2088          * @param el {HTMLElement} the element to element (probably the children div)
2089          * @param callback {function} function to invoke when the animation is done.
2090          * @return {YAHOO.util.Animation} the animation instance
2091          * @static
2092          */
2093         getAnim: function(type, el, callback) {
2094             if (YAHOO.widget[type]) {
2095                 return new YAHOO.widget[type](el, callback);
2096             } else {
2097                 return null;
2098             }
2099         },
2101         /**
2102          * Returns true if the specified animation class is available
2103          * @method isValid
2104          * @param type {string} the type of animation
2105          * @return {boolean} true if valid, false if not
2106          * @static
2107          */
2108         isValid: function(type) {
2109             return (YAHOO.widget[type]);
2110         }
2111     };
2112 } ();
2115  * A 1/2 second fade-in animation.
2116  * @class TVFadeIn
2117  * @constructor
2118  * @param el {HTMLElement} the element to animate
2119  * @param callback {function} function to invoke when the animation is finished
2120  */
2121 YAHOO.widget.TVFadeIn = function(el, callback) {
2122     /**
2123      * The element to animate
2124      * @property el
2125      * @type HTMLElement
2126      */
2127     this.el = el;
2129     /**
2130      * the callback to invoke when the animation is complete
2131      * @property callback
2132      * @type function
2133      */
2134     this.callback = callback;
2138 YAHOO.widget.TVFadeIn.prototype = {
2139     /**
2140      * Performs the animation
2141      * @method animate
2142      */
2143     animate: function() {
2144         var tvanim = this;
2146         var s = this.el.style;
2147         s.opacity = 0.1;
2148         s.filter = "alpha(opacity=10)";
2149         s.display = "";
2151         var dur = 0.4; 
2152         var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur);
2153         a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2154         a.animate();
2155     },
2157     /**
2158      * Clean up and invoke callback
2159      * @method onComplete
2160      */
2161     onComplete: function() {
2162         this.callback();
2163     },
2165     /**
2166      * toString
2167      * @method toString
2168      * @return {string} the string representation of the instance
2169      */
2170     toString: function() {
2171         return "TVFadeIn";
2172     }
2176  * A 1/2 second fade out animation.
2177  * @class TVFadeOut
2178  * @constructor
2179  * @param el {HTMLElement} the element to animate
2180  * @param callback {Function} function to invoke when the animation is finished
2181  */
2182 YAHOO.widget.TVFadeOut = function(el, callback) {
2183     /**
2184      * The element to animate
2185      * @property el
2186      * @type HTMLElement
2187      */
2188     this.el = el;
2190     /**
2191      * the callback to invoke when the animation is complete
2192      * @property callback
2193      * @type function
2194      */
2195     this.callback = callback;
2199 YAHOO.widget.TVFadeOut.prototype = {
2200     /**
2201      * Performs the animation
2202      * @method animate
2203      */
2204     animate: function() {
2205         var tvanim = this;
2206         var dur = 0.4;
2207         var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
2208         a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2209         a.animate();
2210     },
2212     /**
2213      * Clean up and invoke callback
2214      * @method onComplete
2215      */
2216     onComplete: function() {
2217         var s = this.el.style;
2218         s.display = "none";
2219         // s.opacity = 1;
2220         s.filter = "alpha(opacity=100)";
2221         this.callback();
2222     },
2224     /**
2225      * toString
2226      * @method toString
2227      * @return {string} the string representation of the instance
2228      */
2229     toString: function() {
2230         return "TVFadeOut";
2231     }