2 Copyright (c) 2006, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
9 * The treeview widget is a generic tree building tool.
11 * @title TreeView Widget
12 * @requires yahoo, event
14 * @namespace YAHOO.widget
18 * Contains the tree view state data and the root node.
21 * @uses YAHOO.util.EventProvider
23 * @param {string|HTMLElement} id The id of the element, or the element
24 * itself that the tree will be inserted into.
26 YAHOO.widget.TreeView = function(id) {
27 if (id) { this.init(id); }
30 YAHOO.widget.TreeView.prototype = {
33 * The id of tree container element
40 * The host element for this tree
47 * Flat collection of all nodes in this tree
55 * We lock the tree control while waiting for the dynamic loader to return
62 * The animation to use for expanding children, if any
63 * @property _expandAnim
70 * The animation to use for collapsing children, if any
71 * @property _collapseAnim
78 * The current number of animations that are executing
79 * @property _animCount
86 * The maximum number of animations to run at one time.
93 * Sets up the animation for expanding children
94 * @method setExpandAnim
95 * @param {string} type the type of animation (acceptable values defined
96 * in YAHOO.widget.TVAnim)
98 setExpandAnim: function(type) {
99 if (YAHOO.widget.TVAnim.isValid(type)) {
100 this._expandAnim = type;
105 * Sets up the animation for collapsing children
106 * @method setCollapseAnim
107 * @param {string} the type of animation (acceptable values defined in
108 * YAHOO.widget.TVAnim)
110 setCollapseAnim: function(type) {
111 if (YAHOO.widget.TVAnim.isValid(type)) {
112 this._collapseAnim = type;
117 * Perform the expand animation if configured, or just show the
118 * element if not configured or too many animations are in progress
119 * @method animateExpand
120 * @param el {HTMLElement} the element to animate
121 * @param node {YAHOO.util.Node} the node that was expanded
122 * @return {boolean} true if animation could be invoked, false otherwise
124 animateExpand: function(el, node) {
125 this.logger.log("animating expand");
127 if (this._expandAnim && this._animCount < this.maxAnim) {
128 // this.locked = true;
130 var a = YAHOO.widget.TVAnim.getAnim(this._expandAnim, el,
131 function() { tree.expandComplete(node); });
134 this.fireEvent("animStart", {
148 * Perform the collapse animation if configured, or just show the
149 * element if not configured or too many animations are in progress
150 * @method animateCollapse
151 * @param el {HTMLElement} the element to animate
152 * @param node {YAHOO.util.Node} the node that was expanded
153 * @return {boolean} true if animation could be invoked, false otherwise
155 animateCollapse: function(el, node) {
156 this.logger.log("animating collapse");
158 if (this._collapseAnim && this._animCount < this.maxAnim) {
159 // this.locked = true;
161 var a = YAHOO.widget.TVAnim.getAnim(this._collapseAnim, el,
162 function() { tree.collapseComplete(node); });
165 this.fireEvent("animStart", {
179 * Function executed when the expand animation completes
180 * @method expandComplete
182 expandComplete: function(node) {
183 this.logger.log("expand complete: " + this.id);
185 this.fireEvent("animComplete", {
189 // this.locked = false;
193 * Function executed when the collapse animation completes
194 * @method collapseComplete
196 collapseComplete: function(node) {
197 this.logger.log("collapse complete: " + this.id);
199 this.fireEvent("animComplete", {
203 // this.locked = false;
207 * Initializes the tree
209 * @parm {string|HTMLElement} id the id of the element that will hold the tree
216 if ("string" !== typeof id) {
218 this.id = this.generateId(id);
222 * When animation is enabled, this event fires when the animation
226 * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
227 * @parm {String} type the type of animation ("expand" or "collapse")
229 this.createEvent("animStart", this);
232 * When animation is enabled, this event fires when the animation
234 * @event animComplete
236 * @param {YAHOO.widget.Node} node the node that is expanding/collapsing
237 * @parm {String} type the type of animation ("expand" or "collapse")
239 this.createEvent("animComplete", this);
242 * Fires when a node is going to be collapsed. Return false to stop
246 * @param {YAHOO.widget.Node} node the node that is collapsing
248 this.createEvent("collapse", this);
251 * Fires after a node is successfully collapsed. This event will not fire
252 * if the "collapse" event was cancelled.
253 * @event collapseComplete
255 * @param {YAHOO.widget.Node} node the node that was collapsed
257 this.createEvent("collapseComplete", this);
260 * Fires when a node is going to be expanded. Return false to stop
264 * @param {YAHOO.widget.Node} node the node that is expanding
266 this.createEvent("expand", this);
269 * Fires after a node is successfully expanded. This event will not fire
270 * if the "expand" event was cancelled.
271 * @event expandComplete
273 * @param {YAHOO.widget.Node} node the node that was expanded
275 this.createEvent("expandComplete", this);
279 // store a global reference
280 YAHOO.widget.TreeView.trees[this.id] = this;
282 // Set up the root node
283 this.root = new YAHOO.widget.RootNode(this);
285 this.logger = new YAHOO.widget.LogWriter(this.toString());
287 this.logger.log("tree init: " + this.id);
289 //YAHOO.util.Event.onContentReady(this.id, this.handleAvailable, this, true);
290 YAHOO.util.Event.on(this.id, "click", this.handleClick, this, true);
294 //handleAvailable: function() {
295 //var Event = YAHOO.util.Event;
300 * Renders the tree boilerplate and visible nodes
304 var html = this.root.getHtml();
305 this.getEl().innerHTML = html;
306 this.firstDraw = false;
310 * Returns the tree's host element
312 * @return {HTMLElement} the host element
316 this._el = document.getElementById(this.id);
322 * Nodes register themselves with the tree instance when they are created.
324 * @param node {Node} the node to register
327 regNode: function(node) {
328 this._nodes[node.index] = node;
332 * Returns the root node of this tree
334 * @return {Node} the root node
336 getRoot: function() {
341 * Configures this tree to dynamically load all child data
342 * @method setDynamicLoad
343 * @param {function} fnDataLoader the function that will be called to get the data
344 * @param iconMode {int} configures the icon that is displayed when a dynamic
345 * load node is expanded the first time without children. By default, the
346 * "collapse" icon will be used. If set to 1, the leaf node icon will be
349 setDynamicLoad: function(fnDataLoader, iconMode) {
350 this.root.setDynamicLoad(fnDataLoader, iconMode);
354 * Expands all child nodes. Note: this conflicts with the "multiExpand"
355 * node property. If expand all is called in a tree with nodes that
356 * do not allow multiple siblings to be displayed, only the last sibling
360 expandAll: function() {
362 this.root.expandAll();
367 * Collapses all expanded child nodes in the entire tree.
368 * @method collapseAll
370 collapseAll: function() {
372 this.root.collapseAll();
377 * Returns a node in the tree that has the specified index (this index
378 * is created internally, so this function probably will only be used
379 * in html generated for a given node.)
380 * @method getNodeByIndex
381 * @param {int} nodeIndex the index of the node wanted
382 * @return {Node} the node with index=nodeIndex, null if no match
384 getNodeByIndex: function(nodeIndex) {
385 var n = this._nodes[nodeIndex];
386 return (n) ? n : null;
390 * Returns a node that has a matching property and value in the data
391 * object that was passed into its constructor.
392 * @method getNodeByProperty
393 * @param {object} property the property to search (usually a string)
394 * @param {object} value the value we want to find (usuall an int or string)
395 * @return {Node} the matching node, null if no match
397 getNodeByProperty: function(property, value) {
398 for (var i in this._nodes) {
399 var n = this._nodes[i];
400 if (n.data && value == n.data[property]) {
409 * Returns a collection of nodes that have a matching property
410 * and value in the data object that was passed into its constructor.
411 * @method getNodesByProperty
412 * @param {object} property the property to search (usually a string)
413 * @param {object} value the value we want to find (usuall an int or string)
414 * @return {Array} the matching collection of nodes, null if no match
416 getNodesByProperty: function(property, value) {
418 for (var i in this._nodes) {
419 var n = this._nodes[i];
420 if (n.data && value == n.data[property]) {
425 return (values.length) ? values : null;
429 * Removes the node and its children, and optionally refreshes the
430 * branch of the tree that was affected.
432 * @param {Node} The node to remove
433 * @param {boolean} autoRefresh automatically refreshes branch if true
434 * @return {boolean} False is there was a problem, true otherwise.
436 removeNode: function(node, autoRefresh) {
438 // Don't delete the root node
443 // Get the branch that we may need to refresh
449 // Delete the node and its children
450 this._deleteNode(node);
452 // Refresh the parent of the parent
453 if (autoRefresh && p && p.childrenRendered) {
461 * Deletes this nodes child collection, recursively. Also collapses
462 * the node, and resets the dynamic load flag. The primary use for
463 * this method is to purge a node and allow it to fetch its data
465 * @method removeChildren
466 * @param {Node} node the node to purge
468 removeChildren: function(node) {
469 this.logger.log("Removing children for " + node);
470 while (node.children.length) {
471 this._deleteNode(node.children[0]);
474 node.childrenRendered = false;
475 node.dynamicLoadComplete = false;
484 * Deletes the node and recurses children
485 * @method _deleteNode
488 _deleteNode: function(node) {
489 // Remove all the child nodes first
490 this.removeChildren(node);
492 // Remove the node from the tree
497 * Removes the node from the tree, preserving the child collection
498 * to make it possible to insert the branch into another part of the
499 * tree, or another tree.
501 * @param {Node} the node to remove
503 popNode: function(node) {
506 // Update the parent's collection of children
509 for (var i=0, len=p.children.length;i<len;++i) {
510 if (p.children[i] != node) {
511 a[a.length] = p.children[i];
517 // reset the childrenRendered flag for the parent
518 p.childrenRendered = false;
520 // Update the sibling relationship
521 if (node.previousSibling) {
522 node.previousSibling.nextSibling = node.nextSibling;
525 if (node.nextSibling) {
526 node.nextSibling.previousSibling = node.previousSibling;
530 node.previousSibling = null;
531 node.nextSibling = null;
534 // Update the tree's node collection
535 delete this._nodes[node.index];
540 * TreeView instance toString
542 * @return {string} string representation of the tree
544 toString: function() {
545 return "TreeView " + this.id;
549 * Generates an unique id for an element if it doesn't yet have one
553 generateId: function(el) {
557 id = "yui-tv-auto-id-" + YAHOO.widget.TreeView.counter;
558 ++YAHOO.widget.TreeView.counter;
565 * Abstract method that is executed when a node is expanded
567 * @param node {Node} the node that was expanded
568 * @deprecated use treeobj.subscribe("expand") instead
570 onExpand: function(node) { },
573 * Abstract method that is executed when a node is collapsed.
575 * @param node {Node} the node that was collapsed.
576 * @deprecated use treeobj.subscribe("collapse") instead
578 onCollapse: function(node) { }
582 YAHOO.augment(YAHOO.widget.TreeView, YAHOO.util.EventProvider);
585 * Count of all nodes in all trees
586 * @property YAHOO.widget.TreeView.nodeCount
590 YAHOO.widget.TreeView.nodeCount = 0;
593 * Global cache of tree instances
594 * @property YAHOO.widget.TreeView.trees
599 YAHOO.widget.TreeView.trees = [];
602 * Counter for generating a new unique element id
603 * @property YAHOO.widget.TreeView.counter
607 YAHOO.widget.TreeView.counter = 0;
610 * Global method for getting a tree by its id. Used in the generated
612 * @method YAHOO.widget.TreeView.getTree
613 * @param treeId {String} the id of the tree instance
614 * @return {TreeView} the tree instance requested, null if not found.
617 YAHOO.widget.TreeView.getTree = function(treeId) {
618 var t = YAHOO.widget.TreeView.trees[treeId];
619 return (t) ? t : null;
624 * Global method for getting a node by its id. Used in the generated
626 * @method YAHOO.widget.TreeView.getNode
627 * @param treeId {String} the id of the tree instance
628 * @param nodeIndex {String} the index of the node to return
629 * @return {Node} the node instance requested, null if not found
632 YAHOO.widget.TreeView.getNode = function(treeId, nodeIndex) {
633 var t = YAHOO.widget.TreeView.getTree(treeId);
634 return (t) ? t.getNodeByIndex(nodeIndex) : null;
639 * @method YAHOO.widget.TreeView.addHandler
640 * @param el the elment to bind the handler to
641 * @param {string} sType the type of event handler
642 * @param {function} fn the callback to invoke
645 YAHOO.widget.TreeView.addHandler = function (el, sType, fn) {
646 if (el.addEventListener) {
647 el.addEventListener(sType, fn, false);
648 } else if (el.attachEvent) {
649 el.attachEvent("on" + sType, fn);
655 * @method YAHOO.widget.TreeView.removeHandler
656 * @param el the elment to bind the handler to
657 * @param {string} sType the type of event handler
658 * @param {function} fn the callback to invoke
662 YAHOO.widget.TreeView.removeHandler = function (el, sType, fn) {
663 if (el.removeEventListener) {
664 el.removeEventListener(sType, fn, false);
665 } else if (el.detachEvent) {
666 el.detachEvent("on" + sType, fn);
671 * Attempts to preload the images defined in the styles used to draw the tree by
672 * rendering off-screen elements that use the styles.
673 * @method YAHOO.widget.TreeView.preload
674 * @param {string} prefix the prefix to use to generate the names of the
675 * images to preload, default is ygtv
678 YAHOO.widget.TreeView.preload = function(prefix) {
679 prefix = prefix || "ygtv";
680 var styles = ["tn","tm","tmh","tp","tph","ln","lm","lmh","lp","lph","loading"];
684 for (var i = 0; i < styles.length; ++i) {
685 sb[sb.length] = '<span class="' + prefix + styles[i] + '"> </span>';
688 var f = document.createElement("div");
690 s.position = "absolute";
693 f.innerHTML = sb.join("");
695 document.body.appendChild(f);
697 YAHOO.widget.TreeView.removeHandler(window,
698 "load", YAHOO.widget.TreeView.preload);
702 YAHOO.widget.TreeView.addHandler(window,
703 "load", YAHOO.widget.TreeView.preload);
706 * The base class for all tree nodes. The node's presentation and behavior in
707 * response to mouse events is handled in Node subclasses.
708 * @namespace YAHOO.widget
710 * @uses YAHOO.util.EventProvider
711 * @param oData {object} a string or object containing the data that will
712 * be used to render this node
713 * @param oParent {Node} this node's parent node
714 * @param expanded {boolean} the initial expanded/collapsed state
717 YAHOO.widget.Node = function(oData, oParent, expanded) {
718 if (oData) { this.init(oData, oParent, expanded); }
721 YAHOO.widget.Node.prototype = {
724 * The index for this instance obtained from global counter in YAHOO.widget.TreeView.
731 * This node's child node collection.
738 * Tree instance this node is part of
745 * The data linked to this node. This can be any object or primitive
746 * value, and the data can be used in getNodeHtml().
760 * The depth of this node. We start at -1 for the root node.
767 * The href for the node's label. If one is not specified, the href will
768 * be set so that it toggles the node.
775 * The label href target, defaults to current window
782 * The node's expanded/collapsed state
789 * Can multiple children be expanded at once?
790 * @property multiExpand
796 * Should we render children for a collapsed node? It is possible that the
797 * implementer will want to render the hidden data... @todo verify that we
798 * need this, and implement it if we do.
799 * @property renderHidden
805 * This flag is set to true when the html is generated for this node's
806 * children, and set to false when new children are added.
807 * @property childrenRendered
810 childrenRendered: false,
813 * Dynamically loaded nodes only fetch the data the first time they are
814 * expanded. This flag is set to true once the data has been fetched.
815 * @property dynamicLoadComplete
818 dynamicLoadComplete: false,
821 * This node's previous sibling
822 * @property previousSibling
825 previousSibling: null,
828 * This node's next sibling
829 * @property nextSibling
835 * We can set the node up to call an external method to get the child
844 * Function to execute when we need to get this node's child data.
845 * @property dataLoader
851 * This is true for dynamically loading nodes while waiting for the
852 * callback to return.
853 * @property isLoading
859 * The toggle/branch icon will not show if this is set to false. This
860 * could be useful if the implementer wants to have the child contain
861 * extra info about the parent, rather than an actual node.
868 * Used to configure what happens when a dynamic load node is expanded
869 * and we discover that it does not have children. By default, it is
870 * treated as if it still could have children (plus/minus icon). Set
871 * iconMode to have it display like a leaf node instead.
878 * Specifies whether or not the content area of the node should be allowed
894 spacerPath: "http://us.i1.yimg.com/us.yimg.com/i/space.gif",
895 expandedText: "Expanded",
896 collapsedText: "Collapsed",
897 loadingText: "Loading",
901 * Initializes this node, gets some of the properties from the parent
903 * @param oData {object} a string or object containing the data that will
904 * be used to render this node
905 * @param oParent {Node} this node's parent node
906 * @param expanded {boolean} the initial expanded/collapsed state
908 init: function(oData, oParent, expanded) {
912 this.index = YAHOO.widget.TreeView.nodeCount;
913 ++YAHOO.widget.TreeView.nodeCount;
914 this.expanded = expanded;
915 this.logger = new YAHOO.widget.LogWriter(this.toString());
918 * The parentChange event is fired when a parent element is applied
919 * to the node. This is useful if you need to apply tree-level
920 * properties to a tree that need to happen if a node is moved from
921 * one tree to another.
923 * @event parentChange
926 this.createEvent("parentChange", this);
928 // oParent should never be null except when we create the root node.
930 oParent.appendChild(this);
935 * Certain properties for the node cannot be set until the parent
936 * is known. This is called after the node is inserted into a tree.
937 * the parent is also applied to this node's children in order to
938 * make it possible to move a branch from one tree to another.
939 * @method applyParent
940 * @param {Node} parentNode this node's parent node
941 * @return {boolean} true if the application was successful
943 applyParent: function(parentNode) {
948 this.tree = parentNode.tree;
949 this.parent = parentNode;
950 this.depth = parentNode.depth + 1;
953 this.href = "javascript:" + this.getToggleLink();
956 if (! this.multiExpand) {
957 this.multiExpand = parentNode.multiExpand;
960 this.tree.regNode(this);
961 parentNode.childrenRendered = false;
963 // cascade update existing children
964 for (var i=0, len=this.children.length;i<len;++i) {
965 this.children[i].applyParent(this);
968 this.fireEvent("parentChange");
974 * Appends a node to the child collection.
975 * @method appendChild
976 * @param childNode {Node} the new node
977 * @return {Node} the child node
980 appendChild: function(childNode) {
981 if (this.hasChildren()) {
982 var sib = this.children[this.children.length - 1];
983 sib.nextSibling = childNode;
984 childNode.previousSibling = sib;
986 this.children[this.children.length] = childNode;
987 childNode.applyParent(this);
993 * Appends this node to the supplied node's child collection
995 * @param parentNode {Node} the node to append to.
996 * @return {Node} The appended node
998 appendTo: function(parentNode) {
999 return parentNode.appendChild(this);
1003 * Inserts this node before this supplied node
1004 * @method insertBefore
1005 * @param node {Node} the node to insert this node before
1006 * @return {Node} the inserted node
1008 insertBefore: function(node) {
1009 this.logger.log("insertBefore: " + node);
1010 var p = node.parent;
1014 this.tree.popNode(this);
1017 var refIndex = node.isChildOf(p);
1018 //this.logger.log(refIndex);
1019 p.children.splice(refIndex, 0, this);
1020 if (node.previousSibling) {
1021 node.previousSibling.nextSibling = this;
1023 this.previousSibling = node.previousSibling;
1024 this.nextSibling = node;
1025 node.previousSibling = this;
1027 this.applyParent(p);
1034 * Inserts this node after the supplied node
1035 * @method insertAfter
1036 * @param node {Node} the node to insert after
1037 * @return {Node} the inserted node
1039 insertAfter: function(node) {
1040 this.logger.log("insertAfter: " + node);
1041 var p = node.parent;
1045 this.tree.popNode(this);
1048 var refIndex = node.isChildOf(p);
1049 this.logger.log(refIndex);
1051 if (!node.nextSibling) {
1052 return this.appendTo(p);
1055 p.children.splice(refIndex + 1, 0, this);
1057 node.nextSibling.previousSibling = this;
1058 this.previousSibling = node;
1059 this.nextSibling = node.nextSibling;
1060 node.nextSibling = this;
1062 this.applyParent(p);
1069 * Returns true if the Node is a child of supplied Node
1071 * @param parentNode {Node} the Node to check
1072 * @return {boolean} The node index if this Node is a child of
1073 * supplied Node, else -1.
1076 isChildOf: function(parentNode) {
1077 if (parentNode && parentNode.children) {
1078 for (var i=0, len=parentNode.children.length; i<len ; ++i) {
1079 if (parentNode.children[i] === this) {
1089 * Returns a node array of this node's siblings, null if none.
1090 * @method getSiblings
1093 getSiblings: function() {
1094 return this.parent.children;
1098 * Shows this node's children
1099 * @method showChildren
1101 showChildren: function() {
1102 if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
1103 if (this.hasChildren()) {
1104 this.getChildrenEl().style.display = "";
1110 * Hides this node's children
1111 * @method hideChildren
1113 hideChildren: function() {
1114 this.logger.log("hiding " + this.index);
1116 if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
1117 this.getChildrenEl().style.display = "none";
1122 * Returns the id for this node's container div
1124 * @return {string} the element id
1126 getElId: function() {
1127 return "ygtv" + this.index;
1131 * Returns the id for this node's children div
1132 * @method getChildrenElId
1133 * @return {string} the element id for this node's children div
1135 getChildrenElId: function() {
1136 return "ygtvc" + this.index;
1140 * Returns the id for this node's toggle element
1141 * @method getToggleElId
1142 * @return {string} the toggel element id
1144 getToggleElId: function() {
1145 return "ygtvt" + this.index;
1150 * Returns the id for this node's spacer image. The spacer is positioned
1151 * over the toggle and provides feedback for screen readers.
1152 * @method getSpacerId
1153 * @return {string} the id for the spacer image
1156 getSpacerId: function() {
1157 return "ygtvspacer" + this.index;
1162 * Returns this node's container html element
1164 * @return {HTMLElement} the container html element
1167 return document.getElementById(this.getElId());
1171 * Returns the div that was generated for this node's children
1172 * @method getChildrenEl
1173 * @return {HTMLElement} this node's children div
1175 getChildrenEl: function() {
1176 return document.getElementById(this.getChildrenElId());
1180 * Returns the element that is being used for this node's toggle.
1181 * @method getToggleEl
1182 * @return {HTMLElement} this node's toggle html element
1184 getToggleEl: function() {
1185 return document.getElementById(this.getToggleElId());
1189 * Returns the element that is being used for this node's spacer.
1191 * @return {HTMLElement} this node's spacer html element
1194 getSpacer: function() {
1195 return document.getElementById( this.getSpacerId() ) || {};
1200 getStateText: function() {
1201 if (this.isLoading) {
1202 return this.loadingText;
1203 } else if (this.hasChildren(true)) {
1204 if (this.expanded) {
1205 return this.expandedText;
1207 return this.collapsedText;
1216 * Generates the link that will invoke this node's toggle method
1217 * @method getToggleLink
1218 * @return {string} the javascript url for toggling this node
1220 getToggleLink: function() {
1221 return "YAHOO.widget.TreeView.getNode(\'" + this.tree.id + "\'," +
1222 this.index + ").toggle()";
1226 * Hides this nodes children (creating them if necessary), changes the
1230 collapse: function() {
1231 // Only collapse if currently expanded
1232 if (!this.expanded) { return; }
1234 // fire the collapse event handler
1235 var ret = this.tree.onCollapse(this);
1237 if (false === ret) {
1238 this.logger.log("Collapse was stopped by the abstract onCollapse");
1242 ret = this.tree.fireEvent("collapse", this);
1244 if (false === ret) {
1245 this.logger.log("Collapse was stopped by a custom event handler");
1250 if (!this.getEl()) {
1251 this.expanded = false;
1253 // hide the child div
1254 this.hideChildren();
1255 this.expanded = false;
1260 // this.getSpacer().title = this.getStateText();
1262 ret = this.tree.fireEvent("collapseComplete", this);
1267 * Shows this nodes children (creating them if necessary), changes the
1268 * toggle style, and collapses its siblings if multiExpand is not set.
1271 expand: function() {
1272 // Only expand if currently collapsed.
1273 if (this.expanded) { return; }
1275 // fire the expand event handler
1276 var ret = this.tree.onExpand(this);
1278 if (false === ret) {
1279 this.logger.log("Expand was stopped by the abstract onExpand");
1283 ret = this.tree.fireEvent("expand", this);
1285 if (false === ret) {
1286 this.logger.log("Expand was stopped by the custom event handler");
1290 if (!this.getEl()) {
1291 this.expanded = true;
1295 if (! this.childrenRendered) {
1296 this.logger.log("children not rendered yet");
1297 this.getChildrenEl().innerHTML = this.renderChildren();
1299 this.logger.log("CHILDREN RENDERED");
1302 this.expanded = true;
1306 // this.getSpacer().title = this.getStateText();
1308 // We do an extra check for children here because the lazy
1309 // load feature can expose nodes that have no children.
1311 // if (!this.hasChildren()) {
1312 if (this.isLoading) {
1313 this.expanded = false;
1317 if (! this.multiExpand) {
1318 var sibs = this.getSiblings();
1319 for (var i=0; i<sibs.length; ++i) {
1320 if (sibs[i] != this && sibs[i].expanded) {
1326 this.showChildren();
1328 ret = this.tree.fireEvent("expandComplete", this);
1331 updateIcon: function() {
1333 var el = this.getToggleEl();
1335 el.className = this.getStyle();
1341 * Returns the css style name for the toggle
1343 * @return {string} the css class for this node's toggle
1345 getStyle: function() {
1346 // this.logger.log("No children, " + " isDyanmic: " + this.isDynamic() + " expanded: " + this.expanded);
1347 if (this.isLoading) {
1348 this.logger.log("returning the loading icon");
1349 return "ygtvloading";
1351 // location top or bottom, middle nodes also get the top style
1352 var loc = (this.nextSibling) ? "t" : "l";
1354 // type p=plus(expand), m=minus(collapase), n=none(no children)
1356 if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) {
1357 // if (this.hasChildren(true)) {
1358 type = (this.expanded) ? "m" : "p";
1361 // this.logger.log("ygtv" + loc + type);
1362 return "ygtv" + loc + type;
1367 * Returns the hover style for the icon
1368 * @return {string} the css class hover state
1369 * @method getHoverStyle
1371 getHoverStyle: function() {
1372 var s = this.getStyle();
1373 if (this.hasChildren(true) && !this.isLoading) {
1380 * Recursively expands all of this node's children.
1383 expandAll: function() {
1384 for (var i=0;i<this.children.length;++i) {
1385 var c = this.children[i];
1386 if (c.isDynamic()) {
1387 alert("Not supported (lazy load + expand all)");
1389 } else if (! c.multiExpand) {
1390 alert("Not supported (no multi-expand + expand all)");
1400 * Recursively collapses all of this node's children.
1401 * @method collapseAll
1403 collapseAll: function() {
1404 for (var i=0;i<this.children.length;++i) {
1405 this.children[i].collapse();
1406 this.children[i].collapseAll();
1411 * Configures this node for dynamically obtaining the child data
1412 * when the node is first expanded. Calling it without the callback
1413 * will turn off dynamic load for the node.
1414 * @method setDynamicLoad
1415 * @param fmDataLoader {function} the function that will be used to get the data.
1416 * @param iconMode {int} configures the icon that is displayed when a dynamic
1417 * load node is expanded the first time without children. By default, the
1418 * "collapse" icon will be used. If set to 1, the leaf node icon will be
1421 setDynamicLoad: function(fnDataLoader, iconMode) {
1423 this.dataLoader = fnDataLoader;
1424 this._dynLoad = true;
1426 this.dataLoader = null;
1427 this._dynLoad = false;
1431 this.iconMode = iconMode;
1436 * Evaluates if this node is the root node of the tree
1438 * @return {boolean} true if this is the root node
1440 isRoot: function() {
1441 return (this == this.tree.root);
1445 * Evaluates if this node's children should be loaded dynamically. Looks for
1446 * the property both in this instance and the root node. If the tree is
1447 * defined to load all children dynamically, the data callback function is
1448 * defined in the root node
1450 * @return {boolean} true if this node's children are to be loaded dynamically
1452 isDynamic: function() {
1453 var lazy = (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
1454 // this.logger.log("isDynamic: " + lazy);
1459 * Returns the current icon mode. This refers to the way childless dynamic
1460 * load nodes appear.
1461 * @method getIconMode
1462 * @return {int} 0 for collapse style, 1 for leaf node style
1464 getIconMode: function() {
1465 return (this.iconMode || this.tree.root.iconMode);
1469 * Checks if this node has children. If this node is lazy-loading and the
1470 * children have not been rendered, we do not know whether or not there
1471 * are actual children. In most cases, we need to assume that there are
1472 * children (for instance, the toggle needs to show the expandable
1473 * presentation state). In other times we want to know if there are rendered
1474 * children. For the latter, "checkForLazyLoad" should be false.
1475 * @method hasChildren
1476 * @param checkForLazyLoad {boolean} should we check for unloaded children?
1477 * @return {boolean} true if this has children or if it might and we are
1478 * checking for this condition.
1480 hasChildren: function(checkForLazyLoad) {
1481 return ( this.children.length > 0 ||
1482 (checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete) );
1486 * Expands if node is collapsed, collapses otherwise.
1489 toggle: function() {
1490 if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
1491 if (this.expanded) { this.collapse(); } else { this.expand(); }
1496 * Returns the markup for this node and its children.
1498 * @return {string} the markup for this node and its expanded children.
1500 getHtml: function() {
1502 this.childrenRendered = false;
1505 sb[sb.length] = '<div class="ygtvitem" id="' + this.getElId() + '">';
1506 sb[sb.length] = this.getNodeHtml();
1507 sb[sb.length] = this.getChildrenHtml();
1508 sb[sb.length] = '</div>';
1513 * Called when first rendering the tree. We always build the div that will
1514 * contain this nodes children, but we don't render the children themselves
1515 * unless this node is expanded.
1516 * @method getChildrenHtml
1517 * @return {string} the children container div html and any expanded children
1520 getChildrenHtml: function() {
1523 sb[sb.length] = '<div class="ygtvchildren"';
1524 sb[sb.length] = ' id="' + this.getChildrenElId() + '"';
1525 if (!this.expanded) {
1526 sb[sb.length] = ' style="display:none;"';
1528 sb[sb.length] = '>';
1530 // Don't render the actual child node HTML unless this node is expanded.
1531 if ( (this.hasChildren(true) && this.expanded) ||
1532 (this.renderHidden && !this.isDynamic()) ) {
1533 sb[sb.length] = this.renderChildren();
1536 sb[sb.length] = '</div>';
1542 * Generates the markup for the child nodes. This is not done until the node
1544 * @method renderChildren
1545 * @return {string} the html for this node's children
1548 renderChildren: function() {
1550 this.logger.log("rendering children for " + this.index);
1554 if (this.isDynamic() && !this.dynamicLoadComplete) {
1555 this.isLoading = true;
1556 this.tree.locked = true;
1558 if (this.dataLoader) {
1559 this.logger.log("Using dynamic loader defined for this node");
1563 node.dataLoader(node,
1565 node.loadComplete();
1569 } else if (this.tree.root.dataLoader) {
1570 this.logger.log("Using the tree-level dynamic loader");
1574 node.tree.root.dataLoader(node,
1576 node.loadComplete();
1581 this.logger.log("no loader found");
1582 return "Error: data loader not found or not specified.";
1588 return this.completeRender();
1593 * Called when we know we have all the child data.
1594 * @method completeRender
1595 * @return {string} children html
1597 completeRender: function() {
1598 this.logger.log("completeRender: " + this.index + ", # of children: " + this.children.length);
1601 for (var i=0; i < this.children.length; ++i) {
1602 // this.children[i].childrenRendered = false;
1603 sb[sb.length] = this.children[i].getHtml();
1606 this.childrenRendered = true;
1612 * Load complete is the callback function we pass to the data provider
1613 * in dynamic load situations.
1614 * @method loadComplete
1616 loadComplete: function() {
1617 this.logger.log("loadComplete: " + this.index);
1618 this.getChildrenEl().innerHTML = this.completeRender();
1619 this.dynamicLoadComplete = true;
1620 this.isLoading = false;
1622 this.tree.locked = false;
1626 * Returns this node's ancestor at the specified depth.
1627 * @method getAncestor
1628 * @param {int} depth the depth of the ancestor.
1629 * @return {Node} the ancestor
1631 getAncestor: function(depth) {
1632 if (depth >= this.depth || depth < 0) {
1633 this.logger.log("illegal getAncestor depth: " + depth);
1637 var p = this.parent;
1639 while (p.depth > depth) {
1647 * Returns the css class for the spacer at the specified depth for
1648 * this node. If this node's ancestor at the specified depth
1649 * has a next sibling the presentation is different than if it
1650 * does not have a next sibling
1651 * @method getDepthStyle
1652 * @param {int} depth the depth of the ancestor.
1653 * @return {string} the css class for the spacer
1655 getDepthStyle: function(depth) {
1656 return (this.getAncestor(depth).nextSibling) ?
1657 "ygtvdepthcell" : "ygtvblankdepthcell";
1661 * Get the markup for the node. This is designed to be overrided so that we can
1662 * support different types of nodes.
1663 * @method getNodeHtml
1664 * @return {string} The HTML that will render this node.
1666 getNodeHtml: function() {
1667 this.logger.log("Generating html");
1672 * Regenerates the html for this node and its children. To be used when the
1673 * node is expanded and new children have been added.
1676 refresh: function() {
1677 // this.loadComplete();
1678 this.getChildrenEl().innerHTML = this.completeRender();
1681 var el = this.getToggleEl();
1683 el.className = this.getStyle();
1691 * @return {string} string representation of the node
1693 toString: function() {
1694 return "Node (" + this.index + ")";
1699 YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
1702 * A custom YAHOO.widget.Node that handles the unique nature of
1703 * the virtual, presentationless root node.
1704 * @namespace YAHOO.widget
1706 * @extends YAHOO.widget.Node
1707 * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
1710 YAHOO.widget.RootNode = function(oTree) {
1711 // Initialize the node with null params. The root node is a
1712 // special case where the node has no presentation. So we have
1713 // to alter the standard properties a bit.
1714 this.init(null, null, true);
1717 * For the root node, we get the tree reference from as a param
1718 * to the constructor instead of from the parent element.
1723 YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
1725 // overrides YAHOO.widget.Node
1726 getNodeHtml: function() {
1730 toString: function() {
1734 loadComplete: function() {
1738 collapse: function() {},
1739 expand: function() {}
1743 * The default node presentation. The first parameter should be
1744 * either a string that will be used as the node's label, or an object
1745 * that has a string propery called label. By default, the clicking the
1746 * label will toggle the expanded/collapsed state of the node. By
1747 * changing the href property of the instance, this behavior can be
1748 * changed so that the label will go to the specified href.
1749 * @namespace YAHOO.widget
1751 * @extends YAHOO.widget.Node
1753 * @param oData {object} a string or object containing the data that will
1754 * be used to render this node
1755 * @param oParent {YAHOO.widget.Node} this node's parent node
1756 * @param expanded {boolean} the initial expanded/collapsed state
1758 YAHOO.widget.TextNode = function(oData, oParent, expanded) {
1761 this.init(oData, oParent, expanded);
1762 this.setUpLabel(oData);
1765 this.logger = new YAHOO.widget.LogWriter(this.toString());
1768 YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
1771 * The CSS class for the label href. Defaults to ygtvlabel, but can be
1772 * overridden to provide a custom presentation for a specific node.
1773 * @property labelStyle
1776 labelStyle: "ygtvlabel",
1779 * The derived element id of the label for this node
1780 * @property labelElId
1786 * The text for the label. It is assumed that the oData parameter will
1787 * either be a string that will be used as the label, or an object that
1788 * has a property called "label" that we will use.
1794 textNodeParentChange: function() {
1797 * Custom event that is fired when the text node label is clicked. The
1798 * custom event is defined on the tree instance, so there is a single
1799 * event that handles all nodes in the tree. The node clicked is
1800 * provided as an argument
1803 * @for YAHOO.widget.TreeView
1804 * @param {YAHOO.widget.Node} node the node clicked
1806 if (this.tree && !this.tree.hasEvent("labelClick")) {
1807 this.tree.createEvent("labelClick", this.tree);
1813 * Sets up the node label
1814 * @method setUpLabel
1815 * @param oData string containing the label, or an object with a label property
1817 setUpLabel: function(oData) {
1819 // set up the custom event on the tree
1820 this.textNodeParentChange();
1821 this.subscribe("parentChange", this.textNodeParentChange);
1823 if (typeof oData == "string") {
1824 oData = { label: oData };
1826 this.label = oData.label;
1830 this.href = oData.href;
1835 this.target = oData.target;
1839 this.labelStyle = oData.style;
1842 this.labelElId = "ygtvlabelel" + this.index;
1846 * Returns the label element
1847 * @for YAHOO.widget.TextNode
1848 * @method getLabelEl
1849 * @return {object} the element
1851 getLabelEl: function() {
1852 return document.getElementById(this.labelElId);
1855 // overrides YAHOO.widget.Node
1856 getNodeHtml: function() {
1857 this.logger.log("Generating html");
1860 sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
1861 sb[sb.length] = '<tr>';
1863 for (var i=0;i<this.depth;++i) {
1864 //sb[sb.length] = '<td><div class="' + this.getDepthStyle(i) + '"> </div></td>';
1865 //sb[sb.length] = '<td><div class="' + this.getDepthStyle(i) + '"></div></td>';
1866 sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
1869 var getNode = 'YAHOO.widget.TreeView.getNode(\'' +
1870 this.tree.id + '\',' + this.index + ')';
1872 sb[sb.length] = '<td';
1873 // sb[sb.length] = ' onselectstart="return false"';
1874 sb[sb.length] = ' id="' + this.getToggleElId() + '"';
1875 sb[sb.length] = ' class="' + this.getStyle() + '"';
1876 if (this.hasChildren(true)) {
1877 sb[sb.length] = ' onmouseover="this.className=';
1878 sb[sb.length] = getNode + '.getHoverStyle()"';
1879 sb[sb.length] = ' onmouseout="this.className=';
1880 sb[sb.length] = getNode + '.getStyle()"';
1882 sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '">';
1884 sb[sb.length] = '<div class="ygtvspacer">';
1887 sb[sb.length] = '<img id="' + this.getSpacerId() + '"';
1888 sb[sb.length] = ' alt=""';
1889 sb[sb.length] = ' tabindex=0';
1890 sb[sb.length] = ' src="' + this.spacerPath + '"';
1891 sb[sb.length] = ' title="' + this.getStateText() + '"';
1892 sb[sb.length] = ' class="ygtvspacer"';
1893 // sb[sb.length] = ' onkeypress="return ' + getNode + '".onKeyPress()"';
1894 sb[sb.length] = ' />';
1897 //sb[sb.length] = ' ';
1899 sb[sb.length] = '</div>';
1900 sb[sb.length] = '</td>';
1901 sb[sb.length] = '<td ';
1902 sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
1903 sb[sb.length] = ' >';
1904 sb[sb.length] = '<a';
1905 sb[sb.length] = ' id="' + this.labelElId + '"';
1906 sb[sb.length] = ' class="' + this.labelStyle + '"';
1907 sb[sb.length] = ' href="' + this.href + '"';
1908 sb[sb.length] = ' target="' + this.target + '"';
1909 sb[sb.length] = ' onclick="return ' + getNode + '.onLabelClick(' + getNode +')"';
1910 if (this.hasChildren(true)) {
1911 sb[sb.length] = ' onmouseover="document.getElementById(\'';
1912 sb[sb.length] = this.getToggleElId() + '\').className=';
1913 sb[sb.length] = getNode + '.getHoverStyle()"';
1914 sb[sb.length] = ' onmouseout="document.getElementById(\'';
1915 sb[sb.length] = this.getToggleElId() + '\').className=';
1916 sb[sb.length] = getNode + '.getStyle()"';
1918 sb[sb.length] = ' >';
1919 sb[sb.length] = this.label;
1920 sb[sb.length] = '</a>';
1921 sb[sb.length] = '</td>';
1922 sb[sb.length] = '</tr>';
1923 sb[sb.length] = '</table>';
1930 * Executed when the label is clicked. Fires the labelClick custom event.
1931 * @method onLabelClick
1932 * @param me {Node} this node
1933 * @scope the anchor tag clicked
1934 * @return false to cancel the anchor click
1936 onLabelClick: function(me) {
1937 me.logger.log("onLabelClick " + me.label);
1938 return me.tree.fireEvent("labelClick", me);
1942 toString: function() {
1943 return "TextNode (" + this.index + ") " + this.label;
1948 * A menu-specific implementation that differs from TextNode in that only
1949 * one sibling can be expanded at a time.
1950 * @namespace YAHOO.widget
1952 * @extends YAHOO.widget.TextNode
1953 * @param oData {object} a string or object containing the data that will
1954 * be used to render this node
1955 * @param oParent {YAHOO.widget.Node} this node's parent node
1956 * @param expanded {boolean} the initial expanded/collapsed state
1959 YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
1961 this.init(oData, oParent, expanded);
1962 this.setUpLabel(oData);
1966 * Menus usually allow only one branch to be open at a time.
1968 this.multiExpand = false;
1970 this.logger = new YAHOO.widget.LogWriter(this.toString());
1974 YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
1976 toString: function() {
1977 return "MenuNode (" + this.index + ") " + this.label;
1982 * This implementation takes either a string or object for the
1983 * oData argument. If is it a string, we will use it for the display
1984 * of this node (and it can contain any html code). If the parameter
1985 * is an object, we look for a parameter called "html" that will be
1986 * used for this node's display.
1987 * @namespace YAHOO.widget
1989 * @extends YAHOO.widget.Node
1991 * @param oData {object} a string or object containing the data that will
1992 * be used to render this node
1993 * @param oParent {YAHOO.widget.Node} this node's parent node
1994 * @param expanded {boolean} the initial expanded/collapsed state
1995 * @param hasIcon {boolean} specifies whether or not leaf nodes should
1998 YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
2000 this.init(oData, oParent, expanded);
2001 this.initContent(oData, hasIcon);
2005 YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, {
2008 * The CSS class for the html content container. Defaults to ygtvhtml, but
2009 * can be overridden to provide a custom presentation for a specific node.
2010 * @property contentStyle
2013 contentStyle: "ygtvhtml",
2016 * The generated id that will contain the data passed in by the implementer.
2017 * @property contentElId
2023 * The HTML content to use for this node's display
2030 * Sets up the node label
2031 * @property initContent
2032 * @param {object} An html string or object containing an html property
2033 * @param {boolean} hasIcon determines if the node will be rendered with an
2036 initContent: function(oData, hasIcon) {
2037 if (typeof oData == "string") {
2038 oData = { html: oData };
2041 this.html = oData.html;
2042 this.contentElId = "ygtvcontentel" + this.index;
2043 this.hasIcon = hasIcon;
2045 this.logger = new YAHOO.widget.LogWriter(this.toString());
2049 * Returns the outer html element for this node's content
2050 * @method getContentEl
2051 * @return {HTMLElement} the element
2053 getContentEl: function() {
2054 return document.getElementById(this.contentElId);
2057 // overrides YAHOO.widget.Node
2058 getNodeHtml: function() {
2059 this.logger.log("Generating html");
2062 sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0">';
2063 sb[sb.length] = '<tr>';
2065 for (var i=0;i<this.depth;++i) {
2066 //sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"> </td>';
2067 sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
2071 sb[sb.length] = '<td';
2072 sb[sb.length] = ' id="' + this.getToggleElId() + '"';
2073 sb[sb.length] = ' class="' + this.getStyle() + '"';
2074 sb[sb.length] = ' onclick="javascript:' + this.getToggleLink() + '"';
2075 if (this.hasChildren(true)) {
2076 sb[sb.length] = ' onmouseover="this.className=';
2077 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
2078 sb[sb.length] = this.tree.id + '\',' + this.index + ').getHoverStyle()"';
2079 sb[sb.length] = ' onmouseout="this.className=';
2080 sb[sb.length] = 'YAHOO.widget.TreeView.getNode(\'';
2081 sb[sb.length] = this.tree.id + '\',' + this.index + ').getStyle()"';
2083 //sb[sb.length] = '> </td>';
2084 sb[sb.length] = '><div class="ygtvspacer"></div></td>';
2087 sb[sb.length] = '<td';
2088 sb[sb.length] = ' id="' + this.contentElId + '"';
2089 sb[sb.length] = ' class="' + this.contentStyle + '"';
2090 sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
2091 sb[sb.length] = ' >';
2092 sb[sb.length] = this.html;
2093 sb[sb.length] = '</td>';
2094 sb[sb.length] = '</tr>';
2095 sb[sb.length] = '</table>';
2100 toString: function() {
2101 return "HTMLNode (" + this.index + ")";
2106 * A static factory class for tree view expand/collapse animations
2110 YAHOO.widget.TVAnim = function() {
2113 * Constant for the fade in animation
2118 FADE_IN: "TVFadeIn",
2121 * Constant for the fade out animation
2122 * @property FADE_OUT
2126 FADE_OUT: "TVFadeOut",
2129 * Returns a ygAnim instance of the given type
2131 * @param type {string} the type of animation
2132 * @param el {HTMLElement} the element to element (probably the children div)
2133 * @param callback {function} function to invoke when the animation is done.
2134 * @return {YAHOO.util.Animation} the animation instance
2137 getAnim: function(type, el, callback) {
2138 if (YAHOO.widget[type]) {
2139 return new YAHOO.widget[type](el, callback);
2146 * Returns true if the specified animation class is available
2148 * @param type {string} the type of animation
2149 * @return {boolean} true if valid, false if not
2152 isValid: function(type) {
2153 return (YAHOO.widget[type]);
2159 * A 1/2 second fade-in animation.
2162 * @param el {HTMLElement} the element to animate
2163 * @param callback {function} function to invoke when the animation is finished
2165 YAHOO.widget.TVFadeIn = function(el, callback) {
2167 * The element to animate
2174 * the callback to invoke when the animation is complete
2175 * @property callback
2178 this.callback = callback;
2180 this.logger = new YAHOO.widget.LogWriter(this.toString());
2183 YAHOO.widget.TVFadeIn.prototype = {
2185 * Performs the animation
2188 animate: function() {
2191 var s = this.el.style;
2193 s.filter = "alpha(opacity=10)";
2197 var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur);
2198 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2203 * Clean up and invoke callback
2204 * @method onComplete
2206 onComplete: function() {
2213 * @return {string} the string representation of the instance
2215 toString: function() {
2221 * A 1/2 second fade out animation.
2224 * @param el {HTMLElement} the element to animate
2225 * @param callback {Function} function to invoke when the animation is finished
2227 YAHOO.widget.TVFadeOut = function(el, callback) {
2229 * The element to animate
2236 * the callback to invoke when the animation is complete
2237 * @property callback
2240 this.callback = callback;
2242 this.logger = new YAHOO.widget.LogWriter(this.toString());
2245 YAHOO.widget.TVFadeOut.prototype = {
2247 * Performs the animation
2250 animate: function() {
2253 var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
2254 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
2259 * Clean up and invoke callback
2260 * @method onComplete
2262 onComplete: function() {
2263 var s = this.el.style;
2266 s.filter = "alpha(opacity=100)";
2273 * @return {string} the string representation of the instance
2275 toString: function() {