Merge commit 'catalyst/MOODLE_19_STABLE' into mdl19-linuxchix
[moodle-linuxchix.git] / lib / yui / treeview / treeview.js
blob348e0d8630155b44a939dc1298f5157205cebc6b
1 /*
2 Copyright (c) 2008, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
5 version: 2.5.2
6 */
7 /**
8  * The treeview widget is a generic tree building tool.
9  * @module treeview
10  * @title TreeView Widget
11  * @requires yahoo, event
12  * @optional animation
13  * @namespace YAHOO.widget
14  */
16 /**
17  * Contains the tree view state data and the root node.
18  *
19  * @class TreeView
20  * @uses YAHOO.util.EventProvider
21  * @constructor
22  * @param {string|HTMLElement} id The id of the element, or the element
23  * itself that the tree will be inserted into.
24  */
25 YAHOO.widget.TreeView = function(id) {
26     if (id) { this.init(id); }
29 YAHOO.widget.TreeView.prototype = {
31     /**
32      * The id of tree container element
33      * @property id
34      * @type String
35      */
36     id: null,
38     /**
39      * The host element for this tree
40      * @property _el
41      * @private
42      */
43     _el: null,
45      /**
46      * Flat collection of all nodes in this tree.  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         this._expandAnim = (YAHOO.widget.TVAnim.isValid(type)) ? type : null;
101     },
103     /**
104      * Sets up the animation for collapsing children
105      * @method setCollapseAnim
106      * @param {string} the type of animation (acceptable values defined in 
107      * YAHOO.widget.TVAnim)
108      */
109     setCollapseAnim: function(type) {
110         this._collapseAnim = (YAHOO.widget.TVAnim.isValid(type)) ? type : null;
111     },
113     /**
114      * Perform the expand animation if configured, or just show the
115      * element if not configured or too many animations are in progress
116      * @method animateExpand
117      * @param el {HTMLElement} the element to animate
118      * @param node {YAHOO.util.Node} the node that was expanded
119      * @return {boolean} true if animation could be invoked, false otherwise
120      */
121     animateExpand: function(el, node) {
123         if (this._expandAnim && this._animCount < this.maxAnim) {
124             // this.locked = true;
125             var tree = this;
126             var a = YAHOO.widget.TVAnim.getAnim(this._expandAnim, el, 
127                             function() { tree.expandComplete(node); });
128             if (a) { 
129                 ++this._animCount;
130                 this.fireEvent("animStart", {
131                         "node": node, 
132                         "type": "expand"
133                     });
134                 a.animate();
135             }
137             return true;
138         }
140         return false;
141     },
143     /**
144      * Perform the collapse animation if configured, or just show the
145      * element if not configured or too many animations are in progress
146      * @method animateCollapse
147      * @param el {HTMLElement} the element to animate
148      * @param node {YAHOO.util.Node} the node that was expanded
149      * @return {boolean} true if animation could be invoked, false otherwise
150      */
151     animateCollapse: function(el, node) {
153         if (this._collapseAnim && this._animCount < this.maxAnim) {
154             // this.locked = true;
155             var tree = this;
156             var a = YAHOO.widget.TVAnim.getAnim(this._collapseAnim, el, 
157                             function() { tree.collapseComplete(node); });
158             if (a) { 
159                 ++this._animCount;
160                 this.fireEvent("animStart", {
161                         "node": node, 
162                         "type": "collapse"
163                     });
164                 a.animate();
165             }
167             return true;
168         }
170         return false;
171     },
173     /**
174      * Function executed when the expand animation completes
175      * @method expandComplete
176      */
177     expandComplete: function(node) {
178         --this._animCount;
179         this.fireEvent("animComplete", {
180                 "node": node, 
181                 "type": "expand"
182             });
183         // this.locked = false;
184     },
186     /**
187      * Function executed when the collapse animation completes
188      * @method collapseComplete
189      */
190     collapseComplete: function(node) {
191         --this._animCount;
192         this.fireEvent("animComplete", {
193                 "node": node, 
194                 "type": "collapse"
195             });
196         // this.locked = false;
197     },
199     /**
200      * Initializes the tree
201      * @method init
202      * @parm {string|HTMLElement} id the id of the element that will hold the tree
203      * @private
204      */
205     init: function(id) {
207         this.id = id;
209         if ("string" !== typeof id) {
210             this._el = id;
211             this.id = this.generateId(id);
212         }
214         /**
215          * When animation is enabled, this event fires when the animation
216          * starts
217          * @event animStart
218          * @type CustomEvent
219          * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
220          * @parm {String} type the type of animation ("expand" or "collapse")
221          */
222         this.createEvent("animStart", this);
224         /**
225          * When animation is enabled, this event fires when the animation
226          * completes
227          * @event animComplete
228          * @type CustomEvent
229          * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
230          * @parm {String} type the type of animation ("expand" or "collapse")
231          */
232         this.createEvent("animComplete", this);
234         /**
235          * Fires when a node is going to be collapsed.  Return false to stop
236          * the collapse.
237          * @event collapse
238          * @type CustomEvent
239          * @param {YAHOO.widget.Node} node the node that is collapsing
240          */
241         this.createEvent("collapse", this);
243         /**
244          * Fires after a node is successfully collapsed.  This event will not fire
245          * if the "collapse" event was cancelled.
246          * @event collapseComplete
247          * @type CustomEvent
248          * @param {YAHOO.widget.Node} node the node that was collapsed
249          */
250         this.createEvent("collapseComplete", this);
252         /**
253          * Fires when a node is going to be expanded.  Return false to stop
254          * the collapse.
255          * @event expand
256          * @type CustomEvent
257          * @param {YAHOO.widget.Node} node the node that is expanding
258          */
259         this.createEvent("expand", this);
261         /**
262          * Fires after a node is successfully expanded.  This event will not fire
263          * if the "expand" event was cancelled.
264          * @event expandComplete
265          * @type CustomEvent
266          * @param {YAHOO.widget.Node} node the node that was expanded
267          */
268         this.createEvent("expandComplete", this);
270         this._nodes = [];
272         // store a global reference
273         YAHOO.widget.TreeView.trees[this.id] = this;
275         // Set up the root node
276         this.root = new YAHOO.widget.RootNode(this);
278         var LW = YAHOO.widget.LogWriter;
282         // YAHOO.util.Event.onContentReady(this.id, this.handleAvailable, this, true);
283         // YAHOO.util.Event.on(this.id, "click", this.handleClick, this, true);
284     },
286     //handleAvailable: function() {
287         //var Event = YAHOO.util.Event;
288         //Event.on(this.id, 
289     //},
291     /**
292      * Renders the tree boilerplate and visible nodes
293      * @method draw
294      */
295     draw: function() {
296         var html = this.root.getHtml();
297         this.getEl().innerHTML = html;
298         this.firstDraw = false;
299     },
301     /**
302      * Returns the tree's host element
303      * @method getEl
304      * @return {HTMLElement} the host element
305      */
306     getEl: function() {
307         if (! this._el) {
308             this._el = document.getElementById(this.id);
309         }
310         return this._el;
311     },
313     /**
314      * Nodes register themselves with the tree instance when they are created.
315      * @method regNode
316      * @param node {Node} the node to register
317      * @private
318      */
319     regNode: function(node) {
320         this._nodes[node.index] = node;
321     },
323     /**
324      * Returns the root node of this tree
325      * @method getRoot
326      * @return {Node} the root node
327      */
328     getRoot: function() {
329         return this.root;
330     },
332     /**
333      * Configures this tree to dynamically load all child data
334      * @method setDynamicLoad
335      * @param {function} fnDataLoader the function that will be called to get the data
336      * @param iconMode {int} configures the icon that is displayed when a dynamic
337      * load node is expanded the first time without children.  By default, the 
338      * "collapse" icon will be used.  If set to 1, the leaf node icon will be
339      * displayed.
340      */
341     setDynamicLoad: function(fnDataLoader, iconMode) { 
342         this.root.setDynamicLoad(fnDataLoader, iconMode);
343     },
345     /**
346      * Expands all child nodes.  Note: this conflicts with the "multiExpand"
347      * node property.  If expand all is called in a tree with nodes that
348      * do not allow multiple siblings to be displayed, only the last sibling
349      * will be expanded.
350      * @method expandAll
351      */
352     expandAll: function() { 
353         if (!this.locked) {
354             this.root.expandAll(); 
355         }
356     },
358     /**
359      * Collapses all expanded child nodes in the entire tree.
360      * @method collapseAll
361      */
362     collapseAll: function() { 
363         if (!this.locked) {
364             this.root.collapseAll(); 
365         }
366     },
368     /**
369      * Returns a node in the tree that has the specified index (this index
370      * is created internally, so this function probably will only be used
371      * in html generated for a given node.)
372      * @method getNodeByIndex
373      * @param {int} nodeIndex the index of the node wanted
374      * @return {Node} the node with index=nodeIndex, null if no match
375      */
376     getNodeByIndex: function(nodeIndex) {
377         var n = this._nodes[nodeIndex];
378         return (n) ? n : null;
379     },
381     /**
382      * Returns a node that has a matching property and value in the data
383      * object that was passed into its constructor.
384      * @method getNodeByProperty
385      * @param {object} property the property to search (usually a string)
386      * @param {object} value the value we want to find (usuall an int or string)
387      * @return {Node} the matching node, null if no match
388      */
389     getNodeByProperty: function(property, value) {
390         for (var i in this._nodes) {
391             var n = this._nodes[i];
392             if (n.data && value == n.data[property]) {
393                 return n;
394             }
395         }
397         return null;
398     },
400     /**
401      * Returns a collection of nodes that have a matching property 
402      * and value in the data object that was passed into its constructor.  
403      * @method getNodesByProperty
404      * @param {object} property the property to search (usually a string)
405      * @param {object} value the value we want to find (usuall an int or string)
406      * @return {Array} the matching collection of nodes, null if no match
407      */
408     getNodesByProperty: function(property, value) {
409         var values = [];
410         for (var i in this._nodes) {
411             var n = this._nodes[i];
412             if (n.data && value == n.data[property]) {
413                 values.push(n);
414             }
415         }
417         return (values.length) ? values : null;
418     },
420     /**
421      * Returns the treeview node reference for an anscestor element
422      * of the node, or null if it is not contained within any node
423      * in this tree.
424      * @method getNodeByElement
425      * @param {HTMLElement} the element to test
426      * @return {YAHOO.widget.Node} a node reference or null
427      */
428     getNodeByElement: function(el) {
430         var p=el, m, re=/ygtv([^\d]*)(.*)/;
432         do {
434             if (p && p.id) {
435                 m = p.id.match(re);
436                 if (m && m[2]) {
437                     return this.getNodeByIndex(m[2]);
438                 }
439             }
441             p = p.parentNode;
443             if (!p || !p.tagName) {
444                 break;
445             }
447         } 
448         while (p.id !== this.id && p.tagName.toLowerCase() !== "body");
450         return null;
451     },
453     /**
454      * Removes the node and its children, and optionally refreshes the 
455      * branch of the tree that was affected.
456      * @method removeNode
457      * @param {Node} The node to remove
458      * @param {boolean} autoRefresh automatically refreshes branch if true
459      * @return {boolean} False is there was a problem, true otherwise.
460      */
461     removeNode: function(node, autoRefresh) { 
463         // Don't delete the root node
464         if (node.isRoot()) {
465             return false;
466         }
468         // Get the branch that we may need to refresh
469         var p = node.parent;
470         if (p.parent) {
471             p = p.parent;
472         }
474         // Delete the node and its children
475         this._deleteNode(node);
477         // Refresh the parent of the parent
478         if (autoRefresh && p && p.childrenRendered) {
479             p.refresh();
480         }
482         return true;
483     },
485     /**
486      * wait until the animation is complete before deleting 
487      * to avoid javascript errors
488      * @method _removeChildren_animComplete
489      * @param o the custom event payload
490      * @private
491      */
492     _removeChildren_animComplete: function(o) {
493         this.unsubscribe(this._removeChildren_animComplete);
494         this.removeChildren(o.node);
495     },
497     /**
498      * Deletes this nodes child collection, recursively.  Also collapses
499      * the node, and resets the dynamic load flag.  The primary use for
500      * this method is to purge a node and allow it to fetch its data
501      * dynamically again.
502      * @method removeChildren
503      * @param {Node} node the node to purge
504      */
505     removeChildren: function(node) { 
507         if (node.expanded) {
508             // wait until the animation is complete before deleting to
509             // avoid javascript errors
510             if (this._collapseAnim) {
511                 this.subscribe("animComplete", 
512                         this._removeChildren_animComplete, this, true);
513                 YAHOO.widget.Node.prototype.collapse.call(node);
514                 return;
515             }
517             node.collapse();
518         }
520         while (node.children.length) {
521             this._deleteNode(node.children[0]);
522         }
524         if (node.isRoot()) {
525             YAHOO.widget.Node.prototype.expand.call(node);
526         }
528         node.childrenRendered = false;
529         node.dynamicLoadComplete = false;
531         node.updateIcon();
532     },
534     /**
535      * Deletes the node and recurses children
536      * @method _deleteNode
537      * @private
538      */
539     _deleteNode: function(node) { 
540         // Remove all the child nodes first
541         this.removeChildren(node);
543         // Remove the node from the tree
544         this.popNode(node);
545     },
547     /**
548      * Removes the node from the tree, preserving the child collection 
549      * to make it possible to insert the branch into another part of the 
550      * tree, or another tree.
551      * @method popNode
552      * @param {Node} the node to remove
553      */
554     popNode: function(node) { 
555         var p = node.parent;
557         // Update the parent's collection of children
558         var a = [];
560         for (var i=0, len=p.children.length;i<len;++i) {
561             if (p.children[i] != node) {
562                 a[a.length] = p.children[i];
563             }
564         }
566         p.children = a;
568         // reset the childrenRendered flag for the parent
569         p.childrenRendered = false;
571          // Update the sibling relationship
572         if (node.previousSibling) {
573             node.previousSibling.nextSibling = node.nextSibling;
574         }
576         if (node.nextSibling) {
577             node.nextSibling.previousSibling = node.previousSibling;
578         }
580         node.parent = null;
581         node.previousSibling = null;
582         node.nextSibling = null;
583         node.tree = null;
585         // Update the tree's node collection 
586         delete this._nodes[node.index];
587     },
590     /**
591      * TreeView instance toString
592      * @method toString
593      * @return {string} string representation of the tree
594      */
595     toString: function() {
596         return "TreeView " + this.id;
597     },
599     /**
600      * Generates an unique id for an element if it doesn't yet have one
601      * @method generateId
602      * @private
603      */
604     generateId: function(el) {
605         var id = el.id;
607         if (!id) {
608             id = "yui-tv-auto-id-" + YAHOO.widget.TreeView.counter;
609             ++YAHOO.widget.TreeView.counter;
610         }
612         return id;
613     },
615     /**
616      * Abstract method that is executed when a node is expanded
617      * @method onExpand
618      * @param node {Node} the node that was expanded
619      * @deprecated use treeobj.subscribe("expand") instead
620      */
621     onExpand: function(node) { },
623     /**
624      * Abstract method that is executed when a node is collapsed.
625      * @method onCollapse
626      * @param node {Node} the node that was collapsed.
627      * @deprecated use treeobj.subscribe("collapse") instead
628      */
629     onCollapse: function(node) { }
633 YAHOO.augment(YAHOO.widget.TreeView, YAHOO.util.EventProvider);
636  * Running count of all nodes created in all trees.  This is 
637  * used to provide unique identifies for all nodes.  Deleting
638  * nodes does not change the nodeCount.
639  * @property YAHOO.widget.TreeView.nodeCount
640  * @type int
641  * @static
642  */
643 YAHOO.widget.TreeView.nodeCount = 0;
646  * Global cache of tree instances
647  * @property YAHOO.widget.TreeView.trees
648  * @type Array
649  * @static
650  * @private
651  */
652 YAHOO.widget.TreeView.trees = [];
655  * Counter for generating a new unique element id
656  * @property YAHOO.widget.TreeView.counter
657  * @static
658  * @private
659  */
660 YAHOO.widget.TreeView.counter = 0;
663  * Global method for getting a tree by its id.  Used in the generated
664  * tree html.
665  * @method YAHOO.widget.TreeView.getTree
666  * @param treeId {String} the id of the tree instance
667  * @return {TreeView} the tree instance requested, null if not found.
668  * @static
669  */
670 YAHOO.widget.TreeView.getTree = function(treeId) {
671     var t = YAHOO.widget.TreeView.trees[treeId];
672     return (t) ? t : null;
677  * Global method for getting a node by its id.  Used in the generated
678  * tree html.
679  * @method YAHOO.widget.TreeView.getNode
680  * @param treeId {String} the id of the tree instance
681  * @param nodeIndex {String} the index of the node to return
682  * @return {Node} the node instance requested, null if not found
683  * @static
684  */
685 YAHOO.widget.TreeView.getNode = function(treeId, nodeIndex) {
686     var t = YAHOO.widget.TreeView.getTree(treeId);
687     return (t) ? t.getNodeByIndex(nodeIndex) : null;
691  * Add a DOM event
692  * @method YAHOO.widget.TreeView.addHandler
693  * @param el the elment to bind the handler to
694  * @param {string} sType the type of event handler
695  * @param {function} fn the callback to invoke
696  * @static
697  */
698 YAHOO.widget.TreeView.addHandler = function (el, sType, fn) {
699     if (el.addEventListener) {
700         el.addEventListener(sType, fn, false);
701     } else if (el.attachEvent) {
702         el.attachEvent("on" + sType, fn);
703     }
707  * Remove a DOM event
708  * @method YAHOO.widget.TreeView.removeHandler
709  * @param el the elment to bind the handler to
710  * @param {string} sType the type of event handler
711  * @param {function} fn the callback to invoke
712  * @static
713  */
715 YAHOO.widget.TreeView.removeHandler = function (el, sType, fn) {
716     if (el.removeEventListener) {
717         el.removeEventListener(sType, fn, false);
718     } else if (el.detachEvent) {
719         el.detachEvent("on" + sType, fn);
720     }
724  * Attempts to preload the images defined in the styles used to draw the tree by
725  * rendering off-screen elements that use the styles.
726  * @method YAHOO.widget.TreeView.preload
727  * @param {string} prefix the prefix to use to generate the names of the
728  * images to preload, default is ygtv
729  * @static
730  */
731 YAHOO.widget.TreeView.preload = function(e, prefix) {
732     prefix = prefix || "ygtv";
735     var styles = ["tn","tm","tmh","tp","tph","ln","lm","lmh","lp","lph","loading"];
736     // var styles = ["tp"];
738     var sb = [];
739     
740     // save the first one for the outer container
741     for (var i=1; i < styles.length; i=i+1) { 
742         sb[sb.length] = '<span class="' + prefix + styles[i] + '">&#160;</span>';
743     }
745     var f = document.createElement("div");
746     var s = f.style;
747     s.className = prefix + styles[0];
748     s.position = "absolute";
749     s.height = "1px";
750     s.width = "1px";
751     s.top = "-1000px";
752     s.left = "-1000px";
753     f.innerHTML = sb.join("");
755     document.body.appendChild(f);
757     YAHOO.widget.TreeView.removeHandler(window, 
758                 "load", YAHOO.widget.TreeView.preload);
762 YAHOO.widget.TreeView.addHandler(window, 
763                 "load", YAHOO.widget.TreeView.preload);
766  * The base class for all tree nodes.  The node's presentation and behavior in
767  * response to mouse events is handled in Node subclasses.
768  * @namespace YAHOO.widget
769  * @class Node
770  * @uses YAHOO.util.EventProvider
771  * @param oData {object} a string or object containing the data that will
772  * be used to render this node, and any custom attributes that should be
773  * stored with the node (which is available in noderef.data).
774  * @param oParent {Node} this node's parent node
775  * @param expanded {boolean} the initial expanded/collapsed state
776  * @constructor
777  */
778 YAHOO.widget.Node = function(oData, oParent, expanded) {
779     if (oData) { this.init(oData, oParent, expanded); }
782 YAHOO.widget.Node.prototype = {
784     /**
785      * The index for this instance obtained from global counter in YAHOO.widget.TreeView.
786      * @property index
787      * @type int
788      */
789     index: 0,
791     /**
792      * This node's child node collection.
793      * @property children
794      * @type Node[] 
795      */
796     children: null,
798     /**
799      * Tree instance this node is part of
800      * @property tree
801      * @type TreeView
802      */
803     tree: null,
805     /**
806      * The data linked to this node.  This can be any object or primitive
807      * value, and the data can be used in getNodeHtml().
808      * @property data
809      * @type object
810      */
811     data: null,
813     /**
814      * Parent node
815      * @property parent
816      * @type Node
817      */
818     parent: null,
820     /**
821      * The depth of this node.  We start at -1 for the root node.
822      * @property depth
823      * @type int
824      */
825     depth: -1,
827     /**
828      * The href for the node's label.  If one is not specified, the href will
829      * be set so that it toggles the node.
830      * @property href
831      * @type string
832      */
833     href: null,
835     /**
836      * The label href target, defaults to current window
837      * @property target
838      * @type string
839      */
840     target: "_self",
842     /**
843      * The node's expanded/collapsed state
844      * @property expanded
845      * @type boolean
846      */
847     expanded: false,
849     /**
850      * Can multiple children be expanded at once?
851      * @property multiExpand
852      * @type boolean
853      */
854     multiExpand: true,
856     /**
857      * Should we render children for a collapsed node?  It is possible that the
858      * implementer will want to render the hidden data...  @todo verify that we 
859      * need this, and implement it if we do.
860      * @property renderHidden
861      * @type boolean
862      */
863     renderHidden: false,
865     /**
866      * This flag is set to true when the html is generated for this node's
867      * children, and set to false when new children are added.
868      * @property childrenRendered
869      * @type boolean
870      */
871     childrenRendered: false,
873     /**
874      * Dynamically loaded nodes only fetch the data the first time they are
875      * expanded.  This flag is set to true once the data has been fetched.
876      * @property dynamicLoadComplete
877      * @type boolean
878      */
879     dynamicLoadComplete: false,
881     /**
882      * This node's previous sibling
883      * @property previousSibling
884      * @type Node
885      */
886     previousSibling: null,
888     /**
889      * This node's next sibling
890      * @property nextSibling
891      * @type Node
892      */
893     nextSibling: null,
895     /**
896      * We can set the node up to call an external method to get the child
897      * data dynamically.
898      * @property _dynLoad
899      * @type boolean
900      * @private
901      */
902     _dynLoad: false,
904     /**
905      * Function to execute when we need to get this node's child data.
906      * @property dataLoader
907      * @type function
908      */
909     dataLoader: null,
911     /**
912      * This is true for dynamically loading nodes while waiting for the
913      * callback to return.
914      * @property isLoading
915      * @type boolean
916      */
917     isLoading: false,
919     /**
920      * The toggle/branch icon will not show if this is set to false.  This
921      * could be useful if the implementer wants to have the child contain
922      * extra info about the parent, rather than an actual node.
923      * @property hasIcon
924      * @type boolean
925      */
926     hasIcon: true,
928     /**
929      * Used to configure what happens when a dynamic load node is expanded
930      * and we discover that it does not have children.  By default, it is
931      * treated as if it still could have children (plus/minus icon).  Set
932      * iconMode to have it display like a leaf node instead.
933      * @property iconMode
934      * @type int
935      */
936     iconMode: 0,
938     /**
939      * Specifies whether or not the content area of the node should be allowed
940      * to wrap.
941      * @property nowrap
942      * @type boolean
943      * @default false
944      */
945     nowrap: false,
947     /**
948      * If true, the node will alway be rendered as a leaf node.  This can be
949      * used to override the presentation when dynamically loading the entire
950      * tree.  Setting this to true also disables the dynamic load call for the
951      * node.
952      * @property isLeaf
953      * @type boolean
954      * @default false
955      */
956     isLeaf: false,
958     /**
959      * The node type
960      * @property _type
961      * @private
962      */
963     _type: "Node",
965     /*
966     spacerPath: "http://us.i1.yimg.com/us.yimg.com/i/space.gif",
967     expandedText: "Expanded",
968     collapsedText: "Collapsed",
969     loadingText: "Loading",
970     */
972     /**
973      * Initializes this node, gets some of the properties from the parent
974      * @method init
975      * @param oData {object} a string or object containing the data that will
976      * be used to render this node
977      * @param oParent {Node} this node's parent node
978      * @param expanded {boolean} the initial expanded/collapsed state
979      */
980     init: function(oData, oParent, expanded) {
982         this.data       = oData;
983         this.children   = [];
984         this.index      = YAHOO.widget.TreeView.nodeCount;
985         ++YAHOO.widget.TreeView.nodeCount;
986         this.expanded   = expanded;
988         /**
989          * The parentChange event is fired when a parent element is applied
990          * to the node.  This is useful if you need to apply tree-level
991          * properties to a tree that need to happen if a node is moved from
992          * one tree to another.
993          *
994          * @event parentChange
995          * @type CustomEvent
996          */
997         this.createEvent("parentChange", this);
999         // oParent should never be null except when we create the root node.
1000         if (oParent) {
1001             oParent.appendChild(this);
1002         }
1003     },
1005     /**
1006      * Certain properties for the node cannot be set until the parent
1007      * is known. This is called after the node is inserted into a tree.
1008      * the parent is also applied to this node's children in order to
1009      * make it possible to move a branch from one tree to another.
1010      * @method applyParent
1011      * @param {Node} parentNode this node's parent node
1012      * @return {boolean} true if the application was successful
1013      */
1014     applyParent: function(parentNode) {
1015         if (!parentNode) {
1016             return false;
1017         }
1019         this.tree   = parentNode.tree;
1020         this.parent = parentNode;
1021         this.depth  = parentNode.depth + 1;
1023         if (!this.href) {
1024             this.href = "javascript:" + this.getToggleLink();
1025         }
1027         // @todo why was this put here.  This causes new nodes added at the
1028         // root level to lose the menu behavior.
1029         // if (! this.multiExpand) {
1030             // this.multiExpand = parentNode.multiExpand;
1031         // }
1033         this.tree.regNode(this);
1034         parentNode.childrenRendered = false;
1036         // cascade update existing children
1037         for (var i=0, len=this.children.length;i<len;++i) {
1038             this.children[i].applyParent(this);
1039         }
1041         this.fireEvent("parentChange");
1043         return true;
1044     },
1046     /**
1047      * Appends a node to the child collection.
1048      * @method appendChild
1049      * @param childNode {Node} the new node
1050      * @return {Node} the child node
1051      * @private
1052      */
1053     appendChild: function(childNode) {
1054         if (this.hasChildren()) {
1055             var sib = this.children[this.children.length - 1];
1056             sib.nextSibling = childNode;
1057             childNode.previousSibling = sib;
1058         }
1059         this.children[this.children.length] = childNode;
1060         childNode.applyParent(this);
1062         // part of the IE display issue workaround. If child nodes
1063         // are added after the initial render, and the node was
1064         // instantiated with expanded = true, we need to show the
1065         // children div now that the node has a child.
1066         if (this.childrenRendered && this.expanded) {
1067             this.getChildrenEl().style.display = "";
1068         }
1070         return childNode;
1071     },
1073     /**
1074      * Appends this node to the supplied node's child collection
1075      * @method appendTo
1076      * @param parentNode {Node} the node to append to.
1077      * @return {Node} The appended node
1078      */
1079     appendTo: function(parentNode) {
1080         return parentNode.appendChild(this);
1081     },
1083     /**
1084     * Inserts this node before this supplied node
1085     * @method insertBefore
1086     * @param node {Node} the node to insert this node before
1087     * @return {Node} the inserted node
1088     */
1089     insertBefore: function(node) {
1090         var p = node.parent;
1091         if (p) {
1093             if (this.tree) {
1094                 this.tree.popNode(this);
1095             }
1097             var refIndex = node.isChildOf(p);
1098             p.children.splice(refIndex, 0, this);
1099             if (node.previousSibling) {
1100                 node.previousSibling.nextSibling = this;
1101             }
1102             this.previousSibling = node.previousSibling;
1103             this.nextSibling = node;
1104             node.previousSibling = this;
1106             this.applyParent(p);
1107         }
1109         return this;
1110     },
1112     /**
1113     * Inserts this node after the supplied node
1114     * @method insertAfter
1115     * @param node {Node} the node to insert after
1116     * @return {Node} the inserted node
1117     */
1118     insertAfter: function(node) {
1119         var p = node.parent;
1120         if (p) {
1122             if (this.tree) {
1123                 this.tree.popNode(this);
1124             }
1126             var refIndex = node.isChildOf(p);
1128             if (!node.nextSibling) {
1129                 this.nextSibling = null;
1130                 return this.appendTo(p);
1131             }
1133             p.children.splice(refIndex + 1, 0, this);
1135             node.nextSibling.previousSibling = this;
1136             this.previousSibling = node;
1137             this.nextSibling = node.nextSibling;
1138             node.nextSibling = this;
1140             this.applyParent(p);
1141         }
1143         return this;
1144     },
1146     /**
1147     * Returns true if the Node is a child of supplied Node
1148     * @method isChildOf
1149     * @param parentNode {Node} the Node to check
1150     * @return {boolean} The node index if this Node is a child of 
1151     *                   supplied Node, else -1.
1152     * @private
1153     */
1154     isChildOf: function(parentNode) {
1155         if (parentNode && parentNode.children) {
1156             for (var i=0, len=parentNode.children.length; i<len ; ++i) {
1157                 if (parentNode.children[i] === this) {
1158                     return i;
1159                 }
1160             }
1161         }
1163         return -1;
1164     },
1166     /**
1167      * Returns a node array of this node's siblings, null if none.
1168      * @method getSiblings
1169      * @return Node[]
1170      */
1171     getSiblings: function() {
1172         return this.parent.children;
1173     },
1175     /**
1176      * Shows this node's children
1177      * @method showChildren
1178      */
1179     showChildren: function() {
1180         if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
1181             if (this.hasChildren()) {
1182                 this.getChildrenEl().style.display = "";
1183             }
1184         }
1185     },
1187     /**
1188      * Hides this node's children
1189      * @method hideChildren
1190      */
1191     hideChildren: function() {
1193         if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
1194             this.getChildrenEl().style.display = "none";
1195         }
1196     },
1198     /**
1199      * Returns the id for this node's container div
1200      * @method getElId
1201      * @return {string} the element id
1202      */
1203     getElId: function() {
1204         return "ygtv" + this.index;
1205     },
1207     /**
1208      * Returns the id for this node's children div
1209      * @method getChildrenElId
1210      * @return {string} the element id for this node's children div
1211      */
1212     getChildrenElId: function() {
1213         return "ygtvc" + this.index;
1214     },
1216     /**
1217      * Returns the id for this node's toggle element
1218      * @method getToggleElId
1219      * @return {string} the toggel element id
1220      */
1221     getToggleElId: function() {
1222         return "ygtvt" + this.index;
1223     },
1226     /*
1227      * Returns the id for this node's spacer image.  The spacer is positioned
1228      * over the toggle and provides feedback for screen readers.
1229      * @method getSpacerId
1230      * @return {string} the id for the spacer image
1231      */
1232     /*
1233     getSpacerId: function() {
1234         return "ygtvspacer" + this.index;
1235     }, 
1236     */
1238     /**
1239      * Returns this node's container html element
1240      * @method getEl
1241      * @return {HTMLElement} the container html element
1242      */
1243     getEl: function() {
1244         return document.getElementById(this.getElId());
1245     },
1247     /**
1248      * Returns the div that was generated for this node's children
1249      * @method getChildrenEl
1250      * @return {HTMLElement} this node's children div
1251      */
1252     getChildrenEl: function() {
1253         return document.getElementById(this.getChildrenElId());
1254     },
1256     /**
1257      * Returns the element that is being used for this node's toggle.
1258      * @method getToggleEl
1259      * @return {HTMLElement} this node's toggle html element
1260      */
1261     getToggleEl: function() {
1262         return document.getElementById(this.getToggleElId());
1263     },
1265     /*
1266      * Returns the element that is being used for this node's spacer.
1267      * @method getSpacer
1268      * @return {HTMLElement} this node's spacer html element
1269      */
1270     /*
1271     getSpacer: function() {
1272         return document.getElementById( this.getSpacerId() ) || {};
1273     },
1274     */
1276     /*
1277     getStateText: function() {
1278         if (this.isLoading) {
1279             return this.loadingText;
1280         } else if (this.hasChildren(true)) {
1281             if (this.expanded) {
1282                 return this.expandedText;
1283             } else {
1284                 return this.collapsedText;
1285             }
1286         } else {
1287             return "";
1288         }
1289     },
1290     */
1292     /**
1293      * Generates the link that will invoke this node's toggle method
1294      * @method getToggleLink
1295      * @return {string} the javascript url for toggling this node
1296      */
1297     getToggleLink: function() {
1298         return "YAHOO.widget.TreeView.getNode(\'" + this.tree.id + "\'," + 
1299             this.index + ").toggle()";
1300     },
1302     /**
1303      * Hides this nodes children (creating them if necessary), changes the
1304      * @method collapse
1305      * toggle style.
1306      */
1307     collapse: function() {
1308         // Only collapse if currently expanded
1309         if (!this.expanded) { return; }
1311         // fire the collapse event handler
1312         var ret = this.tree.onCollapse(this);
1314         if (false === ret) {
1315             return;
1316         }
1318         ret = this.tree.fireEvent("collapse", this);
1320         if (false === ret) {
1321             return;
1322         }
1325         if (!this.getEl()) {
1326             this.expanded = false;
1327         } else {
1328             // hide the child div
1329             this.hideChildren();
1330             this.expanded = false;
1332             this.updateIcon();
1333         }
1335         // this.getSpacer().title = this.getStateText();
1337         ret = this.tree.fireEvent("collapseComplete", this);
1339     },
1341     /**
1342      * Shows this nodes children (creating them if necessary), changes the
1343      * toggle style, and collapses its siblings if multiExpand is not set.
1344      * @method expand
1345      */
1346     expand: function(lazySource) {
1347         // Only expand if currently collapsed.
1348         if (this.expanded && !lazySource) { 
1349             return; 
1350         }
1352         var ret = true;
1354         // When returning from the lazy load handler, expand is called again
1355         // in order to render the new children.  The "expand" event already
1356         // fired before fething the new data, so we need to skip it now.
1357         if (!lazySource) {
1358             // fire the expand event handler
1359             ret = this.tree.onExpand(this);
1361             if (false === ret) {
1362                 return;
1363             }
1364             
1365             ret = this.tree.fireEvent("expand", this);
1366         }
1368         if (false === ret) {
1369             return;
1370         }
1372         if (!this.getEl()) {
1373             this.expanded = true;
1374             return;
1375         }
1377         if (!this.childrenRendered) {
1378             this.getChildrenEl().innerHTML = this.renderChildren();
1379         } else {
1380         }
1382         this.expanded = true;
1384         this.updateIcon();
1386         // this.getSpacer().title = this.getStateText();
1388         // We do an extra check for children here because the lazy
1389         // load feature can expose nodes that have no children.
1391         // if (!this.hasChildren()) {
1392         if (this.isLoading) {
1393             this.expanded = false;
1394             return;
1395         }
1397         if (! this.multiExpand) {
1398             var sibs = this.getSiblings();
1399             for (var i=0; i<sibs.length; ++i) {
1400                 if (sibs[i] != this && sibs[i].expanded) { 
1401                     sibs[i].collapse(); 
1402                 }
1403             }
1404         }
1406         this.showChildren();
1408         ret = this.tree.fireEvent("expandComplete", this);
1409     },
1411     updateIcon: function() {
1412         if (this.hasIcon) {
1413             var el = this.getToggleEl();
1414             if (el) {
1415                 el.className = this.getStyle();
1416             }
1417         }
1418     },
1420     /**
1421      * Returns the css style name for the toggle
1422      * @method getStyle
1423      * @return {string} the css class for this node's toggle
1424      */
1425     getStyle: function() {
1426         if (this.isLoading) {
1427             return "ygtvloading";
1428         } else {
1429             // location top or bottom, middle nodes also get the top style
1430             var loc = (this.nextSibling) ? "t" : "l";
1432             // type p=plus(expand), m=minus(collapase), n=none(no children)
1433             var type = "n";
1434             if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) {
1435             // if (this.hasChildren(true)) {
1436                 type = (this.expanded) ? "m" : "p";
1437             }
1439             return "ygtv" + loc + type;
1440         }
1441     },
1443     /**
1444      * Returns the hover style for the icon
1445      * @return {string} the css class hover state
1446      * @method getHoverStyle
1447      */
1448     getHoverStyle: function() { 
1449         var s = this.getStyle();
1450         if (this.hasChildren(true) && !this.isLoading) { 
1451             s += "h"; 
1452         }
1453         return s;
1454     },
1456     /**
1457      * Recursively expands all of this node's children.
1458      * @method expandAll
1459      */
1460     expandAll: function() { 
1461         for (var i=0;i<this.children.length;++i) {
1462             var c = this.children[i];
1463             if (c.isDynamic()) {
1464                 alert("Not supported (lazy load + expand all)");
1465                 break;
1466             } else if (! c.multiExpand) {
1467                 alert("Not supported (no multi-expand + expand all)");
1468                 break;
1469             } else {
1470                 c.expand();
1471                 c.expandAll();
1472             }
1473         }
1474     },
1476     /**
1477      * Recursively collapses all of this node's children.
1478      * @method collapseAll
1479      */
1480     collapseAll: function() { 
1481         for (var i=0;i<this.children.length;++i) {
1482             this.children[i].collapse();
1483             this.children[i].collapseAll();
1484         }
1485     },
1487     /**
1488      * Configures this node for dynamically obtaining the child data
1489      * when the node is first expanded.  Calling it without the callback
1490      * will turn off dynamic load for the node.
1491      * @method setDynamicLoad
1492      * @param fmDataLoader {function} the function that will be used to get the data.
1493      * @param iconMode {int} configures the icon that is displayed when a dynamic
1494      * load node is expanded the first time without children.  By default, the 
1495      * "collapse" icon will be used.  If set to 1, the leaf node icon will be
1496      * displayed.
1497      */
1498     setDynamicLoad: function(fnDataLoader, iconMode) { 
1499         if (fnDataLoader) {
1500             this.dataLoader = fnDataLoader;
1501             this._dynLoad = true;
1502         } else {
1503             this.dataLoader = null;
1504             this._dynLoad = false;
1505         }
1507         if (iconMode) {
1508             this.iconMode = iconMode;
1509         }
1510     },
1512     /**
1513      * Evaluates if this node is the root node of the tree
1514      * @method isRoot
1515      * @return {boolean} true if this is the root node
1516      */
1517     isRoot: function() { 
1518         return (this == this.tree.root);
1519     },
1521     /**
1522      * Evaluates if this node's children should be loaded dynamically.  Looks for
1523      * the property both in this instance and the root node.  If the tree is
1524      * defined to load all children dynamically, the data callback function is
1525      * defined in the root node
1526      * @method isDynamic
1527      * @return {boolean} true if this node's children are to be loaded dynamically
1528      */
1529     isDynamic: function() { 
1530         if (this.isLeaf) {
1531             return false;
1532         } else {
1533             return (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
1534             // return lazy;
1535         }
1536     },
1538     /**
1539      * Returns the current icon mode.  This refers to the way childless dynamic
1540      * load nodes appear (this comes into play only after the initial dynamic
1541      * load request produced no children).
1542      * @method getIconMode
1543      * @return {int} 0 for collapse style, 1 for leaf node style
1544      */
1545     getIconMode: function() {
1546         return (this.iconMode || this.tree.root.iconMode);
1547     },
1549     /**
1550      * Checks if this node has children.  If this node is lazy-loading and the
1551      * children have not been rendered, we do not know whether or not there
1552      * are actual children.  In most cases, we need to assume that there are
1553      * children (for instance, the toggle needs to show the expandable 
1554      * presentation state).  In other times we want to know if there are rendered
1555      * children.  For the latter, "checkForLazyLoad" should be false.
1556      * @method hasChildren
1557      * @param checkForLazyLoad {boolean} should we check for unloaded children?
1558      * @return {boolean} true if this has children or if it might and we are
1559      * checking for this condition.
1560      */
1561     hasChildren: function(checkForLazyLoad) { 
1562         if (this.isLeaf) {
1563             return false;
1564         } else {
1565             return ( this.children.length > 0 || 
1566 (checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete) );
1567         }
1568     },
1570     /**
1571      * Expands if node is collapsed, collapses otherwise.
1572      * @method toggle
1573      */
1574     toggle: function() {
1575         if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
1576             if (this.expanded) { this.collapse(); } else { this.expand(); }
1577         }
1578     },
1580     /**
1581      * Returns the markup for this node and its children.
1582      * @method getHtml
1583      * @return {string} the markup for this node and its expanded children.
1584      */
1585     getHtml: function() {
1587         this.childrenRendered = false;
1589         var sb = [];
1590         sb[sb.length] = '<div class="ygtvitem" id="' + this.getElId() + '">';
1591         sb[sb.length] = this.getNodeHtml();
1592         sb[sb.length] = this.getChildrenHtml();
1593         sb[sb.length] = '</div>';
1594         return sb.join("");
1595     },
1597     /**
1598      * Called when first rendering the tree.  We always build the div that will
1599      * contain this nodes children, but we don't render the children themselves
1600      * unless this node is expanded.
1601      * @method getChildrenHtml
1602      * @return {string} the children container div html and any expanded children
1603      * @private
1604      */
1605     getChildrenHtml: function() {
1608         var sb = [];
1609         sb[sb.length] = '<div class="ygtvchildren"';
1610         sb[sb.length] = ' id="' + this.getChildrenElId() + '"';
1612         // This is a workaround for an IE rendering issue, the child div has layout
1613         // in IE, creating extra space if a leaf node is created with the expanded
1614         // property set to true.
1615         if (!this.expanded || !this.hasChildren()) {
1616             sb[sb.length] = ' style="display:none;"';
1617         }
1618         sb[sb.length] = '>';
1621         // Don't render the actual child node HTML unless this node is expanded.
1622         if ( (this.hasChildren(true) && this.expanded) ||
1623                 (this.renderHidden && !this.isDynamic()) ) {
1624             sb[sb.length] = this.renderChildren();
1625         }
1627         sb[sb.length] = '</div>';
1629         return sb.join("");
1630     },
1632     /**
1633      * Generates the markup for the child nodes.  This is not done until the node
1634      * is expanded.
1635      * @method renderChildren
1636      * @return {string} the html for this node's children
1637      * @private
1638      */
1639     renderChildren: function() {
1642         var node = this;
1644         if (this.isDynamic() && !this.dynamicLoadComplete) {
1645             this.isLoading = true;
1646             this.tree.locked = true;
1648             if (this.dataLoader) {
1650                 setTimeout( 
1651                     function() {
1652                         node.dataLoader(node, 
1653                             function() { 
1654                                 node.loadComplete(); 
1655                             });
1656                     }, 10);
1657                 
1658             } else if (this.tree.root.dataLoader) {
1660                 setTimeout( 
1661                     function() {
1662                         node.tree.root.dataLoader(node, 
1663                             function() { 
1664                                 node.loadComplete(); 
1665                             });
1666                     }, 10);
1668             } else {
1669                 return "Error: data loader not found or not specified.";
1670             }
1672             return "";
1674         } else {
1675             return this.completeRender();
1676         }
1677     },
1679     /**
1680      * Called when we know we have all the child data.
1681      * @method completeRender
1682      * @return {string} children html
1683      */
1684     completeRender: function() {
1685         var sb = [];
1687         for (var i=0; i < this.children.length; ++i) {
1688             // this.children[i].childrenRendered = false;
1689             sb[sb.length] = this.children[i].getHtml();
1690         }
1691         
1692         this.childrenRendered = true;
1694         return sb.join("");
1695     },
1697     /**
1698      * Load complete is the callback function we pass to the data provider
1699      * in dynamic load situations.
1700      * @method loadComplete
1701      */
1702     loadComplete: function() {
1703         this.getChildrenEl().innerHTML = this.completeRender();
1704         this.dynamicLoadComplete = true;
1705         this.isLoading = false;
1706         this.expand(true);
1707         this.tree.locked = false;
1708     },
1710     /**
1711      * Returns this node's ancestor at the specified depth.
1712      * @method getAncestor
1713      * @param {int} depth the depth of the ancestor.
1714      * @return {Node} the ancestor
1715      */
1716     getAncestor: function(depth) {
1717         if (depth >= this.depth || depth < 0)  {
1718             return null;
1719         }
1721         var p = this.parent;
1722         
1723         while (p.depth > depth) {
1724             p = p.parent;
1725         }
1727         return p;
1728     },
1730     /**
1731      * Returns the css class for the spacer at the specified depth for
1732      * this node.  If this node's ancestor at the specified depth
1733      * has a next sibling the presentation is different than if it
1734      * does not have a next sibling
1735      * @method getDepthStyle
1736      * @param {int} depth the depth of the ancestor.
1737      * @return {string} the css class for the spacer
1738      */
1739     getDepthStyle: function(depth) {
1740         return (this.getAncestor(depth).nextSibling) ? 
1741             "ygtvdepthcell" : "ygtvblankdepthcell";
1742     },
1744     /**
1745      * Get the markup for the node.  This is designed to be overrided so that we can
1746      * support different types of nodes.
1747      * @method getNodeHtml
1748      * @return {string} The HTML that will render this node.
1749      */
1750     getNodeHtml: function() { 
1751         return ""; 
1752     },
1754     /**
1755      * Regenerates the html for this node and its children.  To be used when the
1756      * node is expanded and new children have been added.
1757      * @method refresh
1758      */
1759     refresh: function() {
1760         // this.loadComplete();
1761         this.getChildrenEl().innerHTML = this.completeRender();
1763         if (this.hasIcon) {
1764             var el = this.getToggleEl();
1765             if (el) {
1766                 el.className = this.getStyle();
1767             }
1768         }
1769     },
1771     /**
1772      * Node toString
1773      * @method toString
1774      * @return {string} string representation of the node
1775      */
1776     toString: function() {
1777         return "Node (" + this.index + ")";
1778     }
1782 YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
1785  * The default node presentation.  The first parameter should be
1786  * either a string that will be used as the node's label, or an object
1787  * that has a string propery called label.  By default, the clicking the
1788  * label will toggle the expanded/collapsed state of the node.  By
1789  * changing the href property of the instance, this behavior can be
1790  * changed so that the label will go to the specified href.
1791  * @namespace YAHOO.widget
1792  * @class TextNode
1793  * @extends YAHOO.widget.Node
1794  * @constructor
1795  * @param oData {object} a string or object containing the data that will
1796  * be used to render this node.
1797  * Valid properties: 
1798  * <dl>
1799  *   <dt>label</dt>
1800  *   <dd>The text for the node's label</dd>
1801  *   <dt>title</dt>
1802  *   <dd>The title attribute for the label anchor</dd>
1803  *   <dt>title</dt>
1804  *   <dd>The title attribute for the label anchor</dd>
1805  *   <dt>href</dt>
1806  *   <dd>The href for the node's label.  By default it is set to
1807  *   expand/collapse the node.</dd>
1808  *   <dt>target</dt>
1809  *   <dd>The target attribute for the label anchor</dd>
1810  *   <dt>style</dt>
1811  *   <dd>A CSS class to apply to the label anchor</dd>
1812  * </dl>
1813  * All other attributes are made available in noderef.data, which
1814  * can be used to store custom attributes.  TreeView.getNode(s)ByProperty
1815  * can be used to retreive a node by one of the attributes.
1816  * @param oParent {YAHOO.widget.Node} this node's parent node
1817  * @param expanded {boolean} the initial expanded/collapsed state
1818  */
1819 YAHOO.widget.TextNode = function(oData, oParent, expanded) {
1821     if (oData) { 
1822         this.init(oData, oParent, expanded);
1823         this.setUpLabel(oData);
1824     }
1828 YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
1829     
1830     /**
1831      * The CSS class for the label href.  Defaults to ygtvlabel, but can be
1832      * overridden to provide a custom presentation for a specific node.
1833      * @property labelStyle
1834      * @type string
1835      */
1836     labelStyle: "ygtvlabel",
1838     /**
1839      * The derived element id of the label for this node
1840      * @property labelElId
1841      * @type string
1842      */
1843     labelElId: null,
1845     /**
1846      * The text for the label.  It is assumed that the oData parameter will
1847      * either be a string that will be used as the label, or an object that
1848      * has a property called "label" that we will use.
1849      * @property label
1850      * @type string
1851      */
1852     label: null,
1854     textNodeParentChange: function() {
1856         /**
1857          * Custom event that is fired when the text node label is clicked.  The
1858          * custom event is defined on the tree instance, so there is a single
1859          * event that handles all nodes in the tree.  The node clicked is 
1860          * provided as an argument
1861          *
1862          * @event labelClick
1863          * @for YAHOO.widget.TreeView
1864          * @param {YAHOO.widget.Node} node the node clicked
1865          */
1866         if (this.tree && !this.tree.hasEvent("labelClick")) {
1867             this.tree.createEvent("labelClick", this.tree);
1868         }
1869        
1870     },
1872     /**
1873      * Sets up the node label
1874      * @method setUpLabel
1875      * @param oData string containing the label, or an object with a label property
1876      */
1877     setUpLabel: function(oData) { 
1878         
1879         // set up the custom event on the tree
1880         this.textNodeParentChange();
1881         this.subscribe("parentChange", this.textNodeParentChange);
1883         if (typeof oData == "string") {
1884             oData = { label: oData };
1885         }
1886         this.label = oData.label;
1887         this.data.label = oData.label;
1888         
1889         // update the link
1890         if (oData.href) {
1891             this.href = encodeURI(oData.href);
1892         }
1894         // set the target
1895         if (oData.target) {
1896             this.target = oData.target;
1897         }
1899         if (oData.style) {
1900             this.labelStyle = oData.style;
1901         }
1903         if (oData.title) {
1904             this.title = oData.title;
1905         }
1907         this.labelElId = "ygtvlabelel" + this.index;
1908     },
1910     /**
1911      * Returns the label element
1912      * @for YAHOO.widget.TextNode
1913      * @method getLabelEl
1914      * @return {object} the element
1915      */
1916     getLabelEl: function() { 
1917         return document.getElementById(this.labelElId);
1918     },
1920     // overrides YAHOO.widget.Node
1921     getNodeHtml: function() { 
1922         var sb = [];
1924         sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
1925         sb[sb.length] = '<tr>';
1926         
1927         for (var i=0;i<this.depth;++i) {
1928             //sb[sb.length] = '<td><div class="' + this.getDepthStyle(i) + '">&#160;</div></td>';
1929             //sb[sb.length] = '<td><div class="' + this.getDepthStyle(i) + '"></div></td>';
1930             sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
1931         }
1933         var getNode = 'YAHOO.widget.TreeView.getNode(\'' +
1934                         this.tree.id + '\',' + this.index + ')';
1936         sb[sb.length] = '<td';
1937         // sb[sb.length] = ' onselectstart="return false"';
1938         sb[sb.length] = ' id="' + this.getToggleElId() + '"';
1939         sb[sb.length] = ' class="' + this.getStyle() + '"';
1940         if (this.hasChildren(true)) {
1941             sb[sb.length] = ' onmouseover="this.className=';
1942             sb[sb.length] = getNode + '.getHoverStyle()"';
1943             sb[sb.length] = ' onmouseout="this.className=';
1944             sb[sb.length] = getNode + '.getStyle()"';
1945         }
1946         sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '">';
1948         sb[sb.length] = '<div class="ygtvspacer">';
1950         /*
1951         sb[sb.length] = '<img id="' + this.getSpacerId() + '"';
1952         sb[sb.length] = ' alt=""';
1953         sb[sb.length] = ' tabindex=0';
1954         sb[sb.length] = ' src="' + this.spacerPath + '"';
1955         sb[sb.length] = ' title="' + this.getStateText() + '"';
1956         sb[sb.length] = ' class="ygtvspacer"';
1957         // sb[sb.length] = ' onkeypress="return ' + getNode + '".onKeyPress()"';
1958         sb[sb.length] = ' />';
1959         */
1961         //sb[sb.length] = '&#160;';
1963         sb[sb.length] = '</div>';
1964         sb[sb.length] = '</td>';
1965         sb[sb.length] = '<td ';
1966         sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
1967         sb[sb.length] = ' >';
1968         sb[sb.length] = '<a';
1969         sb[sb.length] = ' id="' + this.labelElId + '"';
1970         if (this.title) {
1971             sb[sb.length] = ' title="' + this.title + '"';
1972         }
1973         sb[sb.length] = ' class="' + this.labelStyle + '"';
1974         sb[sb.length] = ' href="' + this.href + '"';
1975         sb[sb.length] = ' target="' + this.target + '"';
1976         sb[sb.length] = ' onclick="return ' + getNode + '.onLabelClick(' + getNode +')"';
1977         if (this.hasChildren(true)) {
1978             sb[sb.length] = ' onmouseover="document.getElementById(\'';
1979             sb[sb.length] = this.getToggleElId() + '\').className=';
1980             sb[sb.length] = getNode + '.getHoverStyle()"';
1981             sb[sb.length] = ' onmouseout="document.getElementById(\'';
1982             sb[sb.length] = this.getToggleElId() + '\').className=';
1983             sb[sb.length] = getNode + '.getStyle()"';
1984         }
1985         sb[sb.length] = ' >';
1986         sb[sb.length] = this.label;
1987         sb[sb.length] = '</a>';
1988         sb[sb.length] = '</td>';
1989         sb[sb.length] = '</tr>';
1990         sb[sb.length] = '</table>';
1992         return sb.join("");
1993     },
1996     /**
1997      * Executed when the label is clicked.  Fires the labelClick custom event.
1998      * @method onLabelClick
1999      * @param me {Node} this node
2000      * @scope the anchor tag clicked
2001      * @return false to cancel the anchor click
2002      */
2003     onLabelClick: function(me) { 
2004         return me.tree.fireEvent("labelClick", me);
2005         //return true;
2006     },
2008     toString: function() { 
2009         return "TextNode (" + this.index + ") " + this.label;
2010     }
2014  * A custom YAHOO.widget.Node that handles the unique nature of 
2015  * the virtual, presentationless root node.
2016  * @namespace YAHOO.widget
2017  * @class RootNode
2018  * @extends YAHOO.widget.Node
2019  * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
2020  * @constructor
2021  */
2022 YAHOO.widget.RootNode = function(oTree) {
2023         // Initialize the node with null params.  The root node is a
2024         // special case where the node has no presentation.  So we have
2025         // to alter the standard properties a bit.
2026         this.init(null, null, true);
2027         
2028         /*
2029          * For the root node, we get the tree reference from as a param
2030          * to the constructor instead of from the parent element.
2031          */
2032         this.tree = oTree;
2035 YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
2036     
2037     // overrides YAHOO.widget.Node
2038     getNodeHtml: function() { 
2039         return ""; 
2040     },
2042     toString: function() { 
2043         return "RootNode";
2044     },
2046     loadComplete: function() { 
2047         this.tree.draw();
2048     },
2050     collapse: function() {},
2051     expand: function() {}
2055  * This implementation takes either a string or object for the
2056  * oData argument.  If is it a string, we will use it for the display
2057  * of this node (and it can contain any html code).  If the parameter
2058  * is an object, we look for a parameter called "html" that will be
2059  * used for this node's display.
2060  * @namespace YAHOO.widget
2061  * @class HTMLNode
2062  * @extends YAHOO.widget.Node
2063  * @constructor
2064  * @param oData {object} a string or object containing the data that will
2065  * be used to render this node.  
2066  * Valid configuration properties: 
2067  * <dl>
2068  *   <dt>html</dt>
2069  *   <dd>The html content for the node</dd>
2070  * </dl>
2071  * All other attributes are made available in noderef.data, which
2072  * can be used to store custom attributes.  TreeView.getNode(s)ByProperty
2073  * can be used to retreive a node by one of the attributes.
2074  * @param oParent {YAHOO.widget.Node} this node's parent node
2075  * @param expanded {boolean} the initial expanded/collapsed state
2076  * @param hasIcon {boolean} specifies whether or not leaf nodes should
2077  * be rendered with or without a horizontal line line icon. If the icon
2078  * is not displayed, the content fills the space it would have occupied.
2079  * This option operates independently of the leaf node presentation logic
2080  * for dynamic nodes.
2081  */
2082 YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
2083     if (oData) { 
2084         this.init(oData, oParent, expanded);
2085         this.initContent(oData, hasIcon);
2086     }
2089 YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, {
2091     /**
2092      * The CSS class for the html content container.  Defaults to ygtvhtml, but 
2093      * can be overridden to provide a custom presentation for a specific node.
2094      * @property contentStyle
2095      * @type string
2096      */
2097     contentStyle: "ygtvhtml",
2099     /**
2100      * The generated id that will contain the data passed in by the implementer.
2101      * @property contentElId
2102      * @type string
2103      */
2104     contentElId: null,
2106     /**
2107      * The HTML content to use for this node's display
2108      * @property html
2109      * @type string
2110      */
2111     html: null,
2113     /**
2114      * Sets up the node label
2115      * @property initContent
2116      * @param oData {object} An html string or object containing an html property
2117      * @param hasIcon {boolean} determines if the node will be rendered with an
2118      * icon or not
2119      */
2120     initContent: function(oData, hasIcon) { 
2121         this.setHtml(oData);
2122         this.contentElId = "ygtvcontentel" + this.index;
2123         this.hasIcon = hasIcon;
2125     },
2127     /**
2128      * Synchronizes the node.data, node.html, and the node's content
2129      * @property setHtml
2130      * @param o {object} An html string or object containing an html property
2131      */
2132     setHtml: function(o) {
2134         this.data = o;
2135         this.html = (typeof o === "string") ? o : o.html;
2137         var el = this.getContentEl();
2138         if (el) {
2139             el.innerHTML = this.html;
2140         }
2142     },
2144     /**
2145      * Returns the outer html element for this node's content
2146      * @method getContentEl
2147      * @return {HTMLElement} the element
2148      */
2149     getContentEl: function() { 
2150         return document.getElementById(this.contentElId);
2151     },
2153     // overrides YAHOO.widget.Node
2154     getNodeHtml: function() { 
2155         var sb = [];
2157         sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
2158         sb[sb.length] = '<tr>';
2159         
2160         for (var i=0;i<this.depth;++i) {
2161             //sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '">&#160;</td>';
2162             sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
2163         }
2165         if (this.hasIcon) {
2166             sb[sb.length] = '<td';
2167             sb[sb.length] = ' id="' + this.getToggleElId() + '"';
2168             sb[sb.length] = ' class="' + this.getStyle() + '"';
2169             sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '"';
2170             if (this.hasChildren(true)) {
2171                 sb[sb.length] = ' onmouseover="this.className=';
2172                 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
2173                 sb[sb.length] = this.tree.id + '\',' + this.index +  ').getHoverStyle()"';
2174                 sb[sb.length] = ' onmouseout="this.className=';
2175                 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
2176                 sb[sb.length] = this.tree.id + '\',' + this.index +  ').getStyle()"';
2177             }
2178             //sb[sb.length] = '>&#160;</td>';
2179             sb[sb.length] = '><div class="ygtvspacer"></div></td>';
2180         }
2182         sb[sb.length] = '<td';
2183         sb[sb.length] = ' id="' + this.contentElId + '"';
2184         sb[sb.length] = ' class="' + this.contentStyle + '"';
2185         sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
2186         sb[sb.length] = ' >';
2187         sb[sb.length] = this.html;
2188         sb[sb.length] = '</td>';
2189         sb[sb.length] = '</tr>';
2190         sb[sb.length] = '</table>';
2192         return sb.join("");
2193     },
2195     toString: function() { 
2196         return "HTMLNode (" + this.index + ")";
2197     }
2201  * A menu-specific implementation that differs from TextNode in that only 
2202  * one sibling can be expanded at a time.
2203  * @namespace YAHOO.widget
2204  * @class MenuNode
2205  * @extends YAHOO.widget.TextNode
2206  * @param oData {object} a string or object containing the data that will
2207  * be used to render this node.
2208  * Valid properties: 
2209  * <dl>
2210  *   <dt>label</dt>
2211  *   <dd>The text for the node's label</dd>
2212  *   <dt>title</dt>
2213  *   <dd>The title attribute for the label anchor</dd>
2214  *   <dt>title</dt>
2215  *   <dd>The title attribute for the label anchor</dd>
2216  *   <dt>href</dt>
2217  *   <dd>The href for the node's label.  By default it is set to
2218  *   expand/collapse the node.</dd>
2219  *   <dt>target</dt>
2220  *   <dd>The target attribute for the label anchor</dd>
2221  *   <dt>style</dt>
2222  *   <dd>A CSS class to apply to the label anchor</dd>
2223  * </dl>
2224  * All other attributes are made available in noderef.data, which
2225  * can be used to store custom attributes.  TreeView.getNode(s)ByProperty
2226  * can be used to retreive a node by one of the attributes.
2227  * @param oParent {YAHOO.widget.Node} this node's parent node
2228  * @param expanded {boolean} the initial expanded/collapsed state
2229  * @constructor
2230  */
2231 YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
2232         if (oData) { 
2233                 this.init(oData, oParent, expanded);
2234                 this.setUpLabel(oData);
2235         }
2237     /*
2238      * Menus usually allow only one branch to be open at a time.
2239      */
2240         this.multiExpand = false;
2245 YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
2247     toString: function() { 
2248         return "MenuNode (" + this.index + ") " + this.label;
2249     }
2253  * A static factory class for tree view expand/collapse animations
2254  * @class TVAnim
2255  * @static
2256  */
2257 YAHOO.widget.TVAnim = function() {
2258     return {
2259         /**
2260          * Constant for the fade in animation
2261          * @property FADE_IN
2262          * @type string
2263          * @static
2264          */
2265         FADE_IN: "TVFadeIn",
2267         /**
2268          * Constant for the fade out animation
2269          * @property FADE_OUT
2270          * @type string
2271          * @static
2272          */
2273         FADE_OUT: "TVFadeOut",
2275         /**
2276          * Returns a ygAnim instance of the given type
2277          * @method getAnim
2278          * @param type {string} the type of animation
2279          * @param el {HTMLElement} the element to element (probably the children div)
2280          * @param callback {function} function to invoke when the animation is done.
2281          * @return {YAHOO.util.Animation} the animation instance
2282          * @static
2283          */
2284         getAnim: function(type, el, callback) {
2285             if (YAHOO.widget[type]) {
2286                 return new YAHOO.widget[type](el, callback);
2287             } else {
2288                 return null;
2289             }
2290         },
2292         /**
2293          * Returns true if the specified animation class is available
2294          * @method isValid
2295          * @param type {string} the type of animation
2296          * @return {boolean} true if valid, false if not
2297          * @static
2298          */
2299         isValid: function(type) {
2300             return (YAHOO.widget[type]);
2301         }
2302     };
2303 } ();
2306  * A 1/2 second fade-in animation.
2307  * @class TVFadeIn
2308  * @constructor
2309  * @param el {HTMLElement} the element to animate
2310  * @param callback {function} function to invoke when the animation is finished
2311  */
2312 YAHOO.widget.TVFadeIn = function(el, callback) {
2313     /**
2314      * The element to animate
2315      * @property el
2316      * @type HTMLElement
2317      */
2318     this.el = el;
2320     /**
2321      * the callback to invoke when the animation is complete
2322      * @property callback
2323      * @type function
2324      */
2325     this.callback = callback;
2329 YAHOO.widget.TVFadeIn.prototype = {
2330     /**
2331      * Performs the animation
2332      * @method animate
2333      */
2334     animate: function() {
2335         var tvanim = this;
2337         var s = this.el.style;
2338         s.opacity = 0.1;
2339         s.filter = "alpha(opacity=10)";
2340         s.display = "";
2342         var dur = 0.4; 
2343         var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur);
2344         a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2345         a.animate();
2346     },
2348     /**
2349      * Clean up and invoke callback
2350      * @method onComplete
2351      */
2352     onComplete: function() {
2353         this.callback();
2354     },
2356     /**
2357      * toString
2358      * @method toString
2359      * @return {string} the string representation of the instance
2360      */
2361     toString: function() {
2362         return "TVFadeIn";
2363     }
2367  * A 1/2 second fade out animation.
2368  * @class TVFadeOut
2369  * @constructor
2370  * @param el {HTMLElement} the element to animate
2371  * @param callback {Function} function to invoke when the animation is finished
2372  */
2373 YAHOO.widget.TVFadeOut = function(el, callback) {
2374     /**
2375      * The element to animate
2376      * @property el
2377      * @type HTMLElement
2378      */
2379     this.el = el;
2381     /**
2382      * the callback to invoke when the animation is complete
2383      * @property callback
2384      * @type function
2385      */
2386     this.callback = callback;
2390 YAHOO.widget.TVFadeOut.prototype = {
2391     /**
2392      * Performs the animation
2393      * @method animate
2394      */
2395     animate: function() {
2396         var tvanim = this;
2397         var dur = 0.4;
2398         var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
2399         a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2400         a.animate();
2401     },
2403     /**
2404      * Clean up and invoke callback
2405      * @method onComplete
2406      */
2407     onComplete: function() {
2408         var s = this.el.style;
2409         s.display = "none";
2410         // s.opacity = 1;
2411         s.filter = "alpha(opacity=100)";
2412         this.callback();
2413     },
2415     /**
2416      * toString
2417      * @method toString
2418      * @return {string} the string representation of the instance
2419      */
2420     toString: function() {
2421         return "TVFadeOut";
2422     }
2425 YAHOO.register("treeview", YAHOO.widget.TreeView, {version: "2.5.2", build: "1076"});