2 Copyright (c) 2007, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 * The treeview widget is a generic tree building tool.
10 * @title TreeView Widget
11 * @requires yahoo, event
13 * @namespace YAHOO.widget
17 * Contains the tree view state data and the root node.
20 * @uses YAHOO.util.EventProvider
22 * @param {string|HTMLElement} id The id of the element, or the element
23 * itself that the tree will be inserted into.
25 YAHOO.widget.TreeView = function(id) {
26 if (id) { this.init(id); }
29 YAHOO.widget.TreeView.prototype = {
32 * The id of tree container element
39 * The host element for this tree
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.
56 * We lock the tree control while waiting for the dynamic loader to return
63 * The animation to use for expanding children, if any
64 * @property _expandAnim
71 * The animation to use for collapsing children, if any
72 * @property _collapseAnim
79 * The current number of animations that are executing
80 * @property _animCount
87 * The maximum number of animations to run at one time.
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)
99 setExpandAnim: function(type) {
100 if (YAHOO.widget.TVAnim.isValid(type)) {
101 this._expandAnim = type;
106 * Sets up the animation for collapsing children
107 * @method setCollapseAnim
108 * @param {string} the type of animation (acceptable values defined in
109 * YAHOO.widget.TVAnim)
111 setCollapseAnim: function(type) {
112 if (YAHOO.widget.TVAnim.isValid(type)) {
113 this._collapseAnim = type;
118 * Perform the expand animation if configured, or just show the
119 * element if not configured or too many animations are in progress
120 * @method animateExpand
121 * @param el {HTMLElement} the element to animate
122 * @param node {YAHOO.util.Node} the node that was expanded
123 * @return {boolean} true if animation could be invoked, false otherwise
125 animateExpand: function(el, node) {
126 this.logger.log("animating expand");
128 if (this._expandAnim && this._animCount < this.maxAnim) {
129 // this.locked = true;
131 var a = YAHOO.widget.TVAnim.getAnim(this._expandAnim, el,
132 function() { tree.expandComplete(node); });
135 this.fireEvent("animStart", {
149 * Perform the collapse animation if configured, or just show the
150 * element if not configured or too many animations are in progress
151 * @method animateCollapse
152 * @param el {HTMLElement} the element to animate
153 * @param node {YAHOO.util.Node} the node that was expanded
154 * @return {boolean} true if animation could be invoked, false otherwise
156 animateCollapse: function(el, node) {
157 this.logger.log("animating collapse");
159 if (this._collapseAnim && this._animCount < this.maxAnim) {
160 // this.locked = true;
162 var a = YAHOO.widget.TVAnim.getAnim(this._collapseAnim, el,
163 function() { tree.collapseComplete(node); });
166 this.fireEvent("animStart", {
180 * Function executed when the expand animation completes
181 * @method expandComplete
183 expandComplete: function(node) {
184 this.logger.log("expand complete: " + this.id);
186 this.fireEvent("animComplete", {
190 // this.locked = false;
194 * Function executed when the collapse animation completes
195 * @method collapseComplete
197 collapseComplete: function(node) {
198 this.logger.log("collapse complete: " + this.id);
200 this.fireEvent("animComplete", {
204 // this.locked = false;
208 * Initializes the tree
210 * @parm {string|HTMLElement} id the id of the element that will hold the tree
217 if ("string" !== typeof id) {
219 this.id = this.generateId(id);
223 * When animation is enabled, this event fires when the animation
227 * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
228 * @parm {String} type the type of animation ("expand" or "collapse")
230 this.createEvent("animStart", this);
233 * When animation is enabled, this event fires when the animation
235 * @event animComplete
237 * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
238 * @parm {String} type the type of animation ("expand" or "collapse")
240 this.createEvent("animComplete", this);
243 * Fires when a node is going to be collapsed. Return false to stop
247 * @param {YAHOO.widget.Node} node the node that is collapsing
249 this.createEvent("collapse", this);
252 * Fires after a node is successfully collapsed. This event will not fire
253 * if the "collapse" event was cancelled.
254 * @event collapseComplete
256 * @param {YAHOO.widget.Node} node the node that was collapsed
258 this.createEvent("collapseComplete", this);
261 * Fires when a node is going to be expanded. Return false to stop
265 * @param {YAHOO.widget.Node} node the node that is expanding
267 this.createEvent("expand", this);
270 * Fires after a node is successfully expanded. This event will not fire
271 * if the "expand" event was cancelled.
272 * @event expandComplete
274 * @param {YAHOO.widget.Node} node the node that was expanded
276 this.createEvent("expandComplete", this);
280 // store a global reference
281 YAHOO.widget.TreeView.trees[this.id] = this;
283 // Set up the root node
284 this.root = new YAHOO.widget.RootNode(this);
286 var LW = YAHOO.widget.LogWriter;
288 this.logger = (LW) ? new LW(this.toString()) : YAHOO;
290 this.logger.log("tree init: " + this.id);
292 // YAHOO.util.Event.onContentReady(this.id, this.handleAvailable, this, true);
293 // YAHOO.util.Event.on(this.id, "click", this.handleClick, this, true);
296 //handleAvailable: function() {
297 //var Event = YAHOO.util.Event;
302 * Renders the tree boilerplate and visible nodes
306 var html = this.root.getHtml();
307 this.getEl().innerHTML = html;
308 this.firstDraw = false;
312 * Returns the tree's host element
314 * @return {HTMLElement} the host element
318 this._el = document.getElementById(this.id);
324 * Nodes register themselves with the tree instance when they are created.
326 * @param node {Node} the node to register
329 regNode: function(node) {
330 this._nodes[node.index] = node;
334 * Returns the root node of this tree
336 * @return {Node} the root node
338 getRoot: function() {
343 * Configures this tree to dynamically load all child data
344 * @method setDynamicLoad
345 * @param {function} fnDataLoader the function that will be called to get the data
346 * @param iconMode {int} configures the icon that is displayed when a dynamic
347 * load node is expanded the first time without children. By default, the
348 * "collapse" icon will be used. If set to 1, the leaf node icon will be
351 setDynamicLoad: function(fnDataLoader, iconMode) {
352 this.root.setDynamicLoad(fnDataLoader, iconMode);
356 * Expands all child nodes. Note: this conflicts with the "multiExpand"
357 * node property. If expand all is called in a tree with nodes that
358 * do not allow multiple siblings to be displayed, only the last sibling
362 expandAll: function() {
364 this.root.expandAll();
369 * Collapses all expanded child nodes in the entire tree.
370 * @method collapseAll
372 collapseAll: function() {
374 this.root.collapseAll();
379 * Returns a node in the tree that has the specified index (this index
380 * is created internally, so this function probably will only be used
381 * in html generated for a given node.)
382 * @method getNodeByIndex
383 * @param {int} nodeIndex the index of the node wanted
384 * @return {Node} the node with index=nodeIndex, null if no match
386 getNodeByIndex: function(nodeIndex) {
387 var n = this._nodes[nodeIndex];
388 return (n) ? n : null;
392 * Returns a node that has a matching property and value in the data
393 * object that was passed into its constructor.
394 * @method getNodeByProperty
395 * @param {object} property the property to search (usually a string)
396 * @param {object} value the value we want to find (usuall an int or string)
397 * @return {Node} the matching node, null if no match
399 getNodeByProperty: function(property, value) {
400 for (var i in this._nodes) {
401 var n = this._nodes[i];
402 if (n.data && value == n.data[property]) {
411 * Returns a collection of nodes that have a matching property
412 * and value in the data object that was passed into its constructor.
413 * @method getNodesByProperty
414 * @param {object} property the property to search (usually a string)
415 * @param {object} value the value we want to find (usuall an int or string)
416 * @return {Array} the matching collection of nodes, null if no match
418 getNodesByProperty: function(property, value) {
420 for (var i in this._nodes) {
421 var n = this._nodes[i];
422 if (n.data && value == n.data[property]) {
427 return (values.length) ? values : null;
431 * Removes the node and its children, and optionally refreshes the
432 * branch of the tree that was affected.
434 * @param {Node} The node to remove
435 * @param {boolean} autoRefresh automatically refreshes branch if true
436 * @return {boolean} False is there was a problem, true otherwise.
438 removeNode: function(node, autoRefresh) {
440 // Don't delete the root node
445 // Get the branch that we may need to refresh
451 // Delete the node and its children
452 this._deleteNode(node);
454 // Refresh the parent of the parent
455 if (autoRefresh && p && p.childrenRendered) {
463 * Deletes this nodes child collection, recursively. Also collapses
464 * the node, and resets the dynamic load flag. The primary use for
465 * this method is to purge a node and allow it to fetch its data
467 * @method removeChildren
468 * @param {Node} node the node to purge
470 removeChildren: function(node) {
471 this.logger.log("Removing children for " + node);
472 while (node.children.length) {
473 this._deleteNode(node.children[0]);
476 node.childrenRendered = false;
477 node.dynamicLoadComplete = false;
486 * Deletes the node and recurses children
487 * @method _deleteNode
490 _deleteNode: function(node) {
491 // Remove all the child nodes first
492 this.removeChildren(node);
494 // Remove the node from the tree
499 * Removes the node from the tree, preserving the child collection
500 * to make it possible to insert the branch into another part of the
501 * tree, or another tree.
503 * @param {Node} the node to remove
505 popNode: function(node) {
508 // Update the parent's collection of children
511 for (var i=0, len=p.children.length;i<len;++i) {
512 if (p.children[i] != node) {
513 a[a.length] = p.children[i];
519 // reset the childrenRendered flag for the parent
520 p.childrenRendered = false;
522 // Update the sibling relationship
523 if (node.previousSibling) {
524 node.previousSibling.nextSibling = node.nextSibling;
527 if (node.nextSibling) {
528 node.nextSibling.previousSibling = node.previousSibling;
532 node.previousSibling = null;
533 node.nextSibling = null;
536 // Update the tree's node collection
537 delete this._nodes[node.index];
542 * TreeView instance toString
544 * @return {string} string representation of the tree
546 toString: function() {
547 return "TreeView " + this.id;
551 * Generates an unique id for an element if it doesn't yet have one
555 generateId: function(el) {
559 id = "yui-tv-auto-id-" + YAHOO.widget.TreeView.counter;
560 ++YAHOO.widget.TreeView.counter;
567 * Abstract method that is executed when a node is expanded
569 * @param node {Node} the node that was expanded
570 * @deprecated use treeobj.subscribe("expand") instead
572 onExpand: function(node) { },
575 * Abstract method that is executed when a node is collapsed.
577 * @param node {Node} the node that was collapsed.
578 * @deprecated use treeobj.subscribe("collapse") instead
580 onCollapse: function(node) { }
584 YAHOO.augment(YAHOO.widget.TreeView, YAHOO.util.EventProvider);
587 * Running count of all nodes created in all trees. This is
588 * used to provide unique identifies for all nodes. Deleting
589 * nodes does not change the nodeCount.
590 * @property YAHOO.widget.TreeView.nodeCount
594 YAHOO.widget.TreeView.nodeCount = 0;
597 * Global cache of tree instances
598 * @property YAHOO.widget.TreeView.trees
603 YAHOO.widget.TreeView.trees = [];
606 * Counter for generating a new unique element id
607 * @property YAHOO.widget.TreeView.counter
611 YAHOO.widget.TreeView.counter = 0;
614 * Global method for getting a tree by its id. Used in the generated
616 * @method YAHOO.widget.TreeView.getTree
617 * @param treeId {String} the id of the tree instance
618 * @return {TreeView} the tree instance requested, null if not found.
621 YAHOO.widget.TreeView.getTree = function(treeId) {
622 var t = YAHOO.widget.TreeView.trees[treeId];
623 return (t) ? t : null;
628 * Global method for getting a node by its id. Used in the generated
630 * @method YAHOO.widget.TreeView.getNode
631 * @param treeId {String} the id of the tree instance
632 * @param nodeIndex {String} the index of the node to return
633 * @return {Node} the node instance requested, null if not found
636 YAHOO.widget.TreeView.getNode = function(treeId, nodeIndex) {
637 var t = YAHOO.widget.TreeView.getTree(treeId);
638 return (t) ? t.getNodeByIndex(nodeIndex) : null;
643 * @method YAHOO.widget.TreeView.addHandler
644 * @param el the elment to bind the handler to
645 * @param {string} sType the type of event handler
646 * @param {function} fn the callback to invoke
649 YAHOO.widget.TreeView.addHandler = function (el, sType, fn) {
650 if (el.addEventListener) {
651 el.addEventListener(sType, fn, false);
652 } else if (el.attachEvent) {
653 el.attachEvent("on" + sType, fn);
659 * @method YAHOO.widget.TreeView.removeHandler
660 * @param el the elment to bind the handler to
661 * @param {string} sType the type of event handler
662 * @param {function} fn the callback to invoke
666 YAHOO.widget.TreeView.removeHandler = function (el, sType, fn) {
667 if (el.removeEventListener) {
668 el.removeEventListener(sType, fn, false);
669 } else if (el.detachEvent) {
670 el.detachEvent("on" + sType, fn);
675 * Attempts to preload the images defined in the styles used to draw the tree by
676 * rendering off-screen elements that use the styles.
677 * @method YAHOO.widget.TreeView.preload
678 * @param {string} prefix the prefix to use to generate the names of the
679 * images to preload, default is ygtv
682 YAHOO.widget.TreeView.preload = function(e, prefix) {
683 prefix = prefix || "ygtv";
685 YAHOO.log("Preloading images: " + prefix, "info", "TreeView");
687 var styles = ["tn","tm","tmh","tp","tph","ln","lm","lmh","lp","lph","loading"];
688 // var styles = ["tp"];
692 // save the first one for the outer container
693 for (var i=1; i < styles.length; i=i+1) {
694 sb[sb.length] = '<span class="' + prefix + styles[i] + '"> </span>';
697 var f = document.createElement("div");
699 s.className = prefix + styles[0];
700 s.position = "absolute";
705 f.innerHTML = sb.join("");
707 document.body.appendChild(f);
709 YAHOO.widget.TreeView.removeHandler(window,
710 "load", YAHOO.widget.TreeView.preload);
714 YAHOO.widget.TreeView.addHandler(window,
715 "load", YAHOO.widget.TreeView.preload);
718 * The base class for all tree nodes. The node's presentation and behavior in
719 * response to mouse events is handled in Node subclasses.
720 * @namespace YAHOO.widget
722 * @uses YAHOO.util.EventProvider
723 * @param oData {object} a string or object containing the data that will
724 * be used to render this node
725 * @param oParent {Node} this node's parent node
726 * @param expanded {boolean} the initial expanded/collapsed state
729 YAHOO.widget.Node = function(oData, oParent, expanded) {
730 if (oData) { this.init(oData, oParent, expanded); }
733 YAHOO.widget.Node.prototype = {
736 * The index for this instance obtained from global counter in YAHOO.widget.TreeView.
743 * This node's child node collection.
750 * Tree instance this node is part of
757 * The data linked to this node. This can be any object or primitive
758 * value, and the data can be used in getNodeHtml().
772 * The depth of this node. We start at -1 for the root node.
779 * The href for the node's label. If one is not specified, the href will
780 * be set so that it toggles the node.
787 * The label href target, defaults to current window
794 * The node's expanded/collapsed state
801 * Can multiple children be expanded at once?
802 * @property multiExpand
808 * Should we render children for a collapsed node? It is possible that the
809 * implementer will want to render the hidden data... @todo verify that we
810 * need this, and implement it if we do.
811 * @property renderHidden
817 * This flag is set to true when the html is generated for this node's
818 * children, and set to false when new children are added.
819 * @property childrenRendered
822 childrenRendered: false,
825 * Dynamically loaded nodes only fetch the data the first time they are
826 * expanded. This flag is set to true once the data has been fetched.
827 * @property dynamicLoadComplete
830 dynamicLoadComplete: false,
833 * This node's previous sibling
834 * @property previousSibling
837 previousSibling: null,
840 * This node's next sibling
841 * @property nextSibling
847 * We can set the node up to call an external method to get the child
856 * Function to execute when we need to get this node's child data.
857 * @property dataLoader
863 * This is true for dynamically loading nodes while waiting for the
864 * callback to return.
865 * @property isLoading
871 * The toggle/branch icon will not show if this is set to false. This
872 * could be useful if the implementer wants to have the child contain
873 * extra info about the parent, rather than an actual node.
880 * Used to configure what happens when a dynamic load node is expanded
881 * and we discover that it does not have children. By default, it is
882 * treated as if it still could have children (plus/minus icon). Set
883 * iconMode to have it display like a leaf node instead.
890 * Specifies whether or not the content area of the node should be allowed
906 spacerPath: "http://us.i1.yimg.com/us.yimg.com/i/space.gif",
907 expandedText: "Expanded",
908 collapsedText: "Collapsed",
909 loadingText: "Loading",
913 * Initializes this node, gets some of the properties from the parent
915 * @param oData {object} a string or object containing the data that will
916 * be used to render this node
917 * @param oParent {Node} this node's parent node
918 * @param expanded {boolean} the initial expanded/collapsed state
920 init: function(oData, oParent, expanded) {
924 this.index = YAHOO.widget.TreeView.nodeCount;
925 ++YAHOO.widget.TreeView.nodeCount;
926 this.expanded = expanded;
927 this.logger = new YAHOO.widget.LogWriter(this.toString());
930 * The parentChange event is fired when a parent element is applied
931 * to the node. This is useful if you need to apply tree-level
932 * properties to a tree that need to happen if a node is moved from
933 * one tree to another.
935 * @event parentChange
938 this.createEvent("parentChange", this);
940 // oParent should never be null except when we create the root node.
942 oParent.appendChild(this);
947 * Certain properties for the node cannot be set until the parent
948 * is known. This is called after the node is inserted into a tree.
949 * the parent is also applied to this node's children in order to
950 * make it possible to move a branch from one tree to another.
951 * @method applyParent
952 * @param {Node} parentNode this node's parent node
953 * @return {boolean} true if the application was successful
955 applyParent: function(parentNode) {
960 this.tree = parentNode.tree;
961 this.parent = parentNode;
962 this.depth = parentNode.depth + 1;
965 this.href = "javascript:" + this.getToggleLink();
968 // @todo why was this put here. This causes new nodes added at the
969 // root level to lose the menu behavior.
970 // if (! this.multiExpand) {
971 // this.multiExpand = parentNode.multiExpand;
974 this.tree.regNode(this);
975 parentNode.childrenRendered = false;
977 // cascade update existing children
978 for (var i=0, len=this.children.length;i<len;++i) {
979 this.children[i].applyParent(this);
982 this.fireEvent("parentChange");
988 * Appends a node to the child collection.
989 * @method appendChild
990 * @param childNode {Node} the new node
991 * @return {Node} the child node
994 appendChild: function(childNode) {
995 if (this.hasChildren()) {
996 var sib = this.children[this.children.length - 1];
997 sib.nextSibling = childNode;
998 childNode.previousSibling = sib;
1000 this.children[this.children.length] = childNode;
1001 childNode.applyParent(this);
1003 // part of the IE display issue workaround. If child nodes
1004 // are added after the initial render, and the node was
1005 // instantiated with expanded = true, we need to show the
1006 // children div now that the node has a child.
1007 if (this.childrenRendered && this.expanded) {
1008 this.getChildrenEl().style.display = "";
1015 * Appends this node to the supplied node's child collection
1017 * @param parentNode {Node} the node to append to.
1018 * @return {Node} The appended node
1020 appendTo: function(parentNode) {
1021 return parentNode.appendChild(this);
1025 * Inserts this node before this supplied node
1026 * @method insertBefore
1027 * @param node {Node} the node to insert this node before
1028 * @return {Node} the inserted node
1030 insertBefore: function(node) {
1031 this.logger.log("insertBefore: " + node);
1032 var p = node.parent;
1036 this.tree.popNode(this);
1039 var refIndex = node.isChildOf(p);
1040 //this.logger.log(refIndex);
1041 p.children.splice(refIndex, 0, this);
1042 if (node.previousSibling) {
1043 node.previousSibling.nextSibling = this;
1045 this.previousSibling = node.previousSibling;
1046 this.nextSibling = node;
1047 node.previousSibling = this;
1049 this.applyParent(p);
1056 * Inserts this node after the supplied node
1057 * @method insertAfter
1058 * @param node {Node} the node to insert after
1059 * @return {Node} the inserted node
1061 insertAfter: function(node) {
1062 this.logger.log("insertAfter: " + node);
1063 var p = node.parent;
1067 this.tree.popNode(this);
1070 var refIndex = node.isChildOf(p);
1071 this.logger.log(refIndex);
1073 if (!node.nextSibling) {
1074 this.nextSibling = null;
1075 return this.appendTo(p);
1078 p.children.splice(refIndex + 1, 0, this);
1080 node.nextSibling.previousSibling = this;
1081 this.previousSibling = node;
1082 this.nextSibling = node.nextSibling;
1083 node.nextSibling = this;
1085 this.applyParent(p);
1092 * Returns true if the Node is a child of supplied Node
1094 * @param parentNode {Node} the Node to check
1095 * @return {boolean} The node index if this Node is a child of
1096 * supplied Node, else -1.
1099 isChildOf: function(parentNode) {
1100 if (parentNode && parentNode.children) {
1101 for (var i=0, len=parentNode.children.length; i<len ; ++i) {
1102 if (parentNode.children[i] === this) {
1112 * Returns a node array of this node's siblings, null if none.
1113 * @method getSiblings
1116 getSiblings: function() {
1117 return this.parent.children;
1121 * Shows this node's children
1122 * @method showChildren
1124 showChildren: function() {
1125 if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
1126 if (this.hasChildren()) {
1127 this.getChildrenEl().style.display = "";
1133 * Hides this node's children
1134 * @method hideChildren
1136 hideChildren: function() {
1137 this.logger.log("hiding " + this.index);
1139 if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
1140 this.getChildrenEl().style.display = "none";
1145 * Returns the id for this node's container div
1147 * @return {string} the element id
1149 getElId: function() {
1150 return "ygtv" + this.index;
1154 * Returns the id for this node's children div
1155 * @method getChildrenElId
1156 * @return {string} the element id for this node's children div
1158 getChildrenElId: function() {
1159 return "ygtvc" + this.index;
1163 * Returns the id for this node's toggle element
1164 * @method getToggleElId
1165 * @return {string} the toggel element id
1167 getToggleElId: function() {
1168 return "ygtvt" + this.index;
1173 * Returns the id for this node's spacer image. The spacer is positioned
1174 * over the toggle and provides feedback for screen readers.
1175 * @method getSpacerId
1176 * @return {string} the id for the spacer image
1179 getSpacerId: function() {
1180 return "ygtvspacer" + this.index;
1185 * Returns this node's container html element
1187 * @return {HTMLElement} the container html element
1190 return document.getElementById(this.getElId());
1194 * Returns the div that was generated for this node's children
1195 * @method getChildrenEl
1196 * @return {HTMLElement} this node's children div
1198 getChildrenEl: function() {
1199 return document.getElementById(this.getChildrenElId());
1203 * Returns the element that is being used for this node's toggle.
1204 * @method getToggleEl
1205 * @return {HTMLElement} this node's toggle html element
1207 getToggleEl: function() {
1208 return document.getElementById(this.getToggleElId());
1212 * Returns the element that is being used for this node's spacer.
1214 * @return {HTMLElement} this node's spacer html element
1217 getSpacer: function() {
1218 return document.getElementById( this.getSpacerId() ) || {};
1223 getStateText: function() {
1224 if (this.isLoading) {
1225 return this.loadingText;
1226 } else if (this.hasChildren(true)) {
1227 if (this.expanded) {
1228 return this.expandedText;
1230 return this.collapsedText;
1239 * Generates the link that will invoke this node's toggle method
1240 * @method getToggleLink
1241 * @return {string} the javascript url for toggling this node
1243 getToggleLink: function() {
1244 return "YAHOO.widget.TreeView.getNode(\'" + this.tree.id + "\'," +
1245 this.index + ").toggle()";
1249 * Hides this nodes children (creating them if necessary), changes the
1253 collapse: function() {
1254 // Only collapse if currently expanded
1255 if (!this.expanded) { return; }
1257 // fire the collapse event handler
1258 var ret = this.tree.onCollapse(this);
1260 if (false === ret) {
1261 this.logger.log("Collapse was stopped by the abstract onCollapse");
1265 ret = this.tree.fireEvent("collapse", this);
1267 if (false === ret) {
1268 this.logger.log("Collapse was stopped by a custom event handler");
1273 if (!this.getEl()) {
1274 this.expanded = false;
1276 // hide the child div
1277 this.hideChildren();
1278 this.expanded = false;
1283 // this.getSpacer().title = this.getStateText();
1285 ret = this.tree.fireEvent("collapseComplete", this);
1290 * Shows this nodes children (creating them if necessary), changes the
1291 * toggle style, and collapses its siblings if multiExpand is not set.
1294 expand: function() {
1295 // Only expand if currently collapsed.
1296 if (this.expanded) { return; }
1298 // fire the expand event handler
1299 var ret = this.tree.onExpand(this);
1301 if (false === ret) {
1302 this.logger.log("Expand was stopped by the abstract onExpand");
1306 ret = this.tree.fireEvent("expand", this);
1308 if (false === ret) {
1309 this.logger.log("Expand was stopped by the custom event handler");
1313 if (!this.getEl()) {
1314 this.expanded = true;
1318 if (! this.childrenRendered) {
1319 this.logger.log("children not rendered yet");
1320 this.getChildrenEl().innerHTML = this.renderChildren();
1322 this.logger.log("CHILDREN RENDERED");
1325 this.expanded = true;
1329 // this.getSpacer().title = this.getStateText();
1331 // We do an extra check for children here because the lazy
1332 // load feature can expose nodes that have no children.
1334 // if (!this.hasChildren()) {
1335 if (this.isLoading) {
1336 this.expanded = false;
1340 if (! this.multiExpand) {
1341 var sibs = this.getSiblings();
1342 for (var i=0; i<sibs.length; ++i) {
1343 if (sibs[i] != this && sibs[i].expanded) {
1349 this.showChildren();
1351 ret = this.tree.fireEvent("expandComplete", this);
1354 updateIcon: function() {
1356 var el = this.getToggleEl();
1358 el.className = this.getStyle();
1364 * Returns the css style name for the toggle
1366 * @return {string} the css class for this node's toggle
1368 getStyle: function() {
1369 // this.logger.log("No children, " + " isDyanmic: " + this.isDynamic() + " expanded: " + this.expanded);
1370 if (this.isLoading) {
1371 this.logger.log("returning the loading icon");
1372 return "ygtvloading";
1374 // location top or bottom, middle nodes also get the top style
1375 var loc = (this.nextSibling) ? "t" : "l";
1377 // type p=plus(expand), m=minus(collapase), n=none(no children)
1379 if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) {
1380 // if (this.hasChildren(true)) {
1381 type = (this.expanded) ? "m" : "p";
1384 // this.logger.log("ygtv" + loc + type);
1385 return "ygtv" + loc + type;
1390 * Returns the hover style for the icon
1391 * @return {string} the css class hover state
1392 * @method getHoverStyle
1394 getHoverStyle: function() {
1395 var s = this.getStyle();
1396 if (this.hasChildren(true) && !this.isLoading) {
1403 * Recursively expands all of this node's children.
1406 expandAll: function() {
1407 for (var i=0;i<this.children.length;++i) {
1408 var c = this.children[i];
1409 if (c.isDynamic()) {
1410 alert("Not supported (lazy load + expand all)");
1412 } else if (! c.multiExpand) {
1413 alert("Not supported (no multi-expand + expand all)");
1423 * Recursively collapses all of this node's children.
1424 * @method collapseAll
1426 collapseAll: function() {
1427 for (var i=0;i<this.children.length;++i) {
1428 this.children[i].collapse();
1429 this.children[i].collapseAll();
1434 * Configures this node for dynamically obtaining the child data
1435 * when the node is first expanded. Calling it without the callback
1436 * will turn off dynamic load for the node.
1437 * @method setDynamicLoad
1438 * @param fmDataLoader {function} the function that will be used to get the data.
1439 * @param iconMode {int} configures the icon that is displayed when a dynamic
1440 * load node is expanded the first time without children. By default, the
1441 * "collapse" icon will be used. If set to 1, the leaf node icon will be
1444 setDynamicLoad: function(fnDataLoader, iconMode) {
1446 this.dataLoader = fnDataLoader;
1447 this._dynLoad = true;
1449 this.dataLoader = null;
1450 this._dynLoad = false;
1454 this.iconMode = iconMode;
1459 * Evaluates if this node is the root node of the tree
1461 * @return {boolean} true if this is the root node
1463 isRoot: function() {
1464 return (this == this.tree.root);
1468 * Evaluates if this node's children should be loaded dynamically. Looks for
1469 * the property both in this instance and the root node. If the tree is
1470 * defined to load all children dynamically, the data callback function is
1471 * defined in the root node
1473 * @return {boolean} true if this node's children are to be loaded dynamically
1475 isDynamic: function() {
1476 var lazy = (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
1477 // this.logger.log("isDynamic: " + lazy);
1482 * Returns the current icon mode. This refers to the way childless dynamic
1483 * load nodes appear.
1484 * @method getIconMode
1485 * @return {int} 0 for collapse style, 1 for leaf node style
1487 getIconMode: function() {
1488 return (this.iconMode || this.tree.root.iconMode);
1492 * Checks if this node has children. If this node is lazy-loading and the
1493 * children have not been rendered, we do not know whether or not there
1494 * are actual children. In most cases, we need to assume that there are
1495 * children (for instance, the toggle needs to show the expandable
1496 * presentation state). In other times we want to know if there are rendered
1497 * children. For the latter, "checkForLazyLoad" should be false.
1498 * @method hasChildren
1499 * @param checkForLazyLoad {boolean} should we check for unloaded children?
1500 * @return {boolean} true if this has children or if it might and we are
1501 * checking for this condition.
1503 hasChildren: function(checkForLazyLoad) {
1504 return ( this.children.length > 0 ||
1505 (checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete) );
1509 * Expands if node is collapsed, collapses otherwise.
1512 toggle: function() {
1513 if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
1514 if (this.expanded) { this.collapse(); } else { this.expand(); }
1519 * Returns the markup for this node and its children.
1521 * @return {string} the markup for this node and its expanded children.
1523 getHtml: function() {
1525 this.childrenRendered = false;
1528 sb[sb.length] = '<div class="ygtvitem" id="' + this.getElId() + '">';
1529 sb[sb.length] = this.getNodeHtml();
1530 sb[sb.length] = this.getChildrenHtml();
1531 sb[sb.length] = '</div>';
1536 * Called when first rendering the tree. We always build the div that will
1537 * contain this nodes children, but we don't render the children themselves
1538 * unless this node is expanded.
1539 * @method getChildrenHtml
1540 * @return {string} the children container div html and any expanded children
1543 getChildrenHtml: function() {
1547 sb[sb.length] = '<div class="ygtvchildren"';
1548 sb[sb.length] = ' id="' + this.getChildrenElId() + '"';
1550 // This is a workaround for an IE rendering issue, the child div has layout
1551 // in IE, creating extra space if a leaf node is created with the expanded
1552 // property set to true.
1553 if (!this.expanded || !this.hasChildren()) {
1554 sb[sb.length] = ' style="display:none;"';
1556 sb[sb.length] = '>';
1558 // Don't render the actual child node HTML unless this node is expanded.
1559 if ( (this.hasChildren(true) && this.expanded) ||
1560 (this.renderHidden && !this.isDynamic()) ) {
1561 sb[sb.length] = this.renderChildren();
1564 sb[sb.length] = '</div>';
1570 * Generates the markup for the child nodes. This is not done until the node
1572 * @method renderChildren
1573 * @return {string} the html for this node's children
1576 renderChildren: function() {
1578 this.logger.log("rendering children for " + this.index);
1582 if (this.isDynamic() && !this.dynamicLoadComplete) {
1583 this.isLoading = true;
1584 this.tree.locked = true;
1586 if (this.dataLoader) {
1587 this.logger.log("Using dynamic loader defined for this node");
1591 node.dataLoader(node,
1593 node.loadComplete();
1597 } else if (this.tree.root.dataLoader) {
1598 this.logger.log("Using the tree-level dynamic loader");
1602 node.tree.root.dataLoader(node,
1604 node.loadComplete();
1609 this.logger.log("no loader found");
1610 return "Error: data loader not found or not specified.";
1616 return this.completeRender();
1621 * Called when we know we have all the child data.
1622 * @method completeRender
1623 * @return {string} children html
1625 completeRender: function() {
1626 this.logger.log("completeRender: " + this.index + ", # of children: " + this.children.length);
1629 for (var i=0; i < this.children.length; ++i) {
1630 // this.children[i].childrenRendered = false;
1631 sb[sb.length] = this.children[i].getHtml();
1634 this.childrenRendered = true;
1640 * Load complete is the callback function we pass to the data provider
1641 * in dynamic load situations.
1642 * @method loadComplete
1644 loadComplete: function() {
1645 this.logger.log("loadComplete: " + this.index);
1646 this.getChildrenEl().innerHTML = this.completeRender();
1647 this.dynamicLoadComplete = true;
1648 this.isLoading = false;
1650 this.tree.locked = false;
1654 * Returns this node's ancestor at the specified depth.
1655 * @method getAncestor
1656 * @param {int} depth the depth of the ancestor.
1657 * @return {Node} the ancestor
1659 getAncestor: function(depth) {
1660 if (depth >= this.depth || depth < 0) {
1661 this.logger.log("illegal getAncestor depth: " + depth);
1665 var p = this.parent;
1667 while (p.depth > depth) {
1675 * Returns the css class for the spacer at the specified depth for
1676 * this node. If this node's ancestor at the specified depth
1677 * has a next sibling the presentation is different than if it
1678 * does not have a next sibling
1679 * @method getDepthStyle
1680 * @param {int} depth the depth of the ancestor.
1681 * @return {string} the css class for the spacer
1683 getDepthStyle: function(depth) {
1684 return (this.getAncestor(depth).nextSibling) ?
1685 "ygtvdepthcell" : "ygtvblankdepthcell";
1689 * Get the markup for the node. This is designed to be overrided so that we can
1690 * support different types of nodes.
1691 * @method getNodeHtml
1692 * @return {string} The HTML that will render this node.
1694 getNodeHtml: function() {
1695 this.logger.log("Generating html");
1700 * Regenerates the html for this node and its children. To be used when the
1701 * node is expanded and new children have been added.
1704 refresh: function() {
1705 // this.loadComplete();
1706 this.getChildrenEl().innerHTML = this.completeRender();
1709 var el = this.getToggleEl();
1711 el.className = this.getStyle();
1719 * @return {string} string representation of the node
1721 toString: function() {
1722 return "Node (" + this.index + ")";
1727 YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
1730 * The default node presentation. The first parameter should be
1731 * either a string that will be used as the node's label, or an object
1732 * that has a string propery called label. By default, the clicking the
1733 * label will toggle the expanded/collapsed state of the node. By
1734 * changing the href property of the instance, this behavior can be
1735 * changed so that the label will go to the specified href.
1736 * @namespace YAHOO.widget
1738 * @extends YAHOO.widget.Node
1740 * @param oData {object} a string or object containing the data that will
1741 * be used to render this node
1742 * @param oParent {YAHOO.widget.Node} this node's parent node
1743 * @param expanded {boolean} the initial expanded/collapsed state
1745 YAHOO.widget.TextNode = function(oData, oParent, expanded) {
1748 this.init(oData, oParent, expanded);
1749 this.setUpLabel(oData);
1752 this.logger = new YAHOO.widget.LogWriter(this.toString());
1755 YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
1758 * The CSS class for the label href. Defaults to ygtvlabel, but can be
1759 * overridden to provide a custom presentation for a specific node.
1760 * @property labelStyle
1763 labelStyle: "ygtvlabel",
1766 * The derived element id of the label for this node
1767 * @property labelElId
1773 * The text for the label. It is assumed that the oData parameter will
1774 * either be a string that will be used as the label, or an object that
1775 * has a property called "label" that we will use.
1781 textNodeParentChange: function() {
1784 * Custom event that is fired when the text node label is clicked. The
1785 * custom event is defined on the tree instance, so there is a single
1786 * event that handles all nodes in the tree. The node clicked is
1787 * provided as an argument
1790 * @for YAHOO.widget.TreeView
1791 * @param {YAHOO.widget.Node} node the node clicked
1793 if (this.tree && !this.tree.hasEvent("labelClick")) {
1794 this.tree.createEvent("labelClick", this.tree);
1800 * Sets up the node label
1801 * @method setUpLabel
1802 * @param oData string containing the label, or an object with a label property
1804 setUpLabel: function(oData) {
1806 // set up the custom event on the tree
1807 this.textNodeParentChange();
1808 this.subscribe("parentChange", this.textNodeParentChange);
1810 if (typeof oData == "string") {
1811 oData = { label: oData };
1813 this.label = oData.label;
1814 this.data.label = oData.label;
1818 this.href = oData.href;
1823 this.target = oData.target;
1827 this.labelStyle = oData.style;
1830 this.labelElId = "ygtvlabelel" + this.index;
1834 * Returns the label element
1835 * @for YAHOO.widget.TextNode
1836 * @method getLabelEl
1837 * @return {object} the element
1839 getLabelEl: function() {
1840 return document.getElementById(this.labelElId);
1843 // overrides YAHOO.widget.Node
1844 getNodeHtml: function() {
1845 this.logger.log("Generating html");
1848 sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
1849 sb[sb.length] = '<tr>';
1851 for (var i=0;i<this.depth;++i) {
1852 //sb[sb.length] = '<td><div class="' + this.getDepthStyle(i) + '"> </div></td>';
1853 //sb[sb.length] = '<td><div class="' + this.getDepthStyle(i) + '"></div></td>';
1854 sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
1857 var getNode = 'YAHOO.widget.TreeView.getNode(\'' +
1858 this.tree.id + '\',' + this.index + ')';
1860 sb[sb.length] = '<td';
1861 // sb[sb.length] = ' onselectstart="return false"';
1862 sb[sb.length] = ' id="' + this.getToggleElId() + '"';
1863 sb[sb.length] = ' class="' + this.getStyle() + '"';
1864 if (this.hasChildren(true)) {
1865 sb[sb.length] = ' onmouseover="this.className=';
1866 sb[sb.length] = getNode + '.getHoverStyle()"';
1867 sb[sb.length] = ' onmouseout="this.className=';
1868 sb[sb.length] = getNode + '.getStyle()"';
1870 sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '">';
1872 sb[sb.length] = '<div class="ygtvspacer">';
1875 sb[sb.length] = '<img id="' + this.getSpacerId() + '"';
1876 sb[sb.length] = ' alt=""';
1877 sb[sb.length] = ' tabindex=0';
1878 sb[sb.length] = ' src="' + this.spacerPath + '"';
1879 sb[sb.length] = ' title="' + this.getStateText() + '"';
1880 sb[sb.length] = ' class="ygtvspacer"';
1881 // sb[sb.length] = ' onkeypress="return ' + getNode + '".onKeyPress()"';
1882 sb[sb.length] = ' />';
1885 //sb[sb.length] = ' ';
1887 sb[sb.length] = '</div>';
1888 sb[sb.length] = '</td>';
1889 sb[sb.length] = '<td ';
1890 sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
1891 sb[sb.length] = ' >';
1892 sb[sb.length] = '<a';
1893 sb[sb.length] = ' id="' + this.labelElId + '"';
1894 sb[sb.length] = ' class="' + this.labelStyle + '"';
1895 sb[sb.length] = ' href="' + this.href + '"';
1896 sb[sb.length] = ' target="' + this.target + '"';
1897 sb[sb.length] = ' onclick="return ' + getNode + '.onLabelClick(' + getNode +')"';
1898 if (this.hasChildren(true)) {
1899 sb[sb.length] = ' onmouseover="document.getElementById(\'';
1900 sb[sb.length] = this.getToggleElId() + '\').className=';
1901 sb[sb.length] = getNode + '.getHoverStyle()"';
1902 sb[sb.length] = ' onmouseout="document.getElementById(\'';
1903 sb[sb.length] = this.getToggleElId() + '\').className=';
1904 sb[sb.length] = getNode + '.getStyle()"';
1906 sb[sb.length] = ' >';
1907 sb[sb.length] = this.label;
1908 sb[sb.length] = '</a>';
1909 sb[sb.length] = '</td>';
1910 sb[sb.length] = '</tr>';
1911 sb[sb.length] = '</table>';
1918 * Executed when the label is clicked. Fires the labelClick custom event.
1919 * @method onLabelClick
1920 * @param me {Node} this node
1921 * @scope the anchor tag clicked
1922 * @return false to cancel the anchor click
1924 onLabelClick: function(me) {
1925 me.logger.log("onLabelClick " + me.label);
1926 return me.tree.fireEvent("labelClick", me);
1930 toString: function() {
1931 return "TextNode (" + this.index + ") " + this.label;
1936 * A custom YAHOO.widget.Node that handles the unique nature of
1937 * the virtual, presentationless root node.
1938 * @namespace YAHOO.widget
1940 * @extends YAHOO.widget.Node
1941 * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
1944 YAHOO.widget.RootNode = function(oTree) {
1945 // Initialize the node with null params. The root node is a
1946 // special case where the node has no presentation. So we have
1947 // to alter the standard properties a bit.
1948 this.init(null, null, true);
1951 * For the root node, we get the tree reference from as a param
1952 * to the constructor instead of from the parent element.
1957 YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
1959 // overrides YAHOO.widget.Node
1960 getNodeHtml: function() {
1964 toString: function() {
1968 loadComplete: function() {
1972 collapse: function() {},
1973 expand: function() {}
1977 * This implementation takes either a string or object for the
1978 * oData argument. If is it a string, we will use it for the display
1979 * of this node (and it can contain any html code). If the parameter
1980 * is an object, we look for a parameter called "html" that will be
1981 * used for this node's display.
1982 * @namespace YAHOO.widget
1984 * @extends YAHOO.widget.Node
1986 * @param oData {object} a string or object containing the data that will
1987 * be used to render this node
1988 * @param oParent {YAHOO.widget.Node} this node's parent node
1989 * @param expanded {boolean} the initial expanded/collapsed state
1990 * @param hasIcon {boolean} specifies whether or not leaf nodes should
1993 YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
1995 this.init(oData, oParent, expanded);
1996 this.initContent(oData, hasIcon);
2000 YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, {
2003 * The CSS class for the html content container. Defaults to ygtvhtml, but
2004 * can be overridden to provide a custom presentation for a specific node.
2005 * @property contentStyle
2008 contentStyle: "ygtvhtml",
2011 * The generated id that will contain the data passed in by the implementer.
2012 * @property contentElId
2018 * The HTML content to use for this node's display
2025 * Sets up the node label
2026 * @property initContent
2027 * @param {object} An html string or object containing an html property
2028 * @param {boolean} hasIcon determines if the node will be rendered with an
2031 initContent: function(oData, hasIcon) {
2032 if (typeof oData == "string") {
2033 oData = { html: oData };
2036 this.html = oData.html;
2037 this.contentElId = "ygtvcontentel" + this.index;
2038 this.hasIcon = hasIcon;
2040 this.logger = new YAHOO.widget.LogWriter(this.toString());
2044 * Returns the outer html element for this node's content
2045 * @method getContentEl
2046 * @return {HTMLElement} the element
2048 getContentEl: function() {
2049 return document.getElementById(this.contentElId);
2052 // overrides YAHOO.widget.Node
2053 getNodeHtml: function() {
2054 this.logger.log("Generating html");
2057 sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
2058 sb[sb.length] = '<tr>';
2060 for (var i=0;i<this.depth;++i) {
2061 //sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"> </td>';
2062 sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
2066 sb[sb.length] = '<td';
2067 sb[sb.length] = ' id="' + this.getToggleElId() + '"';
2068 sb[sb.length] = ' class="' + this.getStyle() + '"';
2069 sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '"';
2070 if (this.hasChildren(true)) {
2071 sb[sb.length] = ' onmouseover="this.className=';
2072 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
2073 sb[sb.length] = this.tree.id + '\',' + this.index + ').getHoverStyle()"';
2074 sb[sb.length] = ' onmouseout="this.className=';
2075 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
2076 sb[sb.length] = this.tree.id + '\',' + this.index + ').getStyle()"';
2078 //sb[sb.length] = '> </td>';
2079 sb[sb.length] = '><div class="ygtvspacer"></div></td>';
2082 sb[sb.length] = '<td';
2083 sb[sb.length] = ' id="' + this.contentElId + '"';
2084 sb[sb.length] = ' class="' + this.contentStyle + '"';
2085 sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
2086 sb[sb.length] = ' >';
2087 sb[sb.length] = this.html;
2088 sb[sb.length] = '</td>';
2089 sb[sb.length] = '</tr>';
2090 sb[sb.length] = '</table>';
2095 toString: function() {
2096 return "HTMLNode (" + this.index + ")";
2101 * A menu-specific implementation that differs from TextNode in that only
2102 * one sibling can be expanded at a time.
2103 * @namespace YAHOO.widget
2105 * @extends YAHOO.widget.TextNode
2106 * @param oData {object} a string or object containing the data that will
2107 * be used to render this node
2108 * @param oParent {YAHOO.widget.Node} this node's parent node
2109 * @param expanded {boolean} the initial expanded/collapsed state
2112 YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
2114 this.init(oData, oParent, expanded);
2115 this.setUpLabel(oData);
2119 * Menus usually allow only one branch to be open at a time.
2121 this.multiExpand = false;
2123 this.logger = new YAHOO.widget.LogWriter(this.toString());
2127 YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
2129 toString: function() {
2130 return "MenuNode (" + this.index + ") " + this.label;
2135 * A static factory class for tree view expand/collapse animations
2139 YAHOO.widget.TVAnim = function() {
2142 * Constant for the fade in animation
2147 FADE_IN: "TVFadeIn",
2150 * Constant for the fade out animation
2151 * @property FADE_OUT
2155 FADE_OUT: "TVFadeOut",
2158 * Returns a ygAnim instance of the given type
2160 * @param type {string} the type of animation
2161 * @param el {HTMLElement} the element to element (probably the children div)
2162 * @param callback {function} function to invoke when the animation is done.
2163 * @return {YAHOO.util.Animation} the animation instance
2166 getAnim: function(type, el, callback) {
2167 if (YAHOO.widget[type]) {
2168 return new YAHOO.widget[type](el, callback);
2175 * Returns true if the specified animation class is available
2177 * @param type {string} the type of animation
2178 * @return {boolean} true if valid, false if not
2181 isValid: function(type) {
2182 return (YAHOO.widget[type]);
2188 * A 1/2 second fade-in animation.
2191 * @param el {HTMLElement} the element to animate
2192 * @param callback {function} function to invoke when the animation is finished
2194 YAHOO.widget.TVFadeIn = function(el, callback) {
2196 * The element to animate
2203 * the callback to invoke when the animation is complete
2204 * @property callback
2207 this.callback = callback;
2209 this.logger = new YAHOO.widget.LogWriter(this.toString());
2212 YAHOO.widget.TVFadeIn.prototype = {
2214 * Performs the animation
2217 animate: function() {
2220 var s = this.el.style;
2222 s.filter = "alpha(opacity=10)";
2226 var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur);
2227 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2232 * Clean up and invoke callback
2233 * @method onComplete
2235 onComplete: function() {
2242 * @return {string} the string representation of the instance
2244 toString: function() {
2250 * A 1/2 second fade out animation.
2253 * @param el {HTMLElement} the element to animate
2254 * @param callback {Function} function to invoke when the animation is finished
2256 YAHOO.widget.TVFadeOut = function(el, callback) {
2258 * The element to animate
2265 * the callback to invoke when the animation is complete
2266 * @property callback
2269 this.callback = callback;
2271 this.logger = new YAHOO.widget.LogWriter(this.toString());
2274 YAHOO.widget.TVFadeOut.prototype = {
2276 * Performs the animation
2279 animate: function() {
2282 var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
2283 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2288 * Clean up and invoke callback
2289 * @method onComplete
2291 onComplete: function() {
2292 var s = this.el.style;
2295 s.filter = "alpha(opacity=100)";
2302 * @return {string} the string representation of the instance
2304 toString: function() {
2309 YAHOO.register("treeview", YAHOO.widget.TreeView, {version: "2.3.0", build: "442"});