MDL-11517 reserved word MOD used in table alias in questions backup code
[moodle-pu.git] / lib / yui / treeview / treeview-debug.js
blob1d6c31a6a6ba116ec92be7cd4943a28e4e1654cb
1 /*
2 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.3.0
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.  This is a sparse
47      * array, so the length property can't be relied upon for a
48      * node count for the tree.
49      * @property _nodes
50      * @type Node[]
51      * @private
52      */
53     _nodes: null,
55     /**
56      * We lock the tree control while waiting for the dynamic loader to return
57      * @property locked
58      * @type boolean
59      */
60     locked: false,
62     /**
63      * The animation to use for expanding children, if any
64      * @property _expandAnim
65      * @type string
66      * @private
67      */
68     _expandAnim: null,
70     /**
71      * The animation to use for collapsing children, if any
72      * @property _collapseAnim
73      * @type string
74      * @private
75      */
76     _collapseAnim: null,
78     /**
79      * The current number of animations that are executing
80      * @property _animCount
81      * @type int
82      * @private
83      */
84     _animCount: 0,
86     /**
87      * The maximum number of animations to run at one time.
88      * @property maxAnim
89      * @type int
90      */
91     maxAnim: 2,
93     /**
94      * Sets up the animation for expanding children
95      * @method setExpandAnim
96      * @param {string} type the type of animation (acceptable values defined 
97      * in YAHOO.widget.TVAnim)
98      */
99     setExpandAnim: function(type) {
100         if (YAHOO.widget.TVAnim.isValid(type)) {
101             this._expandAnim = type;
102         }
103     },
105     /**
106      * Sets up the animation for collapsing children
107      * @method setCollapseAnim
108      * @param {string} the type of animation (acceptable values defined in 
109      * YAHOO.widget.TVAnim)
110      */
111     setCollapseAnim: function(type) {
112         if (YAHOO.widget.TVAnim.isValid(type)) {
113             this._collapseAnim = type;
114         }
115     },
117     /**
118      * Perform the expand animation if configured, or just show the
119      * element if not configured or too many animations are in progress
120      * @method animateExpand
121      * @param el {HTMLElement} the element to animate
122      * @param node {YAHOO.util.Node} the node that was expanded
123      * @return {boolean} true if animation could be invoked, false otherwise
124      */
125     animateExpand: function(el, node) {
126         this.logger.log("animating expand");
128         if (this._expandAnim && this._animCount < this.maxAnim) {
129             // this.locked = true;
130             var tree = this;
131             var a = YAHOO.widget.TVAnim.getAnim(this._expandAnim, el, 
132                             function() { tree.expandComplete(node); });
133             if (a) { 
134                 ++this._animCount;
135                 this.fireEvent("animStart", {
136                         "node": node, 
137                         "type": "expand"
138                     });
139                 a.animate();
140             }
142             return true;
143         }
145         return false;
146     },
148     /**
149      * Perform the collapse animation if configured, or just show the
150      * element if not configured or too many animations are in progress
151      * @method animateCollapse
152      * @param el {HTMLElement} the element to animate
153      * @param node {YAHOO.util.Node} the node that was expanded
154      * @return {boolean} true if animation could be invoked, false otherwise
155      */
156     animateCollapse: function(el, node) {
157         this.logger.log("animating collapse");
159         if (this._collapseAnim && this._animCount < this.maxAnim) {
160             // this.locked = true;
161             var tree = this;
162             var a = YAHOO.widget.TVAnim.getAnim(this._collapseAnim, el, 
163                             function() { tree.collapseComplete(node); });
164             if (a) { 
165                 ++this._animCount;
166                 this.fireEvent("animStart", {
167                         "node": node, 
168                         "type": "collapse"
169                     });
170                 a.animate();
171             }
173             return true;
174         }
176         return false;
177     },
179     /**
180      * Function executed when the expand animation completes
181      * @method expandComplete
182      */
183     expandComplete: function(node) {
184         this.logger.log("expand complete: " + this.id);
185         --this._animCount;
186         this.fireEvent("animComplete", {
187                 "node": node, 
188                 "type": "expand"
189             });
190         // this.locked = false;
191     },
193     /**
194      * Function executed when the collapse animation completes
195      * @method collapseComplete
196      */
197     collapseComplete: function(node) {
198         this.logger.log("collapse complete: " + this.id);
199         --this._animCount;
200         this.fireEvent("animComplete", {
201                 "node": node, 
202                 "type": "collapse"
203             });
204         // this.locked = false;
205     },
207     /**
208      * Initializes the tree
209      * @method init
210      * @parm {string|HTMLElement} id the id of the element that will hold the tree
211      * @private
212      */
213     init: function(id) {
215         this.id = id;
217         if ("string" !== typeof id) {
218             this._el = id;
219             this.id = this.generateId(id);
220         }
222         /**
223          * When animation is enabled, this event fires when the animation
224          * starts
225          * @event animStart
226          * @type CustomEvent
227          * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
228          * @parm {String} type the type of animation ("expand" or "collapse")
229          */
230         this.createEvent("animStart", this);
232         /**
233          * When animation is enabled, this event fires when the animation
234          * completes
235          * @event animComplete
236          * @type CustomEvent
237          * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
238          * @parm {String} type the type of animation ("expand" or "collapse")
239          */
240         this.createEvent("animComplete", this);
242         /**
243          * Fires when a node is going to be collapsed.  Return false to stop
244          * the collapse.
245          * @event collapse
246          * @type CustomEvent
247          * @param {YAHOO.widget.Node} node the node that is collapsing
248          */
249         this.createEvent("collapse", this);
251         /**
252          * Fires after a node is successfully collapsed.  This event will not fire
253          * if the "collapse" event was cancelled.
254          * @event collapseComplete
255          * @type CustomEvent
256          * @param {YAHOO.widget.Node} node the node that was collapsed
257          */
258         this.createEvent("collapseComplete", this);
260         /**
261          * Fires when a node is going to be expanded.  Return false to stop
262          * the collapse.
263          * @event expand
264          * @type CustomEvent
265          * @param {YAHOO.widget.Node} node the node that is expanding
266          */
267         this.createEvent("expand", this);
269         /**
270          * Fires after a node is successfully expanded.  This event will not fire
271          * if the "expand" event was cancelled.
272          * @event expandComplete
273          * @type CustomEvent
274          * @param {YAHOO.widget.Node} node the node that was expanded
275          */
276         this.createEvent("expandComplete", this);
278         this._nodes = [];
280         // store a global reference
281         YAHOO.widget.TreeView.trees[this.id] = this;
283         // Set up the root node
284         this.root = new YAHOO.widget.RootNode(this);
286         var LW = YAHOO.widget.LogWriter;
288         this.logger = (LW) ? new LW(this.toString()) : YAHOO;
290         this.logger.log("tree init: " + this.id);
292         // YAHOO.util.Event.onContentReady(this.id, this.handleAvailable, this, true);
293         // YAHOO.util.Event.on(this.id, "click", this.handleClick, this, true);
294     },
296     //handleAvailable: function() {
297         //var Event = YAHOO.util.Event;
298         //Event.on(this.id, 
299     //},
301     /**
302      * Renders the tree boilerplate and visible nodes
303      * @method draw
304      */
305     draw: function() {
306         var html = this.root.getHtml();
307         this.getEl().innerHTML = html;
308         this.firstDraw = false;
309     },
311     /**
312      * Returns the tree's host element
313      * @method getEl
314      * @return {HTMLElement} the host element
315      */
316     getEl: function() {
317         if (! this._el) {
318             this._el = document.getElementById(this.id);
319         }
320         return this._el;
321     },
323     /**
324      * Nodes register themselves with the tree instance when they are created.
325      * @method regNode
326      * @param node {Node} the node to register
327      * @private
328      */
329     regNode: function(node) {
330         this._nodes[node.index] = node;
331     },
333     /**
334      * Returns the root node of this tree
335      * @method getRoot
336      * @return {Node} the root node
337      */
338     getRoot: function() {
339         return this.root;
340     },
342     /**
343      * Configures this tree to dynamically load all child data
344      * @method setDynamicLoad
345      * @param {function} fnDataLoader the function that will be called to get the data
346      * @param iconMode {int} configures the icon that is displayed when a dynamic
347      * load node is expanded the first time without children.  By default, the 
348      * "collapse" icon will be used.  If set to 1, the leaf node icon will be
349      * displayed.
350      */
351     setDynamicLoad: function(fnDataLoader, iconMode) { 
352         this.root.setDynamicLoad(fnDataLoader, iconMode);
353     },
355     /**
356      * Expands all child nodes.  Note: this conflicts with the "multiExpand"
357      * node property.  If expand all is called in a tree with nodes that
358      * do not allow multiple siblings to be displayed, only the last sibling
359      * will be expanded.
360      * @method expandAll
361      */
362     expandAll: function() { 
363         if (!this.locked) {
364             this.root.expandAll(); 
365         }
366     },
368     /**
369      * Collapses all expanded child nodes in the entire tree.
370      * @method collapseAll
371      */
372     collapseAll: function() { 
373         if (!this.locked) {
374             this.root.collapseAll(); 
375         }
376     },
378     /**
379      * Returns a node in the tree that has the specified index (this index
380      * is created internally, so this function probably will only be used
381      * in html generated for a given node.)
382      * @method getNodeByIndex
383      * @param {int} nodeIndex the index of the node wanted
384      * @return {Node} the node with index=nodeIndex, null if no match
385      */
386     getNodeByIndex: function(nodeIndex) {
387         var n = this._nodes[nodeIndex];
388         return (n) ? n : null;
389     },
391     /**
392      * Returns a node that has a matching property and value in the data
393      * object that was passed into its constructor.
394      * @method getNodeByProperty
395      * @param {object} property the property to search (usually a string)
396      * @param {object} value the value we want to find (usuall an int or string)
397      * @return {Node} the matching node, null if no match
398      */
399     getNodeByProperty: function(property, value) {
400         for (var i in this._nodes) {
401             var n = this._nodes[i];
402             if (n.data && value == n.data[property]) {
403                 return n;
404             }
405         }
407         return null;
408     },
410     /**
411      * Returns a collection of nodes that have a matching property 
412      * and value in the data object that was passed into its constructor.  
413      * @method getNodesByProperty
414      * @param {object} property the property to search (usually a string)
415      * @param {object} value the value we want to find (usuall an int or string)
416      * @return {Array} the matching collection of nodes, null if no match
417      */
418     getNodesByProperty: function(property, value) {
419         var values = [];
420         for (var i in this._nodes) {
421             var n = this._nodes[i];
422             if (n.data && value == n.data[property]) {
423                 values.push(n);
424             }
425         }
427         return (values.length) ? values : null;
428     },
430     /**
431      * Removes the node and its children, and optionally refreshes the 
432      * branch of the tree that was affected.
433      * @method removeNode
434      * @param {Node} The node to remove
435      * @param {boolean} autoRefresh automatically refreshes branch if true
436      * @return {boolean} False is there was a problem, true otherwise.
437      */
438     removeNode: function(node, autoRefresh) { 
440         // Don't delete the root node
441         if (node.isRoot()) {
442             return false;
443         }
445         // Get the branch that we may need to refresh
446         var p = node.parent;
447         if (p.parent) {
448             p = p.parent;
449         }
451         // Delete the node and its children
452         this._deleteNode(node);
454         // Refresh the parent of the parent
455         if (autoRefresh && p && p.childrenRendered) {
456             p.refresh();
457         }
459         return true;
460     },
462     /**
463      * Deletes this nodes child collection, recursively.  Also collapses
464      * the node, and resets the dynamic load flag.  The primary use for
465      * this method is to purge a node and allow it to fetch its data
466      * dynamically again.
467      * @method removeChildren
468      * @param {Node} node the node to purge
469      */
470     removeChildren: function(node) { 
471         this.logger.log("Removing children for " + node);
472         while (node.children.length) {
473             this._deleteNode(node.children[0]);
474         }
476         node.childrenRendered = false;
477         node.dynamicLoadComplete = false;
478         if (node.expanded) {
479             node.collapse();
480         } else {
481             node.updateIcon();
482         }
483     },
485     /**
486      * Deletes the node and recurses children
487      * @method _deleteNode
488      * @private
489      */
490     _deleteNode: function(node) { 
491         // Remove all the child nodes first
492         this.removeChildren(node);
494         // Remove the node from the tree
495         this.popNode(node);
496     },
498     /**
499      * Removes the node from the tree, preserving the child collection 
500      * to make it possible to insert the branch into another part of the 
501      * tree, or another tree.
502      * @method popNode
503      * @param {Node} the node to remove
504      */
505     popNode: function(node) { 
506         var p = node.parent;
508         // Update the parent's collection of children
509         var a = [];
511         for (var i=0, len=p.children.length;i<len;++i) {
512             if (p.children[i] != node) {
513                 a[a.length] = p.children[i];
514             }
515         }
517         p.children = a;
519         // reset the childrenRendered flag for the parent
520         p.childrenRendered = false;
522          // Update the sibling relationship
523         if (node.previousSibling) {
524             node.previousSibling.nextSibling = node.nextSibling;
525         }
527         if (node.nextSibling) {
528             node.nextSibling.previousSibling = node.previousSibling;
529         }
531         node.parent = null;
532         node.previousSibling = null;
533         node.nextSibling = null;
534         node.tree = null;
536         // Update the tree's node collection 
537         delete this._nodes[node.index];
538     },
541     /**
542      * TreeView instance toString
543      * @method toString
544      * @return {string} string representation of the tree
545      */
546     toString: function() {
547         return "TreeView " + this.id;
548     },
550     /**
551      * Generates an unique id for an element if it doesn't yet have one
552      * @method generateId
553      * @private
554      */
555     generateId: function(el) {
556         var id = el.id;
558         if (!id) {
559             id = "yui-tv-auto-id-" + YAHOO.widget.TreeView.counter;
560             ++YAHOO.widget.TreeView.counter;
561         }
563         return id;
564     },
566     /**
567      * Abstract method that is executed when a node is expanded
568      * @method onExpand
569      * @param node {Node} the node that was expanded
570      * @deprecated use treeobj.subscribe("expand") instead
571      */
572     onExpand: function(node) { },
574     /**
575      * Abstract method that is executed when a node is collapsed.
576      * @method onCollapse
577      * @param node {Node} the node that was collapsed.
578      * @deprecated use treeobj.subscribe("collapse") instead
579      */
580     onCollapse: function(node) { }
584 YAHOO.augment(YAHOO.widget.TreeView, YAHOO.util.EventProvider);
587  * Running count of all nodes created in all trees.  This is 
588  * used to provide unique identifies for all nodes.  Deleting
589  * nodes does not change the nodeCount.
590  * @property YAHOO.widget.TreeView.nodeCount
591  * @type int
592  * @static
593  */
594 YAHOO.widget.TreeView.nodeCount = 0;
597  * Global cache of tree instances
598  * @property YAHOO.widget.TreeView.trees
599  * @type Array
600  * @static
601  * @private
602  */
603 YAHOO.widget.TreeView.trees = [];
606  * Counter for generating a new unique element id
607  * @property YAHOO.widget.TreeView.counter
608  * @static
609  * @private
610  */
611 YAHOO.widget.TreeView.counter = 0;
614  * Global method for getting a tree by its id.  Used in the generated
615  * tree html.
616  * @method YAHOO.widget.TreeView.getTree
617  * @param treeId {String} the id of the tree instance
618  * @return {TreeView} the tree instance requested, null if not found.
619  * @static
620  */
621 YAHOO.widget.TreeView.getTree = function(treeId) {
622     var t = YAHOO.widget.TreeView.trees[treeId];
623     return (t) ? t : null;
628  * Global method for getting a node by its id.  Used in the generated
629  * tree html.
630  * @method YAHOO.widget.TreeView.getNode
631  * @param treeId {String} the id of the tree instance
632  * @param nodeIndex {String} the index of the node to return
633  * @return {Node} the node instance requested, null if not found
634  * @static
635  */
636 YAHOO.widget.TreeView.getNode = function(treeId, nodeIndex) {
637     var t = YAHOO.widget.TreeView.getTree(treeId);
638     return (t) ? t.getNodeByIndex(nodeIndex) : null;
642  * Add a DOM event
643  * @method YAHOO.widget.TreeView.addHandler
644  * @param el the elment to bind the handler to
645  * @param {string} sType the type of event handler
646  * @param {function} fn the callback to invoke
647  * @static
648  */
649 YAHOO.widget.TreeView.addHandler = function (el, sType, fn) {
650     if (el.addEventListener) {
651         el.addEventListener(sType, fn, false);
652     } else if (el.attachEvent) {
653         el.attachEvent("on" + sType, fn);
654     }
658  * Remove a DOM event
659  * @method YAHOO.widget.TreeView.removeHandler
660  * @param el the elment to bind the handler to
661  * @param {string} sType the type of event handler
662  * @param {function} fn the callback to invoke
663  * @static
664  */
666 YAHOO.widget.TreeView.removeHandler = function (el, sType, fn) {
667     if (el.removeEventListener) {
668         el.removeEventListener(sType, fn, false);
669     } else if (el.detachEvent) {
670         el.detachEvent("on" + sType, fn);
671     }
675  * Attempts to preload the images defined in the styles used to draw the tree by
676  * rendering off-screen elements that use the styles.
677  * @method YAHOO.widget.TreeView.preload
678  * @param {string} prefix the prefix to use to generate the names of the
679  * images to preload, default is ygtv
680  * @static
681  */
682 YAHOO.widget.TreeView.preload = function(e, prefix) {
683     prefix = prefix || "ygtv";
685     YAHOO.log("Preloading images: " + prefix, "info", "TreeView");
687     var styles = ["tn","tm","tmh","tp","tph","ln","lm","lmh","lp","lph","loading"];
688     // var styles = ["tp"];
690     var sb = [];
691     
692     // save the first one for the outer container
693     for (var i=1; i < styles.length; i=i+1) { 
694         sb[sb.length] = '<span class="' + prefix + styles[i] + '">&#160;</span>';
695     }
697     var f = document.createElement("div");
698     var s = f.style;
699     s.className = prefix + styles[0];
700     s.position = "absolute";
701     s.height = "1px";
702     s.width = "1px";
703     s.top = "-1000px";
704     s.left = "-1000px";
705     f.innerHTML = sb.join("");
707     document.body.appendChild(f);
709     YAHOO.widget.TreeView.removeHandler(window, 
710                 "load", YAHOO.widget.TreeView.preload);
714 YAHOO.widget.TreeView.addHandler(window, 
715                 "load", YAHOO.widget.TreeView.preload);
718  * The base class for all tree nodes.  The node's presentation and behavior in
719  * response to mouse events is handled in Node subclasses.
720  * @namespace YAHOO.widget
721  * @class Node
722  * @uses YAHOO.util.EventProvider
723  * @param oData {object} a string or object containing the data that will
724  * be used to render this node
725  * @param oParent {Node} this node's parent node
726  * @param expanded {boolean} the initial expanded/collapsed state
727  * @constructor
728  */
729 YAHOO.widget.Node = function(oData, oParent, expanded) {
730     if (oData) { this.init(oData, oParent, expanded); }
733 YAHOO.widget.Node.prototype = {
735     /**
736      * The index for this instance obtained from global counter in YAHOO.widget.TreeView.
737      * @property index
738      * @type int
739      */
740     index: 0,
742     /**
743      * This node's child node collection.
744      * @property children
745      * @type Node[] 
746      */
747     children: null,
749     /**
750      * Tree instance this node is part of
751      * @property tree
752      * @type TreeView
753      */
754     tree: null,
756     /**
757      * The data linked to this node.  This can be any object or primitive
758      * value, and the data can be used in getNodeHtml().
759      * @property data
760      * @type object
761      */
762     data: null,
764     /**
765      * Parent node
766      * @property parent
767      * @type Node
768      */
769     parent: null,
771     /**
772      * The depth of this node.  We start at -1 for the root node.
773      * @property depth
774      * @type int
775      */
776     depth: -1,
778     /**
779      * The href for the node's label.  If one is not specified, the href will
780      * be set so that it toggles the node.
781      * @property href
782      * @type string
783      */
784     href: null,
786     /**
787      * The label href target, defaults to current window
788      * @property target
789      * @type string
790      */
791     target: "_self",
793     /**
794      * The node's expanded/collapsed state
795      * @property expanded
796      * @type boolean
797      */
798     expanded: false,
800     /**
801      * Can multiple children be expanded at once?
802      * @property multiExpand
803      * @type boolean
804      */
805     multiExpand: true,
807     /**
808      * Should we render children for a collapsed node?  It is possible that the
809      * implementer will want to render the hidden data...  @todo verify that we 
810      * need this, and implement it if we do.
811      * @property renderHidden
812      * @type boolean
813      */
814     renderHidden: false,
816     /**
817      * This flag is set to true when the html is generated for this node's
818      * children, and set to false when new children are added.
819      * @property childrenRendered
820      * @type boolean
821      */
822     childrenRendered: false,
824     /**
825      * Dynamically loaded nodes only fetch the data the first time they are
826      * expanded.  This flag is set to true once the data has been fetched.
827      * @property dynamicLoadComplete
828      * @type boolean
829      */
830     dynamicLoadComplete: false,
832     /**
833      * This node's previous sibling
834      * @property previousSibling
835      * @type Node
836      */
837     previousSibling: null,
839     /**
840      * This node's next sibling
841      * @property nextSibling
842      * @type Node
843      */
844     nextSibling: null,
846     /**
847      * We can set the node up to call an external method to get the child
848      * data dynamically.
849      * @property _dynLoad
850      * @type boolean
851      * @private
852      */
853     _dynLoad: false,
855     /**
856      * Function to execute when we need to get this node's child data.
857      * @property dataLoader
858      * @type function
859      */
860     dataLoader: null,
862     /**
863      * This is true for dynamically loading nodes while waiting for the
864      * callback to return.
865      * @property isLoading
866      * @type boolean
867      */
868     isLoading: false,
870     /**
871      * The toggle/branch icon will not show if this is set to false.  This
872      * could be useful if the implementer wants to have the child contain
873      * extra info about the parent, rather than an actual node.
874      * @property hasIcon
875      * @type boolean
876      */
877     hasIcon: true,
879     /**
880      * Used to configure what happens when a dynamic load node is expanded
881      * and we discover that it does not have children.  By default, it is
882      * treated as if it still could have children (plus/minus icon).  Set
883      * iconMode to have it display like a leaf node instead.
884      * @property iconMode
885      * @type int
886      */
887     iconMode: 0,
889     /**
890      * Specifies whether or not the content area of the node should be allowed
891      * to wrap.
892      * @property nowrap
893      * @type boolean
894      * @default false
895      */
896     nowrap: false,
898     /**
899      * The node type
900      * @property _type
901      * @private
902      */
903     _type: "Node",
905     /*
906     spacerPath: "http://us.i1.yimg.com/us.yimg.com/i/space.gif",
907     expandedText: "Expanded",
908     collapsedText: "Collapsed",
909     loadingText: "Loading",
910     */
912     /**
913      * Initializes this node, gets some of the properties from the parent
914      * @method init
915      * @param oData {object} a string or object containing the data that will
916      * be used to render this node
917      * @param oParent {Node} this node's parent node
918      * @param expanded {boolean} the initial expanded/collapsed state
919      */
920     init: function(oData, oParent, expanded) {
922         this.data       = oData;
923         this.children   = [];
924         this.index      = YAHOO.widget.TreeView.nodeCount;
925         ++YAHOO.widget.TreeView.nodeCount;
926         this.expanded   = expanded;
927         this.logger     = new YAHOO.widget.LogWriter(this.toString());
929         /**
930          * The parentChange event is fired when a parent element is applied
931          * to the node.  This is useful if you need to apply tree-level
932          * properties to a tree that need to happen if a node is moved from
933          * one tree to another.
934          *
935          * @event parentChange
936          * @type CustomEvent
937          */
938         this.createEvent("parentChange", this);
940         // oParent should never be null except when we create the root node.
941         if (oParent) {
942             oParent.appendChild(this);
943         }
944     },
946     /**
947      * Certain properties for the node cannot be set until the parent
948      * is known. This is called after the node is inserted into a tree.
949      * the parent is also applied to this node's children in order to
950      * make it possible to move a branch from one tree to another.
951      * @method applyParent
952      * @param {Node} parentNode this node's parent node
953      * @return {boolean} true if the application was successful
954      */
955     applyParent: function(parentNode) {
956         if (!parentNode) {
957             return false;
958         }
960         this.tree   = parentNode.tree;
961         this.parent = parentNode;
962         this.depth  = parentNode.depth + 1;
964         if (!this.href) {
965             this.href = "javascript:" + this.getToggleLink();
966         }
968         // @todo why was this put here.  This causes new nodes added at the
969         // root level to lose the menu behavior.
970         // if (! this.multiExpand) {
971             // this.multiExpand = parentNode.multiExpand;
972         // }
974         this.tree.regNode(this);
975         parentNode.childrenRendered = false;
977         // cascade update existing children
978         for (var i=0, len=this.children.length;i<len;++i) {
979             this.children[i].applyParent(this);
980         }
982         this.fireEvent("parentChange");
984         return true;
985     },
987     /**
988      * Appends a node to the child collection.
989      * @method appendChild
990      * @param childNode {Node} the new node
991      * @return {Node} the child node
992      * @private
993      */
994     appendChild: function(childNode) {
995         if (this.hasChildren()) {
996             var sib = this.children[this.children.length - 1];
997             sib.nextSibling = childNode;
998             childNode.previousSibling = sib;
999         }
1000         this.children[this.children.length] = childNode;
1001         childNode.applyParent(this);
1003         // part of the IE display issue workaround. If child nodes
1004         // are added after the initial render, and the node was
1005         // instantiated with expanded = true, we need to show the
1006         // children div now that the node has a child.
1007         if (this.childrenRendered && this.expanded) {
1008             this.getChildrenEl().style.display = "";
1009         }
1011         return childNode;
1012     },
1014     /**
1015      * Appends this node to the supplied node's child collection
1016      * @method appendTo
1017      * @param parentNode {Node} the node to append to.
1018      * @return {Node} The appended node
1019      */
1020     appendTo: function(parentNode) {
1021         return parentNode.appendChild(this);
1022     },
1024     /**
1025     * Inserts this node before this supplied node
1026     * @method insertBefore
1027     * @param node {Node} the node to insert this node before
1028     * @return {Node} the inserted node
1029     */
1030     insertBefore: function(node) {
1031         this.logger.log("insertBefore: " + node);
1032         var p = node.parent;
1033         if (p) {
1035             if (this.tree) {
1036                 this.tree.popNode(this);
1037             }
1039             var refIndex = node.isChildOf(p);
1040             //this.logger.log(refIndex);
1041             p.children.splice(refIndex, 0, this);
1042             if (node.previousSibling) {
1043                 node.previousSibling.nextSibling = this;
1044             }
1045             this.previousSibling = node.previousSibling;
1046             this.nextSibling = node;
1047             node.previousSibling = this;
1049             this.applyParent(p);
1050         }
1052         return this;
1053     },
1055     /**
1056     * Inserts this node after the supplied node
1057     * @method insertAfter
1058     * @param node {Node} the node to insert after
1059     * @return {Node} the inserted node
1060     */
1061     insertAfter: function(node) {
1062         this.logger.log("insertAfter: " + node);
1063         var p = node.parent;
1064         if (p) {
1066             if (this.tree) {
1067                 this.tree.popNode(this);
1068             }
1070             var refIndex = node.isChildOf(p);
1071             this.logger.log(refIndex);
1073             if (!node.nextSibling) {
1074                 this.nextSibling = null;
1075                 return this.appendTo(p);
1076             }
1078             p.children.splice(refIndex + 1, 0, this);
1080             node.nextSibling.previousSibling = this;
1081             this.previousSibling = node;
1082             this.nextSibling = node.nextSibling;
1083             node.nextSibling = this;
1085             this.applyParent(p);
1086         }
1088         return this;
1089     },
1091     /**
1092     * Returns true if the Node is a child of supplied Node
1093     * @method isChildOf
1094     * @param parentNode {Node} the Node to check
1095     * @return {boolean} The node index if this Node is a child of 
1096     *                   supplied Node, else -1.
1097     * @private
1098     */
1099     isChildOf: function(parentNode) {
1100         if (parentNode && parentNode.children) {
1101             for (var i=0, len=parentNode.children.length; i<len ; ++i) {
1102                 if (parentNode.children[i] === this) {
1103                     return i;
1104                 }
1105             }
1106         }
1108         return -1;
1109     },
1111     /**
1112      * Returns a node array of this node's siblings, null if none.
1113      * @method getSiblings
1114      * @return Node[]
1115      */
1116     getSiblings: function() {
1117         return this.parent.children;
1118     },
1120     /**
1121      * Shows this node's children
1122      * @method showChildren
1123      */
1124     showChildren: function() {
1125         if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
1126             if (this.hasChildren()) {
1127                 this.getChildrenEl().style.display = "";
1128             }
1129         }
1130     },
1132     /**
1133      * Hides this node's children
1134      * @method hideChildren
1135      */
1136     hideChildren: function() {
1137         this.logger.log("hiding " + this.index);
1139         if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
1140             this.getChildrenEl().style.display = "none";
1141         }
1142     },
1144     /**
1145      * Returns the id for this node's container div
1146      * @method getElId
1147      * @return {string} the element id
1148      */
1149     getElId: function() {
1150         return "ygtv" + this.index;
1151     },
1153     /**
1154      * Returns the id for this node's children div
1155      * @method getChildrenElId
1156      * @return {string} the element id for this node's children div
1157      */
1158     getChildrenElId: function() {
1159         return "ygtvc" + this.index;
1160     },
1162     /**
1163      * Returns the id for this node's toggle element
1164      * @method getToggleElId
1165      * @return {string} the toggel element id
1166      */
1167     getToggleElId: function() {
1168         return "ygtvt" + this.index;
1169     },
1172     /*
1173      * Returns the id for this node's spacer image.  The spacer is positioned
1174      * over the toggle and provides feedback for screen readers.
1175      * @method getSpacerId
1176      * @return {string} the id for the spacer image
1177      */
1178     /*
1179     getSpacerId: function() {
1180         return "ygtvspacer" + this.index;
1181     }, 
1182     */
1184     /**
1185      * Returns this node's container html element
1186      * @method getEl
1187      * @return {HTMLElement} the container html element
1188      */
1189     getEl: function() {
1190         return document.getElementById(this.getElId());
1191     },
1193     /**
1194      * Returns the div that was generated for this node's children
1195      * @method getChildrenEl
1196      * @return {HTMLElement} this node's children div
1197      */
1198     getChildrenEl: function() {
1199         return document.getElementById(this.getChildrenElId());
1200     },
1202     /**
1203      * Returns the element that is being used for this node's toggle.
1204      * @method getToggleEl
1205      * @return {HTMLElement} this node's toggle html element
1206      */
1207     getToggleEl: function() {
1208         return document.getElementById(this.getToggleElId());
1209     },
1211     /*
1212      * Returns the element that is being used for this node's spacer.
1213      * @method getSpacer
1214      * @return {HTMLElement} this node's spacer html element
1215      */
1216     /*
1217     getSpacer: function() {
1218         return document.getElementById( this.getSpacerId() ) || {};
1219     },
1220     */
1222     /*
1223     getStateText: function() {
1224         if (this.isLoading) {
1225             return this.loadingText;
1226         } else if (this.hasChildren(true)) {
1227             if (this.expanded) {
1228                 return this.expandedText;
1229             } else {
1230                 return this.collapsedText;
1231             }
1232         } else {
1233             return "";
1234         }
1235     },
1236     */
1238     /**
1239      * Generates the link that will invoke this node's toggle method
1240      * @method getToggleLink
1241      * @return {string} the javascript url for toggling this node
1242      */
1243     getToggleLink: function() {
1244         return "YAHOO.widget.TreeView.getNode(\'" + this.tree.id + "\'," + 
1245             this.index + ").toggle()";
1246     },
1248     /**
1249      * Hides this nodes children (creating them if necessary), changes the
1250      * @method collapse
1251      * toggle style.
1252      */
1253     collapse: function() {
1254         // Only collapse if currently expanded
1255         if (!this.expanded) { return; }
1257         // fire the collapse event handler
1258         var ret = this.tree.onCollapse(this);
1260         if (false === ret) {
1261             this.logger.log("Collapse was stopped by the abstract onCollapse");
1262             return;
1263         }
1265         ret = this.tree.fireEvent("collapse", this);
1267         if (false === ret) {
1268             this.logger.log("Collapse was stopped by a custom event handler");
1269             return;
1270         }
1273         if (!this.getEl()) {
1274             this.expanded = false;
1275         } else {
1276             // hide the child div
1277             this.hideChildren();
1278             this.expanded = false;
1280             this.updateIcon();
1281         }
1283         // this.getSpacer().title = this.getStateText();
1285         ret = this.tree.fireEvent("collapseComplete", this);
1287     },
1289     /**
1290      * Shows this nodes children (creating them if necessary), changes the
1291      * toggle style, and collapses its siblings if multiExpand is not set.
1292      * @method expand
1293      */
1294     expand: function() {
1295         // Only expand if currently collapsed.
1296         if (this.expanded) { return; }
1298         // fire the expand event handler
1299         var ret = this.tree.onExpand(this);
1301         if (false === ret) {
1302             this.logger.log("Expand was stopped by the abstract onExpand");
1303             return;
1304         }
1305         
1306         ret = this.tree.fireEvent("expand", this);
1308         if (false === ret) {
1309             this.logger.log("Expand was stopped by the custom event handler");
1310             return;
1311         }
1313         if (!this.getEl()) {
1314             this.expanded = true;
1315             return;
1316         }
1318         if (! this.childrenRendered) {
1319             this.logger.log("children not rendered yet");
1320             this.getChildrenEl().innerHTML = this.renderChildren();
1321         } else {
1322             this.logger.log("CHILDREN RENDERED");
1323         }
1325         this.expanded = true;
1327         this.updateIcon();
1329         // this.getSpacer().title = this.getStateText();
1331         // We do an extra check for children here because the lazy
1332         // load feature can expose nodes that have no children.
1334         // if (!this.hasChildren()) {
1335         if (this.isLoading) {
1336             this.expanded = false;
1337             return;
1338         }
1340         if (! this.multiExpand) {
1341             var sibs = this.getSiblings();
1342             for (var i=0; i<sibs.length; ++i) {
1343                 if (sibs[i] != this && sibs[i].expanded) { 
1344                     sibs[i].collapse(); 
1345                 }
1346             }
1347         }
1349         this.showChildren();
1351         ret = this.tree.fireEvent("expandComplete", this);
1352     },
1354     updateIcon: function() {
1355         if (this.hasIcon) {
1356             var el = this.getToggleEl();
1357             if (el) {
1358                 el.className = this.getStyle();
1359             }
1360         }
1361     },
1363     /**
1364      * Returns the css style name for the toggle
1365      * @method getStyle
1366      * @return {string} the css class for this node's toggle
1367      */
1368     getStyle: function() {
1369         // this.logger.log("No children, " + " isDyanmic: " + this.isDynamic() + " expanded: " + this.expanded);
1370         if (this.isLoading) {
1371             this.logger.log("returning the loading icon");
1372             return "ygtvloading";
1373         } else {
1374             // location top or bottom, middle nodes also get the top style
1375             var loc = (this.nextSibling) ? "t" : "l";
1377             // type p=plus(expand), m=minus(collapase), n=none(no children)
1378             var type = "n";
1379             if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) {
1380             // if (this.hasChildren(true)) {
1381                 type = (this.expanded) ? "m" : "p";
1382             }
1384             // this.logger.log("ygtv" + loc + type);
1385             return "ygtv" + loc + type;
1386         }
1387     },
1389     /**
1390      * Returns the hover style for the icon
1391      * @return {string} the css class hover state
1392      * @method getHoverStyle
1393      */
1394     getHoverStyle: function() { 
1395         var s = this.getStyle();
1396         if (this.hasChildren(true) && !this.isLoading) { 
1397             s += "h"; 
1398         }
1399         return s;
1400     },
1402     /**
1403      * Recursively expands all of this node's children.
1404      * @method expandAll
1405      */
1406     expandAll: function() { 
1407         for (var i=0;i<this.children.length;++i) {
1408             var c = this.children[i];
1409             if (c.isDynamic()) {
1410                 alert("Not supported (lazy load + expand all)");
1411                 break;
1412             } else if (! c.multiExpand) {
1413                 alert("Not supported (no multi-expand + expand all)");
1414                 break;
1415             } else {
1416                 c.expand();
1417                 c.expandAll();
1418             }
1419         }
1420     },
1422     /**
1423      * Recursively collapses all of this node's children.
1424      * @method collapseAll
1425      */
1426     collapseAll: function() { 
1427         for (var i=0;i<this.children.length;++i) {
1428             this.children[i].collapse();
1429             this.children[i].collapseAll();
1430         }
1431     },
1433     /**
1434      * Configures this node for dynamically obtaining the child data
1435      * when the node is first expanded.  Calling it without the callback
1436      * will turn off dynamic load for the node.
1437      * @method setDynamicLoad
1438      * @param fmDataLoader {function} the function that will be used to get the data.
1439      * @param iconMode {int} configures the icon that is displayed when a dynamic
1440      * load node is expanded the first time without children.  By default, the 
1441      * "collapse" icon will be used.  If set to 1, the leaf node icon will be
1442      * displayed.
1443      */
1444     setDynamicLoad: function(fnDataLoader, iconMode) { 
1445         if (fnDataLoader) {
1446             this.dataLoader = fnDataLoader;
1447             this._dynLoad = true;
1448         } else {
1449             this.dataLoader = null;
1450             this._dynLoad = false;
1451         }
1453         if (iconMode) {
1454             this.iconMode = iconMode;
1455         }
1456     },
1458     /**
1459      * Evaluates if this node is the root node of the tree
1460      * @method isRoot
1461      * @return {boolean} true if this is the root node
1462      */
1463     isRoot: function() { 
1464         return (this == this.tree.root);
1465     },
1467     /**
1468      * Evaluates if this node's children should be loaded dynamically.  Looks for
1469      * the property both in this instance and the root node.  If the tree is
1470      * defined to load all children dynamically, the data callback function is
1471      * defined in the root node
1472      * @method isDynamic
1473      * @return {boolean} true if this node's children are to be loaded dynamically
1474      */
1475     isDynamic: function() { 
1476         var lazy = (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
1477         // this.logger.log("isDynamic: " + lazy);
1478         return lazy;
1479     },
1481     /**
1482      * Returns the current icon mode.  This refers to the way childless dynamic
1483      * load nodes appear.
1484      * @method getIconMode
1485      * @return {int} 0 for collapse style, 1 for leaf node style
1486      */
1487     getIconMode: function() {
1488         return (this.iconMode || this.tree.root.iconMode);
1489     },
1491     /**
1492      * Checks if this node has children.  If this node is lazy-loading and the
1493      * children have not been rendered, we do not know whether or not there
1494      * are actual children.  In most cases, we need to assume that there are
1495      * children (for instance, the toggle needs to show the expandable 
1496      * presentation state).  In other times we want to know if there are rendered
1497      * children.  For the latter, "checkForLazyLoad" should be false.
1498      * @method hasChildren
1499      * @param checkForLazyLoad {boolean} should we check for unloaded children?
1500      * @return {boolean} true if this has children or if it might and we are
1501      * checking for this condition.
1502      */
1503     hasChildren: function(checkForLazyLoad) { 
1504         return ( this.children.length > 0 || 
1505                 (checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete) );
1506     },
1508     /**
1509      * Expands if node is collapsed, collapses otherwise.
1510      * @method toggle
1511      */
1512     toggle: function() {
1513         if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
1514             if (this.expanded) { this.collapse(); } else { this.expand(); }
1515         }
1516     },
1518     /**
1519      * Returns the markup for this node and its children.
1520      * @method getHtml
1521      * @return {string} the markup for this node and its expanded children.
1522      */
1523     getHtml: function() {
1525         this.childrenRendered = false;
1527         var sb = [];
1528         sb[sb.length] = '<div class="ygtvitem" id="' + this.getElId() + '">';
1529         sb[sb.length] = this.getNodeHtml();
1530         sb[sb.length] = this.getChildrenHtml();
1531         sb[sb.length] = '</div>';
1532         return sb.join("");
1533     },
1535     /**
1536      * Called when first rendering the tree.  We always build the div that will
1537      * contain this nodes children, but we don't render the children themselves
1538      * unless this node is expanded.
1539      * @method getChildrenHtml
1540      * @return {string} the children container div html and any expanded children
1541      * @private
1542      */
1543     getChildrenHtml: function() {
1546         var sb = [];
1547         sb[sb.length] = '<div class="ygtvchildren"';
1548         sb[sb.length] = ' id="' + this.getChildrenElId() + '"';
1550         // This is a workaround for an IE rendering issue, the child div has layout
1551         // in IE, creating extra space if a leaf node is created with the expanded
1552         // property set to true.
1553         if (!this.expanded || !this.hasChildren()) {
1554             sb[sb.length] = ' style="display:none;"';
1555         }
1556         sb[sb.length] = '>';
1558         // Don't render the actual child node HTML unless this node is expanded.
1559         if ( (this.hasChildren(true) && this.expanded) ||
1560                 (this.renderHidden && !this.isDynamic()) ) {
1561             sb[sb.length] = this.renderChildren();
1562         }
1564         sb[sb.length] = '</div>';
1566         return sb.join("");
1567     },
1569     /**
1570      * Generates the markup for the child nodes.  This is not done until the node
1571      * is expanded.
1572      * @method renderChildren
1573      * @return {string} the html for this node's children
1574      * @private
1575      */
1576     renderChildren: function() {
1578         this.logger.log("rendering children for " + this.index);
1580         var node = this;
1582         if (this.isDynamic() && !this.dynamicLoadComplete) {
1583             this.isLoading = true;
1584             this.tree.locked = true;
1586             if (this.dataLoader) {
1587                 this.logger.log("Using dynamic loader defined for this node");
1589                 setTimeout( 
1590                     function() {
1591                         node.dataLoader(node, 
1592                             function() { 
1593                                 node.loadComplete(); 
1594                             });
1595                     }, 10);
1596                 
1597             } else if (this.tree.root.dataLoader) {
1598                 this.logger.log("Using the tree-level dynamic loader");
1600                 setTimeout( 
1601                     function() {
1602                         node.tree.root.dataLoader(node, 
1603                             function() { 
1604                                 node.loadComplete(); 
1605                             });
1606                     }, 10);
1608             } else {
1609                 this.logger.log("no loader found");
1610                 return "Error: data loader not found or not specified.";
1611             }
1613             return "";
1615         } else {
1616             return this.completeRender();
1617         }
1618     },
1620     /**
1621      * Called when we know we have all the child data.
1622      * @method completeRender
1623      * @return {string} children html
1624      */
1625     completeRender: function() {
1626         this.logger.log("completeRender: " + this.index + ", # of children: " + this.children.length);
1627         var sb = [];
1629         for (var i=0; i < this.children.length; ++i) {
1630             // this.children[i].childrenRendered = false;
1631             sb[sb.length] = this.children[i].getHtml();
1632         }
1633         
1634         this.childrenRendered = true;
1636         return sb.join("");
1637     },
1639     /**
1640      * Load complete is the callback function we pass to the data provider
1641      * in dynamic load situations.
1642      * @method loadComplete
1643      */
1644     loadComplete: function() {
1645         this.logger.log("loadComplete: " + this.index);
1646         this.getChildrenEl().innerHTML = this.completeRender();
1647         this.dynamicLoadComplete = true;
1648         this.isLoading = false;
1649         this.expand();
1650         this.tree.locked = false;
1651     },
1653     /**
1654      * Returns this node's ancestor at the specified depth.
1655      * @method getAncestor
1656      * @param {int} depth the depth of the ancestor.
1657      * @return {Node} the ancestor
1658      */
1659     getAncestor: function(depth) {
1660         if (depth >= this.depth || depth < 0)  {
1661             this.logger.log("illegal getAncestor depth: " + depth);
1662             return null;
1663         }
1665         var p = this.parent;
1666         
1667         while (p.depth > depth) {
1668             p = p.parent;
1669         }
1671         return p;
1672     },
1674     /**
1675      * Returns the css class for the spacer at the specified depth for
1676      * this node.  If this node's ancestor at the specified depth
1677      * has a next sibling the presentation is different than if it
1678      * does not have a next sibling
1679      * @method getDepthStyle
1680      * @param {int} depth the depth of the ancestor.
1681      * @return {string} the css class for the spacer
1682      */
1683     getDepthStyle: function(depth) {
1684         return (this.getAncestor(depth).nextSibling) ? 
1685             "ygtvdepthcell" : "ygtvblankdepthcell";
1686     },
1688     /**
1689      * Get the markup for the node.  This is designed to be overrided so that we can
1690      * support different types of nodes.
1691      * @method getNodeHtml
1692      * @return {string} The HTML that will render this node.
1693      */
1694     getNodeHtml: function() { 
1695         this.logger.log("Generating html");
1696         return ""; 
1697     },
1699     /**
1700      * Regenerates the html for this node and its children.  To be used when the
1701      * node is expanded and new children have been added.
1702      * @method refresh
1703      */
1704     refresh: function() {
1705         // this.loadComplete();
1706         this.getChildrenEl().innerHTML = this.completeRender();
1708         if (this.hasIcon) {
1709             var el = this.getToggleEl();
1710             if (el) {
1711                 el.className = this.getStyle();
1712             }
1713         }
1714     },
1716     /**
1717      * Node toString
1718      * @method toString
1719      * @return {string} string representation of the node
1720      */
1721     toString: function() {
1722         return "Node (" + this.index + ")";
1723     }
1727 YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
1730  * The default node presentation.  The first parameter should be
1731  * either a string that will be used as the node's label, or an object
1732  * that has a string propery called label.  By default, the clicking the
1733  * label will toggle the expanded/collapsed state of the node.  By
1734  * changing the href property of the instance, this behavior can be
1735  * changed so that the label will go to the specified href.
1736  * @namespace YAHOO.widget
1737  * @class TextNode
1738  * @extends YAHOO.widget.Node
1739  * @constructor
1740  * @param oData {object} a string or object containing the data that will
1741  * be used to render this node
1742  * @param oParent {YAHOO.widget.Node} this node's parent node
1743  * @param expanded {boolean} the initial expanded/collapsed state
1744  */
1745 YAHOO.widget.TextNode = function(oData, oParent, expanded) {
1747     if (oData) { 
1748         this.init(oData, oParent, expanded);
1749         this.setUpLabel(oData);
1750     }
1752     this.logger     = new YAHOO.widget.LogWriter(this.toString());
1755 YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
1756     
1757     /**
1758      * The CSS class for the label href.  Defaults to ygtvlabel, but can be
1759      * overridden to provide a custom presentation for a specific node.
1760      * @property labelStyle
1761      * @type string
1762      */
1763     labelStyle: "ygtvlabel",
1765     /**
1766      * The derived element id of the label for this node
1767      * @property labelElId
1768      * @type string
1769      */
1770     labelElId: null,
1772     /**
1773      * The text for the label.  It is assumed that the oData parameter will
1774      * either be a string that will be used as the label, or an object that
1775      * has a property called "label" that we will use.
1776      * @property label
1777      * @type string
1778      */
1779     label: null,
1781     textNodeParentChange: function() {
1783         /**
1784          * Custom event that is fired when the text node label is clicked.  The
1785          * custom event is defined on the tree instance, so there is a single
1786          * event that handles all nodes in the tree.  The node clicked is 
1787          * provided as an argument
1788          *
1789          * @event labelClick
1790          * @for YAHOO.widget.TreeView
1791          * @param {YAHOO.widget.Node} node the node clicked
1792          */
1793         if (this.tree && !this.tree.hasEvent("labelClick")) {
1794             this.tree.createEvent("labelClick", this.tree);
1795         }
1796        
1797     },
1799     /**
1800      * Sets up the node label
1801      * @method setUpLabel
1802      * @param oData string containing the label, or an object with a label property
1803      */
1804     setUpLabel: function(oData) { 
1805         
1806         // set up the custom event on the tree
1807         this.textNodeParentChange();
1808         this.subscribe("parentChange", this.textNodeParentChange);
1810         if (typeof oData == "string") {
1811             oData = { label: oData };
1812         }
1813         this.label = oData.label;
1814         this.data.label = oData.label;
1815         
1816         // update the link
1817         if (oData.href) {
1818             this.href = oData.href;
1819         }
1821         // set the target
1822         if (oData.target) {
1823             this.target = oData.target;
1824         }
1826         if (oData.style) {
1827             this.labelStyle = oData.style;
1828         }
1830         this.labelElId = "ygtvlabelel" + this.index;
1831     },
1833     /**
1834      * Returns the label element
1835      * @for YAHOO.widget.TextNode
1836      * @method getLabelEl
1837      * @return {object} the element
1838      */
1839     getLabelEl: function() { 
1840         return document.getElementById(this.labelElId);
1841     },
1843     // overrides YAHOO.widget.Node
1844     getNodeHtml: function() { 
1845         this.logger.log("Generating html");
1846         var sb = [];
1848         sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
1849         sb[sb.length] = '<tr>';
1850         
1851         for (var i=0;i<this.depth;++i) {
1852             //sb[sb.length] = '<td><div class="' + this.getDepthStyle(i) + '">&#160;</div></td>';
1853             //sb[sb.length] = '<td><div class="' + this.getDepthStyle(i) + '"></div></td>';
1854             sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
1855         }
1857         var getNode = 'YAHOO.widget.TreeView.getNode(\'' +
1858                         this.tree.id + '\',' + this.index + ')';
1860         sb[sb.length] = '<td';
1861         // sb[sb.length] = ' onselectstart="return false"';
1862         sb[sb.length] = ' id="' + this.getToggleElId() + '"';
1863         sb[sb.length] = ' class="' + this.getStyle() + '"';
1864         if (this.hasChildren(true)) {
1865             sb[sb.length] = ' onmouseover="this.className=';
1866             sb[sb.length] = getNode + '.getHoverStyle()"';
1867             sb[sb.length] = ' onmouseout="this.className=';
1868             sb[sb.length] = getNode + '.getStyle()"';
1869         }
1870         sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '">';
1872         sb[sb.length] = '<div class="ygtvspacer">';
1874         /*
1875         sb[sb.length] = '<img id="' + this.getSpacerId() + '"';
1876         sb[sb.length] = ' alt=""';
1877         sb[sb.length] = ' tabindex=0';
1878         sb[sb.length] = ' src="' + this.spacerPath + '"';
1879         sb[sb.length] = ' title="' + this.getStateText() + '"';
1880         sb[sb.length] = ' class="ygtvspacer"';
1881         // sb[sb.length] = ' onkeypress="return ' + getNode + '".onKeyPress()"';
1882         sb[sb.length] = ' />';
1883         */
1885         //sb[sb.length] = '&#160;';
1887         sb[sb.length] = '</div>';
1888         sb[sb.length] = '</td>';
1889         sb[sb.length] = '<td ';
1890         sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
1891         sb[sb.length] = ' >';
1892         sb[sb.length] = '<a';
1893         sb[sb.length] = ' id="' + this.labelElId + '"';
1894         sb[sb.length] = ' class="' + this.labelStyle + '"';
1895         sb[sb.length] = ' href="' + this.href + '"';
1896         sb[sb.length] = ' target="' + this.target + '"';
1897         sb[sb.length] = ' onclick="return ' + getNode + '.onLabelClick(' + getNode +')"';
1898         if (this.hasChildren(true)) {
1899             sb[sb.length] = ' onmouseover="document.getElementById(\'';
1900             sb[sb.length] = this.getToggleElId() + '\').className=';
1901             sb[sb.length] = getNode + '.getHoverStyle()"';
1902             sb[sb.length] = ' onmouseout="document.getElementById(\'';
1903             sb[sb.length] = this.getToggleElId() + '\').className=';
1904             sb[sb.length] = getNode + '.getStyle()"';
1905         }
1906         sb[sb.length] = ' >';
1907         sb[sb.length] = this.label;
1908         sb[sb.length] = '</a>';
1909         sb[sb.length] = '</td>';
1910         sb[sb.length] = '</tr>';
1911         sb[sb.length] = '</table>';
1913         return sb.join("");
1914     },
1917     /**
1918      * Executed when the label is clicked.  Fires the labelClick custom event.
1919      * @method onLabelClick
1920      * @param me {Node} this node
1921      * @scope the anchor tag clicked
1922      * @return false to cancel the anchor click
1923      */
1924     onLabelClick: function(me) { 
1925         me.logger.log("onLabelClick " + me.label);
1926         return me.tree.fireEvent("labelClick", me);
1927         //return true;
1928     },
1930     toString: function() { 
1931         return "TextNode (" + this.index + ") " + this.label;
1932     }
1936  * A custom YAHOO.widget.Node that handles the unique nature of 
1937  * the virtual, presentationless root node.
1938  * @namespace YAHOO.widget
1939  * @class RootNode
1940  * @extends YAHOO.widget.Node
1941  * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
1942  * @constructor
1943  */
1944 YAHOO.widget.RootNode = function(oTree) {
1945         // Initialize the node with null params.  The root node is a
1946         // special case where the node has no presentation.  So we have
1947         // to alter the standard properties a bit.
1948         this.init(null, null, true);
1949         
1950         /*
1951          * For the root node, we get the tree reference from as a param
1952          * to the constructor instead of from the parent element.
1953          */
1954         this.tree = oTree;
1957 YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
1958     
1959     // overrides YAHOO.widget.Node
1960     getNodeHtml: function() { 
1961         return ""; 
1962     },
1964     toString: function() { 
1965         return "RootNode";
1966     },
1968     loadComplete: function() { 
1969         this.tree.draw();
1970     },
1972     collapse: function() {},
1973     expand: function() {}
1977  * This implementation takes either a string or object for the
1978  * oData argument.  If is it a string, we will use it for the display
1979  * of this node (and it can contain any html code).  If the parameter
1980  * is an object, we look for a parameter called "html" that will be
1981  * used for this node's display.
1982  * @namespace YAHOO.widget
1983  * @class HTMLNode
1984  * @extends YAHOO.widget.Node
1985  * @constructor
1986  * @param oData {object} a string or object containing the data that will
1987  * be used to render this node
1988  * @param oParent {YAHOO.widget.Node} this node's parent node
1989  * @param expanded {boolean} the initial expanded/collapsed state
1990  * @param hasIcon {boolean} specifies whether or not leaf nodes should
1991  * have an icon
1992  */
1993 YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
1994     if (oData) { 
1995         this.init(oData, oParent, expanded);
1996         this.initContent(oData, hasIcon);
1997     }
2000 YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, {
2002     /**
2003      * The CSS class for the html content container.  Defaults to ygtvhtml, but 
2004      * can be overridden to provide a custom presentation for a specific node.
2005      * @property contentStyle
2006      * @type string
2007      */
2008     contentStyle: "ygtvhtml",
2010     /**
2011      * The generated id that will contain the data passed in by the implementer.
2012      * @property contentElId
2013      * @type string
2014      */
2015     contentElId: null,
2017     /**
2018      * The HTML content to use for this node's display
2019      * @property content
2020      * @type string
2021      */
2022     content: null,
2024     /**
2025      * Sets up the node label
2026      * @property initContent
2027      * @param {object} An html string or object containing an html property
2028      * @param {boolean} hasIcon determines if the node will be rendered with an
2029      * icon or not
2030      */
2031     initContent: function(oData, hasIcon) { 
2032         if (typeof oData == "string") {
2033             oData = { html: oData };
2034         }
2036         this.html = oData.html;
2037         this.contentElId = "ygtvcontentel" + this.index;
2038         this.hasIcon = hasIcon;
2040         this.logger = new YAHOO.widget.LogWriter(this.toString());
2041     },
2043     /**
2044      * Returns the outer html element for this node's content
2045      * @method getContentEl
2046      * @return {HTMLElement} the element
2047      */
2048     getContentEl: function() { 
2049         return document.getElementById(this.contentElId);
2050     },
2052     // overrides YAHOO.widget.Node
2053     getNodeHtml: function() { 
2054         this.logger.log("Generating html");
2055         var sb = [];
2057         sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
2058         sb[sb.length] = '<tr>';
2059         
2060         for (var i=0;i<this.depth;++i) {
2061             //sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '">&#160;</td>';
2062             sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
2063         }
2065         if (this.hasIcon) {
2066             sb[sb.length] = '<td';
2067             sb[sb.length] = ' id="' + this.getToggleElId() + '"';
2068             sb[sb.length] = ' class="' + this.getStyle() + '"';
2069             sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '"';
2070             if (this.hasChildren(true)) {
2071                 sb[sb.length] = ' onmouseover="this.className=';
2072                 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
2073                 sb[sb.length] = this.tree.id + '\',' + this.index +  ').getHoverStyle()"';
2074                 sb[sb.length] = ' onmouseout="this.className=';
2075                 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
2076                 sb[sb.length] = this.tree.id + '\',' + this.index +  ').getStyle()"';
2077             }
2078             //sb[sb.length] = '>&#160;</td>';
2079             sb[sb.length] = '><div class="ygtvspacer"></div></td>';
2080         }
2082         sb[sb.length] = '<td';
2083         sb[sb.length] = ' id="' + this.contentElId + '"';
2084         sb[sb.length] = ' class="' + this.contentStyle + '"';
2085         sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
2086         sb[sb.length] = ' >';
2087         sb[sb.length] = this.html;
2088         sb[sb.length] = '</td>';
2089         sb[sb.length] = '</tr>';
2090         sb[sb.length] = '</table>';
2092         return sb.join("");
2093     },
2095     toString: function() { 
2096         return "HTMLNode (" + this.index + ")";
2097     }
2101  * A menu-specific implementation that differs from TextNode in that only 
2102  * one sibling can be expanded at a time.
2103  * @namespace YAHOO.widget
2104  * @class MenuNode
2105  * @extends YAHOO.widget.TextNode
2106  * @param oData {object} a string or object containing the data that will
2107  * be used to render this node
2108  * @param oParent {YAHOO.widget.Node} this node's parent node
2109  * @param expanded {boolean} the initial expanded/collapsed state
2110  * @constructor
2111  */
2112 YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
2113         if (oData) { 
2114                 this.init(oData, oParent, expanded);
2115                 this.setUpLabel(oData);
2116         }
2118     /*
2119      * Menus usually allow only one branch to be open at a time.
2120      */
2121         this.multiExpand = false;
2123     this.logger     = new YAHOO.widget.LogWriter(this.toString());
2127 YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
2129     toString: function() { 
2130         return "MenuNode (" + this.index + ") " + this.label;
2131     }
2135  * A static factory class for tree view expand/collapse animations
2136  * @class TVAnim
2137  * @static
2138  */
2139 YAHOO.widget.TVAnim = function() {
2140     return {
2141         /**
2142          * Constant for the fade in animation
2143          * @property FADE_IN
2144          * @type string
2145          * @static
2146          */
2147         FADE_IN: "TVFadeIn",
2149         /**
2150          * Constant for the fade out animation
2151          * @property FADE_OUT
2152          * @type string
2153          * @static
2154          */
2155         FADE_OUT: "TVFadeOut",
2157         /**
2158          * Returns a ygAnim instance of the given type
2159          * @method getAnim
2160          * @param type {string} the type of animation
2161          * @param el {HTMLElement} the element to element (probably the children div)
2162          * @param callback {function} function to invoke when the animation is done.
2163          * @return {YAHOO.util.Animation} the animation instance
2164          * @static
2165          */
2166         getAnim: function(type, el, callback) {
2167             if (YAHOO.widget[type]) {
2168                 return new YAHOO.widget[type](el, callback);
2169             } else {
2170                 return null;
2171             }
2172         },
2174         /**
2175          * Returns true if the specified animation class is available
2176          * @method isValid
2177          * @param type {string} the type of animation
2178          * @return {boolean} true if valid, false if not
2179          * @static
2180          */
2181         isValid: function(type) {
2182             return (YAHOO.widget[type]);
2183         }
2184     };
2185 } ();
2188  * A 1/2 second fade-in animation.
2189  * @class TVFadeIn
2190  * @constructor
2191  * @param el {HTMLElement} the element to animate
2192  * @param callback {function} function to invoke when the animation is finished
2193  */
2194 YAHOO.widget.TVFadeIn = function(el, callback) {
2195     /**
2196      * The element to animate
2197      * @property el
2198      * @type HTMLElement
2199      */
2200     this.el = el;
2202     /**
2203      * the callback to invoke when the animation is complete
2204      * @property callback
2205      * @type function
2206      */
2207     this.callback = callback;
2209     this.logger = new YAHOO.widget.LogWriter(this.toString());
2212 YAHOO.widget.TVFadeIn.prototype = {
2213     /**
2214      * Performs the animation
2215      * @method animate
2216      */
2217     animate: function() {
2218         var tvanim = this;
2220         var s = this.el.style;
2221         s.opacity = 0.1;
2222         s.filter = "alpha(opacity=10)";
2223         s.display = "";
2225         var dur = 0.4; 
2226         var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur);
2227         a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2228         a.animate();
2229     },
2231     /**
2232      * Clean up and invoke callback
2233      * @method onComplete
2234      */
2235     onComplete: function() {
2236         this.callback();
2237     },
2239     /**
2240      * toString
2241      * @method toString
2242      * @return {string} the string representation of the instance
2243      */
2244     toString: function() {
2245         return "TVFadeIn";
2246     }
2250  * A 1/2 second fade out animation.
2251  * @class TVFadeOut
2252  * @constructor
2253  * @param el {HTMLElement} the element to animate
2254  * @param callback {Function} function to invoke when the animation is finished
2255  */
2256 YAHOO.widget.TVFadeOut = function(el, callback) {
2257     /**
2258      * The element to animate
2259      * @property el
2260      * @type HTMLElement
2261      */
2262     this.el = el;
2264     /**
2265      * the callback to invoke when the animation is complete
2266      * @property callback
2267      * @type function
2268      */
2269     this.callback = callback;
2271     this.logger = new YAHOO.widget.LogWriter(this.toString());
2274 YAHOO.widget.TVFadeOut.prototype = {
2275     /**
2276      * Performs the animation
2277      * @method animate
2278      */
2279     animate: function() {
2280         var tvanim = this;
2281         var dur = 0.4;
2282         var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
2283         a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2284         a.animate();
2285     },
2287     /**
2288      * Clean up and invoke callback
2289      * @method onComplete
2290      */
2291     onComplete: function() {
2292         var s = this.el.style;
2293         s.display = "none";
2294         // s.opacity = 1;
2295         s.filter = "alpha(opacity=100)";
2296         this.callback();
2297     },
2299     /**
2300      * toString
2301      * @method toString
2302      * @return {string} the string representation of the instance
2303      */
2304     toString: function() {
2305         return "TVFadeOut";
2306     }
2309 YAHOO.register("treeview", YAHOO.widget.TreeView, {version: "2.3.0", build: "442"});