Automatic installer.php lang files by installer_builder (20070726)
[moodle-linuxchix.git] / lib / yui / treeview / treeview-debug.js
blobf7e199000fb499ce01383ee3e7d9884b0e284155
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 */
8 /**
9  * The treeview widget is a generic tree building tool.
10  * @module treeview
11  * @title TreeView Widget
12  * @requires yahoo, event
13  * @optional animation
14  * @namespace YAHOO.widget
15  */
17 /**
18  * Contains the tree view state data and the root node.
19  *
20  * @class TreeView
21  * @uses YAHOO.util.EventProvider
22  * @constructor
23  * @param {string|HTMLElement} id The id of the element, or the element
24  * itself that the tree will be inserted into.
25  */
26 YAHOO.widget.TreeView = function(id) {
27     if (id) { this.init(id); }
30 YAHOO.widget.TreeView.prototype = {
32     /**
33      * The id of tree container element
34      * @property id
35      * @type String
36      */
37     id: null,
39     /**
40      * The host element for this tree
41      * @property _el
42      * @private
43      */
44     _el: null,
46      /**
47      * Flat collection of all nodes in this tree
48      * @property _nodes
49      * @type Node[]
50      * @private
51      */
52     _nodes: null,
54     /**
55      * We lock the tree control while waiting for the dynamic loader to return
56      * @property locked
57      * @type boolean
58      */
59     locked: false,
61     /**
62      * The animation to use for expanding children, if any
63      * @property _expandAnim
64      * @type string
65      * @private
66      */
67     _expandAnim: null,
69     /**
70      * The animation to use for collapsing children, if any
71      * @property _collapseAnim
72      * @type string
73      * @private
74      */
75     _collapseAnim: null,
77     /**
78      * The current number of animations that are executing
79      * @property _animCount
80      * @type int
81      * @private
82      */
83     _animCount: 0,
85     /**
86      * The maximum number of animations to run at one time.
87      * @property maxAnim
88      * @type int
89      */
90     maxAnim: 2,
92     /**
93      * Sets up the animation for expanding children
94      * @method setExpandAnim
95      * @param {string} type the type of animation (acceptable values defined 
96      * in YAHOO.widget.TVAnim)
97      */
98     setExpandAnim: function(type) {
99         if (YAHOO.widget.TVAnim.isValid(type)) {
100             this._expandAnim = type;
101         }
102     },
104     /**
105      * Sets up the animation for collapsing children
106      * @method setCollapseAnim
107      * @param {string} the type of animation (acceptable values defined in 
108      * YAHOO.widget.TVAnim)
109      */
110     setCollapseAnim: function(type) {
111         if (YAHOO.widget.TVAnim.isValid(type)) {
112             this._collapseAnim = type;
113         }
114     },
116     /**
117      * Perform the expand animation if configured, or just show the
118      * element if not configured or too many animations are in progress
119      * @method animateExpand
120      * @param el {HTMLElement} the element to animate
121      * @param node {YAHOO.util.Node} the node that was expanded
122      * @return {boolean} true if animation could be invoked, false otherwise
123      */
124     animateExpand: function(el, node) {
125         this.logger.log("animating expand");
127         if (this._expandAnim && this._animCount < this.maxAnim) {
128             // this.locked = true;
129             var tree = this;
130             var a = YAHOO.widget.TVAnim.getAnim(this._expandAnim, el, 
131                             function() { tree.expandComplete(node); });
132             if (a) { 
133                 ++this._animCount;
134                 this.fireEvent("animStart", {
135                         "node": node, 
136                         "type": "expand"
137                     });
138                 a.animate();
139             }
141             return true;
142         }
144         return false;
145     },
147     /**
148      * Perform the collapse animation if configured, or just show the
149      * element if not configured or too many animations are in progress
150      * @method animateCollapse
151      * @param el {HTMLElement} the element to animate
152      * @param node {YAHOO.util.Node} the node that was expanded
153      * @return {boolean} true if animation could be invoked, false otherwise
154      */
155     animateCollapse: function(el, node) {
156         this.logger.log("animating collapse");
158         if (this._collapseAnim && this._animCount < this.maxAnim) {
159             // this.locked = true;
160             var tree = this;
161             var a = YAHOO.widget.TVAnim.getAnim(this._collapseAnim, el, 
162                             function() { tree.collapseComplete(node); });
163             if (a) { 
164                 ++this._animCount;
165                 this.fireEvent("animStart", {
166                         "node": node, 
167                         "type": "collapse"
168                     });
169                 a.animate();
170             }
172             return true;
173         }
175         return false;
176     },
178     /**
179      * Function executed when the expand animation completes
180      * @method expandComplete
181      */
182     expandComplete: function(node) {
183         this.logger.log("expand complete: " + this.id);
184         --this._animCount;
185         this.fireEvent("animComplete", {
186                 "node": node, 
187                 "type": "expand"
188             });
189         // this.locked = false;
190     },
192     /**
193      * Function executed when the collapse animation completes
194      * @method collapseComplete
195      */
196     collapseComplete: function(node) {
197         this.logger.log("collapse complete: " + this.id);
198         --this._animCount;
199         this.fireEvent("animComplete", {
200                 "node": node, 
201                 "type": "collapse"
202             });
203         // this.locked = false;
204     },
206     /**
207      * Initializes the tree
208      * @method init
209      * @parm {string|HTMLElement} id the id of the element that will hold the tree
210      * @private
211      */
212     init: function(id) {
214         this.id = id;
216         if ("string" !== typeof id) {
217             this._el = id;
218             this.id = this.generateId(id);
219         }
221         /**
222          * When animation is enabled, this event fires when the animation
223          * starts
224          * @event animStart
225          * @type CustomEvent
226          * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
227          * @parm {String} type the type of animation ("expand" or "collapse")
228          */
229         this.createEvent("animStart", this);
231         /**
232          * When animation is enabled, this event fires when the animation
233          * completes
234          * @event animComplete
235          * @type CustomEvent
236          * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
237          * @parm {String} type the type of animation ("expand" or "collapse")
238          */
239         this.createEvent("animComplete", this);
241         /**
242          * Fires when a node is going to be collapsed.  Return false to stop
243          * the collapse.
244          * @event collapse
245          * @type CustomEvent
246          * @param {YAHOO.widget.Node} node the node that is collapsing
247          */
248         this.createEvent("collapse", this);
250         /**
251          * Fires after a node is successfully collapsed.  This event will not fire
252          * if the "collapse" event was cancelled.
253          * @event collapseComplete
254          * @type CustomEvent
255          * @param {YAHOO.widget.Node} node the node that was collapsed
256          */
257         this.createEvent("collapseComplete", this);
259         /**
260          * Fires when a node is going to be expanded.  Return false to stop
261          * the collapse.
262          * @event expand
263          * @type CustomEvent
264          * @param {YAHOO.widget.Node} node the node that is expanding
265          */
266         this.createEvent("expand", this);
268         /**
269          * Fires after a node is successfully expanded.  This event will not fire
270          * if the "expand" event was cancelled.
271          * @event expandComplete
272          * @type CustomEvent
273          * @param {YAHOO.widget.Node} node the node that was expanded
274          */
275         this.createEvent("expandComplete", this);
277         this._nodes = [];
279         // store a global reference
280         YAHOO.widget.TreeView.trees[this.id] = this;
282         // Set up the root node
283         this.root = new YAHOO.widget.RootNode(this);
285         this.logger = new YAHOO.widget.LogWriter(this.toString());
287         this.logger.log("tree init: " + this.id);
289         //YAHOO.util.Event.onContentReady(this.id, this.handleAvailable, this, true);
290         YAHOO.util.Event.on(this.id, "click", this.handleClick, this, true);
291     },
294     //handleAvailable: function() {
295         //var Event = YAHOO.util.Event;
296         //Event.on(this.id, 
297     //},
299     /**
300      * Renders the tree boilerplate and visible nodes
301      * @method draw
302      */
303     draw: function() {
304         var html = this.root.getHtml();
305         this.getEl().innerHTML = html;
306         this.firstDraw = false;
307     },
309     /**
310      * Returns the tree's host element
311      * @method getEl
312      * @return {HTMLElement} the host element
313      */
314     getEl: function() {
315         if (! this._el) {
316             this._el = document.getElementById(this.id);
317         }
318         return this._el;
319     },
321     /**
322      * Nodes register themselves with the tree instance when they are created.
323      * @method regNode
324      * @param node {Node} the node to register
325      * @private
326      */
327     regNode: function(node) {
328         this._nodes[node.index] = node;
329     },
331     /**
332      * Returns the root node of this tree
333      * @method getRoot
334      * @return {Node} the root node
335      */
336     getRoot: function() {
337         return this.root;
338     },
340     /**
341      * Configures this tree to dynamically load all child data
342      * @method setDynamicLoad
343      * @param {function} fnDataLoader the function that will be called to get the data
344      * @param iconMode {int} configures the icon that is displayed when a dynamic
345      * load node is expanded the first time without children.  By default, the 
346      * "collapse" icon will be used.  If set to 1, the leaf node icon will be
347      * displayed.
348      */
349     setDynamicLoad: function(fnDataLoader, iconMode) { 
350         this.root.setDynamicLoad(fnDataLoader, iconMode);
351     },
353     /**
354      * Expands all child nodes.  Note: this conflicts with the "multiExpand"
355      * node property.  If expand all is called in a tree with nodes that
356      * do not allow multiple siblings to be displayed, only the last sibling
357      * will be expanded.
358      * @method expandAll
359      */
360     expandAll: function() { 
361         if (!this.locked) {
362             this.root.expandAll(); 
363         }
364     },
366     /**
367      * Collapses all expanded child nodes in the entire tree.
368      * @method collapseAll
369      */
370     collapseAll: function() { 
371         if (!this.locked) {
372             this.root.collapseAll(); 
373         }
374     },
376     /**
377      * Returns a node in the tree that has the specified index (this index
378      * is created internally, so this function probably will only be used
379      * in html generated for a given node.)
380      * @method getNodeByIndex
381      * @param {int} nodeIndex the index of the node wanted
382      * @return {Node} the node with index=nodeIndex, null if no match
383      */
384     getNodeByIndex: function(nodeIndex) {
385         var n = this._nodes[nodeIndex];
386         return (n) ? n : null;
387     },
389     /**
390      * Returns a node that has a matching property and value in the data
391      * object that was passed into its constructor.
392      * @method getNodeByProperty
393      * @param {object} property the property to search (usually a string)
394      * @param {object} value the value we want to find (usuall an int or string)
395      * @return {Node} the matching node, null if no match
396      */
397     getNodeByProperty: function(property, value) {
398         for (var i in this._nodes) {
399             var n = this._nodes[i];
400             if (n.data && value == n.data[property]) {
401                 return n;
402             }
403         }
405         return null;
406     },
408     /**
409      * Returns a collection of nodes that have a matching property 
410      * and value in the data object that was passed into its constructor.  
411      * @method getNodesByProperty
412      * @param {object} property the property to search (usually a string)
413      * @param {object} value the value we want to find (usuall an int or string)
414      * @return {Array} the matching collection of nodes, null if no match
415      */
416     getNodesByProperty: function(property, value) {
417         var values = [];
418         for (var i in this._nodes) {
419             var n = this._nodes[i];
420             if (n.data && value == n.data[property]) {
421                 values.push(n);
422             }
423         }
425         return (values.length) ? values : null;
426     },
428     /**
429      * Removes the node and its children, and optionally refreshes the 
430      * branch of the tree that was affected.
431      * @method removeNode
432      * @param {Node} The node to remove
433      * @param {boolean} autoRefresh automatically refreshes branch if true
434      * @return {boolean} False is there was a problem, true otherwise.
435      */
436     removeNode: function(node, autoRefresh) { 
438         // Don't delete the root node
439         if (node.isRoot()) {
440             return false;
441         }
443         // Get the branch that we may need to refresh
444         var p = node.parent;
445         if (p.parent) {
446             p = p.parent;
447         }
449         // Delete the node and its children
450         this._deleteNode(node);
452         // Refresh the parent of the parent
453         if (autoRefresh && p && p.childrenRendered) {
454             p.refresh();
455         }
457         return true;
458     },
460     /**
461      * Deletes this nodes child collection, recursively.  Also collapses
462      * the node, and resets the dynamic load flag.  The primary use for
463      * this method is to purge a node and allow it to fetch its data
464      * dynamically again.
465      * @method removeChildren
466      * @param {Node} node the node to purge
467      */
468     removeChildren: function(node) { 
469         this.logger.log("Removing children for " + node);
470         while (node.children.length) {
471             this._deleteNode(node.children[0]);
472         }
474         node.childrenRendered = false;
475         node.dynamicLoadComplete = false;
476         if (node.expanded) {
477             node.collapse();
478         } else {
479             node.updateIcon();
480         }
481     },
483     /**
484      * Deletes the node and recurses children
485      * @method _deleteNode
486      * @private
487      */
488     _deleteNode: function(node) { 
489         // Remove all the child nodes first
490         this.removeChildren(node);
492         // Remove the node from the tree
493         this.popNode(node);
494     },
496     /**
497      * Removes the node from the tree, preserving the child collection 
498      * to make it possible to insert the branch into another part of the 
499      * tree, or another tree.
500      * @method popNode
501      * @param {Node} the node to remove
502      */
503     popNode: function(node) { 
504         var p = node.parent;
506         // Update the parent's collection of children
507         var a = [];
509         for (var i=0, len=p.children.length;i<len;++i) {
510             if (p.children[i] != node) {
511                 a[a.length] = p.children[i];
512             }
513         }
515         p.children = a;
517         // reset the childrenRendered flag for the parent
518         p.childrenRendered = false;
520          // Update the sibling relationship
521         if (node.previousSibling) {
522             node.previousSibling.nextSibling = node.nextSibling;
523         }
525         if (node.nextSibling) {
526             node.nextSibling.previousSibling = node.previousSibling;
527         }
529         node.parent = null;
530         node.previousSibling = null;
531         node.nextSibling = null;
532         node.tree = null;
534         // Update the tree's node collection 
535         delete this._nodes[node.index];
536     },
539     /**
540      * TreeView instance toString
541      * @method toString
542      * @return {string} string representation of the tree
543      */
544     toString: function() {
545         return "TreeView " + this.id;
546     },
548     /**
549      * Generates an unique id for an element if it doesn't yet have one
550      * @method generateId
551      * @private
552      */
553     generateId: function(el) {
554         var id = el.id;
556         if (!id) {
557             id = "yui-tv-auto-id-" + YAHOO.widget.TreeView.counter;
558             ++YAHOO.widget.TreeView.counter;
559         }
561         return id;
562     },
564     /**
565      * Abstract method that is executed when a node is expanded
566      * @method onExpand
567      * @param node {Node} the node that was expanded
568      * @deprecated use treeobj.subscribe("expand") instead
569      */
570     onExpand: function(node) { },
572     /**
573      * Abstract method that is executed when a node is collapsed.
574      * @method onCollapse
575      * @param node {Node} the node that was collapsed.
576      * @deprecated use treeobj.subscribe("collapse") instead
577      */
578     onCollapse: function(node) { }
582 YAHOO.augment(YAHOO.widget.TreeView, YAHOO.util.EventProvider);
585  * Count of all nodes in all trees
586  * @property YAHOO.widget.TreeView.nodeCount
587  * @type int
588  * @static
589  */
590 YAHOO.widget.TreeView.nodeCount = 0;
593  * Global cache of tree instances
594  * @property YAHOO.widget.TreeView.trees
595  * @type Array
596  * @static
597  * @private
598  */
599 YAHOO.widget.TreeView.trees = [];
602  * Counter for generating a new unique element id
603  * @property YAHOO.widget.TreeView.counter
604  * @static
605  * @private
606  */
607 YAHOO.widget.TreeView.counter = 0;
610  * Global method for getting a tree by its id.  Used in the generated
611  * tree html.
612  * @method YAHOO.widget.TreeView.getTree
613  * @param treeId {String} the id of the tree instance
614  * @return {TreeView} the tree instance requested, null if not found.
615  * @static
616  */
617 YAHOO.widget.TreeView.getTree = function(treeId) {
618     var t = YAHOO.widget.TreeView.trees[treeId];
619     return (t) ? t : null;
624  * Global method for getting a node by its id.  Used in the generated
625  * tree html.
626  * @method YAHOO.widget.TreeView.getNode
627  * @param treeId {String} the id of the tree instance
628  * @param nodeIndex {String} the index of the node to return
629  * @return {Node} the node instance requested, null if not found
630  * @static
631  */
632 YAHOO.widget.TreeView.getNode = function(treeId, nodeIndex) {
633     var t = YAHOO.widget.TreeView.getTree(treeId);
634     return (t) ? t.getNodeByIndex(nodeIndex) : null;
638  * Add a DOM event
639  * @method YAHOO.widget.TreeView.addHandler
640  * @param el the elment to bind the handler to
641  * @param {string} sType the type of event handler
642  * @param {function} fn the callback to invoke
643  * @static
644  */
645 YAHOO.widget.TreeView.addHandler = function (el, sType, fn) {
646     if (el.addEventListener) {
647         el.addEventListener(sType, fn, false);
648     } else if (el.attachEvent) {
649         el.attachEvent("on" + sType, fn);
650     }
654  * Remove a DOM event
655  * @method YAHOO.widget.TreeView.removeHandler
656  * @param el the elment to bind the handler to
657  * @param {string} sType the type of event handler
658  * @param {function} fn the callback to invoke
659  * @static
660  */
662 YAHOO.widget.TreeView.removeHandler = function (el, sType, fn) {
663     if (el.removeEventListener) {
664         el.removeEventListener(sType, fn, false);
665     } else if (el.detachEvent) {
666         el.detachEvent("on" + sType, fn);
667     }
671  * Attempts to preload the images defined in the styles used to draw the tree by
672  * rendering off-screen elements that use the styles.
673  * @method YAHOO.widget.TreeView.preload
674  * @param {string} prefix the prefix to use to generate the names of the
675  * images to preload, default is ygtv
676  * @static
677  */
678 YAHOO.widget.TreeView.preload = function(prefix) {
679     prefix = prefix || "ygtv";
680     var styles = ["tn","tm","tmh","tp","tph","ln","lm","lmh","lp","lph","loading"];
682     var sb = [];
683     
684     for (var i = 0; i < styles.length; ++i) { 
685         sb[sb.length] = '<span class="' + prefix + styles[i] + '">&#160;</span>';
686     }
688     var f = document.createElement("div");
689     var s = f.style;
690     s.position = "absolute";
691     s.top = "-1000px";
692     s.left = "-1000px";
693     f.innerHTML = sb.join("");
695     document.body.appendChild(f);
697     YAHOO.widget.TreeView.removeHandler(window, 
698                 "load", YAHOO.widget.TreeView.preload);
702 YAHOO.widget.TreeView.addHandler(window, 
703                 "load", YAHOO.widget.TreeView.preload);
706  * The base class for all tree nodes.  The node's presentation and behavior in
707  * response to mouse events is handled in Node subclasses.
708  * @namespace YAHOO.widget
709  * @class Node
710  * @uses YAHOO.util.EventProvider
711  * @param oData {object} a string or object containing the data that will
712  * be used to render this node
713  * @param oParent {Node} this node's parent node
714  * @param expanded {boolean} the initial expanded/collapsed state
715  * @constructor
716  */
717 YAHOO.widget.Node = function(oData, oParent, expanded) {
718     if (oData) { this.init(oData, oParent, expanded); }
721 YAHOO.widget.Node.prototype = {
723     /**
724      * The index for this instance obtained from global counter in YAHOO.widget.TreeView.
725      * @property index
726      * @type int
727      */
728     index: 0,
730     /**
731      * This node's child node collection.
732      * @property children
733      * @type Node[] 
734      */
735     children: null,
737     /**
738      * Tree instance this node is part of
739      * @property tree
740      * @type TreeView
741      */
742     tree: null,
744     /**
745      * The data linked to this node.  This can be any object or primitive
746      * value, and the data can be used in getNodeHtml().
747      * @property data
748      * @type object
749      */
750     data: null,
752     /**
753      * Parent node
754      * @property parent
755      * @type Node
756      */
757     parent: null,
759     /**
760      * The depth of this node.  We start at -1 for the root node.
761      * @property depth
762      * @type int
763      */
764     depth: -1,
766     /**
767      * The href for the node's label.  If one is not specified, the href will
768      * be set so that it toggles the node.
769      * @property href
770      * @type string
771      */
772     href: null,
774     /**
775      * The label href target, defaults to current window
776      * @property target
777      * @type string
778      */
779     target: "_self",
781     /**
782      * The node's expanded/collapsed state
783      * @property expanded
784      * @type boolean
785      */
786     expanded: false,
788     /**
789      * Can multiple children be expanded at once?
790      * @property multiExpand
791      * @type boolean
792      */
793     multiExpand: true,
795     /**
796      * Should we render children for a collapsed node?  It is possible that the
797      * implementer will want to render the hidden data...  @todo verify that we 
798      * need this, and implement it if we do.
799      * @property renderHidden
800      * @type boolean
801      */
802     renderHidden: false,
804     /**
805      * This flag is set to true when the html is generated for this node's
806      * children, and set to false when new children are added.
807      * @property childrenRendered
808      * @type boolean
809      */
810     childrenRendered: false,
812     /**
813      * Dynamically loaded nodes only fetch the data the first time they are
814      * expanded.  This flag is set to true once the data has been fetched.
815      * @property dynamicLoadComplete
816      * @type boolean
817      */
818     dynamicLoadComplete: false,
820     /**
821      * This node's previous sibling
822      * @property previousSibling
823      * @type Node
824      */
825     previousSibling: null,
827     /**
828      * This node's next sibling
829      * @property nextSibling
830      * @type Node
831      */
832     nextSibling: null,
834     /**
835      * We can set the node up to call an external method to get the child
836      * data dynamically.
837      * @property _dynLoad
838      * @type boolean
839      * @private
840      */
841     _dynLoad: false,
843     /**
844      * Function to execute when we need to get this node's child data.
845      * @property dataLoader
846      * @type function
847      */
848     dataLoader: null,
850     /**
851      * This is true for dynamically loading nodes while waiting for the
852      * callback to return.
853      * @property isLoading
854      * @type boolean
855      */
856     isLoading: false,
858     /**
859      * The toggle/branch icon will not show if this is set to false.  This
860      * could be useful if the implementer wants to have the child contain
861      * extra info about the parent, rather than an actual node.
862      * @property hasIcon
863      * @type boolean
864      */
865     hasIcon: true,
867     /**
868      * Used to configure what happens when a dynamic load node is expanded
869      * and we discover that it does not have children.  By default, it is
870      * treated as if it still could have children (plus/minus icon).  Set
871      * iconMode to have it display like a leaf node instead.
872      * @property iconMode
873      * @type int
874      */
875     iconMode: 0,
877     /**
878      * Specifies whether or not the content area of the node should be allowed
879      * to wrap.
880      * @property nowrap
881      * @type boolean
882      * @default true
883      */
884     nowrap: false,
886     /**
887      * The node type
888      * @property _type
889      * @private
890      */
891     _type: "Node",
893     /*
894     spacerPath: "http://us.i1.yimg.com/us.yimg.com/i/space.gif",
895     expandedText: "Expanded",
896     collapsedText: "Collapsed",
897     loadingText: "Loading",
898     */
900     /**
901      * Initializes this node, gets some of the properties from the parent
902      * @method init
903      * @param oData {object} a string or object containing the data that will
904      * be used to render this node
905      * @param oParent {Node} this node's parent node
906      * @param expanded {boolean} the initial expanded/collapsed state
907      */
908     init: function(oData, oParent, expanded) {
910         this.data       = oData;
911         this.children   = [];
912         this.index      = YAHOO.widget.TreeView.nodeCount;
913         ++YAHOO.widget.TreeView.nodeCount;
914         this.expanded   = expanded;
915         this.logger     = new YAHOO.widget.LogWriter(this.toString());
917         /**
918          * The parentChange event is fired when a parent element is applied
919          * to the node.  This is useful if you need to apply tree-level
920          * properties to a tree that need to happen if a node is moved from
921          * one tree to another.
922          *
923          * @event parentChange
924          * @type CustomEvent
925          */
926         this.createEvent("parentChange", this);
928         // oParent should never be null except when we create the root node.
929         if (oParent) {
930             oParent.appendChild(this);
931         }
932     },
934     /**
935      * Certain properties for the node cannot be set until the parent
936      * is known. This is called after the node is inserted into a tree.
937      * the parent is also applied to this node's children in order to
938      * make it possible to move a branch from one tree to another.
939      * @method applyParent
940      * @param {Node} parentNode this node's parent node
941      * @return {boolean} true if the application was successful
942      */
943     applyParent: function(parentNode) {
944         if (!parentNode) {
945             return false;
946         }
948         this.tree   = parentNode.tree;
949         this.parent = parentNode;
950         this.depth  = parentNode.depth + 1;
952         if (!this.href) {
953             this.href = "javascript:" + this.getToggleLink();
954         }
956         if (! this.multiExpand) {
957             this.multiExpand = parentNode.multiExpand;
958         }
960         this.tree.regNode(this);
961         parentNode.childrenRendered = false;
963         // cascade update existing children
964         for (var i=0, len=this.children.length;i<len;++i) {
965             this.children[i].applyParent(this);
966         }
968         this.fireEvent("parentChange");
970         return true;
971     },
973     /**
974      * Appends a node to the child collection.
975      * @method appendChild
976      * @param childNode {Node} the new node
977      * @return {Node} the child node
978      * @private
979      */
980     appendChild: function(childNode) {
981         if (this.hasChildren()) {
982             var sib = this.children[this.children.length - 1];
983             sib.nextSibling = childNode;
984             childNode.previousSibling = sib;
985         }
986         this.children[this.children.length] = childNode;
987         childNode.applyParent(this);
989         return childNode;
990     },
992     /**
993      * Appends this node to the supplied node's child collection
994      * @method appendTo
995      * @param parentNode {Node} the node to append to.
996      * @return {Node} The appended node
997      */
998     appendTo: function(parentNode) {
999         return parentNode.appendChild(this);
1000     },
1002     /**
1003     * Inserts this node before this supplied node
1004     * @method insertBefore
1005     * @param node {Node} the node to insert this node before
1006     * @return {Node} the inserted node
1007     */
1008     insertBefore: function(node) {
1009         this.logger.log("insertBefore: " + node);
1010         var p = node.parent;
1011         if (p) {
1013             if (this.tree) {
1014                 this.tree.popNode(this);
1015             }
1017             var refIndex = node.isChildOf(p);
1018             //this.logger.log(refIndex);
1019             p.children.splice(refIndex, 0, this);
1020             if (node.previousSibling) {
1021                 node.previousSibling.nextSibling = this;
1022             }
1023             this.previousSibling = node.previousSibling;
1024             this.nextSibling = node;
1025             node.previousSibling = this;
1027             this.applyParent(p);
1028         }
1030         return this;
1031     },
1033     /**
1034     * Inserts this node after the supplied node
1035     * @method insertAfter
1036     * @param node {Node} the node to insert after
1037     * @return {Node} the inserted node
1038     */
1039     insertAfter: function(node) {
1040         this.logger.log("insertAfter: " + node);
1041         var p = node.parent;
1042         if (p) {
1044             if (this.tree) {
1045                 this.tree.popNode(this);
1046             }
1048             var refIndex = node.isChildOf(p);
1049             this.logger.log(refIndex);
1051             if (!node.nextSibling) {
1052                 return this.appendTo(p);
1053             }
1055             p.children.splice(refIndex + 1, 0, this);
1057             node.nextSibling.previousSibling = this;
1058             this.previousSibling = node;
1059             this.nextSibling = node.nextSibling;
1060             node.nextSibling = this;
1062             this.applyParent(p);
1063         }
1065         return this;
1066     },
1068     /**
1069     * Returns true if the Node is a child of supplied Node
1070     * @method isChildOf
1071     * @param parentNode {Node} the Node to check
1072     * @return {boolean} The node index if this Node is a child of 
1073     *                   supplied Node, else -1.
1074     * @private
1075     */
1076     isChildOf: function(parentNode) {
1077         if (parentNode && parentNode.children) {
1078             for (var i=0, len=parentNode.children.length; i<len ; ++i) {
1079                 if (parentNode.children[i] === this) {
1080                     return i;
1081                 }
1082             }
1083         }
1085         return -1;
1086     },
1088     /**
1089      * Returns a node array of this node's siblings, null if none.
1090      * @method getSiblings
1091      * @return Node[]
1092      */
1093     getSiblings: function() {
1094         return this.parent.children;
1095     },
1097     /**
1098      * Shows this node's children
1099      * @method showChildren
1100      */
1101     showChildren: function() {
1102         if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
1103             if (this.hasChildren()) {
1104                 this.getChildrenEl().style.display = "";
1105             }
1106         }
1107     },
1109     /**
1110      * Hides this node's children
1111      * @method hideChildren
1112      */
1113     hideChildren: function() {
1114         this.logger.log("hiding " + this.index);
1116         if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
1117             this.getChildrenEl().style.display = "none";
1118         }
1119     },
1121     /**
1122      * Returns the id for this node's container div
1123      * @method getElId
1124      * @return {string} the element id
1125      */
1126     getElId: function() {
1127         return "ygtv" + this.index;
1128     },
1130     /**
1131      * Returns the id for this node's children div
1132      * @method getChildrenElId
1133      * @return {string} the element id for this node's children div
1134      */
1135     getChildrenElId: function() {
1136         return "ygtvc" + this.index;
1137     },
1139     /**
1140      * Returns the id for this node's toggle element
1141      * @method getToggleElId
1142      * @return {string} the toggel element id
1143      */
1144     getToggleElId: function() {
1145         return "ygtvt" + this.index;
1146     },
1149     /*
1150      * Returns the id for this node's spacer image.  The spacer is positioned
1151      * over the toggle and provides feedback for screen readers.
1152      * @method getSpacerId
1153      * @return {string} the id for the spacer image
1154      */
1155     /*
1156     getSpacerId: function() {
1157         return "ygtvspacer" + this.index;
1158     }, 
1159     */
1161     /**
1162      * Returns this node's container html element
1163      * @method getEl
1164      * @return {HTMLElement} the container html element
1165      */
1166     getEl: function() {
1167         return document.getElementById(this.getElId());
1168     },
1170     /**
1171      * Returns the div that was generated for this node's children
1172      * @method getChildrenEl
1173      * @return {HTMLElement} this node's children div
1174      */
1175     getChildrenEl: function() {
1176         return document.getElementById(this.getChildrenElId());
1177     },
1179     /**
1180      * Returns the element that is being used for this node's toggle.
1181      * @method getToggleEl
1182      * @return {HTMLElement} this node's toggle html element
1183      */
1184     getToggleEl: function() {
1185         return document.getElementById(this.getToggleElId());
1186     },
1188     /*
1189      * Returns the element that is being used for this node's spacer.
1190      * @method getSpacer
1191      * @return {HTMLElement} this node's spacer html element
1192      */
1193     /*
1194     getSpacer: function() {
1195         return document.getElementById( this.getSpacerId() ) || {};
1196     },
1197     */
1199     /*
1200     getStateText: function() {
1201         if (this.isLoading) {
1202             return this.loadingText;
1203         } else if (this.hasChildren(true)) {
1204             if (this.expanded) {
1205                 return this.expandedText;
1206             } else {
1207                 return this.collapsedText;
1208             }
1209         } else {
1210             return "";
1211         }
1212     },
1213     */
1215     /**
1216      * Generates the link that will invoke this node's toggle method
1217      * @method getToggleLink
1218      * @return {string} the javascript url for toggling this node
1219      */
1220     getToggleLink: function() {
1221         return "YAHOO.widget.TreeView.getNode(\'" + this.tree.id + "\'," + 
1222             this.index + ").toggle()";
1223     },
1225     /**
1226      * Hides this nodes children (creating them if necessary), changes the
1227      * @method collapse
1228      * toggle style.
1229      */
1230     collapse: function() {
1231         // Only collapse if currently expanded
1232         if (!this.expanded) { return; }
1234         // fire the collapse event handler
1235         var ret = this.tree.onCollapse(this);
1237         if (false === ret) {
1238             this.logger.log("Collapse was stopped by the abstract onCollapse");
1239             return;
1240         }
1242         ret = this.tree.fireEvent("collapse", this);
1244         if (false === ret) {
1245             this.logger.log("Collapse was stopped by a custom event handler");
1246             return;
1247         }
1250         if (!this.getEl()) {
1251             this.expanded = false;
1252         } else {
1253             // hide the child div
1254             this.hideChildren();
1255             this.expanded = false;
1257             this.updateIcon();
1258         }
1260         // this.getSpacer().title = this.getStateText();
1262         ret = this.tree.fireEvent("collapseComplete", this);
1264     },
1266     /**
1267      * Shows this nodes children (creating them if necessary), changes the
1268      * toggle style, and collapses its siblings if multiExpand is not set.
1269      * @method expand
1270      */
1271     expand: function() {
1272         // Only expand if currently collapsed.
1273         if (this.expanded) { return; }
1275         // fire the expand event handler
1276         var ret = this.tree.onExpand(this);
1278         if (false === ret) {
1279             this.logger.log("Expand was stopped by the abstract onExpand");
1280             return;
1281         }
1282         
1283         ret = this.tree.fireEvent("expand", this);
1285         if (false === ret) {
1286             this.logger.log("Expand was stopped by the custom event handler");
1287             return;
1288         }
1290         if (!this.getEl()) {
1291             this.expanded = true;
1292             return;
1293         }
1295         if (! this.childrenRendered) {
1296             this.logger.log("children not rendered yet");
1297             this.getChildrenEl().innerHTML = this.renderChildren();
1298         } else {
1299             this.logger.log("CHILDREN RENDERED");
1300         }
1302         this.expanded = true;
1304         this.updateIcon();
1306         // this.getSpacer().title = this.getStateText();
1308         // We do an extra check for children here because the lazy
1309         // load feature can expose nodes that have no children.
1311         // if (!this.hasChildren()) {
1312         if (this.isLoading) {
1313             this.expanded = false;
1314             return;
1315         }
1317         if (! this.multiExpand) {
1318             var sibs = this.getSiblings();
1319             for (var i=0; i<sibs.length; ++i) {
1320                 if (sibs[i] != this && sibs[i].expanded) { 
1321                     sibs[i].collapse(); 
1322                 }
1323             }
1324         }
1326         this.showChildren();
1328         ret = this.tree.fireEvent("expandComplete", this);
1329     },
1331     updateIcon: function() {
1332         if (this.hasIcon) {
1333             var el = this.getToggleEl();
1334             if (el) {
1335                 el.className = this.getStyle();
1336             }
1337         }
1338     },
1340     /**
1341      * Returns the css style name for the toggle
1342      * @method getStyle
1343      * @return {string} the css class for this node's toggle
1344      */
1345     getStyle: function() {
1346         // this.logger.log("No children, " + " isDyanmic: " + this.isDynamic() + " expanded: " + this.expanded);
1347         if (this.isLoading) {
1348             this.logger.log("returning the loading icon");
1349             return "ygtvloading";
1350         } else {
1351             // location top or bottom, middle nodes also get the top style
1352             var loc = (this.nextSibling) ? "t" : "l";
1354             // type p=plus(expand), m=minus(collapase), n=none(no children)
1355             var type = "n";
1356             if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) {
1357             // if (this.hasChildren(true)) {
1358                 type = (this.expanded) ? "m" : "p";
1359             }
1361             // this.logger.log("ygtv" + loc + type);
1362             return "ygtv" + loc + type;
1363         }
1364     },
1366     /**
1367      * Returns the hover style for the icon
1368      * @return {string} the css class hover state
1369      * @method getHoverStyle
1370      */
1371     getHoverStyle: function() { 
1372         var s = this.getStyle();
1373         if (this.hasChildren(true) && !this.isLoading) { 
1374             s += "h"; 
1375         }
1376         return s;
1377     },
1379     /**
1380      * Recursively expands all of this node's children.
1381      * @method expandAll
1382      */
1383     expandAll: function() { 
1384         for (var i=0;i<this.children.length;++i) {
1385             var c = this.children[i];
1386             if (c.isDynamic()) {
1387                 alert("Not supported (lazy load + expand all)");
1388                 break;
1389             } else if (! c.multiExpand) {
1390                 alert("Not supported (no multi-expand + expand all)");
1391                 break;
1392             } else {
1393                 c.expand();
1394                 c.expandAll();
1395             }
1396         }
1397     },
1399     /**
1400      * Recursively collapses all of this node's children.
1401      * @method collapseAll
1402      */
1403     collapseAll: function() { 
1404         for (var i=0;i<this.children.length;++i) {
1405             this.children[i].collapse();
1406             this.children[i].collapseAll();
1407         }
1408     },
1410     /**
1411      * Configures this node for dynamically obtaining the child data
1412      * when the node is first expanded.  Calling it without the callback
1413      * will turn off dynamic load for the node.
1414      * @method setDynamicLoad
1415      * @param fmDataLoader {function} the function that will be used to get the data.
1416      * @param iconMode {int} configures the icon that is displayed when a dynamic
1417      * load node is expanded the first time without children.  By default, the 
1418      * "collapse" icon will be used.  If set to 1, the leaf node icon will be
1419      * displayed.
1420      */
1421     setDynamicLoad: function(fnDataLoader, iconMode) { 
1422         if (fnDataLoader) {
1423             this.dataLoader = fnDataLoader;
1424             this._dynLoad = true;
1425         } else {
1426             this.dataLoader = null;
1427             this._dynLoad = false;
1428         }
1430         if (iconMode) {
1431             this.iconMode = iconMode;
1432         }
1433     },
1435     /**
1436      * Evaluates if this node is the root node of the tree
1437      * @method isRoot
1438      * @return {boolean} true if this is the root node
1439      */
1440     isRoot: function() { 
1441         return (this == this.tree.root);
1442     },
1444     /**
1445      * Evaluates if this node's children should be loaded dynamically.  Looks for
1446      * the property both in this instance and the root node.  If the tree is
1447      * defined to load all children dynamically, the data callback function is
1448      * defined in the root node
1449      * @method isDynamic
1450      * @return {boolean} true if this node's children are to be loaded dynamically
1451      */
1452     isDynamic: function() { 
1453         var lazy = (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
1454         // this.logger.log("isDynamic: " + lazy);
1455         return lazy;
1456     },
1458     /**
1459      * Returns the current icon mode.  This refers to the way childless dynamic
1460      * load nodes appear.
1461      * @method getIconMode
1462      * @return {int} 0 for collapse style, 1 for leaf node style
1463      */
1464     getIconMode: function() {
1465         return (this.iconMode || this.tree.root.iconMode);
1466     },
1468     /**
1469      * Checks if this node has children.  If this node is lazy-loading and the
1470      * children have not been rendered, we do not know whether or not there
1471      * are actual children.  In most cases, we need to assume that there are
1472      * children (for instance, the toggle needs to show the expandable 
1473      * presentation state).  In other times we want to know if there are rendered
1474      * children.  For the latter, "checkForLazyLoad" should be false.
1475      * @method hasChildren
1476      * @param checkForLazyLoad {boolean} should we check for unloaded children?
1477      * @return {boolean} true if this has children or if it might and we are
1478      * checking for this condition.
1479      */
1480     hasChildren: function(checkForLazyLoad) { 
1481         return ( this.children.length > 0 || 
1482                 (checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete) );
1483     },
1485     /**
1486      * Expands if node is collapsed, collapses otherwise.
1487      * @method toggle
1488      */
1489     toggle: function() {
1490         if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
1491             if (this.expanded) { this.collapse(); } else { this.expand(); }
1492         }
1493     },
1495     /**
1496      * Returns the markup for this node and its children.
1497      * @method getHtml
1498      * @return {string} the markup for this node and its expanded children.
1499      */
1500     getHtml: function() {
1502         this.childrenRendered = false;
1504         var sb = [];
1505         sb[sb.length] = '<div class="ygtvitem" id="' + this.getElId() + '">';
1506         sb[sb.length] = this.getNodeHtml();
1507         sb[sb.length] = this.getChildrenHtml();
1508         sb[sb.length] = '</div>';
1509         return sb.join("");
1510     },
1512     /**
1513      * Called when first rendering the tree.  We always build the div that will
1514      * contain this nodes children, but we don't render the children themselves
1515      * unless this node is expanded.
1516      * @method getChildrenHtml
1517      * @return {string} the children container div html and any expanded children
1518      * @private
1519      */
1520     getChildrenHtml: function() {
1522         var sb = [];
1523         sb[sb.length] = '<div class="ygtvchildren"';
1524         sb[sb.length] = ' id="' + this.getChildrenElId() + '"';
1525         if (!this.expanded) {
1526             sb[sb.length] = ' style="display:none;"';
1527         }
1528         sb[sb.length] = '>';
1530         // Don't render the actual child node HTML unless this node is expanded.
1531         if ( (this.hasChildren(true) && this.expanded) ||
1532                 (this.renderHidden && !this.isDynamic()) ) {
1533             sb[sb.length] = this.renderChildren();
1534         }
1536         sb[sb.length] = '</div>';
1538         return sb.join("");
1539     },
1541     /**
1542      * Generates the markup for the child nodes.  This is not done until the node
1543      * is expanded.
1544      * @method renderChildren
1545      * @return {string} the html for this node's children
1546      * @private
1547      */
1548     renderChildren: function() {
1550         this.logger.log("rendering children for " + this.index);
1552         var node = this;
1554         if (this.isDynamic() && !this.dynamicLoadComplete) {
1555             this.isLoading = true;
1556             this.tree.locked = true;
1558             if (this.dataLoader) {
1559                 this.logger.log("Using dynamic loader defined for this node");
1561                 setTimeout( 
1562                     function() {
1563                         node.dataLoader(node, 
1564                             function() { 
1565                                 node.loadComplete(); 
1566                             });
1567                     }, 10);
1568                 
1569             } else if (this.tree.root.dataLoader) {
1570                 this.logger.log("Using the tree-level dynamic loader");
1572                 setTimeout( 
1573                     function() {
1574                         node.tree.root.dataLoader(node, 
1575                             function() { 
1576                                 node.loadComplete(); 
1577                             });
1578                     }, 10);
1580             } else {
1581                 this.logger.log("no loader found");
1582                 return "Error: data loader not found or not specified.";
1583             }
1585             return "";
1587         } else {
1588             return this.completeRender();
1589         }
1590     },
1592     /**
1593      * Called when we know we have all the child data.
1594      * @method completeRender
1595      * @return {string} children html
1596      */
1597     completeRender: function() {
1598         this.logger.log("completeRender: " + this.index + ", # of children: " + this.children.length);
1599         var sb = [];
1601         for (var i=0; i < this.children.length; ++i) {
1602             // this.children[i].childrenRendered = false;
1603             sb[sb.length] = this.children[i].getHtml();
1604         }
1605         
1606         this.childrenRendered = true;
1608         return sb.join("");
1609     },
1611     /**
1612      * Load complete is the callback function we pass to the data provider
1613      * in dynamic load situations.
1614      * @method loadComplete
1615      */
1616     loadComplete: function() {
1617         this.logger.log("loadComplete: " + this.index);
1618         this.getChildrenEl().innerHTML = this.completeRender();
1619         this.dynamicLoadComplete = true;
1620         this.isLoading = false;
1621         this.expand();
1622         this.tree.locked = false;
1623     },
1625     /**
1626      * Returns this node's ancestor at the specified depth.
1627      * @method getAncestor
1628      * @param {int} depth the depth of the ancestor.
1629      * @return {Node} the ancestor
1630      */
1631     getAncestor: function(depth) {
1632         if (depth >= this.depth || depth < 0)  {
1633             this.logger.log("illegal getAncestor depth: " + depth);
1634             return null;
1635         }
1637         var p = this.parent;
1638         
1639         while (p.depth > depth) {
1640             p = p.parent;
1641         }
1643         return p;
1644     },
1646     /**
1647      * Returns the css class for the spacer at the specified depth for
1648      * this node.  If this node's ancestor at the specified depth
1649      * has a next sibling the presentation is different than if it
1650      * does not have a next sibling
1651      * @method getDepthStyle
1652      * @param {int} depth the depth of the ancestor.
1653      * @return {string} the css class for the spacer
1654      */
1655     getDepthStyle: function(depth) {
1656         return (this.getAncestor(depth).nextSibling) ? 
1657             "ygtvdepthcell" : "ygtvblankdepthcell";
1658     },
1660     /**
1661      * Get the markup for the node.  This is designed to be overrided so that we can
1662      * support different types of nodes.
1663      * @method getNodeHtml
1664      * @return {string} The HTML that will render this node.
1665      */
1666     getNodeHtml: function() { 
1667         this.logger.log("Generating html");
1668         return ""; 
1669     },
1671     /**
1672      * Regenerates the html for this node and its children.  To be used when the
1673      * node is expanded and new children have been added.
1674      * @method refresh
1675      */
1676     refresh: function() {
1677         // this.loadComplete();
1678         this.getChildrenEl().innerHTML = this.completeRender();
1680         if (this.hasIcon) {
1681             var el = this.getToggleEl();
1682             if (el) {
1683                 el.className = this.getStyle();
1684             }
1685         }
1686     },
1688     /**
1689      * Node toString
1690      * @method toString
1691      * @return {string} string representation of the node
1692      */
1693     toString: function() {
1694         return "Node (" + this.index + ")";
1695     }
1699 YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
1702  * A custom YAHOO.widget.Node that handles the unique nature of 
1703  * the virtual, presentationless root node.
1704  * @namespace YAHOO.widget
1705  * @class RootNode
1706  * @extends YAHOO.widget.Node
1707  * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
1708  * @constructor
1709  */
1710 YAHOO.widget.RootNode = function(oTree) {
1711         // Initialize the node with null params.  The root node is a
1712         // special case where the node has no presentation.  So we have
1713         // to alter the standard properties a bit.
1714         this.init(null, null, true);
1715         
1716         /*
1717          * For the root node, we get the tree reference from as a param
1718          * to the constructor instead of from the parent element.
1719          */
1720         this.tree = oTree;
1723 YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
1724     
1725     // overrides YAHOO.widget.Node
1726     getNodeHtml: function() { 
1727         return ""; 
1728     },
1730     toString: function() { 
1731         return "RootNode";
1732     },
1734     loadComplete: function() { 
1735         this.tree.draw();
1736     },
1738     collapse: function() {},
1739     expand: function() {}
1743  * The default node presentation.  The first parameter should be
1744  * either a string that will be used as the node's label, or an object
1745  * that has a string propery called label.  By default, the clicking the
1746  * label will toggle the expanded/collapsed state of the node.  By
1747  * changing the href property of the instance, this behavior can be
1748  * changed so that the label will go to the specified href.
1749  * @namespace YAHOO.widget
1750  * @class TextNode
1751  * @extends YAHOO.widget.Node
1752  * @constructor
1753  * @param oData {object} a string or object containing the data that will
1754  * be used to render this node
1755  * @param oParent {YAHOO.widget.Node} this node's parent node
1756  * @param expanded {boolean} the initial expanded/collapsed state
1757  */
1758 YAHOO.widget.TextNode = function(oData, oParent, expanded) {
1760     if (oData) { 
1761         this.init(oData, oParent, expanded);
1762         this.setUpLabel(oData);
1763     }
1765     this.logger     = new YAHOO.widget.LogWriter(this.toString());
1768 YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
1769     
1770     /**
1771      * The CSS class for the label href.  Defaults to ygtvlabel, but can be
1772      * overridden to provide a custom presentation for a specific node.
1773      * @property labelStyle
1774      * @type string
1775      */
1776     labelStyle: "ygtvlabel",
1778     /**
1779      * The derived element id of the label for this node
1780      * @property labelElId
1781      * @type string
1782      */
1783     labelElId: null,
1785     /**
1786      * The text for the label.  It is assumed that the oData parameter will
1787      * either be a string that will be used as the label, or an object that
1788      * has a property called "label" that we will use.
1789      * @property label
1790      * @type string
1791      */
1792     label: null,
1794     textNodeParentChange: function() {
1796         /**
1797          * Custom event that is fired when the text node label is clicked.  The
1798          * custom event is defined on the tree instance, so there is a single
1799          * event that handles all nodes in the tree.  The node clicked is 
1800          * provided as an argument
1801          *
1802          * @event labelClick
1803          * @for YAHOO.widget.TreeView
1804          * @param {YAHOO.widget.Node} node the node clicked
1805          */
1806         if (this.tree && !this.tree.hasEvent("labelClick")) {
1807             this.tree.createEvent("labelClick", this.tree);
1808         }
1809        
1810     },
1812     /**
1813      * Sets up the node label
1814      * @method setUpLabel
1815      * @param oData string containing the label, or an object with a label property
1816      */
1817     setUpLabel: function(oData) { 
1818         
1819         // set up the custom event on the tree
1820         this.textNodeParentChange();
1821         this.subscribe("parentChange", this.textNodeParentChange);
1823         if (typeof oData == "string") {
1824             oData = { label: oData };
1825         }
1826         this.label = oData.label;
1827         
1828         // update the link
1829         if (oData.href) {
1830             this.href = oData.href;
1831         }
1833         // set the target
1834         if (oData.target) {
1835             this.target = oData.target;
1836         }
1838         if (oData.style) {
1839             this.labelStyle = oData.style;
1840         }
1842         this.labelElId = "ygtvlabelel" + this.index;
1843     },
1845     /**
1846      * Returns the label element
1847      * @for YAHOO.widget.TextNode
1848      * @method getLabelEl
1849      * @return {object} the element
1850      */
1851     getLabelEl: function() { 
1852         return document.getElementById(this.labelElId);
1853     },
1855     // overrides YAHOO.widget.Node
1856     getNodeHtml: function() { 
1857         this.logger.log("Generating html");
1858         var sb = [];
1860         sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
1861         sb[sb.length] = '<tr>';
1862         
1863         for (var i=0;i<this.depth;++i) {
1864             //sb[sb.length] = '<td><div class="' + this.getDepthStyle(i) + '">&#160;</div></td>';
1865             //sb[sb.length] = '<td><div class="' + this.getDepthStyle(i) + '"></div></td>';
1866             sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
1867         }
1869         var getNode = 'YAHOO.widget.TreeView.getNode(\'' +
1870                         this.tree.id + '\',' + this.index + ')';
1872         sb[sb.length] = '<td';
1873         // sb[sb.length] = ' onselectstart="return false"';
1874         sb[sb.length] = ' id="' + this.getToggleElId() + '"';
1875         sb[sb.length] = ' class="' + this.getStyle() + '"';
1876         if (this.hasChildren(true)) {
1877             sb[sb.length] = ' onmouseover="this.className=';
1878             sb[sb.length] = getNode + '.getHoverStyle()"';
1879             sb[sb.length] = ' onmouseout="this.className=';
1880             sb[sb.length] = getNode + '.getStyle()"';
1881         }
1882         sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '">';
1884         sb[sb.length] = '<div class="ygtvspacer">';
1886         /*
1887         sb[sb.length] = '<img id="' + this.getSpacerId() + '"';
1888         sb[sb.length] = ' alt=""';
1889         sb[sb.length] = ' tabindex=0';
1890         sb[sb.length] = ' src="' + this.spacerPath + '"';
1891         sb[sb.length] = ' title="' + this.getStateText() + '"';
1892         sb[sb.length] = ' class="ygtvspacer"';
1893         // sb[sb.length] = ' onkeypress="return ' + getNode + '".onKeyPress()"';
1894         sb[sb.length] = ' />';
1895         */
1897         //sb[sb.length] = '&#160;';
1899         sb[sb.length] = '</div>';
1900         sb[sb.length] = '</td>';
1901         sb[sb.length] = '<td ';
1902         sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
1903         sb[sb.length] = ' >';
1904         sb[sb.length] = '<a';
1905         sb[sb.length] = ' id="' + this.labelElId + '"';
1906         sb[sb.length] = ' class="' + this.labelStyle + '"';
1907         sb[sb.length] = ' href="' + this.href + '"';
1908         sb[sb.length] = ' target="' + this.target + '"';
1909         sb[sb.length] = ' onclick="return ' + getNode + '.onLabelClick(' + getNode +')"';
1910         if (this.hasChildren(true)) {
1911             sb[sb.length] = ' onmouseover="document.getElementById(\'';
1912             sb[sb.length] = this.getToggleElId() + '\').className=';
1913             sb[sb.length] = getNode + '.getHoverStyle()"';
1914             sb[sb.length] = ' onmouseout="document.getElementById(\'';
1915             sb[sb.length] = this.getToggleElId() + '\').className=';
1916             sb[sb.length] = getNode + '.getStyle()"';
1917         }
1918         sb[sb.length] = ' >';
1919         sb[sb.length] = this.label;
1920         sb[sb.length] = '</a>';
1921         sb[sb.length] = '</td>';
1922         sb[sb.length] = '</tr>';
1923         sb[sb.length] = '</table>';
1925         return sb.join("");
1926     },
1929     /**
1930      * Executed when the label is clicked.  Fires the labelClick custom event.
1931      * @method onLabelClick
1932      * @param me {Node} this node
1933      * @scope the anchor tag clicked
1934      * @return false to cancel the anchor click
1935      */
1936     onLabelClick: function(me) { 
1937         me.logger.log("onLabelClick " + me.label);
1938         return me.tree.fireEvent("labelClick", me);
1939         //return true;
1940     },
1942     toString: function() { 
1943         return "TextNode (" + this.index + ") " + this.label;
1944     }
1948  * A menu-specific implementation that differs from TextNode in that only 
1949  * one sibling can be expanded at a time.
1950  * @namespace YAHOO.widget
1951  * @class MenuNode
1952  * @extends YAHOO.widget.TextNode
1953  * @param oData {object} a string or object containing the data that will
1954  * be used to render this node
1955  * @param oParent {YAHOO.widget.Node} this node's parent node
1956  * @param expanded {boolean} the initial expanded/collapsed state
1957  * @constructor
1958  */
1959 YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
1960         if (oData) { 
1961                 this.init(oData, oParent, expanded);
1962                 this.setUpLabel(oData);
1963         }
1965     /*
1966      * Menus usually allow only one branch to be open at a time.
1967      */
1968         this.multiExpand = false;
1970     this.logger     = new YAHOO.widget.LogWriter(this.toString());
1974 YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
1976     toString: function() { 
1977         return "MenuNode (" + this.index + ") " + this.label;
1978     }
1982  * This implementation takes either a string or object for the
1983  * oData argument.  If is it a string, we will use it for the display
1984  * of this node (and it can contain any html code).  If the parameter
1985  * is an object, we look for a parameter called "html" that will be
1986  * used for this node's display.
1987  * @namespace YAHOO.widget
1988  * @class HTMLNode
1989  * @extends YAHOO.widget.Node
1990  * @constructor
1991  * @param oData {object} a string or object containing the data that will
1992  * be used to render this node
1993  * @param oParent {YAHOO.widget.Node} this node's parent node
1994  * @param expanded {boolean} the initial expanded/collapsed state
1995  * @param hasIcon {boolean} specifies whether or not leaf nodes should
1996  * have an icon
1997  */
1998 YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
1999     if (oData) { 
2000         this.init(oData, oParent, expanded);
2001         this.initContent(oData, hasIcon);
2002     }
2005 YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, {
2007     /**
2008      * The CSS class for the html content container.  Defaults to ygtvhtml, but 
2009      * can be overridden to provide a custom presentation for a specific node.
2010      * @property contentStyle
2011      * @type string
2012      */
2013     contentStyle: "ygtvhtml",
2015     /**
2016      * The generated id that will contain the data passed in by the implementer.
2017      * @property contentElId
2018      * @type string
2019      */
2020     contentElId: null,
2022     /**
2023      * The HTML content to use for this node's display
2024      * @property content
2025      * @type string
2026      */
2027     content: null,
2029     /**
2030      * Sets up the node label
2031      * @property initContent
2032      * @param {object} An html string or object containing an html property
2033      * @param {boolean} hasIcon determines if the node will be rendered with an
2034      * icon or not
2035      */
2036     initContent: function(oData, hasIcon) { 
2037         if (typeof oData == "string") {
2038             oData = { html: oData };
2039         }
2041         this.html = oData.html;
2042         this.contentElId = "ygtvcontentel" + this.index;
2043         this.hasIcon = hasIcon;
2045         this.logger = new YAHOO.widget.LogWriter(this.toString());
2046     },
2048     /**
2049      * Returns the outer html element for this node's content
2050      * @method getContentEl
2051      * @return {HTMLElement} the element
2052      */
2053     getContentEl: function() { 
2054         return document.getElementById(this.contentElId);
2055     },
2057     // overrides YAHOO.widget.Node
2058     getNodeHtml: function() { 
2059         this.logger.log("Generating html");
2060         var sb = [];
2062         sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
2063         sb[sb.length] = '<tr>';
2064         
2065         for (var i=0;i<this.depth;++i) {
2066             //sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '">&#160;</td>';
2067             sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
2068         }
2070         if (this.hasIcon) {
2071             sb[sb.length] = '<td';
2072             sb[sb.length] = ' id="' + this.getToggleElId() + '"';
2073             sb[sb.length] = ' class="' + this.getStyle() + '"';
2074             sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '"';
2075             if (this.hasChildren(true)) {
2076                 sb[sb.length] = ' onmouseover="this.className=';
2077                 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
2078                 sb[sb.length] = this.tree.id + '\',' + this.index +  ').getHoverStyle()"';
2079                 sb[sb.length] = ' onmouseout="this.className=';
2080                 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
2081                 sb[sb.length] = this.tree.id + '\',' + this.index +  ').getStyle()"';
2082             }
2083             //sb[sb.length] = '>&#160;</td>';
2084             sb[sb.length] = '><div class="ygtvspacer"></div></td>';
2085         }
2087         sb[sb.length] = '<td';
2088         sb[sb.length] = ' id="' + this.contentElId + '"';
2089         sb[sb.length] = ' class="' + this.contentStyle + '"';
2090         sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
2091         sb[sb.length] = ' >';
2092         sb[sb.length] = this.html;
2093         sb[sb.length] = '</td>';
2094         sb[sb.length] = '</tr>';
2095         sb[sb.length] = '</table>';
2097         return sb.join("");
2098     },
2100     toString: function() { 
2101         return "HTMLNode (" + this.index + ")";
2102     }
2106  * A static factory class for tree view expand/collapse animations
2107  * @class TVAnim
2108  * @static
2109  */
2110 YAHOO.widget.TVAnim = function() {
2111     return {
2112         /**
2113          * Constant for the fade in animation
2114          * @property FADE_IN
2115          * @type string
2116          * @static
2117          */
2118         FADE_IN: "TVFadeIn",
2120         /**
2121          * Constant for the fade out animation
2122          * @property FADE_OUT
2123          * @type string
2124          * @static
2125          */
2126         FADE_OUT: "TVFadeOut",
2128         /**
2129          * Returns a ygAnim instance of the given type
2130          * @method getAnim
2131          * @param type {string} the type of animation
2132          * @param el {HTMLElement} the element to element (probably the children div)
2133          * @param callback {function} function to invoke when the animation is done.
2134          * @return {YAHOO.util.Animation} the animation instance
2135          * @static
2136          */
2137         getAnim: function(type, el, callback) {
2138             if (YAHOO.widget[type]) {
2139                 return new YAHOO.widget[type](el, callback);
2140             } else {
2141                 return null;
2142             }
2143         },
2145         /**
2146          * Returns true if the specified animation class is available
2147          * @method isValid
2148          * @param type {string} the type of animation
2149          * @return {boolean} true if valid, false if not
2150          * @static
2151          */
2152         isValid: function(type) {
2153             return (YAHOO.widget[type]);
2154         }
2155     };
2156 } ();
2159  * A 1/2 second fade-in animation.
2160  * @class TVFadeIn
2161  * @constructor
2162  * @param el {HTMLElement} the element to animate
2163  * @param callback {function} function to invoke when the animation is finished
2164  */
2165 YAHOO.widget.TVFadeIn = function(el, callback) {
2166     /**
2167      * The element to animate
2168      * @property el
2169      * @type HTMLElement
2170      */
2171     this.el = el;
2173     /**
2174      * the callback to invoke when the animation is complete
2175      * @property callback
2176      * @type function
2177      */
2178     this.callback = callback;
2180     this.logger = new YAHOO.widget.LogWriter(this.toString());
2183 YAHOO.widget.TVFadeIn.prototype = {
2184     /**
2185      * Performs the animation
2186      * @method animate
2187      */
2188     animate: function() {
2189         var tvanim = this;
2191         var s = this.el.style;
2192         s.opacity = 0.1;
2193         s.filter = "alpha(opacity=10)";
2194         s.display = "";
2196         var dur = 0.4; 
2197         var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur);
2198         a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2199         a.animate();
2200     },
2202     /**
2203      * Clean up and invoke callback
2204      * @method onComplete
2205      */
2206     onComplete: function() {
2207         this.callback();
2208     },
2210     /**
2211      * toString
2212      * @method toString
2213      * @return {string} the string representation of the instance
2214      */
2215     toString: function() {
2216         return "TVFadeIn";
2217     }
2221  * A 1/2 second fade out animation.
2222  * @class TVFadeOut
2223  * @constructor
2224  * @param el {HTMLElement} the element to animate
2225  * @param callback {Function} function to invoke when the animation is finished
2226  */
2227 YAHOO.widget.TVFadeOut = function(el, callback) {
2228     /**
2229      * The element to animate
2230      * @property el
2231      * @type HTMLElement
2232      */
2233     this.el = el;
2235     /**
2236      * the callback to invoke when the animation is complete
2237      * @property callback
2238      * @type function
2239      */
2240     this.callback = callback;
2242     this.logger = new YAHOO.widget.LogWriter(this.toString());
2245 YAHOO.widget.TVFadeOut.prototype = {
2246     /**
2247      * Performs the animation
2248      * @method animate
2249      */
2250     animate: function() {
2251         var tvanim = this;
2252         var dur = 0.4;
2253         var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
2254         a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2255         a.animate();
2256     },
2258     /**
2259      * Clean up and invoke callback
2260      * @method onComplete
2261      */
2262     onComplete: function() {
2263         var s = this.el.style;
2264         s.display = "none";
2265         // s.opacity = 1;
2266         s.filter = "alpha(opacity=100)";
2267         this.callback();
2268     },
2270     /**
2271      * toString
2272      * @method toString
2273      * @return {string} the string representation of the instance
2274      */
2275     toString: function() {
2276         return "TVFadeOut";
2277     }