2 Copyright (c) 2009, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.net/yui/license.txt
8 var Dom = YAHOO.util.Dom,
9 Event = YAHOO.util.Event,
11 Widget = YAHOO.widget;
16 * The treeview widget is a generic tree building tool.
18 * @title TreeView Widget
19 * @requires yahoo, dom, event
20 * @optional animation, json, calendar
21 * @namespace YAHOO.widget
25 * Contains the tree view state data and the root node.
28 * @uses YAHOO.util.EventProvider
30 * @param {string|HTMLElement} id The id of the element, or the element itself that the tree will be inserted into.
31 * Existing markup in this element, if valid, will be used to build the tree
32 * @param {Array|Object|String} oConfig (optional) If present, it will be used to build the tree via method <a href="#method_buildTreeFromObject">buildTreeFromObject</a>
35 YAHOO.widget.TreeView = function(id, oConfig) {
36 if (id) { this.init(id); }
38 this.buildTreeFromObject(oConfig);
39 } else if (Lang.trim(this._el.innerHTML)) {
40 this.buildTreeFromMarkup(id);
44 var TV = Widget.TreeView;
49 * The id of tree container element
56 * The host element for this tree
64 * Flat collection of all nodes in this tree. This is a sparse
65 * array, so the length property can't be relied upon for a
66 * node count for the tree.
74 * We lock the tree control while waiting for the dynamic loader to return
81 * The animation to use for expanding children, if any
82 * @property _expandAnim
89 * The animation to use for collapsing children, if any
90 * @property _collapseAnim
97 * The current number of animations that are executing
98 * @property _animCount
105 * The maximum number of animations to run at one time.
112 * Whether there is any subscriber to dblClickEvent
113 * @property _hasDblClickSubscriber
117 _hasDblClickSubscriber: false,
120 * Stores the timer used to check for double clicks
121 * @property _dblClickTimer
122 * @type window.timer object
125 _dblClickTimer: null,
128 * A reference to the Node currently having the focus or null if none.
129 * @property currentFocus
130 * @type YAHOO.widget.Node
135 * If true, only one Node can be highlighted at a time
136 * @property singleNodeHighlight
141 singleNodeHighlight: false,
144 * A reference to the Node that is currently highlighted.
145 * It is only meaningful if singleNodeHighlight is enabled
146 * @property _currentlyHighlighted
147 * @type YAHOO.widget.Node
152 _currentlyHighlighted: null,
155 * Sets up the animation for expanding children
156 * @method setExpandAnim
157 * @param {string} type the type of animation (acceptable values defined
158 * in YAHOO.widget.TVAnim)
160 setExpandAnim: function(type) {
161 this._expandAnim = (Widget.TVAnim.isValid(type)) ? type : null;
165 * Sets up the animation for collapsing children
166 * @method setCollapseAnim
167 * @param {string} type of animation (acceptable values defined in
168 * YAHOO.widget.TVAnim)
170 setCollapseAnim: function(type) {
171 this._collapseAnim = (Widget.TVAnim.isValid(type)) ? type : null;
175 * Perform the expand animation if configured, or just show the
176 * element if not configured or too many animations are in progress
177 * @method animateExpand
178 * @param el {HTMLElement} the element to animate
179 * @param node {YAHOO.util.Node} the node that was expanded
180 * @return {boolean} true if animation could be invoked, false otherwise
182 animateExpand: function(el, node) {
183 this.logger.log("animating expand");
185 if (this._expandAnim && this._animCount < this.maxAnim) {
186 // this.locked = true;
188 var a = Widget.TVAnim.getAnim(this._expandAnim, el,
189 function() { tree.expandComplete(node); });
192 this.fireEvent("animStart", {
206 * Perform the collapse animation if configured, or just show the
207 * element if not configured or too many animations are in progress
208 * @method animateCollapse
209 * @param el {HTMLElement} the element to animate
210 * @param node {YAHOO.util.Node} the node that was expanded
211 * @return {boolean} true if animation could be invoked, false otherwise
213 animateCollapse: function(el, node) {
214 this.logger.log("animating collapse");
216 if (this._collapseAnim && this._animCount < this.maxAnim) {
217 // this.locked = true;
219 var a = Widget.TVAnim.getAnim(this._collapseAnim, el,
220 function() { tree.collapseComplete(node); });
223 this.fireEvent("animStart", {
237 * Function executed when the expand animation completes
238 * @method expandComplete
240 expandComplete: function(node) {
241 this.logger.log("expand complete: " + this.id);
243 this.fireEvent("animComplete", {
247 // this.locked = false;
251 * Function executed when the collapse animation completes
252 * @method collapseComplete
254 collapseComplete: function(node) {
255 this.logger.log("collapse complete: " + this.id);
257 this.fireEvent("animComplete", {
261 // this.locked = false;
265 * Initializes the tree
267 * @parm {string|HTMLElement} id the id of the element that will hold the tree
271 this._el = Dom.get(id);
272 this.id = Dom.generateId(this._el,"yui-tv-auto-id-");
275 * When animation is enabled, this event fires when the animation
279 * @param {YAHOO.widget.Node} oArgs.node the node that is expanding/collapsing
280 * @param {String} oArgs.type the type of animation ("expand" or "collapse")
282 this.createEvent("animStart", this);
285 * When animation is enabled, this event fires when the animation
287 * @event animComplete
289 * @param {YAHOO.widget.Node} oArgs.node the node that is expanding/collapsing
290 * @param {String} oArgs.type the type of animation ("expand" or "collapse")
292 this.createEvent("animComplete", this);
295 * Fires when a node is going to be collapsed. Return false to stop
299 * @param {YAHOO.widget.Node} node the node that is collapsing
301 this.createEvent("collapse", this);
304 * Fires after a node is successfully collapsed. This event will not fire
305 * if the "collapse" event was cancelled.
306 * @event collapseComplete
308 * @param {YAHOO.widget.Node} node the node that was collapsed
310 this.createEvent("collapseComplete", this);
313 * Fires when a node is going to be expanded. Return false to stop
317 * @param {YAHOO.widget.Node} node the node that is expanding
319 this.createEvent("expand", this);
322 * Fires after a node is successfully expanded. This event will not fire
323 * if the "expand" event was cancelled.
324 * @event expandComplete
326 * @param {YAHOO.widget.Node} node the node that was expanded
328 this.createEvent("expandComplete", this);
331 * Fires when the Enter key is pressed on a node that has the focus
332 * @event enterKeyPressed
334 * @param {YAHOO.widget.Node} node the node that has the focus
336 this.createEvent("enterKeyPressed", this);
339 * Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a Click.
340 * The listener may return false to cancel toggling and focusing on the node.
343 * @param oArgs.event {HTMLEvent} The event object
344 * @param oArgs.node {YAHOO.widget.Node} node the node that was clicked
346 this.createEvent("clickEvent", this);
349 * Fires when the focus receives the focus, when it changes from a Node
350 * to another Node or when it is completely lost (blurred)
351 * @event focusChanged
353 * @param oArgs.oldNode {YAHOO.widget.Node} Node that had the focus or null if none
354 * @param oArgs.newNode {YAHOO.widget.Node} Node that receives the focus or null if none
357 this.createEvent('focusChanged',this);
360 * Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a double Click
361 * @event dblClickEvent
363 * @param oArgs.event {HTMLEvent} The event object
364 * @param oArgs.node {YAHOO.widget.Node} node the node that was clicked
367 this.createEvent("dblClickEvent", {
369 onSubscribeCallback: function() {
370 self._hasDblClickSubscriber = true;
375 * Custom event that is fired when the text node label is clicked.
376 * The node clicked is provided as an argument
380 * @param {YAHOO.widget.Node} node the node clicked
381 * @deprecated use clickEvent or dblClickEvent
383 this.createEvent("labelClick", this);
386 * Custom event fired when the highlight of a node changes.
387 * The node that triggered the change is provided as an argument:
388 * The status of the highlight can be checked in
389 * <a href="YAHOO.widget.Node.html#property_highlightState">nodeRef.highlightState</a>.
390 * Depending on <a href="YAHOO.widget.Node.html#property_propagateHighlight">nodeRef.propagateHighlight</a>, other nodes might have changed
391 * @event highlightEvent
393 * @param node {YAHOO.widget.Node} the node that started the change in highlighting state
395 this.createEvent("highlightEvent",this);
401 // store a global reference
402 TV.trees[this.id] = this;
404 // Set up the root node
405 this.root = new Widget.RootNode(this);
407 var LW = Widget.LogWriter;
409 this.logger = (LW) ? new LW(this.toString()) : YAHOO;
411 this.logger.log("tree init: " + this.id);
413 if (this._initEditor) {
417 // YAHOO.util.Event.onContentReady(this.id, this.handleAvailable, this, true);
418 // YAHOO.util.Event.on(this.id, "click", this.handleClick, this, true);
421 //handleAvailable: function() {
422 //var Event = YAHOO.util.Event;
426 * Builds the TreeView from an object.
427 * This is the method called by the constructor to build the tree when it has a second argument.
428 * A tree can be described by an array of objects, each object corresponding to a node.
429 * Node descriptions may contain values for any property of a node plus the following extra properties: <ul>
430 * <li>type: can be one of the following:<ul>
431 * <li> A shortname for a node type (<code>'text','menu','html'</code>) </li>
432 * <li>The name of a Node class under YAHOO.widget (<code>'TextNode', 'MenuNode', 'DateNode'</code>, etc) </li>
433 * <li>a reference to an actual class: <code>YAHOO.widget.DateNode</code></li>
435 * <li>children: an array containing further node definitions</li></ul>
436 * A string instead of an object will produce a node of type 'text' with the given string as its label.
437 * @method buildTreeFromObject
438 * @param oConfig {Array|Object|String} array containing a full description of the tree.
439 * An object or a string will be turned into an array with the given object or string as its only element.
442 buildTreeFromObject: function (oConfig) {
443 var logger = this.logger;
444 logger.log('Building tree from object');
445 var build = function (parent, oConfig) {
446 var i, item, node, children, type, NodeType, ThisType;
447 for (i = 0; i < oConfig.length; i++) {
449 if (Lang.isString(item)) {
450 node = new Widget.TextNode(item, parent);
451 } else if (Lang.isObject(item)) {
452 children = item.children;
453 delete item.children;
454 type = item.type || 'text';
456 switch (Lang.isString(type) && type.toLowerCase()) {
458 node = new Widget.TextNode(item, parent);
461 node = new Widget.MenuNode(item, parent);
464 node = new Widget.HTMLNode(item, parent);
467 if (Lang.isString(type)) {
468 NodeType = Widget[type];
472 if (Lang.isObject(NodeType)) {
473 for (ThisType = NodeType; ThisType && ThisType !== Widget.Node; ThisType = ThisType.superclass.constructor) {}
475 node = new NodeType(item, parent);
477 logger.log('Invalid type in node definition: ' + type,'error');
480 logger.log('Invalid type in node definition: ' + type,'error');
484 build(node,children);
487 logger.log('Invalid node definition','error');
491 if (!Lang.isArray(oConfig)) {
496 build(this.root,oConfig);
499 * Builds the TreeView from existing markup. Markup should consist of <UL> or <OL> elements containing <LI> elements.
500 * Each <LI> can have one element used as label and a second optional element which is to be a <UL> or <OL>
501 * containing nested nodes.
502 * Depending on what the first element of the <LI> element is, the following Nodes will be created: <ul>
503 * <li>plain text: a regular TextNode</li>
504 * <li>anchor <A>: a TextNode with its <code>href</code> and <code>target</code> taken from the anchor</li>
505 * <li>anything else: an HTMLNode</li></ul>
506 * Only the first outermost (un-)ordered list in the markup and its children will be parsed.
507 * Nodes will be collapsed unless an <LI> tag has a className called 'expanded'.
508 * All other className attributes will be copied over to the Node className property.
509 * If the <LI> element contains an attribute called <code>yuiConfig</code>, its contents should be a JSON-encoded object
510 * as the one used in method <a href="#method_buildTreeFromObject">buildTreeFromObject</a>.
511 * @method buildTreeFromMarkup
512 * @param id {string|HTMLElement} The id of the element that contains the markup or a reference to it.
514 buildTreeFromMarkup: function (id) {
515 this.logger.log('Building tree from existing markup');
516 var build = function (markup) {
517 var el, child, branch = [], config = {}, label, yuiConfig;
518 // Dom's getFirstChild and getNextSibling skip over text elements
519 for (el = Dom.getFirstChild(markup); el; el = Dom.getNextSibling(el)) {
520 switch (el.tagName.toUpperCase()) {
524 expanded: Dom.hasClass(el,'expanded'),
525 title: el.title || el.alt || null,
526 className: Lang.trim(el.className.replace(/\bexpanded\b/,'')) || null
528 // I cannot skip over text elements here because I want them for labels
529 child = el.firstChild;
530 if (child.nodeType == 3) {
531 // nodes with only whitespace, tabs and new lines don't count, they are probably just formatting.
532 label = Lang.trim(child.nodeValue.replace(/[\n\t\r]*/g,''));
534 config.type = 'text';
535 config.label = label;
537 child = Dom.getNextSibling(child);
541 if (child.tagName.toUpperCase() == 'A') {
542 config.type = 'text';
543 config.label = child.innerHTML;
544 config.href = child.href;
545 config.target = child.target;
546 config.title = child.title || child.alt || config.title;
548 config.type = 'html';
549 var d = document.createElement('div');
550 d.appendChild(child.cloneNode(true));
551 config.html = d.innerHTML;
552 config.hasIcon = true;
555 // see if after the label it has a further list which will become children of this node.
556 child = Dom.getNextSibling(child);
557 switch (child && child.tagName.toUpperCase()) {
560 config.children = build(child);
563 // if there are further elements or text, it will be ignored.
565 if (YAHOO.lang.JSON) {
566 yuiConfig = el.getAttribute('yuiConfig');
568 yuiConfig = YAHOO.lang.JSON.parse(yuiConfig);
569 config = YAHOO.lang.merge(config,yuiConfig);
577 this.logger.log('ULs or OLs can only contain LI elements, not other UL or OL. This will not work in some browsers','error');
581 children: build(child)
590 var markup = Dom.getChildrenBy(Dom.get(id),function (el) {
591 var tag = el.tagName.toUpperCase();
592 return tag == 'UL' || tag == 'OL';
595 this.buildTreeFromObject(build(markup[0]));
597 this.logger.log('Markup contains no UL or OL elements','warn');
601 * Returns the TD element where the event has occurred
602 * @method _getEventTargetTdEl
605 _getEventTargetTdEl: function (ev) {
606 var target = Event.getTarget(ev);
607 // go up looking for a TD with a className with a ygtv prefix
608 while (target && !(target.tagName.toUpperCase() == 'TD' && Dom.hasClass(target.parentNode,'ygtvrow'))) {
609 target = Dom.getAncestorByTagName(target,'td');
611 if (Lang.isNull(target)) { return null; }
612 // If it is a spacer cell, do nothing
613 if (/\bygtv(blank)?depthcell/.test(target.className)) { return null;}
614 // If it has an id, search for the node number and see if it belongs to a node in this tree.
616 var m = target.id.match(/\bygtv([^\d]*)(.*)/);
617 if (m && m[2] && this._nodes[m[2]]) {
624 * Event listener for click events
625 * @method _onClickEvent
628 _onClickEvent: function (ev) {
630 td = this._getEventTargetTdEl(ev),
633 toggle = function (force) {
635 if (force || !node.href) {
638 Event.preventDefault(ev);
641 // For some reason IE8 is providing an event object with
642 // most of the fields missing, but only when clicking on
643 // the node's label, and only when working with inline
644 // editing. This generates a "Member not found" error
645 // in that browser. Determine if this is a browser
646 // bug, or a problem with this code. Already checked to
647 // see if the problem has to do with access the event
648 // in the outer scope, and that isn't the problem.
649 // Maybe the markup for inline editing is broken.
658 node = this.getNodeByElement(td);
663 // exception to handle deprecated event labelClick
664 // @TODO take another look at this deprecation. It is common for people to
665 // only be interested in the label click, so why make them have to test
666 // the node type to figure out whether the click was on the label?
667 target = Event.getTarget(ev);
668 if (Dom.hasClass(target, node.labelStyle) || Dom.getAncestorByClassName(target,node.labelStyle)) {
669 this.logger.log("onLabelClick " + node.label);
670 this.fireEvent('labelClick',node);
673 // If it is a toggle cell, toggle
674 if (/\bygtv[tl][mp]h?h?/.test(td.className)) {
677 if (this._dblClickTimer) {
678 window.clearTimeout(this._dblClickTimer);
679 this._dblClickTimer = null;
681 if (this._hasDblClickSubscriber) {
682 this._dblClickTimer = window.setTimeout(function () {
683 self._dblClickTimer = null;
684 if (self.fireEvent('clickEvent', {event:ev,node:node}) !== false) {
689 if (self.fireEvent('clickEvent', {event:ev,node:node}) !== false) {
698 * Event listener for double-click events
699 * @method _onDblClickEvent
702 _onDblClickEvent: function (ev) {
703 if (!this._hasDblClickSubscriber) { return; }
704 var td = this._getEventTargetTdEl(ev);
707 if (!(/\bygtv[tl][mp]h?h?/.test(td.className))) {
708 this.fireEvent('dblClickEvent', {event:ev, node:this.getNodeByElement(td)});
709 if (this._dblClickTimer) {
710 window.clearTimeout(this._dblClickTimer);
711 this._dblClickTimer = null;
716 * Event listener for mouse over events
717 * @method _onMouseOverEvent
720 _onMouseOverEvent:function (ev) {
722 if ((target = this._getEventTargetTdEl(ev)) && (target = this.getNodeByElement(target)) && (target = target.getToggleEl())) {
723 target.className = target.className.replace(/\bygtv([lt])([mp])\b/gi,'ygtv$1$2h');
727 * Event listener for mouse out events
728 * @method _onMouseOutEvent
731 _onMouseOutEvent: function (ev) {
733 if ((target = this._getEventTargetTdEl(ev)) && (target = this.getNodeByElement(target)) && (target = target.getToggleEl())) {
734 target.className = target.className.replace(/\bygtv([lt])([mp])h\b/gi,'ygtv$1$2');
738 * Event listener for key down events
739 * @method _onKeyDownEvent
742 _onKeyDownEvent: function (ev) {
743 var target = Event.getTarget(ev),
744 node = this.getNodeByElement(target),
746 KEY = YAHOO.util.KeyListener.KEY;
750 this.logger.log('UP');
752 if (newNode.previousSibling) {
753 newNode = newNode.previousSibling;
755 newNode = newNode.parent;
757 } while (newNode && !newNode._canHaveFocus());
758 if (newNode) { newNode.focus(); }
759 Event.preventDefault(ev);
762 this.logger.log('DOWN');
764 if (newNode.nextSibling) {
765 newNode = newNode.nextSibling;
768 newNode = (newNode.children.length || null) && newNode.children[0];
770 } while (newNode && !newNode._canHaveFocus);
771 if (newNode) { newNode.focus();}
772 Event.preventDefault(ev);
775 this.logger.log('LEFT');
777 if (newNode.parent) {
778 newNode = newNode.parent;
780 newNode = newNode.previousSibling;
782 } while (newNode && !newNode._canHaveFocus());
783 if (newNode) { newNode.focus();}
784 Event.preventDefault(ev);
787 this.logger.log('RIGHT');
790 focusOnExpand = function (newNode) {
791 self.unsubscribe('expandComplete',focusOnExpand);
792 moveFocusRight(newNode);
794 moveFocusRight = function (newNode) {
796 if (newNode.isDynamic() && !newNode.childrenRendered) {
797 self.subscribe('expandComplete',focusOnExpand);
803 if (newNode.children.length) {
804 newNode = newNode.children[0];
806 newNode = newNode.nextSibling;
809 } while (newNode && !newNode._canHaveFocus());
810 if (newNode) { newNode.focus();}
813 moveFocusRight(newNode);
814 Event.preventDefault(ev);
817 this.logger.log('ENTER: ' + newNode.href);
820 window.open(node.href,node.target);
822 window.location(node.href);
827 this.fireEvent('enterKeyPressed',node);
828 Event.preventDefault(ev);
831 this.logger.log('HOME');
832 newNode = this.getRoot();
833 if (newNode.children.length) {newNode = newNode.children[0];}
834 if (newNode._canHaveFocus()) { newNode.focus(); }
835 Event.preventDefault(ev);
838 this.logger.log('END');
839 newNode = newNode.parent.children;
840 newNode = newNode[newNode.length -1];
841 if (newNode._canHaveFocus()) { newNode.focus(); }
842 Event.preventDefault(ev);
845 // this.logger.log('PAGE_UP');
847 // case KEY.PAGE_DOWN:
848 // this.logger.log('PAGE_DOWN');
850 case 107: // plus key
852 this.logger.log('Shift-PLUS');
853 node.parent.expandAll();
855 this.logger.log('PLUS');
859 case 109: // minus key
861 this.logger.log('Shift-MINUS');
862 node.parent.collapseAll();
864 this.logger.log('MINUS');
873 * Renders the tree boilerplate and visible nodes
877 var html = this.root.getHtml(),
880 if (!this._hasEvents) {
881 Event.on(el, 'click', this._onClickEvent, this, true);
882 Event.on(el, 'dblclick', this._onDblClickEvent, this, true);
883 Event.on(el, 'mouseover', this._onMouseOverEvent, this, true);
884 Event.on(el, 'mouseout', this._onMouseOutEvent, this, true);
885 Event.on(el, 'keydown', this._onKeyDownEvent, this, true);
887 this._hasEvents = true;
891 * Returns the tree's host element
893 * @return {HTMLElement} the host element
897 this._el = Dom.get(this.id);
903 * Nodes register themselves with the tree instance when they are created.
905 * @param node {Node} the node to register
908 regNode: function(node) {
909 this._nodes[node.index] = node;
913 * Returns the root node of this tree
915 * @return {Node} the root node
917 getRoot: function() {
922 * Configures this tree to dynamically load all child data
923 * @method setDynamicLoad
924 * @param {function} fnDataLoader the function that will be called to get the data
925 * @param iconMode {int} configures the icon that is displayed when a dynamic
926 * load node is expanded the first time without children. By default, the
927 * "collapse" icon will be used. If set to 1, the leaf node icon will be
930 setDynamicLoad: function(fnDataLoader, iconMode) {
931 this.root.setDynamicLoad(fnDataLoader, iconMode);
935 * Expands all child nodes. Note: this conflicts with the "multiExpand"
936 * node property. If expand all is called in a tree with nodes that
937 * do not allow multiple siblings to be displayed, only the last sibling
941 expandAll: function() {
943 this.root.expandAll();
948 * Collapses all expanded child nodes in the entire tree.
949 * @method collapseAll
951 collapseAll: function() {
953 this.root.collapseAll();
958 * Returns a node in the tree that has the specified index (this index
959 * is created internally, so this function probably will only be used
960 * in html generated for a given node.)
961 * @method getNodeByIndex
962 * @param {int} nodeIndex the index of the node wanted
963 * @return {Node} the node with index=nodeIndex, null if no match
965 getNodeByIndex: function(nodeIndex) {
966 var n = this._nodes[nodeIndex];
967 return (n) ? n : null;
971 * Returns a node that has a matching property and value in the data
972 * object that was passed into its constructor.
973 * @method getNodeByProperty
974 * @param {object} property the property to search (usually a string)
975 * @param {object} value the value we want to find (usuall an int or string)
976 * @return {Node} the matching node, null if no match
978 getNodeByProperty: function(property, value) {
979 for (var i in this._nodes) {
980 if (this._nodes.hasOwnProperty(i)) {
981 var n = this._nodes[i];
982 if ((property in n && n[property] == value) || (n.data && value == n.data[property])) {
992 * Returns a collection of nodes that have a matching property
993 * and value in the data object that was passed into its constructor.
994 * @method getNodesByProperty
995 * @param {object} property the property to search (usually a string)
996 * @param {object} value the value we want to find (usuall an int or string)
997 * @return {Array} the matching collection of nodes, null if no match
999 getNodesByProperty: function(property, value) {
1001 for (var i in this._nodes) {
1002 if (this._nodes.hasOwnProperty(i)) {
1003 var n = this._nodes[i];
1004 if ((property in n && n[property] == value) || (n.data && value == n.data[property])) {
1010 return (values.length) ? values : null;
1015 * Returns a collection of nodes that have passed the test function
1016 * passed as its only argument.
1017 * The function will receive a reference to each node to be tested.
1018 * @method getNodesBy
1019 * @param {function} a boolean function that receives a Node instance and returns true to add the node to the results list
1020 * @return {Array} the matching collection of nodes, null if no match
1022 getNodesBy: function(fn) {
1024 for (var i in this._nodes) {
1025 if (this._nodes.hasOwnProperty(i)) {
1026 var n = this._nodes[i];
1032 return (values.length) ? values : null;
1035 * Returns the treeview node reference for an ancestor element
1036 * of the node, or null if it is not contained within any node
1038 * @method getNodeByElement
1039 * @param el {HTMLElement} the element to test
1040 * @return {YAHOO.widget.Node} a node reference or null
1042 getNodeByElement: function(el) {
1044 var p=el, m, re=/ygtv([^\d]*)(.*)/;
1051 return this.getNodeByIndex(m[2]);
1057 if (!p || !p.tagName) {
1062 while (p.id !== this.id && p.tagName.toLowerCase() !== "body");
1068 * When in singleNodeHighlight it returns the node highlighted
1069 * or null if none. Returns null if singleNodeHighlight is false.
1070 * @method getHighlightedNode
1071 * @return {YAHOO.widget.Node} a node reference or null
1073 getHighlightedNode: function() {
1074 return this._currentlyHighlighted;
1079 * Removes the node and its children, and optionally refreshes the
1080 * branch of the tree that was affected.
1081 * @method removeNode
1082 * @param {Node} node to remove
1083 * @param {boolean} autoRefresh automatically refreshes branch if true
1084 * @return {boolean} False is there was a problem, true otherwise.
1086 removeNode: function(node, autoRefresh) {
1088 // Don't delete the root node
1089 if (node.isRoot()) {
1093 // Get the branch that we may need to refresh
1094 var p = node.parent;
1099 // Delete the node and its children
1100 this._deleteNode(node);
1102 // Refresh the parent of the parent
1103 if (autoRefresh && p && p.childrenRendered) {
1111 * wait until the animation is complete before deleting
1112 * to avoid javascript errors
1113 * @method _removeChildren_animComplete
1114 * @param o the custom event payload
1117 _removeChildren_animComplete: function(o) {
1118 this.unsubscribe(this._removeChildren_animComplete);
1119 this.removeChildren(o.node);
1123 * Deletes this nodes child collection, recursively. Also collapses
1124 * the node, and resets the dynamic load flag. The primary use for
1125 * this method is to purge a node and allow it to fetch its data
1126 * dynamically again.
1127 * @method removeChildren
1128 * @param {Node} node the node to purge
1130 removeChildren: function(node) {
1132 if (node.expanded) {
1133 // wait until the animation is complete before deleting to
1134 // avoid javascript errors
1135 if (this._collapseAnim) {
1136 this.subscribe("animComplete",
1137 this._removeChildren_animComplete, this, true);
1138 Widget.Node.prototype.collapse.call(node);
1145 this.logger.log("Removing children for " + node);
1146 while (node.children.length) {
1147 this._deleteNode(node.children[0]);
1150 if (node.isRoot()) {
1151 Widget.Node.prototype.expand.call(node);
1154 node.childrenRendered = false;
1155 node.dynamicLoadComplete = false;
1161 * Deletes the node and recurses children
1162 * @method _deleteNode
1165 _deleteNode: function(node) {
1166 // Remove all the child nodes first
1167 this.removeChildren(node);
1169 // Remove the node from the tree
1174 * Removes the node from the tree, preserving the child collection
1175 * to make it possible to insert the branch into another part of the
1176 * tree, or another tree.
1178 * @param {Node} node to remove
1180 popNode: function(node) {
1181 var p = node.parent;
1183 // Update the parent's collection of children
1186 for (var i=0, len=p.children.length;i<len;++i) {
1187 if (p.children[i] != node) {
1188 a[a.length] = p.children[i];
1194 // reset the childrenRendered flag for the parent
1195 p.childrenRendered = false;
1197 // Update the sibling relationship
1198 if (node.previousSibling) {
1199 node.previousSibling.nextSibling = node.nextSibling;
1202 if (node.nextSibling) {
1203 node.nextSibling.previousSibling = node.previousSibling;
1206 if (this.currentFocus == node) {
1207 this.currentFocus = null;
1209 if (this._currentlyHighlighted == node) {
1210 this._currentlyHighlighted = null;
1214 node.previousSibling = null;
1215 node.nextSibling = null;
1218 // Update the tree's node collection
1219 delete this._nodes[node.index];
1223 * Nulls out the entire TreeView instance and related objects, removes attached
1224 * event listeners, and clears out DOM elements inside the container. After
1225 * calling this method, the instance reference should be expliclitly nulled by
1226 * implementer, as in myDataTable = null. Use with caution!
1230 destroy : function() {
1231 // Since the label editor can be separated from the main TreeView control
1232 // the destroy method for it might not be there.
1233 if (this._destroyEditor) { this._destroyEditor(); }
1234 var el = this.getEl();
1235 Event.removeListener(el,'click');
1236 Event.removeListener(el,'dblclick');
1237 Event.removeListener(el,'mouseover');
1238 Event.removeListener(el,'mouseout');
1239 Event.removeListener(el,'keydown');
1240 for (var i = 0 ; i < this._nodes.length; i++) {
1241 var node = this._nodes[i];
1242 if (node && node.destroy) {node.destroy(); }
1245 this._hasEvents = false;
1252 * TreeView instance toString
1254 * @return {string} string representation of the tree
1256 toString: function() {
1257 return "TreeView " + this.id;
1261 * Count of nodes in tree
1262 * @method getNodeCount
1263 * @return {int} number of nodes in the tree
1265 getNodeCount: function() {
1266 return this.getRoot().getNodeCount();
1270 * Returns an object which could be used to rebuild the tree.
1271 * It can be passed to the tree constructor to reproduce the same tree.
1272 * It will return false if any node loads dynamically, regardless of whether it is loaded or not.
1273 * @method getTreeDefinition
1274 * @return {Object | false} definition of the tree or false if any node is defined as dynamic
1276 getTreeDefinition: function() {
1277 return this.getRoot().getNodeDefinition();
1281 * Abstract method that is executed when a node is expanded
1283 * @param node {Node} the node that was expanded
1284 * @deprecated use treeobj.subscribe("expand") instead
1286 onExpand: function(node) { },
1289 * Abstract method that is executed when a node is collapsed.
1290 * @method onCollapse
1291 * @param node {Node} the node that was collapsed.
1292 * @deprecated use treeobj.subscribe("collapse") instead
1294 onCollapse: function(node) { },
1297 * Sets the value of a property for all loaded nodes in the tree.
1298 * @method setNodesProperty
1299 * @param name {string} Name of the property to be set
1300 * @param value {any} value to be set
1301 * @param refresh {boolean} if present and true, it does a refresh
1303 setNodesProperty: function(name, value, refresh) {
1304 this.root.setNodesProperty(name,value);
1306 this.root.refresh();
1310 * Event listener to toggle node highlight.
1311 * Can be assigned as listener to clickEvent, dblClickEvent and enterKeyPressed.
1312 * It returns false to prevent the default action.
1313 * @method onEventToggleHighlight
1314 * @param oArgs {any} it takes the arguments of any of the events mentioned above
1315 * @return {false} Always cancels the default action for the event
1317 onEventToggleHighlight: function (oArgs) {
1319 if ('node' in oArgs && oArgs.node instanceof Widget.Node) {
1321 } else if (oArgs instanceof Widget.Node) {
1326 node.toggleHighlight();
1333 /* Backwards compatibility aliases */
1334 var PROT = TV.prototype;
1336 * Renders the tree boilerplate and visible nodes.
1339 * @deprecated Use render instead
1341 PROT.draw = PROT.render;
1343 /* end backwards compatibility aliases */
1345 YAHOO.augment(TV, YAHOO.util.EventProvider);
1348 * Running count of all nodes created in all trees. This is
1349 * used to provide unique identifies for all nodes. Deleting
1350 * nodes does not change the nodeCount.
1351 * @property YAHOO.widget.TreeView.nodeCount
1358 * Global cache of tree instances
1359 * @property YAHOO.widget.TreeView.trees
1367 * Global method for getting a tree by its id. Used in the generated
1369 * @method YAHOO.widget.TreeView.getTree
1370 * @param treeId {String} the id of the tree instance
1371 * @return {TreeView} the tree instance requested, null if not found.
1374 TV.getTree = function(treeId) {
1375 var t = TV.trees[treeId];
1376 return (t) ? t : null;
1381 * Global method for getting a node by its id. Used in the generated
1383 * @method YAHOO.widget.TreeView.getNode
1384 * @param treeId {String} the id of the tree instance
1385 * @param nodeIndex {String} the index of the node to return
1386 * @return {Node} the node instance requested, null if not found
1389 TV.getNode = function(treeId, nodeIndex) {
1390 var t = TV.getTree(treeId);
1391 return (t) ? t.getNodeByIndex(nodeIndex) : null;
1396 * Class name assigned to elements that have the focus
1398 * @property TreeView.FOCUS_CLASS_NAME
1402 * @default "ygtvfocus"
1405 TV.FOCUS_CLASS_NAME = 'ygtvfocus';
1412 var Dom = YAHOO.util.Dom,
1414 Event = YAHOO.util.Event;
1416 * The base class for all tree nodes. The node's presentation and behavior in
1417 * response to mouse events is handled in Node subclasses.
1418 * @namespace YAHOO.widget
1420 * @uses YAHOO.util.EventProvider
1421 * @param oData {object} a string or object containing the data that will
1422 * be used to render this node, and any custom attributes that should be
1423 * stored with the node (which is available in noderef.data).
1424 * All values in oData will be used to set equally named properties in the node
1425 * as long as the node does have such properties, they are not undefined, private or functions,
1426 * the rest of the values will be stored in noderef.data
1427 * @param oParent {Node} this node's parent node
1428 * @param expanded {boolean} the initial expanded/collapsed state (deprecated, use oData.expanded)
1431 YAHOO.widget.Node = function(oData, oParent, expanded) {
1432 if (oData) { this.init(oData, oParent, expanded); }
1435 YAHOO.widget.Node.prototype = {
1438 * The index for this instance obtained from global counter in YAHOO.widget.TreeView.
1445 * This node's child node collection.
1446 * @property children
1452 * Tree instance this node is part of
1459 * The data linked to this node. This can be any object or primitive
1460 * value, and the data can be used in getNodeHtml().
1474 * The depth of this node. We start at -1 for the root node.
1481 * The node's expanded/collapsed state
1482 * @property expanded
1488 * Can multiple children be expanded at once?
1489 * @property multiExpand
1495 * Should we render children for a collapsed node? It is possible that the
1496 * implementer will want to render the hidden data... @todo verify that we
1497 * need this, and implement it if we do.
1498 * @property renderHidden
1501 renderHidden: false,
1504 * This flag is set to true when the html is generated for this node's
1505 * children, and set to false when new children are added.
1506 * @property childrenRendered
1509 childrenRendered: false,
1512 * Dynamically loaded nodes only fetch the data the first time they are
1513 * expanded. This flag is set to true once the data has been fetched.
1514 * @property dynamicLoadComplete
1517 dynamicLoadComplete: false,
1520 * This node's previous sibling
1521 * @property previousSibling
1524 previousSibling: null,
1527 * This node's next sibling
1528 * @property nextSibling
1534 * We can set the node up to call an external method to get the child
1536 * @property _dynLoad
1543 * Function to execute when we need to get this node's child data.
1544 * @property dataLoader
1550 * This is true for dynamically loading nodes while waiting for the
1551 * callback to return.
1552 * @property isLoading
1558 * The toggle/branch icon will not show if this is set to false. This
1559 * could be useful if the implementer wants to have the child contain
1560 * extra info about the parent, rather than an actual node.
1567 * Used to configure what happens when a dynamic load node is expanded
1568 * and we discover that it does not have children. By default, it is
1569 * treated as if it still could have children (plus/minus icon). Set
1570 * iconMode to have it display like a leaf node instead.
1571 * @property iconMode
1577 * Specifies whether or not the content area of the node should be allowed
1586 * If true, the node will alway be rendered as a leaf node. This can be
1587 * used to override the presentation when dynamically loading the entire
1588 * tree. Setting this to true also disables the dynamic load call for the
1597 * The CSS class for the html content container. Defaults to ygtvhtml, but
1598 * can be overridden to provide a custom presentation for a specific node.
1599 * @property contentStyle
1606 * The generated id that will contain the data passed in by the implementer.
1607 * @property contentElId
1613 * Enables node highlighting. If true, the node can be highlighted and/or propagate highlighting
1614 * @property enableHighlight
1618 enableHighlight: true,
1621 * Stores the highlight state. Can be any of:
1623 * <li>0 - not highlighted</li>
1624 * <li>1 - highlighted</li>
1625 * <li>2 - some children highlighted</li>
1627 * @property highlightState
1635 * Tells whether highlighting will be propagated up to the parents of the clicked node
1636 * @property propagateHighlightUp
1641 propagateHighlightUp: false,
1644 * Tells whether highlighting will be propagated down to the children of the clicked node
1645 * @property propagateHighlightDown
1650 propagateHighlightDown: false,
1653 * User-defined className to be added to the Node
1654 * @property className
1671 spacerPath: "http://l.yimg.com/a/i/space.gif",
1672 expandedText: "Expanded",
1673 collapsedText: "Collapsed",
1674 loadingText: "Loading",
1678 * Initializes this node, gets some of the properties from the parent
1680 * @param oData {object} a string or object containing the data that will
1681 * be used to render this node
1682 * @param oParent {Node} this node's parent node
1683 * @param expanded {boolean} the initial expanded/collapsed state
1685 init: function(oData, oParent, expanded) {
1689 this.index = YAHOO.widget.TreeView.nodeCount;
1690 ++YAHOO.widget.TreeView.nodeCount;
1691 this.contentElId = "ygtvcontentel" + this.index;
1693 if (Lang.isObject(oData)) {
1694 for (var property in oData) {
1695 if (oData.hasOwnProperty(property)) {
1696 if (property.charAt(0) != '_' && !Lang.isUndefined(this[property]) && !Lang.isFunction(this[property]) ) {
1697 this[property] = oData[property];
1699 this.data[property] = oData[property];
1704 if (!Lang.isUndefined(expanded) ) { this.expanded = expanded; }
1706 this.logger = new YAHOO.widget.LogWriter(this.toString());
1709 * The parentChange event is fired when a parent element is applied
1710 * to the node. This is useful if you need to apply tree-level
1711 * properties to a tree that need to happen if a node is moved from
1712 * one tree to another.
1714 * @event parentChange
1717 this.createEvent("parentChange", this);
1719 // oParent should never be null except when we create the root node.
1721 oParent.appendChild(this);
1726 * Certain properties for the node cannot be set until the parent
1727 * is known. This is called after the node is inserted into a tree.
1728 * the parent is also applied to this node's children in order to
1729 * make it possible to move a branch from one tree to another.
1730 * @method applyParent
1731 * @param {Node} parentNode this node's parent node
1732 * @return {boolean} true if the application was successful
1734 applyParent: function(parentNode) {
1739 this.tree = parentNode.tree;
1740 this.parent = parentNode;
1741 this.depth = parentNode.depth + 1;
1743 // @todo why was this put here. This causes new nodes added at the
1744 // root level to lose the menu behavior.
1745 // if (! this.multiExpand) {
1746 // this.multiExpand = parentNode.multiExpand;
1749 this.tree.regNode(this);
1750 parentNode.childrenRendered = false;
1752 // cascade update existing children
1753 for (var i=0, len=this.children.length;i<len;++i) {
1754 this.children[i].applyParent(this);
1757 this.fireEvent("parentChange");
1763 * Appends a node to the child collection.
1764 * @method appendChild
1765 * @param childNode {Node} the new node
1766 * @return {Node} the child node
1769 appendChild: function(childNode) {
1770 if (this.hasChildren()) {
1771 var sib = this.children[this.children.length - 1];
1772 sib.nextSibling = childNode;
1773 childNode.previousSibling = sib;
1775 this.children[this.children.length] = childNode;
1776 childNode.applyParent(this);
1778 // part of the IE display issue workaround. If child nodes
1779 // are added after the initial render, and the node was
1780 // instantiated with expanded = true, we need to show the
1781 // children div now that the node has a child.
1782 if (this.childrenRendered && this.expanded) {
1783 this.getChildrenEl().style.display = "";
1790 * Appends this node to the supplied node's child collection
1792 * @param parentNode {Node} the node to append to.
1793 * @return {Node} The appended node
1795 appendTo: function(parentNode) {
1796 return parentNode.appendChild(this);
1800 * Inserts this node before this supplied node
1801 * @method insertBefore
1802 * @param node {Node} the node to insert this node before
1803 * @return {Node} the inserted node
1805 insertBefore: function(node) {
1806 this.logger.log("insertBefore: " + node);
1807 var p = node.parent;
1811 this.tree.popNode(this);
1814 var refIndex = node.isChildOf(p);
1815 //this.logger.log(refIndex);
1816 p.children.splice(refIndex, 0, this);
1817 if (node.previousSibling) {
1818 node.previousSibling.nextSibling = this;
1820 this.previousSibling = node.previousSibling;
1821 this.nextSibling = node;
1822 node.previousSibling = this;
1824 this.applyParent(p);
1831 * Inserts this node after the supplied node
1832 * @method insertAfter
1833 * @param node {Node} the node to insert after
1834 * @return {Node} the inserted node
1836 insertAfter: function(node) {
1837 this.logger.log("insertAfter: " + node);
1838 var p = node.parent;
1842 this.tree.popNode(this);
1845 var refIndex = node.isChildOf(p);
1846 this.logger.log(refIndex);
1848 if (!node.nextSibling) {
1849 this.nextSibling = null;
1850 return this.appendTo(p);
1853 p.children.splice(refIndex + 1, 0, this);
1855 node.nextSibling.previousSibling = this;
1856 this.previousSibling = node;
1857 this.nextSibling = node.nextSibling;
1858 node.nextSibling = this;
1860 this.applyParent(p);
1867 * Returns true if the Node is a child of supplied Node
1869 * @param parentNode {Node} the Node to check
1870 * @return {boolean} The node index if this Node is a child of
1871 * supplied Node, else -1.
1874 isChildOf: function(parentNode) {
1875 if (parentNode && parentNode.children) {
1876 for (var i=0, len=parentNode.children.length; i<len ; ++i) {
1877 if (parentNode.children[i] === this) {
1887 * Returns a node array of this node's siblings, null if none.
1888 * @method getSiblings
1891 getSiblings: function() {
1892 var sib = this.parent.children.slice(0);
1893 for (var i=0;i < sib.length && sib[i] != this;i++) {}
1895 if (sib.length) { return sib; }
1900 * Shows this node's children
1901 * @method showChildren
1903 showChildren: function() {
1904 if (!this.tree.animateExpand(this.getChildrenEl(), this)) {
1905 if (this.hasChildren()) {
1906 this.getChildrenEl().style.display = "";
1912 * Hides this node's children
1913 * @method hideChildren
1915 hideChildren: function() {
1916 this.logger.log("hiding " + this.index);
1918 if (!this.tree.animateCollapse(this.getChildrenEl(), this)) {
1919 this.getChildrenEl().style.display = "none";
1924 * Returns the id for this node's container div
1926 * @return {string} the element id
1928 getElId: function() {
1929 return "ygtv" + this.index;
1933 * Returns the id for this node's children div
1934 * @method getChildrenElId
1935 * @return {string} the element id for this node's children div
1937 getChildrenElId: function() {
1938 return "ygtvc" + this.index;
1942 * Returns the id for this node's toggle element
1943 * @method getToggleElId
1944 * @return {string} the toggel element id
1946 getToggleElId: function() {
1947 return "ygtvt" + this.index;
1952 * Returns the id for this node's spacer image. The spacer is positioned
1953 * over the toggle and provides feedback for screen readers.
1954 * @method getSpacerId
1955 * @return {string} the id for the spacer image
1958 getSpacerId: function() {
1959 return "ygtvspacer" + this.index;
1964 * Returns this node's container html element
1966 * @return {HTMLElement} the container html element
1969 return Dom.get(this.getElId());
1973 * Returns the div that was generated for this node's children
1974 * @method getChildrenEl
1975 * @return {HTMLElement} this node's children div
1977 getChildrenEl: function() {
1978 return Dom.get(this.getChildrenElId());
1982 * Returns the element that is being used for this node's toggle.
1983 * @method getToggleEl
1984 * @return {HTMLElement} this node's toggle html element
1986 getToggleEl: function() {
1987 return Dom.get(this.getToggleElId());
1990 * Returns the outer html element for this node's content
1991 * @method getContentEl
1992 * @return {HTMLElement} the element
1994 getContentEl: function() {
1995 return Dom.get(this.contentElId);
2000 * Returns the element that is being used for this node's spacer.
2002 * @return {HTMLElement} this node's spacer html element
2005 getSpacer: function() {
2006 return document.getElementById( this.getSpacerId() ) || {};
2011 getStateText: function() {
2012 if (this.isLoading) {
2013 return this.loadingText;
2014 } else if (this.hasChildren(true)) {
2015 if (this.expanded) {
2016 return this.expandedText;
2018 return this.collapsedText;
2027 * Hides this nodes children (creating them if necessary), changes the toggle style.
2030 collapse: function() {
2031 // Only collapse if currently expanded
2032 if (!this.expanded) { return; }
2034 // fire the collapse event handler
2035 var ret = this.tree.onCollapse(this);
2037 if (false === ret) {
2038 this.logger.log("Collapse was stopped by the abstract onCollapse");
2042 ret = this.tree.fireEvent("collapse", this);
2044 if (false === ret) {
2045 this.logger.log("Collapse was stopped by a custom event handler");
2050 if (!this.getEl()) {
2051 this.expanded = false;
2053 // hide the child div
2054 this.hideChildren();
2055 this.expanded = false;
2060 // this.getSpacer().title = this.getStateText();
2062 ret = this.tree.fireEvent("collapseComplete", this);
2067 * Shows this nodes children (creating them if necessary), changes the
2068 * toggle style, and collapses its siblings if multiExpand is not set.
2071 expand: function(lazySource) {
2072 // Only expand if currently collapsed.
2073 if (this.isLoading || (this.expanded && !lazySource)) {
2079 // When returning from the lazy load handler, expand is called again
2080 // in order to render the new children. The "expand" event already
2081 // fired before fething the new data, so we need to skip it now.
2083 // fire the expand event handler
2084 ret = this.tree.onExpand(this);
2086 if (false === ret) {
2087 this.logger.log("Expand was stopped by the abstract onExpand");
2091 ret = this.tree.fireEvent("expand", this);
2094 if (false === ret) {
2095 this.logger.log("Expand was stopped by the custom event handler");
2099 if (!this.getEl()) {
2100 this.expanded = true;
2104 if (!this.childrenRendered) {
2105 this.logger.log("children not rendered yet");
2106 this.getChildrenEl().innerHTML = this.renderChildren();
2108 this.logger.log("children already rendered");
2111 this.expanded = true;
2115 // this.getSpacer().title = this.getStateText();
2117 // We do an extra check for children here because the lazy
2118 // load feature can expose nodes that have no children.
2120 // if (!this.hasChildren()) {
2121 if (this.isLoading) {
2122 this.expanded = false;
2126 if (! this.multiExpand) {
2127 var sibs = this.getSiblings();
2128 for (var i=0; sibs && i<sibs.length; ++i) {
2129 if (sibs[i] != this && sibs[i].expanded) {
2135 this.showChildren();
2137 ret = this.tree.fireEvent("expandComplete", this);
2140 updateIcon: function() {
2142 var el = this.getToggleEl();
2144 el.className = el.className.replace(/\bygtv(([tl][pmn]h?)|(loading))\b/gi,this.getStyle());
2150 * Returns the css style name for the toggle
2152 * @return {string} the css class for this node's toggle
2154 getStyle: function() {
2155 // this.logger.log("No children, " + " isDyanmic: " + this.isDynamic() + " expanded: " + this.expanded);
2156 if (this.isLoading) {
2157 this.logger.log("returning the loading icon");
2158 return "ygtvloading";
2160 // location top or bottom, middle nodes also get the top style
2161 var loc = (this.nextSibling) ? "t" : "l";
2163 // type p=plus(expand), m=minus(collapase), n=none(no children)
2165 if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) {
2166 // if (this.hasChildren(true)) {
2167 type = (this.expanded) ? "m" : "p";
2170 // this.logger.log("ygtv" + loc + type);
2171 return "ygtv" + loc + type;
2176 * Returns the hover style for the icon
2177 * @return {string} the css class hover state
2178 * @method getHoverStyle
2180 getHoverStyle: function() {
2181 var s = this.getStyle();
2182 if (this.hasChildren(true) && !this.isLoading) {
2189 * Recursively expands all of this node's children.
2192 expandAll: function() {
2193 var l = this.children.length;
2194 for (var i=0;i<l;++i) {
2195 var c = this.children[i];
2196 if (c.isDynamic()) {
2197 this.logger.log("Not supported (lazy load + expand all)");
2199 } else if (! c.multiExpand) {
2200 this.logger.log("Not supported (no multi-expand + expand all)");
2210 * Recursively collapses all of this node's children.
2211 * @method collapseAll
2213 collapseAll: function() {
2214 for (var i=0;i<this.children.length;++i) {
2215 this.children[i].collapse();
2216 this.children[i].collapseAll();
2221 * Configures this node for dynamically obtaining the child data
2222 * when the node is first expanded. Calling it without the callback
2223 * will turn off dynamic load for the node.
2224 * @method setDynamicLoad
2225 * @param fmDataLoader {function} the function that will be used to get the data.
2226 * @param iconMode {int} configures the icon that is displayed when a dynamic
2227 * load node is expanded the first time without children. By default, the
2228 * "collapse" icon will be used. If set to 1, the leaf node icon will be
2231 setDynamicLoad: function(fnDataLoader, iconMode) {
2233 this.dataLoader = fnDataLoader;
2234 this._dynLoad = true;
2236 this.dataLoader = null;
2237 this._dynLoad = false;
2241 this.iconMode = iconMode;
2246 * Evaluates if this node is the root node of the tree
2248 * @return {boolean} true if this is the root node
2250 isRoot: function() {
2251 return (this == this.tree.root);
2255 * Evaluates if this node's children should be loaded dynamically. Looks for
2256 * the property both in this instance and the root node. If the tree is
2257 * defined to load all children dynamically, the data callback function is
2258 * defined in the root node
2260 * @return {boolean} true if this node's children are to be loaded dynamically
2262 isDynamic: function() {
2266 return (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad));
2267 // this.logger.log("isDynamic: " + lazy);
2273 * Returns the current icon mode. This refers to the way childless dynamic
2274 * load nodes appear (this comes into play only after the initial dynamic
2275 * load request produced no children).
2276 * @method getIconMode
2277 * @return {int} 0 for collapse style, 1 for leaf node style
2279 getIconMode: function() {
2280 return (this.iconMode || this.tree.root.iconMode);
2284 * Checks if this node has children. If this node is lazy-loading and the
2285 * children have not been rendered, we do not know whether or not there
2286 * are actual children. In most cases, we need to assume that there are
2287 * children (for instance, the toggle needs to show the expandable
2288 * presentation state). In other times we want to know if there are rendered
2289 * children. For the latter, "checkForLazyLoad" should be false.
2290 * @method hasChildren
2291 * @param checkForLazyLoad {boolean} should we check for unloaded children?
2292 * @return {boolean} true if this has children or if it might and we are
2293 * checking for this condition.
2295 hasChildren: function(checkForLazyLoad) {
2299 return ( this.children.length > 0 ||
2300 (checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete)
2306 * Expands if node is collapsed, collapses otherwise.
2309 toggle: function() {
2310 if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) {
2311 if (this.expanded) { this.collapse(); } else { this.expand(); }
2316 * Returns the markup for this node and its children.
2318 * @return {string} the markup for this node and its expanded children.
2320 getHtml: function() {
2322 this.childrenRendered = false;
2324 return ['<div class="ygtvitem" id="' , this.getElId() , '">' ,this.getNodeHtml() , this.getChildrenHtml() ,'</div>'].join("");
2328 * Called when first rendering the tree. We always build the div that will
2329 * contain this nodes children, but we don't render the children themselves
2330 * unless this node is expanded.
2331 * @method getChildrenHtml
2332 * @return {string} the children container div html and any expanded children
2335 getChildrenHtml: function() {
2339 sb[sb.length] = '<div class="ygtvchildren" id="' + this.getChildrenElId() + '"';
2341 // This is a workaround for an IE rendering issue, the child div has layout
2342 // in IE, creating extra space if a leaf node is created with the expanded
2343 // property set to true.
2344 if (!this.expanded || !this.hasChildren()) {
2345 sb[sb.length] = ' style="display:none;"';
2347 sb[sb.length] = '>';
2349 // this.logger.log(["index", this.index,
2350 // "hasChildren", this.hasChildren(true),
2351 // "expanded", this.expanded,
2352 // "renderHidden", this.renderHidden,
2353 // "isDynamic", this.isDynamic()]);
2355 // Don't render the actual child node HTML unless this node is expanded.
2356 if ( (this.hasChildren(true) && this.expanded) ||
2357 (this.renderHidden && !this.isDynamic()) ) {
2358 sb[sb.length] = this.renderChildren();
2361 sb[sb.length] = '</div>';
2367 * Generates the markup for the child nodes. This is not done until the node
2369 * @method renderChildren
2370 * @return {string} the html for this node's children
2373 renderChildren: function() {
2375 this.logger.log("rendering children for " + this.index);
2379 if (this.isDynamic() && !this.dynamicLoadComplete) {
2380 this.isLoading = true;
2381 this.tree.locked = true;
2383 if (this.dataLoader) {
2384 this.logger.log("Using dynamic loader defined for this node");
2388 node.dataLoader(node,
2390 node.loadComplete();
2394 } else if (this.tree.root.dataLoader) {
2395 this.logger.log("Using the tree-level dynamic loader");
2399 node.tree.root.dataLoader(node,
2401 node.loadComplete();
2406 this.logger.log("no loader found");
2407 return "Error: data loader not found or not specified.";
2413 return this.completeRender();
2418 * Called when we know we have all the child data.
2419 * @method completeRender
2420 * @return {string} children html
2422 completeRender: function() {
2423 this.logger.log("completeRender: " + this.index + ", # of children: " + this.children.length);
2426 for (var i=0; i < this.children.length; ++i) {
2427 // this.children[i].childrenRendered = false;
2428 sb[sb.length] = this.children[i].getHtml();
2431 this.childrenRendered = true;
2437 * Load complete is the callback function we pass to the data provider
2438 * in dynamic load situations.
2439 * @method loadComplete
2441 loadComplete: function() {
2442 this.logger.log(this.index + " loadComplete, children: " + this.children.length);
2443 this.getChildrenEl().innerHTML = this.completeRender();
2444 if (this.propagateHighlightDown) {
2445 if (this.highlightState === 1 && !this.tree.singleNodeHighlight) {
2446 for (var i = 0; i < this.children.length; i++) {
2447 this.children[i].highlight(true);
2449 } else if (this.highlightState === 0 || this.tree.singleNodeHighlight) {
2450 for (i = 0; i < this.children.length; i++) {
2451 this.children[i].unhighlight(true);
2453 } // if (highlighState == 2) leave child nodes with whichever highlight state they are set
2456 this.dynamicLoadComplete = true;
2457 this.isLoading = false;
2459 this.tree.locked = false;
2463 * Returns this node's ancestor at the specified depth.
2464 * @method getAncestor
2465 * @param {int} depth the depth of the ancestor.
2466 * @return {Node} the ancestor
2468 getAncestor: function(depth) {
2469 if (depth >= this.depth || depth < 0) {
2470 this.logger.log("illegal getAncestor depth: " + depth);
2474 var p = this.parent;
2476 while (p.depth > depth) {
2484 * Returns the css class for the spacer at the specified depth for
2485 * this node. If this node's ancestor at the specified depth
2486 * has a next sibling the presentation is different than if it
2487 * does not have a next sibling
2488 * @method getDepthStyle
2489 * @param {int} depth the depth of the ancestor.
2490 * @return {string} the css class for the spacer
2492 getDepthStyle: function(depth) {
2493 return (this.getAncestor(depth).nextSibling) ?
2494 "ygtvdepthcell" : "ygtvblankdepthcell";
2498 * Get the markup for the node. This may be overrided so that we can
2499 * support different types of nodes.
2500 * @method getNodeHtml
2501 * @return {string} The HTML that will render this node.
2503 getNodeHtml: function() {
2504 this.logger.log("Generating html");
2507 sb[sb.length] = '<table id="ygtvtableel' + this.index + '" border="0" cellpadding="0" cellspacing="0" class="ygtvtable ygtvdepth' + this.depth;
2508 if (this.enableHighlight) {
2509 sb[sb.length] = ' ygtv-highlight' + this.highlightState;
2511 if (this.className) {
2512 sb[sb.length] = ' ' + this.className;
2514 sb[sb.length] = '"><tr class="ygtvrow">';
2516 for (var i=0;i<this.depth;++i) {
2517 sb[sb.length] = '<td class="ygtvcell ' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>';
2521 sb[sb.length] = '<td id="' + this.getToggleElId();
2522 sb[sb.length] = '" class="ygtvcell ';
2523 sb[sb.length] = this.getStyle() ;
2524 sb[sb.length] = '"><a href="#" class="ygtvspacer"> </a></td>';
2527 sb[sb.length] = '<td id="' + this.contentElId;
2528 sb[sb.length] = '" class="ygtvcell ';
2529 sb[sb.length] = this.contentStyle + ' ygtvcontent" ';
2530 sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : '';
2531 sb[sb.length] = ' >';
2532 sb[sb.length] = this.getContentHtml();
2533 sb[sb.length] = '</td></tr></table>';
2539 * Get the markup for the contents of the node. This is designed to be overrided so that we can
2540 * support different types of nodes.
2541 * @method getContentHtml
2542 * @return {string} The HTML that will render the content of this node.
2544 getContentHtml: function () {
2549 * Regenerates the html for this node and its children. To be used when the
2550 * node is expanded and new children have been added.
2553 refresh: function() {
2554 // this.loadComplete();
2555 this.getChildrenEl().innerHTML = this.completeRender();
2558 var el = this.getToggleEl();
2560 el.className = el.className.replace(/\bygtv[lt][nmp]h*\b/gi,this.getStyle());
2568 * @return {string} string representation of the node
2570 toString: function() {
2571 return this._type + " (" + this.index + ")";
2574 * array of items that had the focus set on them
2575 * so that they can be cleaned when focus is lost
2576 * @property _focusHighlightedItems
2577 * @type Array of DOM elements
2580 _focusHighlightedItems: [],
2582 * DOM element that actually got the browser focus
2583 * @property _focusedItem
2590 * Returns true if there are any elements in the node that can
2591 * accept the real actual browser focus
2592 * @method _canHaveFocus
2593 * @return {boolean} success
2596 _canHaveFocus: function() {
2597 return this.getEl().getElementsByTagName('a').length > 0;
2600 * Removes the focus of previously selected Node
2601 * @method _removeFocus
2604 _removeFocus:function () {
2605 if (this._focusedItem) {
2606 Event.removeListener(this._focusedItem,'blur');
2607 this._focusedItem = null;
2610 while ((el = this._focusHighlightedItems.shift())) { // yes, it is meant as an assignment, really
2611 Dom.removeClass(el,YAHOO.widget.TreeView.FOCUS_CLASS_NAME );
2615 * Sets the focus on the node element.
2616 * It will only be able to set the focus on nodes that have anchor elements in it.
2617 * Toggle or branch icons have anchors and can be focused on.
2618 * If will fail in nodes that have no anchor
2620 * @return {boolean} success
2622 focus: function () {
2623 var focused = false, self = this;
2625 if (this.tree.currentFocus) {
2626 this.tree.currentFocus._removeFocus();
2629 var expandParent = function (node) {
2631 expandParent(node.parent);
2632 node.parent.expand();
2639 return (/ygtv(([tl][pmn]h?)|(content))/).test(el.className);
2642 self.getEl().firstChild ,
2644 Dom.addClass(el, YAHOO.widget.TreeView.FOCUS_CLASS_NAME );
2646 var aEl = el.getElementsByTagName('a');
2650 self._focusedItem = aEl;
2651 Event.on(aEl,'blur',function () {
2652 //console.log('f1');
2653 self.tree.fireEvent('focusChanged',{oldNode:self.tree.currentFocus,newNode:null});
2654 self.tree.currentFocus = null;
2655 self._removeFocus();
2660 self._focusHighlightedItems.push(el);
2664 //console.log('f2');
2665 this.tree.fireEvent('focusChanged',{oldNode:this.tree.currentFocus,newNode:this});
2666 this.tree.currentFocus = this;
2668 //console.log('f3');
2669 this.tree.fireEvent('focusChanged',{oldNode:self.tree.currentFocus,newNode:null});
2670 this.tree.currentFocus = null;
2671 this._removeFocus();
2677 * Count of nodes in a branch
2678 * @method getNodeCount
2679 * @return {int} number of nodes in the branch
2681 getNodeCount: function() {
2682 for (var i = 0, count = 0;i< this.children.length;i++) {
2683 count += this.children[i].getNodeCount();
2689 * Returns an object which could be used to build a tree out of this node and its children.
2690 * It can be passed to the tree constructor to reproduce this node as a tree.
2691 * It will return false if the node or any children loads dynamically, regardless of whether it is loaded or not.
2692 * @method getNodeDefinition
2693 * @return {Object | false} definition of the tree or false if the node or any children is defined as dynamic
2695 getNodeDefinition: function() {
2697 if (this.isDynamic()) { return false; }
2699 var def, defs = Lang.merge(this.data), children = [];
2703 if (this.expanded) {defs.expanded = this.expanded; }
2704 if (!this.multiExpand) { defs.multiExpand = this.multiExpand; }
2705 if (!this.renderHidden) { defs.renderHidden = this.renderHidden; }
2706 if (!this.hasIcon) { defs.hasIcon = this.hasIcon; }
2707 if (this.nowrap) { defs.nowrap = this.nowrap; }
2708 if (this.className) { defs.className = this.className; }
2709 if (this.editable) { defs.editable = this.editable; }
2710 if (this.enableHighlight) { defs.enableHighlight = this.enableHighlight; }
2711 if (this.highlightState) { defs.highlightState = this.highlightState; }
2712 if (this.propagateHighlightUp) { defs.propagateHighlightUp = this.propagateHighlightUp; }
2713 if (this.propagateHighlightDown) { defs.propagateHighlightDown = this.propagateHighlightDown; }
2714 defs.type = this._type;
2718 for (var i = 0; i < this.children.length;i++) {
2719 def = this.children[i].getNodeDefinition();
2720 if (def === false) { return false;}
2723 if (children.length) { defs.children = children; }
2729 * Generates the link that will invoke this node's toggle method
2730 * @method getToggleLink
2731 * @return {string} the javascript url for toggling this node
2733 getToggleLink: function() {
2734 return 'return false;';
2738 * Sets the value of property for this node and all loaded descendants.
2739 * Only public and defined properties can be set, not methods.
2740 * Values for unknown properties will be assigned to the refNode.data object
2741 * @method setNodesProperty
2742 * @param name {string} Name of the property to be set
2743 * @param value {any} value to be set
2744 * @param refresh {boolean} if present and true, it does a refresh
2746 setNodesProperty: function(name, value, refresh) {
2747 if (name.charAt(0) != '_' && !Lang.isUndefined(this[name]) && !Lang.isFunction(this[name]) ) {
2750 this.data[name] = value;
2752 for (var i = 0; i < this.children.length;i++) {
2753 this.children[i].setNodesProperty(name,value);
2760 * Toggles the highlighted state of a Node
2761 * @method toggleHighlight
2763 toggleHighlight: function() {
2764 if (this.enableHighlight) {
2765 // unhighlights only if fully highligthed. For not or partially highlighted it will highlight
2766 if (this.highlightState == 1) {
2775 * Turns highlighting on node.
2777 * @param _silent {boolean} optional, don't fire the highlightEvent
2779 highlight: function(_silent) {
2780 if (this.enableHighlight) {
2781 if (this.tree.singleNodeHighlight) {
2782 if (this.tree._currentlyHighlighted) {
2783 this.tree._currentlyHighlighted.unhighlight(_silent);
2785 this.tree._currentlyHighlighted = this;
2787 this.highlightState = 1;
2788 this._setHighlightClassName();
2789 if (!this.tree.singleNodeHighlight) {
2790 if (this.propagateHighlightDown) {
2791 for (var i = 0;i < this.children.length;i++) {
2792 this.children[i].highlight(true);
2795 if (this.propagateHighlightUp) {
2797 this.parent._childrenHighlighted();
2802 this.tree.fireEvent('highlightEvent',this);
2807 * Turns highlighting off a node.
2808 * @method unhighlight
2809 * @param _silent {boolean} optional, don't fire the highlightEvent
2811 unhighlight: function(_silent) {
2812 if (this.enableHighlight) {
2813 // might have checked singleNodeHighlight but it wouldn't really matter either way
2814 this.tree._currentlyHighlighted = null;
2815 this.highlightState = 0;
2816 this._setHighlightClassName();
2817 if (!this.tree.singleNodeHighlight) {
2818 if (this.propagateHighlightDown) {
2819 for (var i = 0;i < this.children.length;i++) {
2820 this.children[i].unhighlight(true);
2823 if (this.propagateHighlightUp) {
2825 this.parent._childrenHighlighted();
2830 this.tree.fireEvent('highlightEvent',this);
2835 * Checks whether all or part of the children of a node are highlighted and
2836 * sets the node highlight to full, none or partial highlight.
2837 * If set to propagate it will further call the parent
2838 * @method _childrenHighlighted
2841 _childrenHighlighted: function() {
2842 var yes = false, no = false;
2843 if (this.enableHighlight) {
2844 for (var i = 0;i < this.children.length;i++) {
2845 switch(this.children[i].highlightState) {
2858 this.highlightState = 2;
2860 this.highlightState = 1;
2862 this.highlightState = 0;
2864 this._setHighlightClassName();
2865 if (this.propagateHighlightUp) {
2867 this.parent._childrenHighlighted();
2874 * Changes the classNames on the toggle and content containers to reflect the current highlighting
2875 * @method _setHighlightClassName
2878 _setHighlightClassName: function() {
2879 var el = Dom.get('ygtvtableel' + this.index);
2881 el.className = el.className.replace(/\bygtv-highlight\d\b/gi,'ygtv-highlight' + this.highlightState);
2887 YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider);
2891 * A custom YAHOO.widget.Node that handles the unique nature of
2892 * the virtual, presentationless root node.
2893 * @namespace YAHOO.widget
2895 * @extends YAHOO.widget.Node
2896 * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to
2899 YAHOO.widget.RootNode = function(oTree) {
2900 // Initialize the node with null params. The root node is a
2901 // special case where the node has no presentation. So we have
2902 // to alter the standard properties a bit.
2903 this.init(null, null, true);
2906 * For the root node, we get the tree reference from as a param
2907 * to the constructor instead of from the parent element.
2912 YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, {
2919 * @default "RootNode"
2923 // overrides YAHOO.widget.Node
2924 getNodeHtml: function() {
2928 toString: function() {
2932 loadComplete: function() {
2937 * Count of nodes in tree.
2938 * It overrides Nodes.getNodeCount because the root node should not be counted.
2939 * @method getNodeCount
2940 * @return {int} number of nodes in the tree
2942 getNodeCount: function() {
2943 for (var i = 0, count = 0;i< this.children.length;i++) {
2944 count += this.children[i].getNodeCount();
2950 * Returns an object which could be used to build a tree out of this node and its children.
2951 * It can be passed to the tree constructor to reproduce this node as a tree.
2952 * Since the RootNode is automatically created by treeView,
2953 * its own definition is excluded from the returned node definition
2954 * which only contains its children.
2955 * @method getNodeDefinition
2956 * @return {Object | false} definition of the tree or false if any child node is defined as dynamic
2958 getNodeDefinition: function() {
2960 for (var def, defs = [], i = 0; i < this.children.length;i++) {
2961 def = this.children[i].getNodeDefinition();
2962 if (def === false) { return false;}
2968 collapse: function() {},
2969 expand: function() {},
2970 getSiblings: function() { return null; },
2971 focus: function () {}
2976 var Dom = YAHOO.util.Dom,
2978 Event = YAHOO.util.Event;
2980 * The default node presentation. The first parameter should be
2981 * either a string that will be used as the node's label, or an object
2982 * that has at least a string property called label. By default, clicking the
2983 * label will toggle the expanded/collapsed state of the node. By
2984 * setting the href property of the instance, this behavior can be
2985 * changed so that the label will go to the specified href.
2986 * @namespace YAHOO.widget
2988 * @extends YAHOO.widget.Node
2990 * @param oData {object} a string or object containing the data that will
2991 * be used to render this node.
2992 * Providing a string is the same as providing an object with a single property named label.
2993 * All values in the oData will be used to set equally named properties in the node
2994 * as long as the node does have such properties, they are not undefined, private or functions.
2995 * All attributes are made available in noderef.data, which
2996 * can be used to store custom attributes. TreeView.getNode(s)ByProperty
2997 * can be used to retrieve a node by one of the attributes.
2998 * @param oParent {YAHOO.widget.Node} this node's parent node
2999 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
3001 YAHOO.widget.TextNode = function(oData, oParent, expanded) {
3004 if (Lang.isString(oData)) {
3005 oData = { label: oData };
3007 this.init(oData, oParent, expanded);
3008 this.setUpLabel(oData);
3011 this.logger = new YAHOO.widget.LogWriter(this.toString());
3014 YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, {
3017 * The CSS class for the label href. Defaults to ygtvlabel, but can be
3018 * overridden to provide a custom presentation for a specific node.
3019 * @property labelStyle
3022 labelStyle: "ygtvlabel",
3025 * The derived element id of the label for this node
3026 * @property labelElId
3032 * The text for the label. It is assumed that the oData parameter will
3033 * either be a string that will be used as the label, or an object that
3034 * has a property called "label" that we will use.
3041 * The text for the title (tooltip) for the label element
3048 * The href for the node's label. If one is not specified, the href will
3049 * be set so that it toggles the node.
3056 * The label href target, defaults to current window
3067 * @default "TextNode"
3073 * Sets up the node label
3074 * @method setUpLabel
3075 * @param oData string containing the label, or an object with a label property
3077 setUpLabel: function(oData) {
3079 if (Lang.isString(oData)) {
3085 this.labelStyle = oData.style;
3089 this.label = oData.label;
3091 this.labelElId = "ygtvlabelel" + this.index;
3096 * Returns the label element
3097 * @for YAHOO.widget.TextNode
3098 * @method getLabelEl
3099 * @return {object} the element
3101 getLabelEl: function() {
3102 return Dom.get(this.labelElId);
3105 // overrides YAHOO.widget.Node
3106 getContentHtml: function() {
3108 sb[sb.length] = this.href?'<a':'<span';
3109 sb[sb.length] = ' id="' + this.labelElId + '"';
3110 sb[sb.length] = ' class="' + this.labelStyle + '"';
3112 sb[sb.length] = ' href="' + this.href + '"';
3113 sb[sb.length] = ' target="' + this.target + '"';
3116 sb[sb.length] = ' title="' + this.title + '"';
3118 sb[sb.length] = ' >';
3119 sb[sb.length] = this.label;
3120 sb[sb.length] = this.href?'</a>':'</span>';
3127 * Returns an object which could be used to build a tree out of this node and its children.
3128 * It can be passed to the tree constructor to reproduce this node as a tree.
3129 * It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not.
3130 * @method getNodeDefinition
3131 * @return {Object | false} definition of the tree or false if this node or any descendant is defined as dynamic
3133 getNodeDefinition: function() {
3134 var def = YAHOO.widget.TextNode.superclass.getNodeDefinition.call(this);
3135 if (def === false) { return false; }
3137 // Node specific properties
3138 def.label = this.label;
3139 if (this.labelStyle != 'ygtvlabel') { def.style = this.labelStyle; }
3140 if (this.title) { def.title = this.title; }
3141 if (this.href) { def.href = this.href; }
3142 if (this.target != '_self') { def.target = this.target; }
3148 toString: function() {
3149 return YAHOO.widget.TextNode.superclass.toString.call(this) + ": " + this.label;
3153 onLabelClick: function() {
3156 refresh: function() {
3157 YAHOO.widget.TextNode.superclass.refresh.call(this);
3158 var label = this.getLabelEl();
3159 label.innerHTML = this.label;
3160 if (label.tagName.toUpperCase() == 'A') {
3161 label.href = this.href;
3162 label.target = this.target;
3173 * A menu-specific implementation that differs from TextNode in that only
3174 * one sibling can be expanded at a time.
3175 * @namespace YAHOO.widget
3177 * @extends YAHOO.widget.TextNode
3178 * @param oData {object} a string or object containing the data that will
3179 * be used to render this node.
3180 * Providing a string is the same as providing an object with a single property named label.
3181 * All values in the oData will be used to set equally named properties in the node
3182 * as long as the node does have such properties, they are not undefined, private or functions.
3183 * All attributes are made available in noderef.data, which
3184 * can be used to store custom attributes. TreeView.getNode(s)ByProperty
3185 * can be used to retrieve a node by one of the attributes.
3186 * @param oParent {YAHOO.widget.Node} this node's parent node
3187 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
3190 YAHOO.widget.MenuNode = function(oData, oParent, expanded) {
3191 YAHOO.widget.MenuNode.superclass.constructor.call(this,oData,oParent,expanded);
3194 * Menus usually allow only one branch to be open at a time.
3196 this.multiExpand = false;
3200 YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, {
3206 * @default "MenuNode"
3213 var Dom = YAHOO.util.Dom,
3215 Event = YAHOO.util.Event;
3218 * This implementation takes either a string or object for the
3219 * oData argument. If is it a string, it will use it for the display
3220 * of this node (and it can contain any html code). If the parameter
3221 * is an object,it looks for a parameter called "html" that will be
3222 * used for this node's display.
3223 * @namespace YAHOO.widget
3225 * @extends YAHOO.widget.Node
3227 * @param oData {object} a string or object containing the data that will
3228 * be used to render this node.
3229 * Providing a string is the same as providing an object with a single property named html.
3230 * All values in the oData will be used to set equally named properties in the node
3231 * as long as the node does have such properties, they are not undefined, private or functions.
3232 * All other attributes are made available in noderef.data, which
3233 * can be used to store custom attributes. TreeView.getNode(s)ByProperty
3234 * can be used to retrieve a node by one of the attributes.
3235 * @param oParent {YAHOO.widget.Node} this node's parent node
3236 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
3237 * @param hasIcon {boolean} specifies whether or not leaf nodes should
3238 * be rendered with or without a horizontal line line and/or toggle icon. If the icon
3239 * is not displayed, the content fills the space it would have occupied.
3240 * This option operates independently of the leaf node presentation logic
3241 * for dynamic nodes.
3242 * (deprecated; use oData.hasIcon)
3244 YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) {
3246 this.init(oData, oParent, expanded);
3247 this.initContent(oData, hasIcon);
3251 YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, {
3254 * The CSS class for the html content container. Defaults to ygtvhtml, but
3255 * can be overridden to provide a custom presentation for a specific node.
3256 * @property contentStyle
3259 contentStyle: "ygtvhtml",
3263 * The HTML content to use for this node's display
3274 * @default "HTMLNode"
3279 * Sets up the node label
3280 * @property initContent
3281 * @param oData {object} An html string or object containing an html property
3282 * @param hasIcon {boolean} determines if the node will be rendered with an
3285 initContent: function(oData, hasIcon) {
3286 this.setHtml(oData);
3287 this.contentElId = "ygtvcontentel" + this.index;
3288 if (!Lang.isUndefined(hasIcon)) { this.hasIcon = hasIcon; }
3290 this.logger = new YAHOO.widget.LogWriter(this.toString());
3294 * Synchronizes the node.html, and the node's content
3296 * @param o {object} An html string or object containing an html property
3298 setHtml: function(o) {
3300 this.html = (typeof o === "string") ? o : o.html;
3302 var el = this.getContentEl();
3304 el.innerHTML = this.html;
3309 // overrides YAHOO.widget.Node
3310 getContentHtml: function() {
3315 * Returns an object which could be used to build a tree out of this node and its children.
3316 * It can be passed to the tree constructor to reproduce this node as a tree.
3317 * It will return false if any node loads dynamically, regardless of whether it is loaded or not.
3318 * @method getNodeDefinition
3319 * @return {Object | false} definition of the tree or false if any node is defined as dynamic
3321 getNodeDefinition: function() {
3322 var def = YAHOO.widget.HTMLNode.superclass.getNodeDefinition.call(this);
3323 if (def === false) { return false; }
3324 def.html = this.html;
3332 var Dom = YAHOO.util.Dom,
3334 Event = YAHOO.util.Event,
3335 Calendar = YAHOO.widget.Calendar;
3338 * A Date-specific implementation that differs from TextNode in that it uses
3339 * YAHOO.widget.Calendar as an in-line editor, if available
3340 * If Calendar is not available, it behaves as a plain TextNode.
3341 * @namespace YAHOO.widget
3343 * @extends YAHOO.widget.TextNode
3344 * @param oData {object} a string or object containing the data that will
3345 * be used to render this node.
3346 * Providing a string is the same as providing an object with a single property named label.
3347 * All values in the oData will be used to set equally named properties in the node
3348 * as long as the node does have such properties, they are not undefined, private nor functions.
3349 * All attributes are made available in noderef.data, which
3350 * can be used to store custom attributes. TreeView.getNode(s)ByProperty
3351 * can be used to retrieve a node by one of the attributes.
3352 * @param oParent {YAHOO.widget.Node} this node's parent node
3353 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded)
3356 YAHOO.widget.DateNode = function(oData, oParent, expanded) {
3357 YAHOO.widget.DateNode.superclass.constructor.call(this,oData, oParent, expanded);
3360 YAHOO.extend(YAHOO.widget.DateNode, YAHOO.widget.TextNode, {
3367 * @default "DateNode"
3372 * Configuration object for the Calendar editor, if used.
3373 * See <a href="http://developer.yahoo.com/yui/calendar/#internationalization">http://developer.yahoo.com/yui/calendar/#internationalization</a>
3374 * @property calendarConfig
3376 calendarConfig: null,
3381 * If YAHOO.widget.Calendar is available, it will pop up a Calendar to enter a new date. Otherwise, it falls back to a plain <input> textbox
3382 * @method fillEditorContainer
3383 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3386 fillEditorContainer: function (editorData) {
3388 var cal, container = editorData.inputContainer;
3390 if (Lang.isUndefined(Calendar)) {
3391 Dom.replaceClass(editorData.editorPanel,'ygtv-edit-DateNode','ygtv-edit-TextNode');
3392 YAHOO.widget.DateNode.superclass.fillEditorContainer.call(this, editorData);
3396 if (editorData.nodeType != this._type) {
3397 editorData.nodeType = this._type;
3398 editorData.saveOnEnter = false;
3400 editorData.node.destroyEditorContents(editorData);
3402 editorData.inputObject = cal = new Calendar(container.appendChild(document.createElement('div')));
3403 if (this.calendarConfig) {
3404 cal.cfg.applyConfig(this.calendarConfig,true);
3405 cal.cfg.fireQueue();
3407 cal.selectEvent.subscribe(function () {
3408 this.tree._closeEditor(true);
3411 cal = editorData.inputObject;
3414 editorData.oldValue = this.label;
3415 cal.cfg.setProperty("selected",this.label, false);
3417 var delim = cal.cfg.getProperty('DATE_FIELD_DELIMITER');
3418 var pageDate = this.label.split(delim);
3419 cal.cfg.setProperty('pagedate',pageDate[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] + delim + pageDate[cal.cfg.getProperty('MDY_YEAR_POSITION') -1]);
3420 cal.cfg.fireQueue();
3423 cal.oDomContainer.focus();
3426 * Returns the value from the input element.
3427 * Overrides Node.getEditorValue.
3428 * @method getEditorValue
3429 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3430 * @return {string} date entered
3433 getEditorValue: function (editorData) {
3434 if (Lang.isUndefined(Calendar)) {
3435 return editorData.inputElement.value;
3437 var cal = editorData.inputObject,
3438 date = cal.getSelectedDates()[0],
3441 dd[cal.cfg.getProperty('MDY_DAY_POSITION') -1] = date.getDate();
3442 dd[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] = date.getMonth() + 1;
3443 dd[cal.cfg.getProperty('MDY_YEAR_POSITION') -1] = date.getFullYear();
3444 return dd.join(cal.cfg.getProperty('DATE_FIELD_DELIMITER'));
3449 * Finally displays the newly entered date in the tree.
3450 * Overrides Node.displayEditedValue.
3451 * @method displayEditedValue
3452 * @param value {string} date to be displayed and stored in the node
3453 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3455 displayEditedValue: function (value,editorData) {
3456 var node = editorData.node;
3458 node.getLabelEl().innerHTML = value;
3461 * Returns an object which could be used to build a tree out of this node and its children.
3462 * It can be passed to the tree constructor to reproduce this node as a tree.
3463 * It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not.
3464 * @method getNodeDefinition
3465 * @return {Object | false} definition of the node or false if this node or any descendant is defined as dynamic
3467 getNodeDefinition: function() {
3468 var def = YAHOO.widget.DateNode.superclass.getNodeDefinition.call(this);
3469 if (def === false) { return false; }
3470 if (this.calendarConfig) { def.calendarConfig = this.calendarConfig; }
3479 var Dom = YAHOO.util.Dom,
3481 Event = YAHOO.util.Event,
3482 TV = YAHOO.widget.TreeView,
3483 TVproto = TV.prototype;
3486 * An object to store information used for in-line editing
3487 * for all Nodes of all TreeViews. It contains:
3489 * <li>active {boolean}, whether there is an active cell editor </li>
3490 * <li>whoHasIt {YAHOO.widget.TreeView} TreeView instance that is currently using the editor</li>
3491 * <li>nodeType {string} value of static Node._type property, allows reuse of input element if node is of the same type.</li>
3492 * <li>editorPanel {HTMLelement (<div>)} element holding the in-line editor</li>
3493 * <li>inputContainer {HTMLelement (<div>)} element which will hold the type-specific input element(s) to be filled by the fillEditorContainer method</li>
3494 * <li>buttonsContainer {HTMLelement (<div>)} element which holds the <button> elements for Ok/Cancel. If you don't want any of the buttons, hide it via CSS styles, don't destroy it</li>
3495 * <li>node {YAHOO.widget.Node} reference to the Node being edited</li>
3496 * <li>saveOnEnter {boolean}, whether the Enter key should be accepted as a Save command (Esc. is always taken as Cancel), disable for multi-line input elements </li>
3497 * <li>oldValue {any} value before editing</li>
3499 * Editors are free to use this object to store additional data.
3500 * @property editorData
3502 * @for YAHOO.widget.TreeView
3506 whoHasIt:null, // which TreeView has it
3509 inputContainer:null,
3510 buttonsContainer:null,
3511 node:null, // which Node is being edited
3514 // Each node type is free to add its own properties to this as it sees fit.
3518 * Validator function for edited data, called from the TreeView instance scope,
3519 * receives the arguments (newValue, oldValue, nodeInstance)
3520 * and returns either the validated (or type-converted) value or undefined.
3521 * An undefined return will prevent the editor from closing
3522 * @property validator
3525 * @for YAHOO.widget.TreeView
3527 TVproto.validator = null;
3530 * Entry point for initializing the editing plug-in.
3531 * TreeView will call this method on initializing if it exists
3532 * @method _initEditor
3533 * @for YAHOO.widget.TreeView
3537 TVproto._initEditor = function () {
3539 * Fires when the user clicks on the ok button of a node editor
3540 * @event editorSaveEvent
3542 * @param oArgs.newValue {mixed} the new value just entered
3543 * @param oArgs.oldValue {mixed} the value originally in the tree
3544 * @param oArgs.node {YAHOO.widget.Node} the node that has the focus
3545 * @for YAHOO.widget.TreeView
3547 this.createEvent("editorSaveEvent", this);
3550 * Fires when the user clicks on the cancel button of a node editor
3551 * @event editorCancelEvent
3553 * @param {YAHOO.widget.Node} node the node that has the focus
3554 * @for YAHOO.widget.TreeView
3556 this.createEvent("editorCancelEvent", this);
3561 * Entry point of the editing plug-in.
3562 * TreeView will call this method if it exists when a node label is clicked
3563 * @method _nodeEditing
3564 * @param node {YAHOO.widget.Node} the node to be edited
3565 * @return {Boolean} true to indicate that the node is editable and prevent any further bubbling of the click.
3566 * @for YAHOO.widget.TreeView
3572 TVproto._nodeEditing = function (node) {
3573 if (node.fillEditorContainer && node.editable) {
3574 var ed, topLeft, buttons, button, editorData = TV.editorData;
3575 editorData.active = true;
3576 editorData.whoHasIt = this;
3577 if (!editorData.nodeType) {
3578 editorData.editorPanel = ed = document.body.appendChild(document.createElement('div'));
3579 Dom.addClass(ed,'ygtv-label-editor');
3581 buttons = editorData.buttonsContainer = ed.appendChild(document.createElement('div'));
3582 Dom.addClass(buttons,'ygtv-button-container');
3583 button = buttons.appendChild(document.createElement('button'));
3584 Dom.addClass(button,'ygtvok');
3585 button.innerHTML = ' ';
3586 button = buttons.appendChild(document.createElement('button'));
3587 Dom.addClass(button,'ygtvcancel');
3588 button.innerHTML = ' ';
3589 Event.on(buttons, 'click', function (ev) {
3590 this.logger.log('click on editor');
3591 var target = Event.getTarget(ev);
3592 var node = TV.editorData.node;
3593 if (Dom.hasClass(target,'ygtvok')) {
3594 node.logger.log('ygtvok');
3595 Event.stopEvent(ev);
3596 this._closeEditor(true);
3598 if (Dom.hasClass(target,'ygtvcancel')) {
3599 node.logger.log('ygtvcancel');
3600 Event.stopEvent(ev);
3601 this._closeEditor(false);
3605 editorData.inputContainer = ed.appendChild(document.createElement('div'));
3606 Dom.addClass(editorData.inputContainer,'ygtv-input');
3608 Event.on(ed,'keydown',function (ev) {
3609 var editorData = TV.editorData,
3610 KEY = YAHOO.util.KeyListener.KEY;
3611 switch (ev.keyCode) {
3613 this.logger.log('ENTER');
3614 Event.stopEvent(ev);
3615 if (editorData.saveOnEnter) {
3616 this._closeEditor(true);
3620 this.logger.log('ESC');
3621 Event.stopEvent(ev);
3622 this._closeEditor(false);
3630 ed = editorData.editorPanel;
3632 editorData.node = node;
3633 if (editorData.nodeType) {
3634 Dom.removeClass(ed,'ygtv-edit-' + editorData.nodeType);
3636 Dom.addClass(ed,' ygtv-edit-' + node._type);
3637 topLeft = Dom.getXY(node.getContentEl());
3638 Dom.setStyle(ed,'left',topLeft[0] + 'px');
3639 Dom.setStyle(ed,'top',topLeft[1] + 'px');
3640 Dom.setStyle(ed,'display','block');
3642 node.fillEditorContainer(editorData);
3644 return true; // If inline editor available, don't do anything else.
3649 * Method to be associated with an event (clickEvent, dblClickEvent or enterKeyPressed) to pop up the contents editor
3650 * It calls the corresponding node editNode method.
3651 * @method onEventEditNode
3652 * @param oArgs {object} Object passed as arguments to TreeView event listeners
3653 * @for YAHOO.widget.TreeView
3656 TVproto.onEventEditNode = function (oArgs) {
3657 if (oArgs instanceof YAHOO.widget.Node) {
3659 } else if (oArgs.node instanceof YAHOO.widget.Node) {
3660 oArgs.node.editNode();
3665 * Method to be called when the inline editing is finished and the editor is to be closed
3666 * @method _closeEditor
3667 * @param save {Boolean} true if the edited value is to be saved, false if discarded
3669 * @for YAHOO.widget.TreeView
3672 TVproto._closeEditor = function (save) {
3673 var ed = TV.editorData,
3677 close = ed.node.saveEditorValue(ed) !== false;
3679 this.fireEvent( 'editorCancelEvent', node);
3683 Dom.setStyle(ed.editorPanel,'display','none');
3690 * Entry point for TreeView's destroy method to destroy whatever the editing plug-in has created
3691 * @method _destroyEditor
3693 * @for YAHOO.widget.TreeView
3695 TVproto._destroyEditor = function() {
3696 var ed = TV.editorData;
3697 if (ed && ed.nodeType && (!ed.active || ed.whoHasIt === this)) {
3698 Event.removeListener(ed.editorPanel,'keydown');
3699 Event.removeListener(ed.buttonContainer,'click');
3700 ed.node.destroyEditorContents(ed);
3701 document.body.removeChild(ed.editorPanel);
3702 ed.nodeType = ed.editorPanel = ed.inputContainer = ed.buttonsContainer = ed.whoHasIt = ed.node = null;
3707 var Nproto = YAHOO.widget.Node.prototype;
3710 * Signals if the label is editable. (Ignored on TextNodes with href set.)
3711 * @property editable
3713 * @for YAHOO.widget.Node
3715 Nproto.editable = false;
3718 * pops up the contents editor, if there is one and the node is declared editable
3720 * @for YAHOO.widget.Node
3723 Nproto.editNode = function () {
3724 this.tree._nodeEditing(this);
3730 /** Placeholder for a function that should provide the inline node label editor.
3731 * Leaving it set to null will indicate that this node type is not editable.
3732 * It should be overridden by nodes that provide inline editing.
3733 * The Node-specific editing element (input box, textarea or whatever) should be inserted into editorData.inputContainer.
3734 * @method fillEditorContainer
3735 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3737 * @for YAHOO.widget.Node
3739 Nproto.fillEditorContainer = null;
3743 * Node-specific destroy function to empty the contents of the inline editor panel.
3744 * This function is the worst case alternative that will purge all possible events and remove the editor contents.
3745 * Method Event.purgeElement is somewhat costly so if it can be replaced by specifc Event.removeListeners, it is better to do so.
3746 * @method destroyEditorContents
3747 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3748 * @for YAHOO.widget.Node
3750 Nproto.destroyEditorContents = function (editorData) {
3751 // In the worst case, if the input editor (such as the Calendar) has no destroy method
3752 // we can only try to remove all possible events on it.
3753 Event.purgeElement(editorData.inputContainer,true);
3754 editorData.inputContainer.innerHTML = '';
3758 * Saves the value entered into the editor.
3759 * @method saveEditorValue
3760 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3761 * @return {false or none} a return of exactly false will prevent the editor from closing
3762 * @for YAHOO.widget.Node
3764 Nproto.saveEditorValue = function (editorData) {
3765 var node = editorData.node,
3767 validator = node.tree.validator;
3769 value = this.getEditorValue(editorData);
3771 if (Lang.isFunction(validator)) {
3772 value = validator(value,editorData.oldValue,node);
3773 if (Lang.isUndefined(value)) {
3778 if (this.tree.fireEvent( 'editorSaveEvent', {
3780 oldValue:editorData.oldValue,
3783 this.displayEditedValue(value,editorData);
3789 * Returns the value(s) from the input element(s) .
3790 * Should be overridden by each node type.
3791 * @method getEditorValue
3792 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3793 * @return {any} value entered
3794 * @for YAHOO.widget.Node
3797 Nproto.getEditorValue = function (editorData) {
3801 * Finally displays the newly edited value(s) in the tree.
3802 * Should be overridden by each node type.
3803 * @method displayEditedValue
3804 * @param value {any} value to be displayed and stored in the node
3805 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3806 * @for YAHOO.widget.Node
3808 Nproto.displayEditedValue = function (value,editorData) {
3811 var TNproto = YAHOO.widget.TextNode.prototype;
3816 * Places an <input> textbox in the input container and loads the label text into it.
3817 * @method fillEditorContainer
3818 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3820 * @for YAHOO.widget.TextNode
3822 TNproto.fillEditorContainer = function (editorData) {
3825 // If last node edited is not of the same type as this one, delete it and fill it with our editor
3826 if (editorData.nodeType != this._type) {
3827 editorData.nodeType = this._type;
3828 editorData.saveOnEnter = true;
3829 editorData.node.destroyEditorContents(editorData);
3831 editorData.inputElement = input = editorData.inputContainer.appendChild(document.createElement('input'));
3834 // if the last node edited was of the same time, reuse the input element.
3835 input = editorData.inputElement;
3837 editorData.oldValue = this.label;
3838 input.value = this.label;
3844 * Returns the value from the input element.
3845 * Overrides Node.getEditorValue.
3846 * @method getEditorValue
3847 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3848 * @return {string} value entered
3849 * @for YAHOO.widget.TextNode
3852 TNproto.getEditorValue = function (editorData) {
3853 return editorData.inputElement.value;
3857 * Finally displays the newly edited value in the tree.
3858 * Overrides Node.displayEditedValue.
3859 * @method displayEditedValue
3860 * @param value {string} value to be displayed and stored in the node
3861 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3862 * @for YAHOO.widget.TextNode
3864 TNproto.displayEditedValue = function (value,editorData) {
3865 var node = editorData.node;
3867 node.getLabelEl().innerHTML = value;
3871 * Destroys the contents of the inline editor panel.
3872 * Overrides Node.destroyEditorContent.
3873 * Since we didn't set any event listeners on this inline editor, it is more efficient to avoid the generic method in Node.
3874 * @method destroyEditorContents
3875 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information
3876 * @for YAHOO.widget.TextNode
3878 TNproto.destroyEditorContents = function (editorData) {
3879 editorData.inputContainer.innerHTML = '';
3884 * A static factory class for tree view expand/collapse animations
3888 YAHOO.widget.TVAnim = function() {
3891 * Constant for the fade in animation
3896 FADE_IN: "TVFadeIn",
3899 * Constant for the fade out animation
3900 * @property FADE_OUT
3904 FADE_OUT: "TVFadeOut",
3907 * Returns a ygAnim instance of the given type
3909 * @param type {string} the type of animation
3910 * @param el {HTMLElement} the element to element (probably the children div)
3911 * @param callback {function} function to invoke when the animation is done.
3912 * @return {YAHOO.util.Animation} the animation instance
3915 getAnim: function(type, el, callback) {
3916 if (YAHOO.widget[type]) {
3917 return new YAHOO.widget[type](el, callback);
3924 * Returns true if the specified animation class is available
3926 * @param type {string} the type of animation
3927 * @return {boolean} true if valid, false if not
3930 isValid: function(type) {
3931 return (YAHOO.widget[type]);
3937 * A 1/2 second fade-in animation.
3940 * @param el {HTMLElement} the element to animate
3941 * @param callback {function} function to invoke when the animation is finished
3943 YAHOO.widget.TVFadeIn = function(el, callback) {
3945 * The element to animate
3952 * the callback to invoke when the animation is complete
3953 * @property callback
3956 this.callback = callback;
3958 this.logger = new YAHOO.widget.LogWriter(this.toString());
3961 YAHOO.widget.TVFadeIn.prototype = {
3963 * Performs the animation
3966 animate: function() {
3969 var s = this.el.style;
3971 s.filter = "alpha(opacity=10)";
3975 var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur);
3976 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
3981 * Clean up and invoke callback
3982 * @method onComplete
3984 onComplete: function() {
3991 * @return {string} the string representation of the instance
3993 toString: function() {
3999 * A 1/2 second fade out animation.
4002 * @param el {HTMLElement} the element to animate
4003 * @param callback {Function} function to invoke when the animation is finished
4005 YAHOO.widget.TVFadeOut = function(el, callback) {
4007 * The element to animate
4014 * the callback to invoke when the animation is complete
4015 * @property callback
4018 this.callback = callback;
4020 this.logger = new YAHOO.widget.LogWriter(this.toString());
4023 YAHOO.widget.TVFadeOut.prototype = {
4025 * Performs the animation
4028 animate: function() {
4031 var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur);
4032 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
4037 * Clean up and invoke callback
4038 * @method onComplete
4040 onComplete: function() {
4041 var s = this.el.style;
4044 s.filter = "alpha(opacity=100)";
4051 * @return {string} the string representation of the instance
4053 toString: function() {
4058 YAHOO.register("treeview", YAHOO.widget.TreeView, {version: "2.8.0r4", build: "2449"});