treeview.js
上传用户:q2283699q
上传日期:2022-05-17
资源大小:10704k
文件大小:66k
源码类别:

Ftp客户端

开发平台:

Visual C++

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