| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 /* 2 Copyright (c) 2008, Yahoo! Inc. All rights reserved. 3 Code licensed under the BSD License: 4 http://developer.yahoo.net/yui/license.txt 5 version: 2.6.0 6 */ 7 (function () { 8 var Dom = YAHOO.util.Dom, 9 Event = YAHOO.util.Event, 10 Lang = YAHOO.lang, 11 Widget = YAHOO.widget; 12 13 /** 14 * The treeview widget is a generic tree building tool. 15 * @module treeview 16 * @title TreeView Widget 17 * @requires yahoo, event 18 * @optional animation 19 * @namespace YAHOO.widget 20 */ 21 22 /** 23 * Contains the tree view state data and the root node. 24 * 25 * @class TreeView 26 * @uses YAHOO.util.EventProvider 27 * @constructor 28 * @param {string|HTMLElement} id The id of the element, or the element itself that the tree will be inserted into. Existing markup in this element, if valid, will be used to build the tree 29 * @param {Array|object|string} oConfig (optional) An array containing the definition of the tree. Objects will be converted to arrays of one element. A string will produce a single TextNode 30 * 31 */ 32 YAHOO.widget.TreeView = function(id, oConfig) { 33 if (id) { this.init(id); } 34 if (oConfig) { 35 if (!Lang.isArray(oConfig)) { 36 oConfig = [oConfig]; 37 } 38 this.buildTreeFromObject(oConfig); 39 } else if (Lang.trim(this._el.innerHTML)) { 40 this.buildTreeFromMarkup(id); 41 } 42 }; 43 44 var TV = Widget.TreeView; 45 46 TV.prototype = { 47 48 /** 49 * The id of tree container element 50 * @property id 51 * @type String 52 */ 53 id: null, 54 55 /** 56 * The host element for this tree 57 * @property _el 58 * @private 59 * @type HTMLelement 60 */ 61 _el: null, 62 63 /** 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. 67 * @property _nodes 68 * @type Node[] 69 * @private 70 */ 71 _nodes: null, 72 73 /** 74 * We lock the tree control while waiting for the dynamic loader to return 75 * @property locked 76 * @type boolean 77 */ 78 locked: false, 79 80 /** 81 * The animation to use for expanding children, if any 82 * @property _expandAnim 83 * @type string 84 * @private 85 */ 86 _expandAnim: null, 87 88 /** 89 * The animation to use for collapsing children, if any 90 * @property _collapseAnim 91 * @type string 92 * @private 93 */ 94 _collapseAnim: null, 95 96 /** 97 * The current number of animations that are executing 98 * @property _animCount 99 * @type int 100 * @private 101 */ 102 _animCount: 0, 103 104 /** 105 * The maximum number of animations to run at one time. 106 * @property maxAnim 107 * @type int 108 */ 109 maxAnim: 2, 110 111 /** 112 * Whether there is any subscriber to dblClickEvent 113 * @property _hasDblClickSubscriber 114 * @type boolean 115 * @private 116 */ 117 _hasDblClickSubscriber: false, 118 119 /** 120 * Stores the timer used to check for double clicks 121 * @property _dblClickTimer 122 * @type window.timer object 123 * @private 124 */ 125 _dblClickTimer: null, 126 127 128 /** 129 * Sets up the animation for expanding children 130 * @method setExpandAnim 131 * @param {string} type the type of animation (acceptable values defined 132 * in YAHOO.widget.TVAnim) 133 */ 134 setExpandAnim: function(type) { 135 this._expandAnim = (Widget.TVAnim.isValid(type)) ? type : null; 136 }, 137 138 /** 139 * Sets up the animation for collapsing children 140 * @method setCollapseAnim 141 * @param {string} the type of animation (acceptable values defined in 142 * YAHOO.widget.TVAnim) 143 */ 144 setCollapseAnim: function(type) { 145 this._collapseAnim = (Widget.TVAnim.isValid(type)) ? type : null; 146 }, 147 148 /** 149 * Perform the expand animation if configured, or just show the 150 * element if not configured or too many animations are in progress 151 * @method animateExpand 152 * @param el {HTMLElement} the element to animate 153 * @param node {YAHOO.util.Node} the node that was expanded 154 * @return {boolean} true if animation could be invoked, false otherwise 155 */ 156 animateExpand: function(el, node) { 157 158 if (this._expandAnim && this._animCount < this.maxAnim) { 159 // this.locked = true; 160 var tree = this; 161 var a = Widget.TVAnim.getAnim(this._expandAnim, el, 162 function() { tree.expandComplete(node); }); 163 if (a) { 164 ++this._animCount; 165 this.fireEvent("animStart", { 166 "node": node, 167 "type": "expand" 168 }); 169 a.animate(); 170 } 171 172 return true; 173 } 174 175 return false; 176 }, 177 178 /** 179 * Perform the collapse animation if configured, or just show the 180 * element if not configured or too many animations are in progress 181 * @method animateCollapse 182 * @param el {HTMLElement} the element to animate 183 * @param node {YAHOO.util.Node} the node that was expanded 184 * @return {boolean} true if animation could be invoked, false otherwise 185 */ 186 animateCollapse: function(el, node) { 187 188 if (this._collapseAnim && this._animCount < this.maxAnim) { 189 // this.locked = true; 190 var tree = this; 191 var a = Widget.TVAnim.getAnim(this._collapseAnim, el, 192 function() { tree.collapseComplete(node); }); 193 if (a) { 194 ++this._animCount; 195 this.fireEvent("animStart", { 196 "node": node, 197 "type": "collapse" 198 }); 199 a.animate(); 200 } 201 202 return true; 203 } 204 205 return false; 206 }, 207 208 /** 209 * Function executed when the expand animation completes 210 * @method expandComplete 211 */ 212 expandComplete: function(node) { 213 --this._animCount; 214 this.fireEvent("animComplete", { 215 "node": node, 216 "type": "expand" 217 }); 218 // this.locked = false; 219 }, 220 221 /** 222 * Function executed when the collapse animation completes 223 * @method collapseComplete 224 */ 225 collapseComplete: function(node) { 226 --this._animCount; 227 this.fireEvent("animComplete", { 228 "node": node, 229 "type": "collapse" 230 }); 231 // this.locked = false; 232 }, 233 234 /** 235 * Initializes the tree 236 * @method init 237 * @parm {string|HTMLElement} id the id of the element that will hold the tree 238 * @private 239 */ 240 init: function(id) { 241 this._el = Dom.get(id); 242 this.id = Dom.generateId(this._el,"yui-tv-auto-id-"); 243 244 /** 245 * When animation is enabled, this event fires when the animation 246 * starts 247 * @event animStart 248 * @type CustomEvent 249 * @param {YAHOO.widget.Node} node the node that is expanding/collapsing 250 * @parm {String} type the type of animation ("expand" or "collapse") 251 */ 252 this.createEvent("animStart", this); 253 254 /** 255 * When animation is enabled, this event fires when the animation 256 * completes 257 * @event animComplete 258 * @type CustomEvent 259 * @param {YAHOO.widget.Node} node the node that is expanding/collapsing 260 * @parm {String} type the type of animation ("expand" or "collapse") 261 */ 262 this.createEvent("animComplete", this); 263 264 /** 265 * Fires when a node is going to be collapsed. Return false to stop 266 * the collapse. 267 * @event collapse 268 * @type CustomEvent 269 * @param {YAHOO.widget.Node} node the node that is collapsing 270 */ 271 this.createEvent("collapse", this); 272 273 /** 274 * Fires after a node is successfully collapsed. This event will not fire 275 * if the "collapse" event was cancelled. 276 * @event collapseComplete 277 * @type CustomEvent 278 * @param {YAHOO.widget.Node} node the node that was collapsed 279 */ 280 this.createEvent("collapseComplete", this); 281 282 /** 283 * Fires when a node is going to be expanded. Return false to stop 284 * the collapse. 285 * @event expand 286 * @type CustomEvent 287 * @param {YAHOO.widget.Node} node the node that is expanding 288 */ 289 this.createEvent("expand", this); 290 291 /** 292 * Fires after a node is successfully expanded. This event will not fire 293 * if the "expand" event was cancelled. 294 * @event expandComplete 295 * @type CustomEvent 296 * @param {YAHOO.widget.Node} node the node that was expanded 297 */ 298 this.createEvent("expandComplete", this); 299 300 /** 301 * Fires when the Enter key is pressed on a node that has the focus 302 * @event enterKeyPressed 303 * @type CustomEvent 304 * @param {YAHOO.widget.Node} node the node that has the focus 305 */ 306 this.createEvent("enterKeyPressed", this); 307 308 /** 309 * Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a Click. 310 * The listener may return false to cancel toggling and focusing on the node. 311 * @event clickEvent 312 * @type CustomEvent 313 * @param oArgs.event {HTMLEvent} The event object 314 * @param oArgs.node {YAHOO.widget.Node} node the node that was clicked 315 */ 316 this.createEvent("clickEvent", this); 317 318 /** 319 * Fires when the label in a TextNode or MenuNode or content in an HTMLNode receives a double Click 320 * @event dblClickEvent 321 * @type CustomEvent 322 * @param oArgs.event {HTMLEvent} The event object 323 * @param oArgs.node {YAHOO.widget.Node} node the node that was clicked 324 */ 325 var self = this; 326 this.createEvent("dblClickEvent", { 327 scope:this, 328 onSubscribeCallback: function() { 329 self._hasDblClickSubscriber = true; 330 } 331 }); 332 333 /** 334 * Custom event that is fired when the text node label is clicked. 335 * The node clicked is provided as an argument 336 * 337 * @event labelClick 338 * @type CustomEvent 339 * @param {YAHOO.widget.Node} node the node clicked 340 * @deprecated use clickEvent or dblClickEvent 341 */ 342 this.createEvent("labelClick", this); 343 344 345 this._nodes = []; 346 347 // store a global reference 348 TV.trees[this.id] = this; 349 350 // Set up the root node 351 this.root = new Widget.RootNode(this); 352 353 var LW = Widget.LogWriter; 354 355 356 357 // YAHOO.util.Event.onContentReady(this.id, this.handleAvailable, this, true); 358 // YAHOO.util.Event.on(this.id, "click", this.handleClick, this, true); 359 }, 360 361 //handleAvailable: function() { 362 //var Event = YAHOO.util.Event; 363 //Event.on(this.id, 364 //}, 365 /** 366 * Builds the TreeView from an object. This is the method called by the constructor to build the tree when it has a second argument. 367 * @method buildTreeFromObject 368 * @param oConfig {Array} array containing a full description of the tree 369 * 370 */ 371 buildTreeFromObject: function (oConfig) { 372 var build = function (parent, oConfig) { 373 var i, item, node, children, type, NodeType, ThisType; 374 for (i = 0; i < oConfig.length; i++) { 375 item = oConfig[i]; 376 if (Lang.isString(item)) { 377 node = new Widget.TextNode(item, parent); 378 } else if (Lang.isObject(item)) { 379 children = item.children; 380 delete item.children; 381 type = item.type || 'text'; 382 delete item.type; 383 switch (type.toLowerCase()) { 384 case 'text': 385 node = new Widget.TextNode(item, parent); 386 break; 387 case 'menu': 388 node = new Widget.MenuNode(item, parent); 389 break; 390 case 'html': 391 node = new Widget.HTMLNode(item, parent); 392 break; 393 default: 394 NodeType = Widget[type]; 395 if (Lang.isObject(NodeType)) { 396 for (ThisType = NodeType; ThisType && ThisType !== Widget.Node; ThisType = ThisType.superclass.constructor) {} 397 if (ThisType) { 398 node = new NodeType(item, parent); 399 } else { 400 } 401 } else { 402 } 403 } 404 if (children) { 405 build(node,children); 406 } 407 } else { 408 } 409 } 410 }; 411 412 413 build(this.root,oConfig); 414 }, 415 /** 416 * Builds the TreeView from existing markup. Markup should consist of <UL> or <OL> elements, possibly nested. 417 * Depending what the <LI> elements contain the following will be created: <ul> 418 * <li>plain text: a regular TextNode</li> 419 * <li>an (un-)ordered list: a nested branch</li> 420 * <li>anything else: an HTMLNode</li></ul> 421 * Only the first outermost (un-)ordered list in the markup and its children will be parsed. 422 * Tree will be fully collapsed. 423 * HTMLNodes have hasIcon set to true if the markup for that node has a className called hasIcon. 424 * @method buildTreeFromMarkup 425 * @param {string|HTMLElement} id the id of the element that contains the markup or a reference to it. 426 */ 427 buildTreeFromMarkup: function (id) { 428 var build = function (parent,markup) { 429 var el, node, child, text; 430 for (el = Dom.getFirstChild(markup); el; el = Dom.getNextSibling(el)) { 431 if (el.nodeType == 1) { 432 switch (el.tagName.toUpperCase()) { 433 case 'LI': 434 for (child = el.firstChild; child; child = child.nextSibling) { 435 if (child.nodeType == 3) { 436 text = Lang.trim(child.nodeValue); 437 if (text.length) { 438 node = new Widget.TextNode(text, parent, false); 439 } 440 } else { 441 switch (child.tagName.toUpperCase()) { 442 case 'UL': 443 case 'OL': 444 build(node,child); 445 break; 446 case 'A': 447 node = new Widget.TextNode({ 448 label:child.innerHTML, 449 href: child.href, 450 target:child.target, 451 title:child.title ||child.alt 452 },parent,false); 453 break; 454 default: 455 node = new Widget.HTMLNode(child.parentNode.innerHTML, parent, false, true); 456 break; 457 } 458 } 459 } 460 break; 461 case 'UL': 462 case 'OL': 463 build(node, el); 464 break; 465 } 466 } 467 } 468 469 }; 470 var markup = Dom.getChildrenBy(Dom.get(id),function (el) { 471 var tag = el.tagName.toUpperCase(); 472 return tag == 'UL' || tag == 'OL'; 473 }); 474 if (markup.length) { 475 build(this.root, markup[0]); 476 } else { 477 } 478 }, 479 /** 480 * Renders the tree boilerplate and visible nodes 481 * @method render 482 */ 483 render: function() { 484 var html = this.root.getHtml(); 485 this.getEl().innerHTML = html; 486 var getTarget = function (ev) { 487 var target = Event.getTarget(ev); 488 if (target.tagName.toUpperCase() != 'TD') { target = Dom.getAncestorByTagName(target,'td'); } 489 if (Lang.isNull(target)) { return null; } 490 if (target.className.length === 0) { 491 target = target.previousSibling; 492 if (Lang.isNull(target)) { return null; } 493 } 494 return target; 495 }; 496 if (!this._hasEvents) { 497 Event.on( 498 this.getEl(), 499 'click', 500 function (ev) { 501 var self = this, 502 el = Event.getTarget(ev), 503 node = this.getNodeByElement(el); 504 if (!node) { return; } 505 506 var toggle = function () { 507 if (node.expanded) { 508 node.collapse(); 509 } else { 510 node.expand(); 511 } 512 node.focus(); 513 }; 514 515 if (Dom.hasClass(el, node.labelStyle) || Dom.getAncestorByClassName(el,node.labelStyle)) { 516 this.fireEvent('labelClick',node); 517 } 518 while (el && !Dom.hasClass(el.parentNode,'ygtvrow') && !/ygtv[tl][mp]h?h?/.test(el.className)) { 519 el = Dom.getAncestorByTagName(el,'td'); 520 } 521 if (el) { 522 // If it is a spacer cell, do nothing 523 if (/ygtv(blank)?depthcell/.test(el.className)) { return;} 524 // If it is a toggle cell, toggle 525 if (/ygtv[tl][mp]h?h?/.test(el.className)) { 526 toggle(); 527 } else { 528 if (this._dblClickTimer) { 529 window.clearTimeout(this._dblClickTimer); 530 this._dblClickTimer = null; 531 } else { 532 if (this._hasDblClickSubscriber) { 533 this._dblClickTimer = window.setTimeout(function () { 534 self._dblClickTimer = null; 535 if (self.fireEvent('clickEvent', {event:ev,node:node}) !== false) { 536 toggle(); 537 } 538 }, 200); 539 } else { 540 if (self.fireEvent('clickEvent', {event:ev,node:node}) !== false) { 541 toggle(); 542 } 543 } 544 } 545 } 546 } 547 }, 548 this, 549 true 550 ); 551 552 Event.on( 553 this.getEl(), 554 'dblclick', 555 function (ev) { 556 if (!this._hasDblClickSubscriber) { return; } 557 var el = Event.getTarget(ev); 558 while (!Dom.hasClass(el.parentNode,'ygtvrow')) { 559 el = Dom.getAncestorByTagName(el,'td'); 560 } 561 if (/ygtv(blank)?depthcell/.test(el.className)) { return;} 562 if (!(/ygtv[tl][mp]h?h?/.test(el.className))) { 563 this.fireEvent('dblClickEvent', {event:ev, node:this.getNodeByElement(el)}); 564 if (this._dblClickTimer) { 565 window.clearTimeout(this._dblClickTimer); 566 this._dblClickTimer = null; 567 } 568 } 569 }, 570 this, 571 true 572 ); 573 Event.on( 574 this.getEl(), 575 'mouseover', 576 function (ev) { 577 var target = getTarget(ev); 578 if (target) { 579 target.className = target.className.replace(/ygtv([lt])([mp])/gi, 'ygtv$1$2h').replace(/h+/, 'h'); 580 } 581 } 582 ); 583 Event.on( 584 this.getEl(), 585 'mouseout', 586 function (ev) { 587 var target = getTarget(ev); 588 if (target) { 589 target.className = target.className.replace(/ygtv([lt])([mp])h/gi,'ygtv$1$2'); 590 } 591 } 592 ); 593 Event.on( 594 this.getEl(), 595 'keydown', 596 function (ev) { 597 var target = Event.getTarget(ev), 598 node = this.getNodeByElement(target), 599 newNode = node, 600 KEY = YAHOO.util.KeyListener.KEY; 601 602 switch(ev.keyCode) { 603 case KEY.UP: 604 do { 605 if (newNode.previousSibling) { 606 newNode = newNode.previousSibling; 607 } else { 608 newNode = newNode.parent; 609 } 610 } while (newNode && !newNode.focus()); 611 if (!newNode) { node.focus(); } 612 Event.preventDefault(ev); 613 break; 614 case KEY.DOWN: 615 do { 616 if (newNode.nextSibling) { 617 newNode = newNode.nextSibling; 618 } else { 619 newNode.expand(); 620 newNode = (newNode.children.length || null) && newNode.children[0]; 621 } 622 } while (newNode && !newNode.focus()); 623 if (!newNode) { node.focus(); } 624 Event.preventDefault(ev); 625 break; 626 case KEY.LEFT: 627 do { 628 if (newNode.parent) { 629 newNode = newNode.parent; 630 } else { 631 newNode = newNode.previousSibling; 632 } 633 } while (newNode && !newNode.focus()); 634 if (!newNode) { node.focus(); } 635 Event.preventDefault(ev); 636 break; 637 case KEY.RIGHT: 638 do { 639 newNode.expand(); 640 if (newNode.children.length) { 641 newNode = newNode.children[0]; 642 } else { 643 newNode = newNode.nextSibling; 644 } 645 } while (newNode && !newNode.focus()); 646 if (!newNode) { node.focus(); } 647 Event.preventDefault(ev); 648 break; 649 case KEY.ENTER: 650 if (node.href) { 651 if (node.target) { 652 window.open(node.href,node.target); 653 } else { 654 window.location(node.href); 655 } 656 } else { 657 node.toggle(); 658 } 659 this.fireEvent('enterKeyPressed',node); 660 Event.preventDefault(ev); 661 break; 662 case KEY.HOME: 663 newNode = this.getRoot(); 664 if (newNode.children.length) {newNode = newNode.children[0];} 665 if (!newNode.focus()) { node.focus(); } 666 Event.preventDefault(ev); 667 break; 668 case KEY.END: 669 newNode = newNode.parent.children; 670 newNode = newNode[newNode.length -1]; 671 if (!newNode.focus()) { node.focus(); } 672 Event.preventDefault(ev); 673 break; 674 // case KEY.PAGE_UP: 675 // break; 676 // case KEY.PAGE_DOWN: 677 // break; 678 case 107: // plus key 679 if (ev.shiftKey) { 680 node.parent.expandAll(); 681 } else { 682 node.expand(); 683 } 684 break; 685 case 109: // minus key 686 if (ev.shiftKey) { 687 node.parent.collapseAll(); 688 } else { 689 node.collapse(); 690 } 691 break; 692 default: 693 break; 694 } 695 }, 696 this, 697 true 698 ); 699 } 700 this._hasEvents = true; 701 }, 702 703 /** 704 * Returns the tree's host element 705 * @method getEl 706 * @return {HTMLElement} the host element 707 */ 708 getEl: function() { 709 if (! this._el) { 710 this._el = Dom.get(this.id); 711 } 712 return this._el; 713 }, 714 715 /** 716 * Nodes register themselves with the tree instance when they are created. 717 * @method regNode 718 * @param node {Node} the node to register 719 * @private 720 */ 721 regNode: function(node) { 722 this._nodes[node.index] = node; 723 }, 724 725 /** 726 * Returns the root node of this tree 727 * @method getRoot 728 * @return {Node} the root node 729 */ 730 getRoot: function() { 731 return this.root; 732 }, 733 734 /** 735 * Configures this tree to dynamically load all child data 736 * @method setDynamicLoad 737 * @param {function} fnDataLoader the function that will be called to get the data 738 * @param iconMode {int} configures the icon that is displayed when a dynamic 739 * load node is expanded the first time without children. By default, the 740 * "collapse" icon will be used. If set to 1, the leaf node icon will be 741 * displayed. 742 */ 743 setDynamicLoad: function(fnDataLoader, iconMode) { 744 this.root.setDynamicLoad(fnDataLoader, iconMode); 745 }, 746 747 /** 748 * Expands all child nodes. Note: this conflicts with the "multiExpand" 749 * node property. If expand all is called in a tree with nodes that 750 * do not allow multiple siblings to be displayed, only the last sibling 751 * will be expanded. 752 * @method expandAll 753 */ 754 expandAll: function() { 755 if (!this.locked) { 756 this.root.expandAll(); 757 } 758 }, 759 760 /** 761 * Collapses all expanded child nodes in the entire tree. 762 * @method collapseAll 763 */ 764 collapseAll: function() { 765 if (!this.locked) { 766 this.root.collapseAll(); 767 } 768 }, 769 770 /** 771 * Returns a node in the tree that has the specified index (this index 772 * is created internally, so this function probably will only be used 773 * in html generated for a given node.) 774 * @method getNodeByIndex 775 * @param {int} nodeIndex the index of the node wanted 776 * @return {Node} the node with index=nodeIndex, null if no match 777 */ 778 getNodeByIndex: function(nodeIndex) { 779 var n = this._nodes[nodeIndex]; 780 return (n) ? n : null; 781 }, 782 783 /** 784 * Returns a node that has a matching property and value in the data 785 * object that was passed into its constructor. 786 * @method getNodeByProperty 787 * @param {object} property the property to search (usually a string) 788 * @param {object} value the value we want to find (usuall an int or string) 789 * @return {Node} the matching node, null if no match 790 */ 791 getNodeByProperty: function(property, value) { 792 for (var i in this._nodes) { 793 if (this._nodes.hasOwnProperty(i)) { 794 var n = this._nodes[i]; 795 if (n.data && value == n.data[property]) { 796 return n; 797 } 798 } 799 } 800 801 return null; 802 }, 803 804 /** 805 * Returns a collection of nodes that have a matching property 806 * and value in the data object that was passed into its constructor. 807 * @method getNodesByProperty 808 * @param {object} property the property to search (usually a string) 809 * @param {object} value the value we want to find (usuall an int or string) 810 * @return {Array} the matching collection of nodes, null if no match 811 */ 812 getNodesByProperty: function(property, value) { 813 var values = []; 814 for (var i in this._nodes) { 815 if (this._nodes.hasOwnProperty(i)) { 816 var n = this._nodes[i]; 817 if (n.data && value == n.data[property]) { 818 values.push(n); 819 } 820 } 821 } 822 823 return (values.length) ? values : null; 824 }, 825 826 /** 827 * Returns the treeview node reference for an anscestor element 828 * of the node, or null if it is not contained within any node 829 * in this tree. 830 * @method getNodeByElement 831 * @param {HTMLElement} the element to test 832 * @return {YAHOO.widget.Node} a node reference or null 833 */ 834 getNodeByElement: function(el) { 835 836 var p=el, m, re=/ygtv([^\d]*)(.*)/; 837 838 do { 839 840 if (p && p.id) { 841 m = p.id.match(re); 842 if (m && m[2]) { 843 return this.getNodeByIndex(m[2]); 844 } 845 } 846 847 p = p.parentNode; 848 849 if (!p || !p.tagName) { 850 break; 851 } 852 853 } 854 while (p.id !== this.id && p.tagName.toLowerCase() !== "body"); 855 856 return null; 857 }, 858 859 /** 860 * Removes the node and its children, and optionally refreshes the 861 * branch of the tree that was affected. 862 * @method removeNode 863 * @param {Node} The node to remove 864 * @param {boolean} autoRefresh automatically refreshes branch if true 865 * @return {boolean} False is there was a problem, true otherwise. 866 */ 867 removeNode: function(node, autoRefresh) { 868 869 // Don't delete the root node 870 if (node.isRoot()) { 871 return false; 872 } 873 874 // Get the branch that we may need to refresh 875 var p = node.parent; 876 if (p.parent) { 877 p = p.parent; 878 } 879 880 // Delete the node and its children 881 this._deleteNode(node); 882 883 // Refresh the parent of the parent 884 if (autoRefresh && p && p.childrenRendered) { 885 p.refresh(); 886 } 887 888 return true; 889 }, 890 891 /** 892 * wait until the animation is complete before deleting 893 * to avoid javascript errors 894 * @method _removeChildren_animComplete 895 * @param o the custom event payload 896 * @private 897 */ 898 _removeChildren_animComplete: function(o) { 899 this.unsubscribe(this._removeChildren_animComplete); 900 this.removeChildren(o.node); 901 }, 902 903 /** 904 * Deletes this nodes child collection, recursively. Also collapses 905 * the node, and resets the dynamic load flag. The primary use for 906 * this method is to purge a node and allow it to fetch its data 907 * dynamically again. 908 * @method removeChildren 909 * @param {Node} node the node to purge 910 */ 911 removeChildren: function(node) { 912 913 if (node.expanded) { 914 // wait until the animation is complete before deleting to 915 // avoid javascript errors 916 if (this._collapseAnim) { 917 this.subscribe("animComplete", 918 this._removeChildren_animComplete, this, true); 919 Widget.Node.prototype.collapse.call(node); 920 return; 921 } 922 923 node.collapse(); 924 } 925 926 while (node.children.length) { 927 this._deleteNode(node.children[0]); 928 } 929 930 if (node.isRoot()) { 931 Widget.Node.prototype.expand.call(node); 932 } 933 934 node.childrenRendered = false; 935 node.dynamicLoadComplete = false; 936 937 node.updateIcon(); 938 }, 939 940 /** 941 * Deletes the node and recurses children 942 * @method _deleteNode 943 * @private 944 */ 945 _deleteNode: function(node) { 946 // Remove all the child nodes first 947 this.removeChildren(node); 948 949 // Remove the node from the tree 950 this.popNode(node); 951 }, 952 953 /** 954 * Removes the node from the tree, preserving the child collection 955 * to make it possible to insert the branch into another part of the 956 * tree, or another tree. 957 * @method popNode 958 * @param {Node} the node to remove 959 */ 960 popNode: function(node) { 961 var p = node.parent; 962 963 // Update the parent's collection of children 964 var a = []; 965 966 for (var i=0, len=p.children.length;i<len;++i) { 967 if (p.children[i] != node) { 968 a[a.length] = p.children[i]; 969 } 970 } 971 972 p.children = a; 973 974 // reset the childrenRendered flag for the parent 975 p.childrenRendered = false; 976 977 // Update the sibling relationship 978 if (node.previousSibling) { 979 node.previousSibling.nextSibling = node.nextSibling; 980 } 981 982 if (node.nextSibling) { 983 node.nextSibling.previousSibling = node.previousSibling; 984 } 985 986 node.parent = null; 987 node.previousSibling = null; 988 node.nextSibling = null; 989 node.tree = null; 990 991 // Update the tree's node collection 992 delete this._nodes[node.index]; 993 }, 994 995 /** 996 * Nulls out the entire TreeView instance and related objects, removes attached 997 * event listeners, and clears out DOM elements inside the container. After 998 * calling this method, the instance reference should be expliclitly nulled by 999 * implementer, as in myDataTable = null. Use with caution! 1000 * 1001 * @method destroy 1002 */ 1003 destroy : function() { 1004 // Since the label editor can be separated from the main TreeView control 1005 // the destroy method for it might not be there. 1006 if (this._destroyEditor) { this._destroyEditor(); } 1007 var el = this.getEl(); 1008 Event.removeListener(el,'click'); 1009 Event.removeListener(el,'dblclick'); 1010 Event.removeListener(el,'mouseover'); 1011 Event.removeListener(el,'mouseout'); 1012 Event.removeListener(el,'keydown'); 1013 for (var i = 0 ; i < this._nodes.length; i++) { 1014 var node = this._nodes[i]; 1015 if (node && node.destroy) {node.destroy(); } 1016 } 1017 el.parentNode.removeChild(el); 1018 this._hasEvents = false; 1019 }, 1020 1021 1022 1023 1024 /** 1025 * TreeView instance toString 1026 * @method toString 1027 * @return {string} string representation of the tree 1028 */ 1029 toString: function() { 1030 return "TreeView " + this.id; 1031 }, 1032 1033 /** 1034 * Count of nodes in tree 1035 * @method getNodeCount 1036 * @return {int} number of nodes in the tree 1037 */ 1038 getNodeCount: function() { 1039 return this.getRoot().getNodeCount(); 1040 }, 1041 1042 /** 1043 * Returns an object which could be used to rebuild the tree. 1044 * It can be passed to the tree constructor to reproduce the same tree. 1045 * It will return false if any node loads dynamically, regardless of whether it is loaded or not. 1046 * @method getTreeDefinition 1047 * @return {Object | false} definition of the tree or false if any node is defined as dynamic 1048 */ 1049 getTreeDefinition: function() { 1050 return this.getRoot().getNodeDefinition(); 1051 }, 1052 1053 /** 1054 * Abstract method that is executed when a node is expanded 1055 * @method onExpand 1056 * @param node {Node} the node that was expanded 1057 * @deprecated use treeobj.subscribe("expand") instead 1058 */ 1059 onExpand: function(node) { }, 1060 1061 /** 1062 * Abstract method that is executed when a node is collapsed. 1063 * @method onCollapse 1064 * @param node {Node} the node that was collapsed. 1065 * @deprecated use treeobj.subscribe("collapse") instead 1066 */ 1067 onCollapse: function(node) { } 1068 1069 }; 1070 1071 /* Backwards compatibility aliases */ 1072 var PROT = TV.prototype; 1073 /** 1074 * Renders the tree boilerplate and visible nodes. 1075 * Alias for render 1076 * @method draw 1077 * @deprecated Use render instead 1078 */ 1079 PROT.draw = PROT.render; 1080 1081 /* end backwards compatibility aliases */ 1082 1083 YAHOO.augment(TV, YAHOO.util.EventProvider); 1084 1085 /** 1086 * Running count of all nodes created in all trees. This is 1087 * used to provide unique identifies for all nodes. Deleting 1088 * nodes does not change the nodeCount. 1089 * @property YAHOO.widget.TreeView.nodeCount 1090 * @type int 1091 * @static 1092 */ 1093 TV.nodeCount = 0; 1094 1095 /** 1096 * Global cache of tree instances 1097 * @property YAHOO.widget.TreeView.trees 1098 * @type Array 1099 * @static 1100 * @private 1101 */ 1102 TV.trees = []; 1103 1104 /** 1105 * Global method for getting a tree by its id. Used in the generated 1106 * tree html. 1107 * @method YAHOO.widget.TreeView.getTree 1108 * @param treeId {String} the id of the tree instance 1109 * @return {TreeView} the tree instance requested, null if not found. 1110 * @static 1111 */ 1112 TV.getTree = function(treeId) { 1113 var t = TV.trees[treeId]; 1114 return (t) ? t : null; 1115 }; 1116 1117 1118 /** 1119 * Global method for getting a node by its id. Used in the generated 1120 * tree html. 1121 * @method YAHOO.widget.TreeView.getNode 1122 * @param treeId {String} the id of the tree instance 1123 * @param nodeIndex {String} the index of the node to return 1124 * @return {Node} the node instance requested, null if not found 1125 * @static 1126 */ 1127 TV.getNode = function(treeId, nodeIndex) { 1128 var t = TV.getTree(treeId); 1129 return (t) ? t.getNodeByIndex(nodeIndex) : null; 1130 }; 1131 1132 1133 /** 1134 * Class name assigned to elements that have the focus 1135 * 1136 * @property TreeView.FOCUS_CLASS_NAME 1137 * @type String 1138 * @static 1139 * @final 1140 * @default "ygtvfocus" 1141 1142 */ 1143 TV.FOCUS_CLASS_NAME = 'ygtvfocus'; 1144 1145 /** 1146 * Attempts to preload the images defined in the styles used to draw the tree by 1147 * rendering off-screen elements that use the styles. 1148 * @method YAHOO.widget.TreeView.preload 1149 * @param {string} prefix the prefix to use to generate the names of the 1150 * images to preload, default is ygtv 1151 * @static 1152 */ 1153 TV.preload = function(e, prefix) { 1154 prefix = prefix || "ygtv"; 1155 1156 1157 var styles = ["tn","tm","tmh","tp","tph","ln","lm","lmh","lp","lph","loading"]; 1158 // var styles = ["tp"]; 1159 1160 var sb = []; 1161 1162 // save the first one for the outer container 1163 for (var i=1; i < styles.length; i=i+1) { 1164 sb[sb.length] = '<span class="' + prefix + styles[i] + '"> </span>'; 1165 } 1166 1167 var f = document.createElement("div"); 1168 var s = f.style; 1169 s.className = prefix + styles[0]; 1170 s.position = "absolute"; 1171 s.height = "1px"; 1172 s.width = "1px"; 1173 s.top = "-1000px"; 1174 s.left = "-1000px"; 1175 f.innerHTML = sb.join(""); 1176 1177 document.body.appendChild(f); 1178 1179 Event.removeListener(window, "load", TV.preload); 1180 1181 }; 1182 1183 Event.addListener(window,"load", TV.preload); 1184 })(); 1185 (function () { 1186 var Dom = YAHOO.util.Dom, 1187 Lang = YAHOO.lang, 1188 Event = YAHOO.util.Event; 1189 /** 1190 * The base class for all tree nodes. The node's presentation and behavior in 1191 * response to mouse events is handled in Node subclasses. 1192 * @namespace YAHOO.widget 1193 * @class Node 1194 * @uses YAHOO.util.EventProvider 1195 * @param oData {object} a string or object containing the data that will 1196 * be used to render this node, and any custom attributes that should be 1197 * stored with the node (which is available in noderef.data). 1198 * All values in oData will be used to set equally named properties in the node 1199 * as long as the node does have such properties, they are not undefined, private or functions. 1200 * @param oParent {Node} this node's parent node 1201 * @param expanded {boolean} the initial expanded/collapsed state (deprecated, use oData.expanded) 1202 * @constructor 1203 */ 1204 YAHOO.widget.Node = function(oData, oParent, expanded) { 1205 if (oData) { this.init(oData, oParent, expanded); } 1206 }; 1207 1208 YAHOO.widget.Node.prototype = { 1209 1210 /** 1211 * The index for this instance obtained from global counter in YAHOO.widget.TreeView. 1212 * @property index 1213 * @type int 1214 */ 1215 index: 0, 1216 1217 /** 1218 * This node's child node collection. 1219 * @property children 1220 * @type Node[] 1221 */ 1222 children: null, 1223 1224 /** 1225 * Tree instance this node is part of 1226 * @property tree 1227 * @type TreeView 1228 */ 1229 tree: null, 1230 1231 /** 1232 * The data linked to this node. This can be any object or primitive 1233 * value, and the data can be used in getNodeHtml(). 1234 * @property data 1235 * @type object 1236 */ 1237 data: null, 1238 1239 /** 1240 * Parent node 1241 * @property parent 1242 * @type Node 1243 */ 1244 parent: null, 1245 1246 /** 1247 * The depth of this node. We start at -1 for the root node. 1248 * @property depth 1249 * @type int 1250 */ 1251 depth: -1, 1252 1253 /** 1254 * The href for the node's label. If one is not specified, the href will 1255 * be set so that it toggles the node. 1256 * @property href 1257 * @type string 1258 */ 1259 href: null, 1260 1261 /** 1262 * The label href target, defaults to current window 1263 * @property target 1264 * @type string 1265 */ 1266 target: "_self", 1267 1268 /** 1269 * The node's expanded/collapsed state 1270 * @property expanded 1271 * @type boolean 1272 */ 1273 expanded: false, 1274 1275 /** 1276 * Can multiple children be expanded at once? 1277 * @property multiExpand 1278 * @type boolean 1279 */ 1280 multiExpand: true, 1281 1282 /** 1283 * Should we render children for a collapsed node? It is possible that the 1284 * implementer will want to render the hidden data... @todo verify that we 1285 * need this, and implement it if we do. 1286 * @property renderHidden 1287 * @type boolean 1288 */ 1289 renderHidden: false, 1290 1291 /** 1292 * This flag is set to true when the html is generated for this node's 1293 * children, and set to false when new children are added. 1294 * @property childrenRendered 1295 * @type boolean 1296 */ 1297 childrenRendered: false, 1298 1299 /** 1300 * Dynamically loaded nodes only fetch the data the first time they are 1301 * expanded. This flag is set to true once the data has been fetched. 1302 * @property dynamicLoadComplete 1303 * @type boolean 1304 */ 1305 dynamicLoadComplete: false, 1306 1307 /** 1308 * This node's previous sibling 1309 * @property previousSibling 1310 * @type Node 1311 */ 1312 previousSibling: null, 1313 1314 /** 1315 * This node's next sibling 1316 * @property nextSibling 1317 * @type Node 1318 */ 1319 nextSibling: null, 1320 1321 /** 1322 * We can set the node up to call an external method to get the child 1323 * data dynamically. 1324 * @property _dynLoad 1325 * @type boolean 1326 * @private 1327 */ 1328 _dynLoad: false, 1329 1330 /** 1331 * Function to execute when we need to get this node's child data. 1332 * @property dataLoader 1333 * @type function 1334 */ 1335 dataLoader: null, 1336 1337 /** 1338 * This is true for dynamically loading nodes while waiting for the 1339 * callback to return. 1340 * @property isLoading 1341 * @type boolean 1342 */ 1343 isLoading: false, 1344 1345 /** 1346 * The toggle/branch icon will not show if this is set to false. This 1347 * could be useful if the implementer wants to have the child contain 1348 * extra info about the parent, rather than an actual node. 1349 * @property hasIcon 1350 * @type boolean 1351 */ 1352 hasIcon: true, 1353 1354 /** 1355 * Used to configure what happens when a dynamic load node is expanded 1356 * and we discover that it does not have children. By default, it is 1357 * treated as if it still could have children (plus/minus icon). Set 1358 * iconMode to have it display like a leaf node instead. 1359 * @property iconMode 1360 * @type int 1361 */ 1362 iconMode: 0, 1363 1364 /** 1365 * Specifies whether or not the content area of the node should be allowed 1366 * to wrap. 1367 * @property nowrap 1368 * @type boolean 1369 * @default false 1370 */ 1371 nowrap: false, 1372 1373 /** 1374 * If true, the node will alway be rendered as a leaf node. This can be 1375 * used to override the presentation when dynamically loading the entire 1376 * tree. Setting this to true also disables the dynamic load call for the 1377 * node. 1378 * @property isLeaf 1379 * @type boolean 1380 * @default false 1381 */ 1382 isLeaf: false, 1383 1384 /** 1385 * The CSS class for the html content container. Defaults to ygtvhtml, but 1386 * can be overridden to provide a custom presentation for a specific node. 1387 * @property contentStyle 1388 * @type string 1389 */ 1390 contentStyle: "", 1391 1392 /** 1393 * The generated id that will contain the data passed in by the implementer. 1394 * @property contentElId 1395 * @type string 1396 */ 1397 contentElId: null, 1398 /** 1399 * The node type 1400 * @property _type 1401 * @private 1402 * @type string 1403 * @default "Node" 1404 */ 1405 _type: "Node", 1406 1407 /* 1408 spacerPath: "http://us.i1.yimg.com/us.yimg.com/i/space.gif", 1409 expandedText: "Expanded", 1410 collapsedText: "Collapsed", 1411 loadingText: "Loading", 1412 */ 1413 1414 /** 1415 * Initializes this node, gets some of the properties from the parent 1416 * @method init 1417 * @param oData {object} a string or object containing the data that will 1418 * be used to render this node 1419 * @param oParent {Node} this node's parent node 1420 * @param expanded {boolean} the initial expanded/collapsed state 1421 */ 1422 init: function(oData, oParent, expanded) { 1423 1424 this.data = oData; 1425 this.children = []; 1426 this.index = YAHOO.widget.TreeView.nodeCount; 1427 ++YAHOO.widget.TreeView.nodeCount; 1428 this.contentElId = "ygtvcontentel" + this.index; 1429 1430 if (Lang.isObject(oData)) { 1431 for (var property in oData) { 1432 if (property.charAt(0) != '_' && oData.hasOwnProperty(property) && !Lang.isUndefined(this[property]) && !Lang.isFunction(this[property]) ) { 1433 this[property] = oData[property]; 1434 } 1435 } 1436 } 1437 if (!Lang.isUndefined(expanded) ) { this.expanded = expanded; } 1438 1439 1440 /** 1441 * The parentChange event is fired when a parent element is applied 1442 * to the node. This is useful if you need to apply tree-level 1443 * properties to a tree that need to happen if a node is moved from 1444 * one tree to another. 1445 * 1446 * @event parentChange 1447 * @type CustomEvent 1448 */ 1449 this.createEvent("parentChange", this); 1450 1451 // oParent should never be null except when we create the root node. 1452 if (oParent) { 1453 oParent.appendChild(this); 1454 } 1455 }, 1456 1457 /** 1458 * Certain properties for the node cannot be set until the parent 1459 * is known. This is called after the node is inserted into a tree. 1460 * the parent is also applied to this node's children in order to 1461 * make it possible to move a branch from one tree to another. 1462 * @method applyParent 1463 * @param {Node} parentNode this node's parent node 1464 * @return {boolean} true if the application was successful 1465 */ 1466 applyParent: function(parentNode) { 1467 if (!parentNode) { 1468 return false; 1469 } 1470 1471 this.tree = parentNode.tree; 1472 this.parent = parentNode; 1473 this.depth = parentNode.depth + 1; 1474 1475 // @todo why was this put here. This causes new nodes added at the 1476 // root level to lose the menu behavior. 1477 // if (! this.multiExpand) { 1478 // this.multiExpand = parentNode.multiExpand; 1479 // } 1480 1481 this.tree.regNode(this); 1482 parentNode.childrenRendered = false; 1483 1484 // cascade update existing children 1485 for (var i=0, len=this.children.length;i<len;++i) { 1486 this.children[i].applyParent(this); 1487 } 1488 1489 this.fireEvent("parentChange"); 1490 1491 return true; 1492 }, 1493 1494 /** 1495 * Appends a node to the child collection. 1496 * @method appendChild 1497 * @param childNode {Node} the new node 1498 * @return {Node} the child node 1499 * @private 1500 */ 1501 appendChild: function(childNode) { 1502 if (this.hasChildren()) { 1503 var sib = this.children[this.children.length - 1]; 1504 sib.nextSibling = childNode; 1505 childNode.previousSibling = sib; 1506 } 1507 this.children[this.children.length] = childNode; 1508 childNode.applyParent(this); 1509 1510 // part of the IE display issue workaround. If child nodes 1511 // are added after the initial render, and the node was 1512 // instantiated with expanded = true, we need to show the 1513 // children div now that the node has a child. 1514 if (this.childrenRendered && this.expanded) { 1515 this.getChildrenEl().style.display = ""; 1516 } 1517 1518 return childNode; 1519 }, 1520 1521 /** 1522 * Appends this node to the supplied node's child collection 1523 * @method appendTo 1524 * @param parentNode {Node} the node to append to. 1525 * @return {Node} The appended node 1526 */ 1527 appendTo: function(parentNode) { 1528 return parentNode.appendChild(this); 1529 }, 1530 1531 /** 1532 * Inserts this node before this supplied node 1533 * @method insertBefore 1534 * @param node {Node} the node to insert this node before 1535 * @return {Node} the inserted node 1536 */ 1537 insertBefore: function(node) { 1538 var p = node.parent; 1539 if (p) { 1540 1541 if (this.tree) { 1542 this.tree.popNode(this); 1543 } 1544 1545 var refIndex = node.isChildOf(p); 1546 p.children.splice(refIndex, 0, this); 1547 if (node.previousSibling) { 1548 node.previousSibling.nextSibling = this; 1549 } 1550 this.previousSibling = node.previousSibling; 1551 this.nextSibling = node; 1552 node.previousSibling = this; 1553 1554 this.applyParent(p); 1555 } 1556 1557 return this; 1558 }, 1559 1560 /** 1561 * Inserts this node after the supplied node 1562 * @method insertAfter 1563 * @param node {Node} the node to insert after 1564 * @return {Node} the inserted node 1565 */ 1566 insertAfter: function(node) { 1567 var p = node.parent; 1568 if (p) { 1569 1570 if (this.tree) { 1571 this.tree.popNode(this); 1572 } 1573 1574 var refIndex = node.isChildOf(p); 1575 1576 if (!node.nextSibling) { 1577 this.nextSibling = null; 1578 return this.appendTo(p); 1579 } 1580 1581 p.children.splice(refIndex + 1, 0, this); 1582 1583 node.nextSibling.previousSibling = this; 1584 this.previousSibling = node; 1585 this.nextSibling = node.nextSibling; 1586 node.nextSibling = this; 1587 1588 this.applyParent(p); 1589 } 1590 1591 return this; 1592 }, 1593 1594 /** 1595 * Returns true if the Node is a child of supplied Node 1596 * @method isChildOf 1597 * @param parentNode {Node} the Node to check 1598 * @return {boolean} The node index if this Node is a child of 1599 * supplied Node, else -1. 1600 * @private 1601 */ 1602 isChildOf: function(parentNode) { 1603 if (parentNode && parentNode.children) { 1604 for (var i=0, len=parentNode.children.length; i<len ; ++i) { 1605 if (parentNode.children[i] === this) { 1606 return i; 1607 } 1608 } 1609 } 1610 1611 return -1; 1612 }, 1613 1614 /** 1615 * Returns a node array of this node's siblings, null if none. 1616 * @method getSiblings 1617 * @return Node[] 1618 */ 1619 getSiblings: function() { 1620 var sib = this.parent.children.slice(0); 1621 for (var i=0;i < sib.length && sib[i] != this;i++) {} 1622 sib.splice(i,1); 1623 if (sib.length) { return sib; } 1624 return null; 1625 }, 1626 1627 /** 1628 * Shows this node's children 1629 * @method showChildren 1630 */ 1631 showChildren: function() { 1632 if (!this.tree.animateExpand(this.getChildrenEl(), this)) { 1633 if (this.hasChildren()) { 1634 this.getChildrenEl().style.display = ""; 1635 } 1636 } 1637 }, 1638 1639 /** 1640 * Hides this node's children 1641 * @method hideChildren 1642 */ 1643 hideChildren: function() { 1644 1645 if (!this.tree.animateCollapse(this.getChildrenEl(), this)) { 1646 this.getChildrenEl().style.display = "none"; 1647 } 1648 }, 1649 1650 /** 1651 * Returns the id for this node's container div 1652 * @method getElId 1653 * @return {string} the element id 1654 */ 1655 getElId: function() { 1656 return "ygtv" + this.index; 1657 }, 1658 1659 /** 1660 * Returns the id for this node's children div 1661 * @method getChildrenElId 1662 * @return {string} the element id for this node's children div 1663 */ 1664 getChildrenElId: function() { 1665 return "ygtvc" + this.index; 1666 }, 1667 1668 /** 1669 * Returns the id for this node's toggle element 1670 * @method getToggleElId 1671 * @return {string} the toggel element id 1672 */ 1673 getToggleElId: function() { 1674 return "ygtvt" + this.index; 1675 }, 1676 1677 1678 /* 1679 * Returns the id for this node's spacer image. The spacer is positioned 1680 * over the toggle and provides feedback for screen readers. 1681 * @method getSpacerId 1682 * @return {string} the id for the spacer image 1683 */ 1684 /* 1685 getSpacerId: function() { 1686 return "ygtvspacer" + this.index; 1687 }, 1688 */ 1689 1690 /** 1691 * Returns this node's container html element 1692 * @method getEl 1693 * @return {HTMLElement} the container html element 1694 */ 1695 getEl: function() { 1696 return Dom.get(this.getElId()); 1697 }, 1698 1699 /** 1700 * Returns the div that was generated for this node's children 1701 * @method getChildrenEl 1702 * @return {HTMLElement} this node's children div 1703 */ 1704 getChildrenEl: function() { 1705 return Dom.get(this.getChildrenElId()); 1706 }, 1707 1708 /** 1709 * Returns the element that is being used for this node's toggle. 1710 * @method getToggleEl 1711 * @return {HTMLElement} this node's toggle html element 1712 */ 1713 getToggleEl: function() { 1714 return Dom.get(this.getToggleElId()); 1715 }, 1716 /** 1717 * Returns the outer html element for this node's content 1718 * @method getContentEl 1719 * @return {HTMLElement} the element 1720 */ 1721 getContentEl: function() { 1722 return Dom.get(this.contentElId); 1723 }, 1724 1725 1726 /* 1727 * Returns the element that is being used for this node's spacer. 1728 * @method getSpacer 1729 * @return {HTMLElement} this node's spacer html element 1730 */ 1731 /* 1732 getSpacer: function() { 1733 return document.getElementById( this.getSpacerId() ) || {}; 1734 }, 1735 */ 1736 1737 /* 1738 getStateText: function() { 1739 if (this.isLoading) { 1740 return this.loadingText; 1741 } else if (this.hasChildren(true)) { 1742 if (this.expanded) { 1743 return this.expandedText; 1744 } else { 1745 return this.collapsedText; 1746 } 1747 } else { 1748 return ""; 1749 } 1750 }, 1751 */ 1752 1753 /** 1754 * Hides this nodes children (creating them if necessary), changes the toggle style. 1755 * @method collapse 1756 */ 1757 collapse: function() { 1758 // Only collapse if currently expanded 1759 if (!this.expanded) { return; } 1760 1761 // fire the collapse event handler 1762 var ret = this.tree.onCollapse(this); 1763 1764 if (false === ret) { 1765 return; 1766 } 1767 1768 ret = this.tree.fireEvent("collapse", this); 1769 1770 if (false === ret) { 1771 return; 1772 } 1773 1774 1775 if (!this.getEl()) { 1776 this.expanded = false; 1777 } else { 1778 // hide the child div 1779 this.hideChildren(); 1780 this.expanded = false; 1781 1782 this.updateIcon(); 1783 } 1784 1785 // this.getSpacer().title = this.getStateText(); 1786 1787 ret = this.tree.fireEvent("collapseComplete", this); 1788 1789 }, 1790 1791 /** 1792 * Shows this nodes children (creating them if necessary), changes the 1793 * toggle style, and collapses its siblings if multiExpand is not set. 1794 * @method expand 1795 */ 1796 expand: function(lazySource) { 1797 // Only expand if currently collapsed. 1798 if (this.expanded && !lazySource) { 1799 return; 1800 } 1801 1802 var ret = true; 1803 1804 // When returning from the lazy load handler, expand is called again 1805 // in order to render the new children. The "expand" event already 1806 // fired before fething the new data, so we need to skip it now. 1807 if (!lazySource) { 1808 // fire the expand event handler 1809 ret = this.tree.onExpand(this); 1810 1811 if (false === ret) { 1812 return; 1813 } 1814 1815 ret = this.tree.fireEvent("expand", this); 1816 } 1817 1818 if (false === ret) { 1819 return; 1820 } 1821 1822 if (!this.getEl()) { 1823 this.expanded = true; 1824 return; 1825 } 1826 1827 if (!this.childrenRendered) { 1828 this.getChildrenEl().innerHTML = this.renderChildren(); 1829 } else { 1830 } 1831 1832 this.expanded = true; 1833 1834 this.updateIcon(); 1835 1836 // this.getSpacer().title = this.getStateText(); 1837 1838 // We do an extra check for children here because the lazy 1839 // load feature can expose nodes that have no children. 1840 1841 // if (!this.hasChildren()) { 1842 if (this.isLoading) { 1843 this.expanded = false; 1844 return; 1845 } 1846 1847 if (! this.multiExpand) { 1848 var sibs = this.getSiblings(); 1849 for (var i=0; sibs && i<sibs.length; ++i) { 1850 if (sibs[i] != this && sibs[i].expanded) { 1851 sibs[i].collapse(); 1852 } 1853 } 1854 } 1855 1856 this.showChildren(); 1857 1858 ret = this.tree.fireEvent("expandComplete", this); 1859 }, 1860 1861 updateIcon: function() { 1862 if (this.hasIcon) { 1863 var el = this.getToggleEl(); 1864 if (el) { 1865 el.className = el.className.replace(/ygtv(([tl][pmn]h?)|(loading))/,this.getStyle()); 1866 } 1867 } 1868 }, 1869 1870 /** 1871 * Returns the css style name for the toggle 1872 * @method getStyle 1873 * @return {string} the css class for this node's toggle 1874 */ 1875 getStyle: function() { 1876 if (this.isLoading) { 1877 return "ygtvloading"; 1878 } else { 1879 // location top or bottom, middle nodes also get the top style 1880 var loc = (this.nextSibling) ? "t" : "l"; 1881 1882 // type p=plus(expand), m=minus(collapase), n=none(no children) 1883 var type = "n"; 1884 if (this.hasChildren(true) || (this.isDynamic() && !this.getIconMode())) { 1885 // if (this.hasChildren(true)) { 1886 type = (this.expanded) ? "m" : "p"; 1887 } 1888 1889 return "ygtv" + loc + type; 1890 } 1891 }, 1892 1893 /** 1894 * Returns the hover style for the icon 1895 * @return {string} the css class hover state 1896 * @method getHoverStyle 1897 */ 1898 getHoverStyle: function() { 1899 var s = this.getStyle(); 1900 if (this.hasChildren(true) && !this.isLoading) { 1901 s += "h"; 1902 } 1903 return s; 1904 }, 1905 1906 /** 1907 * Recursively expands all of this node's children. 1908 * @method expandAll 1909 */ 1910 expandAll: function() { 1911 for (var i=0;i<this.children.length;++i) { 1912 var c = this.children[i]; 1913 if (c.isDynamic()) { 1914 break; 1915 } else if (! c.multiExpand) { 1916 break; 1917 } else { 1918 c.expand(); 1919 c.expandAll(); 1920 } 1921 } 1922 }, 1923 1924 /** 1925 * Recursively collapses all of this node's children. 1926 * @method collapseAll 1927 */ 1928 collapseAll: function() { 1929 for (var i=0;i<this.children.length;++i) { 1930 this.children[i].collapse(); 1931 this.children[i].collapseAll(); 1932 } 1933 }, 1934 1935 /** 1936 * Configures this node for dynamically obtaining the child data 1937 * when the node is first expanded. Calling it without the callback 1938 * will turn off dynamic load for the node. 1939 * @method setDynamicLoad 1940 * @param fmDataLoader {function} the function that will be used to get the data. 1941 * @param iconMode {int} configures the icon that is displayed when a dynamic 1942 * load node is expanded the first time without children. By default, the 1943 * "collapse" icon will be used. If set to 1, the leaf node icon will be 1944 * displayed. 1945 */ 1946 setDynamicLoad: function(fnDataLoader, iconMode) { 1947 if (fnDataLoader) { 1948 this.dataLoader = fnDataLoader; 1949 this._dynLoad = true; 1950 } else { 1951 this.dataLoader = null; 1952 this._dynLoad = false; 1953 } 1954 1955 if (iconMode) { 1956 this.iconMode = iconMode; 1957 } 1958 }, 1959 1960 /** 1961 * Evaluates if this node is the root node of the tree 1962 * @method isRoot 1963 * @return {boolean} true if this is the root node 1964 */ 1965 isRoot: function() { 1966 return (this == this.tree.root); 1967 }, 1968 1969 /** 1970 * Evaluates if this node's children should be loaded dynamically. Looks for 1971 * the property both in this instance and the root node. If the tree is 1972 * defined to load all children dynamically, the data callback function is 1973 * defined in the root node 1974 * @method isDynamic 1975 * @return {boolean} true if this node's children are to be loaded dynamically 1976 */ 1977 isDynamic: function() { 1978 if (this.isLeaf) { 1979 return false; 1980 } else { 1981 return (!this.isRoot() && (this._dynLoad || this.tree.root._dynLoad)); 1982 // return lazy; 1983 } 1984 }, 1985 1986 /** 1987 * Returns the current icon mode. This refers to the way childless dynamic 1988 * load nodes appear (this comes into play only after the initial dynamic 1989 * load request produced no children). 1990 * @method getIconMode 1991 * @return {int} 0 for collapse style, 1 for leaf node style 1992 */ 1993 getIconMode: function() { 1994 return (this.iconMode || this.tree.root.iconMode); 1995 }, 1996 1997 /** 1998 * Checks if this node has children. If this node is lazy-loading and the 1999 * children have not been rendered, we do not know whether or not there 2000 * are actual children. In most cases, we need to assume that there are 2001 * children (for instance, the toggle needs to show the expandable 2002 * presentation state). In other times we want to know if there are rendered 2003 * children. For the latter, "checkForLazyLoad" should be false. 2004 * @method hasChildren 2005 * @param checkForLazyLoad {boolean} should we check for unloaded children? 2006 * @return {boolean} true if this has children or if it might and we are 2007 * checking for this condition. 2008 */ 2009 hasChildren: function(checkForLazyLoad) { 2010 if (this.isLeaf) { 2011 return false; 2012 } else { 2013 return ( this.children.length > 0 || 2014 (checkForLazyLoad && this.isDynamic() && !this.dynamicLoadComplete) ); 2015 } 2016 }, 2017 2018 /** 2019 * Expands if node is collapsed, collapses otherwise. 2020 * @method toggle 2021 */ 2022 toggle: function() { 2023 if (!this.tree.locked && ( this.hasChildren(true) || this.isDynamic()) ) { 2024 if (this.expanded) { this.collapse(); } else { this.expand(); } 2025 } 2026 }, 2027 2028 /** 2029 * Returns the markup for this node and its children. 2030 * @method getHtml 2031 * @return {string} the markup for this node and its expanded children. 2032 */ 2033 getHtml: function() { 2034 2035 this.childrenRendered = false; 2036 2037 var sb = []; 2038 sb[sb.length] = '<div class="ygtvitem" id="' + this.getElId() + '">'; 2039 sb[sb.length] = this.getNodeHtml(); 2040 sb[sb.length] = this.getChildrenHtml(); 2041 sb[sb.length] = '</div>'; 2042 return sb.join(""); 2043 }, 2044 2045 /** 2046 * Called when first rendering the tree. We always build the div that will 2047 * contain this nodes children, but we don't render the children themselves 2048 * unless this node is expanded. 2049 * @method getChildrenHtml 2050 * @return {string} the children container div html and any expanded children 2051 * @private 2052 */ 2053 getChildrenHtml: function() { 2054 2055 2056 var sb = []; 2057 sb[sb.length] = '<div class="ygtvchildren"'; 2058 sb[sb.length] = ' id="' + this.getChildrenElId() + '"'; 2059 2060 // This is a workaround for an IE rendering issue, the child div has layout 2061 // in IE, creating extra space if a leaf node is created with the expanded 2062 // property set to true. 2063 if (!this.expanded || !this.hasChildren()) { 2064 sb[sb.length] = ' style="display:none;"'; 2065 } 2066 sb[sb.length] = '>'; 2067 2068 2069 // Don't render the actual child node HTML unless this node is expanded. 2070 if ( (this.hasChildren(true) && this.expanded) || 2071 (this.renderHidden && !this.isDynamic()) ) { 2072 sb[sb.length] = this.renderChildren(); 2073 } 2074 2075 sb[sb.length] = '</div>'; 2076 2077 return sb.join(""); 2078 }, 2079 2080 /** 2081 * Generates the markup for the child nodes. This is not done until the node 2082 * is expanded. 2083 * @method renderChildren 2084 * @return {string} the html for this node's children 2085 * @private 2086 */ 2087 renderChildren: function() { 2088 2089 2090 var node = this; 2091 2092 if (this.isDynamic() && !this.dynamicLoadComplete) { 2093 this.isLoading = true; 2094 this.tree.locked = true; 2095 2096 if (this.dataLoader) { 2097 2098 setTimeout( 2099 function() { 2100 node.dataLoader(node, 2101 function() { 2102 node.loadComplete(); 2103 }); 2104 }, 10); 2105 2106 } else if (this.tree.root.dataLoader) { 2107 2108 setTimeout( 2109 function() { 2110 node.tree.root.dataLoader(node, 2111 function() { 2112 node.loadComplete(); 2113 }); 2114 }, 10); 2115 2116 } else { 2117 return "Error: data loader not found or not specified."; 2118 } 2119 2120 return ""; 2121 2122 } else { 2123 return this.completeRender(); 2124 } 2125 }, 2126 2127 /** 2128 * Called when we know we have all the child data. 2129 * @method completeRender 2130 * @return {string} children html 2131 */ 2132 completeRender: function() { 2133 var sb = []; 2134 2135 for (var i=0; i < this.children.length; ++i) { 2136 // this.children[i].childrenRendered = false; 2137 sb[sb.length] = this.children[i].getHtml(); 2138 } 2139 2140 this.childrenRendered = true; 2141 2142 return sb.join(""); 2143 }, 2144 2145 /** 2146 * Load complete is the callback function we pass to the data provider 2147 * in dynamic load situations. 2148 * @method loadComplete 2149 */ 2150 loadComplete: function() { 2151 this.getChildrenEl().innerHTML = this.completeRender(); 2152 this.dynamicLoadComplete = true; 2153 this.isLoading = false; 2154 this.expand(true); 2155 this.tree.locked = false; 2156 }, 2157 2158 /** 2159 * Returns this node's ancestor at the specified depth. 2160 * @method getAncestor 2161 * @param {int} depth the depth of the ancestor. 2162 * @return {Node} the ancestor 2163 */ 2164 getAncestor: function(depth) { 2165 if (depth >= this.depth || depth < 0) { 2166 return null; 2167 } 2168 2169 var p = this.parent; 2170 2171 while (p.depth > depth) { 2172 p = p.parent; 2173 } 2174 2175 return p; 2176 }, 2177 2178 /** 2179 * Returns the css class for the spacer at the specified depth for 2180 * this node. If this node's ancestor at the specified depth 2181 * has a next sibling the presentation is different than if it 2182 * does not have a next sibling 2183 * @method getDepthStyle 2184 * @param {int} depth the depth of the ancestor. 2185 * @return {string} the css class for the spacer 2186 */ 2187 getDepthStyle: function(depth) { 2188 return (this.getAncestor(depth).nextSibling) ? 2189 "ygtvdepthcell" : "ygtvblankdepthcell"; 2190 }, 2191 2192 /** 2193 * Get the markup for the node. This may be overrided so that we can 2194 * support different types of nodes. 2195 * @method getNodeHtml 2196 * @return {string} The HTML that will render this node. 2197 */ 2198 getNodeHtml: function() { 2199 var sb = []; 2200 2201 sb[sb.length] = '<table border="0" cellpadding="0" cellspacing="0" class="ygtvdepth' + this.depth + '">'; 2202 sb[sb.length] = '<tr class="ygtvrow">'; 2203 2204 for (var i=0;i<this.depth;++i) { 2205 sb[sb.length] = '<td class="' + this.getDepthStyle(i) + '"><div class="ygtvspacer"></div></td>'; 2206 } 2207 2208 if (this.hasIcon) { 2209 sb[sb.length] = '<td'; 2210 sb[sb.length] = ' id="' + this.getToggleElId() + '"'; 2211 sb[sb.length] = ' class="' + this.getStyle() + '"'; 2212 sb[sb.length] = '><a href="#" class="ygtvspacer"> </a></td>'; 2213 } 2214 2215 sb[sb.length] = '<td'; 2216 sb[sb.length] = ' id="' + this.contentElId + '"'; 2217 sb[sb.length] = ' class="' + this.contentStyle + ' ygtvcontent" '; 2218 sb[sb.length] = (this.nowrap) ? ' nowrap="nowrap" ' : ''; 2219 sb[sb.length] = ' >'; 2220 sb[sb.length] = this.getContentHtml(); 2221 sb[sb.length] = '</td>'; 2222 sb[sb.length] = '</tr>'; 2223 sb[sb.length] = '</table>'; 2224 2225 return sb.join(""); 2226 2227 }, 2228 /** 2229 * Get the markup for the contents of the node. This is designed to be overrided so that we can 2230 * support different types of nodes. 2231 * @method getContentHtml 2232 * @return {string} The HTML that will render the content of this node. 2233 */ 2234 getContentHtml: function () { 2235 return ""; 2236 }, 2237 2238 /** 2239 * Regenerates the html for this node and its children. To be used when the 2240 * node is expanded and new children have been added. 2241 * @method refresh 2242 */ 2243 refresh: function() { 2244 // this.loadComplete(); 2245 this.getChildrenEl().innerHTML = this.completeRender(); 2246 2247 if (this.hasIcon) { 2248 var el = this.getToggleEl(); 2249 if (el) { 2250 el.className = this.getStyle(); 2251 } 2252 } 2253 }, 2254 2255 /** 2256 * Node toString 2257 * @method toString 2258 * @return {string} string representation of the node 2259 */ 2260 toString: function() { 2261 return this._type + " (" + this.index + ")"; 2262 }, 2263 /** 2264 * array of items that had the focus set on them 2265 * so that they can be cleaned when focus is lost 2266 * @property _focusHighlightedItems 2267 * @type Array of DOM elements 2268 * @private 2269 */ 2270 _focusHighlightedItems: [], 2271 _focusedItem: null, 2272 /** 2273 * Sets the focus on the node element. 2274 * It will only be able to set the focus on nodes that have anchor elements in it. 2275 * Toggle or branch icons have anchors and can be focused on. 2276 * If will fail in nodes that have no anchor 2277 * @method focus 2278 * @return {boolean} success 2279 */ 2280 focus: function () { 2281 var focused = false, self = this; 2282 2283 var removeListeners = function () { 2284 var el; 2285 if (self._focusedItem) { 2286 Event.removeListener(self._focusedItem,'blur'); 2287 self._focusedItem = null; 2288 } 2289 2290 while ((el = self._focusHighlightedItems.shift())) { // yes, it is meant as an assignment, really 2291 Dom.removeClass(el,YAHOO.widget.TreeView.FOCUS_CLASS_NAME ); 2292 } 2293 }; 2294 removeListeners(); 2295 2296 Dom.getElementsBy ( 2297 function (el) { 2298 return /ygtv(([tl][pmn]h?)|(content))/.test(el.className); 2299 } , 2300 'td' , 2301 this.getEl().firstChild , 2302 function (el) { 2303 Dom.addClass(el, YAHOO.widget.TreeView.FOCUS_CLASS_NAME ); 2304 if (!focused) { 2305 var aEl = el.getElementsByTagName('a'); 2306 if (aEl.length) { 2307 aEl = aEl[0]; 2308 aEl.focus(); 2309 self._focusedItem = aEl; 2310 Event.on(aEl,'blur',removeListeners); 2311 focused = true; 2312 } 2313 } 2314 self._focusHighlightedItems.push(el); 2315 } 2316 ); 2317 if (!focused) { removeListeners(); } 2318 return focused; 2319 }, 2320 2321 /** 2322 * Count of nodes in tree 2323 * @method getNodeCount 2324 * @return {int} number of nodes in the tree 2325 */ 2326 getNodeCount: function() { 2327 for (var i = 0, count = 0;i< this.children.length;i++) { 2328 count += this.children[i].getNodeCount(); 2329 } 2330 return count + 1; 2331 }, 2332 2333 /** 2334 * Returns an object which could be used to build a tree out of this node and its children. 2335 * It can be passed to the tree constructor to reproduce this node as a tree. 2336 * It will return false if the node or any children loads dynamically, regardless of whether it is loaded or not. 2337 * @method getNodeDefinition 2338 * @return {Object | false} definition of the tree or false if the node or any children is defined as dynamic 2339 */ 2340 getNodeDefinition: function() { 2341 2342 if (this.isDynamic()) { return false; } 2343 2344 var def, defs = this.data, children = []; 2345 2346 2347 if (this.href) { defs.href = this.href; } 2348 if (this.target != '_self') { defs.target = this.target; } 2349 if (this.expanded) {defs.expanded = this.expanded; } 2350 if (!this.multiExpand) { defs.multiExpand = this.multiExpand; } 2351 if (!this.hasIcon) { defs.hasIcon = this.hasIcon; } 2352 if (this.nowrap) { defs.nowrap = this.nowrap; } 2353 defs.type = this._type; 2354 2355 2356 2357 for (var i = 0; i < this.children.length;i++) { 2358 def = this.children[i].getNodeDefinition(); 2359 if (def === false) { return false;} 2360 children.push(def); 2361 } 2362 if (children.length) { defs.children = children; } 2363 return defs; 2364 }, 2365 2366 2367 /** 2368 * Generates the link that will invoke this node's toggle method 2369 * @method getToggleLink 2370 * @return {string} the javascript url for toggling this node 2371 */ 2372 getToggleLink: function() { 2373 return 'return false;'; 2374 } 2375 2376 }; 2377 2378 YAHOO.augment(YAHOO.widget.Node, YAHOO.util.EventProvider); 2379 })(); 2380 (function () { 2381 var Dom = YAHOO.util.Dom, 2382 Lang = YAHOO.lang, 2383 Event = YAHOO.util.Event; 2384 /** 2385 * The default node presentation. The first parameter should be 2386 * either a string that will be used as the node's label, or an object 2387 * that has at least a string property called label. By default, clicking the 2388 * label will toggle the expanded/collapsed state of the node. By 2389 * setting the href property of the instance, this behavior can be 2390 * changed so that the label will go to the specified href. 2391 * @namespace YAHOO.widget 2392 * @class TextNode 2393 * @extends YAHOO.widget.Node 2394 * @constructor 2395 * @param oData {object} a string or object containing the data that will 2396 * be used to render this node. 2397 * Providing a string is the same as providing an object with a single property named label. 2398 * All values in the oData will be used to set equally named properties in the node 2399 * as long as the node does have such properties, they are not undefined, private or functions. 2400 * All attributes are made available in noderef.data, which 2401 * can be used to store custom attributes. TreeView.getNode(s)ByProperty 2402 * can be used to retrieve a node by one of the attributes. 2403 * @param oParent {YAHOO.widget.Node} this node's parent node 2404 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded) 2405 */ 2406 YAHOO.widget.TextNode = function(oData, oParent, expanded) { 2407 2408 if (oData) { 2409 if (Lang.isString(oData)) { 2410 oData = { label: oData }; 2411 } 2412 this.init(oData, oParent, expanded); 2413 this.setUpLabel(oData); 2414 } 2415 2416 }; 2417 2418 YAHOO.extend(YAHOO.widget.TextNode, YAHOO.widget.Node, { 2419 2420 /** 2421 * The CSS class for the label href. Defaults to ygtvlabel, but can be 2422 * overridden to provide a custom presentation for a specific node. 2423 * @property labelStyle 2424 * @type string 2425 */ 2426 labelStyle: "ygtvlabel", 2427 2428 /** 2429 * The derived element id of the label for this node 2430 * @property labelElId 2431 * @type string 2432 */ 2433 labelElId: null, 2434 2435 /** 2436 * The text for the label. It is assumed that the oData parameter will 2437 * either be a string that will be used as the label, or an object that 2438 * has a property called "label" that we will use. 2439 * @property label 2440 * @type string 2441 */ 2442 label: null, 2443 2444 /** 2445 * The text for the title (tooltip) for the label element 2446 * @property title 2447 * @type string 2448 */ 2449 title: null, 2450 2451 /** 2452 * The node type 2453 * @property _type 2454 * @private 2455 * @type string 2456 * @default "TextNode" 2457 */ 2458 _type: "TextNode", 2459 2460 2461 /** 2462 * Sets up the node label 2463 * @method setUpLabel 2464 * @param oData string containing the label, or an object with a label property 2465 */ 2466 setUpLabel: function(oData) { 2467 2468 if (Lang.isString(oData)) { 2469 oData = { 2470 label: oData 2471 }; 2472 } else { 2473 if (oData.style) { 2474 this.labelStyle = oData.style; 2475 } 2476 } 2477 2478 this.label = oData.label; 2479 2480 this.labelElId = "ygtvlabelel" + this.index; 2481 2482 }, 2483 2484 /** 2485 * Returns the label element 2486 * @for YAHOO.widget.TextNode 2487 * @method getLabelEl 2488 * @return {object} the element 2489 */ 2490 getLabelEl: function() { 2491 return Dom.get(this.labelElId); 2492 }, 2493 2494 // overrides YAHOO.widget.Node 2495 getContentHtml: function() { 2496 var sb = []; 2497 sb[sb.length] = this.href?'<a':'<span'; 2498 sb[sb.length] = ' id="' + this.labelElId + '"'; 2499 if (this.title) { 2500 sb[sb.length] = ' title="' + this.title + '"'; 2501 } 2502 sb[sb.length] = ' class="' + this.labelStyle + '"'; 2503 if (this.href) { 2504 sb[sb.length] = ' href="' + this.href + '"'; 2505 sb[sb.length] = ' target="' + this.target + '"'; 2506 } 2507 sb[sb.length] = ' >'; 2508 sb[sb.length] = this.label; 2509 sb[sb.length] = this.href?'</a>':'</span>'; 2510 return sb.join(""); 2511 }, 2512 2513 2514 2515 /** 2516 * Returns an object which could be used to build a tree out of this node and its children. 2517 * It can be passed to the tree constructor to reproduce this node as a tree. 2518 * It will return false if the node or any descendant loads dynamically, regardless of whether it is loaded or not. 2519 * @method getNodeDefinition 2520 * @return {Object | false} definition of the tree or false if this node or any descendant is defined as dynamic 2521 */ 2522 getNodeDefinition: function() { 2523 var def = YAHOO.widget.TextNode.superclass.getNodeDefinition.call(this); 2524 if (def === false) { return false; } 2525 2526 // Node specific properties 2527 def.label = this.label; 2528 if (this.labelStyle != 'ygtvlabel') { def.style = this.labelStyle; } 2529 if (this.title) { def.title = this.title ; } 2530 2531 return def; 2532 2533 }, 2534 2535 toString: function() { 2536 return YAHOO.widget.TextNode.superclass.toString.call(this) + ": " + this.label; 2537 }, 2538 2539 // deprecated 2540 onLabelClick: function() { 2541 return false; 2542 } 2543 }); 2544 })(); 2545 /** 2546 * A custom YAHOO.widget.Node that handles the unique nature of 2547 * the virtual, presentationless root node. 2548 * @namespace YAHOO.widget 2549 * @class RootNode 2550 * @extends YAHOO.widget.Node 2551 * @param oTree {YAHOO.widget.TreeView} The tree instance this node belongs to 2552 * @constructor 2553 */ 2554 YAHOO.widget.RootNode = function(oTree) { 2555 // Initialize the node with null params. The root node is a 2556 // special case where the node has no presentation. So we have 2557 // to alter the standard properties a bit. 2558 this.init(null, null, true); 2559 2560 /* 2561 * For the root node, we get the tree reference from as a param 2562 * to the constructor instead of from the parent element. 2563 */ 2564 this.tree = oTree; 2565 }; 2566 2567 YAHOO.extend(YAHOO.widget.RootNode, YAHOO.widget.Node, { 2568 2569 /** 2570 * The node type 2571 * @property _type 2572 * @type string 2573 * @private 2574 * @default "RootNode" 2575 */ 2576 _type: "RootNode", 2577 2578 // overrides YAHOO.widget.Node 2579 getNodeHtml: function() { 2580 return ""; 2581 }, 2582 2583 toString: function() { 2584 return this._type; 2585 }, 2586 2587 loadComplete: function() { 2588 this.tree.draw(); 2589 }, 2590 2591 /** 2592 * Count of nodes in tree. 2593 * It overrides Nodes.getNodeCount because the root node should not be counted. 2594 * @method getNodeCount 2595 * @return {int} number of nodes in the tree 2596 */ 2597 getNodeCount: function() { 2598 for (var i = 0, count = 0;i< this.children.length;i++) { 2599 count += this.children[i].getNodeCount(); 2600 } 2601 return count; 2602 }, 2603 2604 /** 2605 * Returns an object which could be used to build a tree out of this node and its children. 2606 * It can be passed to the tree constructor to reproduce this node as a tree. 2607 * Since the RootNode is automatically created by treeView, 2608 * its own definition is excluded from the returned node definition 2609 * which only contains its children. 2610 * @method getNodeDefinition 2611 * @return {Object | false} definition of the tree or false if any child node is defined as dynamic 2612 */ 2613 getNodeDefinition: function() { 2614 2615 for (var def, defs = [], i = 0; i < this.children.length;i++) { 2616 def = this.children[i].getNodeDefinition(); 2617 if (def === false) { return false;} 2618 defs.push(def); 2619 } 2620 return defs; 2621 }, 2622 2623 collapse: function() {}, 2624 expand: function() {}, 2625 getSiblings: function() { return null; }, 2626 focus: function () {} 2627 2628 }); 2629 (function () { 2630 var Dom = YAHOO.util.Dom, 2631 Lang = YAHOO.lang, 2632 Event = YAHOO.util.Event; 2633 2634 /** 2635 * This implementation takes either a string or object for the 2636 * oData argument. If is it a string, it will use it for the display 2637 * of this node (and it can contain any html code). If the parameter 2638 * is an object,it looks for a parameter called "html" that will be 2639 * used for this node's display. 2640 * @namespace YAHOO.widget 2641 * @class HTMLNode 2642 * @extends YAHOO.widget.Node 2643 * @constructor 2644 * @param oData {object} a string or object containing the data that will 2645 * be used to render this node. 2646 * Providing a string is the same as providing an object with a single property named html. 2647 * All values in the oData will be used to set equally named properties in the node 2648 * as long as the node does have such properties, they are not undefined, private or functions. 2649 * All other attributes are made available in noderef.data, which 2650 * can be used to store custom attributes. TreeView.getNode(s)ByProperty 2651 * can be used to retrieve a node by one of the attributes. 2652 * @param oParent {YAHOO.widget.Node} this node's parent node 2653 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded) 2654 * @param hasIcon {boolean} specifies whether or not leaf nodes should 2655 * be rendered with or without a horizontal line line and/or toggle icon. If the icon 2656 * is not displayed, the content fills the space it would have occupied. 2657 * This option operates independently of the leaf node presentation logic 2658 * for dynamic nodes. 2659 * (deprecated; use oData.hasIcon) 2660 */ 2661 YAHOO.widget.HTMLNode = function(oData, oParent, expanded, hasIcon) { 2662 if (oData) { 2663 this.init(oData, oParent, expanded); 2664 this.initContent(oData, hasIcon); 2665 } 2666 }; 2667 2668 YAHOO.extend(YAHOO.widget.HTMLNode, YAHOO.widget.Node, { 2669 2670 /** 2671 * The CSS class for the html content container. Defaults to ygtvhtml, but 2672 * can be overridden to provide a custom presentation for a specific node. 2673 * @property contentStyle 2674 * @type string 2675 */ 2676 contentStyle: "ygtvhtml", 2677 2678 2679 /** 2680 * The HTML content to use for this node's display 2681 * @property html 2682 * @type string 2683 */ 2684 html: null, 2685 2686 /** 2687 * The node type 2688 * @property _type 2689 * @private 2690 * @type string 2691 * @default "HTMLNode" 2692 */ 2693 _type: "HTMLNode", 2694 2695 /** 2696 * Sets up the node label 2697 * @property initContent 2698 * @param oData {object} An html string or object containing an html property 2699 * @param hasIcon {boolean} determines if the node will be rendered with an 2700 * icon or not 2701 */ 2702 initContent: function(oData, hasIcon) { 2703 this.setHtml(oData); 2704 this.contentElId = "ygtvcontentel" + this.index; 2705 if (!Lang.isUndefined(hasIcon)) { this.hasIcon = hasIcon; } 2706 2707 }, 2708 2709 /** 2710 * Synchronizes the node.data, node.html, and the node's content 2711 * @property setHtml 2712 * @param o {object} An html string or object containing an html property 2713 */ 2714 setHtml: function(o) { 2715 2716 this.data = o; 2717 this.html = (typeof o === "string") ? o : o.html; 2718 2719 var el = this.getContentEl(); 2720 if (el) { 2721 el.innerHTML = this.html; 2722 } 2723 2724 }, 2725 2726 // overrides YAHOO.widget.Node 2727 getContentHtml: function() { 2728 return this.html; 2729 }, 2730 2731 /** 2732 * Returns an object which could be used to build a tree out of this node and its children. 2733 * It can be passed to the tree constructor to reproduce this node as a tree. 2734 * It will return false if any node loads dynamically, regardless of whether it is loaded or not. 2735 * @method getNodeDefinition 2736 * @return {Object | false} definition of the tree or false if any node is defined as dynamic 2737 */ 2738 getNodeDefinition: function() { 2739 var def = YAHOO.widget.HTMLNode.superclass.getNodeDefinition.call(this); 2740 if (def === false) { return false; } 2741 def.html = this.html; 2742 return def; 2743 2744 } 2745 }); 2746 })(); 2747 /** 2748 * A menu-specific implementation that differs from TextNode in that only 2749 * one sibling can be expanded at a time. 2750 * @namespace YAHOO.widget 2751 * @class MenuNode 2752 * @extends YAHOO.widget.TextNode 2753 * @param oData {object} a string or object containing the data that will 2754 * be used to render this node. 2755 * Providing a string is the same as providing an object with a single property named label. 2756 * All values in the oData will be used to set equally named properties in the node 2757 * as long as the node does have such properties, they are not undefined, private or functions. 2758 * All attributes are made available in noderef.data, which 2759 * can be used to store custom attributes. TreeView.getNode(s)ByProperty 2760 * can be used to retrieve a node by one of the attributes. 2761 * @param oParent {YAHOO.widget.Node} this node's parent node 2762 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded) 2763 * @constructor 2764 */ 2765 YAHOO.widget.MenuNode = function(oData, oParent, expanded) { 2766 YAHOO.widget.MenuNode.superclass.constructor.call(this,oData,oParent,expanded); 2767 2768 /* 2769 * Menus usually allow only one branch to be open at a time. 2770 */ 2771 this.multiExpand = false; 2772 2773 }; 2774 2775 YAHOO.extend(YAHOO.widget.MenuNode, YAHOO.widget.TextNode, { 2776 2777 /** 2778 * The node type 2779 * @property _type 2780 * @private 2781 * @default "MenuNode" 2782 */ 2783 _type: "MenuNode" 2784 2785 }); 2786 (function () { 2787 var Dom = YAHOO.util.Dom, 2788 Lang = YAHOO.lang, 2789 Event = YAHOO.util.Event, 2790 Calendar = YAHOO.widget.Calendar; 2791 2792 /** 2793 * A Date-specific implementation that differs from TextNode in that it uses 2794 * YAHOO.widget.Calendar as an in-line editor, if available 2795 * If Calendar is not available, it behaves as a plain TextNode. 2796 * @namespace YAHOO.widget 2797 * @class DateNode 2798 * @extends YAHOO.widget.TextNode 2799 * @param oData {object} a string or object containing the data that will 2800 * be used to render this node. 2801 * Providing a string is the same as providing an object with a single property named label. 2802 * All values in the oData will be used to set equally named properties in the node 2803 * as long as the node does have such properties, they are not undefined, private nor functions. 2804 * All attributes are made available in noderef.data, which 2805 * can be used to store custom attributes. TreeView.getNode(s)ByProperty 2806 * can be used to retrieve a node by one of the attributes. 2807 * @param oParent {YAHOO.widget.Node} this node's parent node 2808 * @param expanded {boolean} the initial expanded/collapsed state (deprecated; use oData.expanded) 2809 * @constructor 2810 */ 2811 YAHOO.widget.DateNode = function(oData, oParent, expanded) { 2812 YAHOO.widget.DateNode.superclass.constructor.call(this,oData, oParent, expanded); 2813 }; 2814 2815 YAHOO.extend(YAHOO.widget.DateNode, YAHOO.widget.TextNode, { 2816 2817 /** 2818 * The node type 2819 * @property _type 2820 * @type string 2821 * @private 2822 * @default "DateNode" 2823 */ 2824 _type: "DateNode", 2825 2826 /** 2827 * Configuration object for the Calendar editor, if used. 2828 * See <a href="http://developer.yahoo.com/yui/calendar/#internationalization">http://developer.yahoo.com/yui/calendar/#internationalization</a> 2829 * @property calendarConfig 2830 */ 2831 calendarConfig: null, 2832 2833 2834 2835 /** 2836 * 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 2837 * @method fillEditorContainer 2838 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information 2839 * @return void 2840 */ 2841 fillEditorContainer: function (editorData) { 2842 2843 var cal, container = editorData.inputContainer; 2844 2845 if (Lang.isUndefined(Calendar)) { 2846 Dom.replaceClass(editorData.editorPanel,'ygtv-edit-DateNode','ygtv-edit-TextNode'); 2847 YAHOO.widget.DateNode.superclass.fillEditorContainer.call(this, editorData); 2848 return; 2849 } 2850 2851 if (editorData.nodeType != this._type) { 2852 editorData.nodeType = this._type; 2853 editorData.saveOnEnter = false; 2854 2855 editorData.node.destroyEditorContents(editorData); 2856 2857 editorData.inputObject = cal = new Calendar(container.appendChild(document.createElement('div'))); 2858 if (this.calendarConfig) { 2859 cal.cfg.applyConfig(this.calendarConfig,true); 2860 cal.cfg.fireQueue(); 2861 } 2862 cal.selectEvent.subscribe(function () { 2863 this.tree._closeEditor(true); 2864 },this,true); 2865 } else { 2866 cal = editorData.inputObject; 2867 } 2868 2869 cal.cfg.setProperty("selected",this.label, false); 2870 2871 var delim = cal.cfg.getProperty('DATE_FIELD_DELIMITER'); 2872 var pageDate = this.label.split(delim); 2873 cal.cfg.setProperty('pagedate',pageDate[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] + delim + pageDate[cal.cfg.getProperty('MDY_YEAR_POSITION') -1]); 2874 cal.cfg.fireQueue(); 2875 2876 cal.render(); 2877 cal.oDomContainer.focus(); 2878 }, 2879 /** 2880 * Saves the date entered in the editor into the DateNode label property and displays it. 2881 * Overrides Node.saveEditorValue 2882 * @method saveEditorValue 2883 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information 2884 */ 2885 saveEditorValue: function (editorData) { 2886 var node = editorData.node, value; 2887 if (Lang.isUndefined(Calendar)) { 2888 value = editorData.inputElement.value; 2889 } else { 2890 var cal = editorData.inputObject, 2891 date = cal.getSelectedDates()[0], 2892 dd = []; 2893 2894 dd[cal.cfg.getProperty('MDY_DAY_POSITION') -1] = date.getDate(); 2895 dd[cal.cfg.getProperty('MDY_MONTH_POSITION') -1] = date.getMonth() + 1; 2896 dd[cal.cfg.getProperty('MDY_YEAR_POSITION') -1] = date.getFullYear(); 2897 value = dd.join(cal.cfg.getProperty('DATE_FIELD_DELIMITER')); 2898 } 2899 2900 node.label = value; 2901 node.data.label = value; 2902 node.getLabelEl().innerHTML = value; 2903 } 2904 2905 }); 2906 })(); 2907 (function () { 2908 var Dom = YAHOO.util.Dom, 2909 Lang = YAHOO.lang, 2910 Event = YAHOO.util.Event, 2911 TV = YAHOO.widget.TreeView, 2912 TVproto = TV.prototype; 2913 2914 /** 2915 * An object to store information used for in-line editing 2916 * for all Nodes of all TreeViews. It contains: 2917 * <ul> 2918 * <li>active {boolean}, whether there is an active cell editor </li> 2919 * <li>whoHasIt {YAHOO.widget.TreeView} TreeView instance that is currently using the editor</li> 2920 * <li>nodeType {string} value of static Node._type property, allows reuse of input element if node is of the same type.</li> 2921 * <li>editorPanel {HTMLelement (<div>)} element holding the in-line editor</li> 2922 * <li>inputContainer {HTMLelement (<div>)} element which will hold the type-specific input element(s) to be filled by the fillEditorContainer method</li> 2923 * <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> 2924 * <li>node {YAHOO.widget.Node} reference to the Node being edited</li> 2925 * <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> 2926 * </ul> 2927 * Editors are free to use this object to store additional data. 2928 * @property editorData 2929 * @static 2930 * @for YAHOO.widget.TreeView 2931 */ 2932 TV.editorData = { 2933 active:false, 2934 whoHasIt:null, // which TreeView has it 2935 nodeType:null, 2936 editorPanel:null, 2937 inputContainer:null, 2938 buttonsContainer:null, 2939 node:null, // which Node is being edited 2940 saveOnEnter:true 2941 // Each node type is free to add its own properties to this as it sees fit. 2942 }; 2943 2944 /** 2945 * Entry point of the editing plug-in. 2946 * TreeView will call this method if it exists when a node label is clicked 2947 * @method _nodeEditing 2948 * @param node {YAHOO.widget.Node} the node to be edited 2949 * @return {Boolean} true to indicate that the node is editable and prevent any further bubbling of the click. 2950 * @for YAHOO.widget.TreeView 2951 */ 2952 2953 2954 TVproto._nodeEditing = function (node) { 2955 if (node.fillEditorContainer && node.editable) { 2956 var ed, topLeft, buttons, button, editorData = TV.editorData; 2957 editorData.active = true; 2958 editorData.whoHasIt = this; 2959 if (!editorData.nodeType) { 2960 editorData.editorPanel = ed = document.body.appendChild(document.createElement('div')); 2961 Dom.addClass(ed,'ygtv-label-editor'); 2962 2963 buttons = editorData.buttonsContainer = ed.appendChild(document.createElement('div')); 2964 Dom.addClass(buttons,'ygtv-button-container'); 2965 button = buttons.appendChild(document.createElement('button')); 2966 Dom.addClass(button,'ygtvok'); 2967 button.innerHTML = ' '; 2968 button = buttons.appendChild(document.createElement('button')); 2969 Dom.addClass(button,'ygtvcancel'); 2970 button.innerHTML = ' '; 2971 Event.on(buttons, 'click', function (ev) { 2972 var target = Event.getTarget(ev); 2973 var node = TV.editorData.node; 2974 if (Dom.hasClass(target,'ygtvok')) { 2975 Event.stopEvent(ev); 2976 this._closeEditor(true); 2977 } 2978 if (Dom.hasClass(target,'ygtvcancel')) { 2979 Event.stopEvent(ev); 2980 this._closeEditor(false); 2981 } 2982 }, this, true); 2983 2984 editorData.inputContainer = ed.appendChild(document.createElement('div')); 2985 Dom.addClass(editorData.inputContainer,'ygtv-input'); 2986 2987 Event.on(ed,'keydown',function (ev) { 2988 var editorData = TV.editorData, 2989 KEY = YAHOO.util.KeyListener.KEY; 2990 switch (ev.keyCode) { 2991 case KEY.ENTER: 2992 Event.stopEvent(ev); 2993 if (editorData.saveOnEnter) { 2994 this._closeEditor(true); 2995 } 2996 break; 2997 case KEY.ESCAPE: 2998 Event.stopEvent(ev); 2999 this._closeEditor(false); 3000 break; 3001 } 3002 },this,true); 3003 3004 3005 3006 } else { 3007 ed = editorData.editorPanel; 3008 } 3009 editorData.node = node; 3010 if (editorData.nodeType) { 3011 Dom.removeClass(ed,'ygtv-edit-' + editorData.nodeType); 3012 } 3013 Dom.addClass(ed,' ygtv-edit-' + node._type); 3014 topLeft = Dom.getXY(node.getContentEl()); 3015 Dom.setStyle(ed,'left',topLeft[0] + 'px'); 3016 Dom.setStyle(ed,'top',topLeft[1] + 'px'); 3017 Dom.setStyle(ed,'display','block'); 3018 ed.focus(); 3019 node.fillEditorContainer(editorData); 3020 3021 return true; // If inline editor available, don't do anything else. 3022 } 3023 }; 3024 3025 /** 3026 * Method to be associated with an event (clickEvent, dblClickEvent or enterKeyPressed) to pop up the contents editor 3027 * It calls the corresponding node editNode method. 3028 * @method onEventEditNode 3029 * @param oArgs {object} Object passed as arguments to TreeView event listeners 3030 * @for YAHOO.widget.TreeView 3031 */ 3032 3033 TVproto.onEventEditNode = function (oArgs) { 3034 if (oArgs instanceof YAHOO.widget.Node) { 3035 oArgs.editNode(); 3036 } else if (oArgs.node instanceof YAHOO.widget.Node) { 3037 oArgs.node.editNode(); 3038 } 3039 }; 3040 3041 /** 3042 * Method to be called when the inline editing is finished and the editor is to be closed 3043 * @method _closeEditor 3044 * @param save {Boolean} true if the edited value is to be saved, false if discarded 3045 * @private 3046 * @for YAHOO.widget.TreeView 3047 */ 3048 3049 TVproto._closeEditor = function (save) { 3050 var ed = TV.editorData, 3051 node = ed.node; 3052 if (save) { 3053 ed.node.saveEditorValue(ed); 3054 } 3055 Dom.setStyle(ed.editorPanel,'display','none'); 3056 ed.active = false; 3057 node.focus(); 3058 }; 3059 3060 /** 3061 * Entry point for TreeView's destroy method to destroy whatever the editing plug-in has created 3062 * @method _destroyEditor 3063 * @private 3064 * @for YAHOO.widget.TreeView 3065 */ 3066 TVproto._destroyEditor = function() { 3067 var ed = TV.editorData; 3068 if (ed && ed.nodeType && (!ed.active || ed.whoHasIt === this)) { 3069 Event.removeListener(ed.editorPanel,'keydown'); 3070 Event.removeListener(ed.buttonContainer,'click'); 3071 ed.node.destroyEditorContents(ed); 3072 document.body.removeChild(ed.editorPanel); 3073 ed.nodeType = ed.editorPanel = ed.inputContainer = ed.buttonsContainer = ed.whoHasIt = ed.node = null; 3074 ed.active = false; 3075 } 3076 }; 3077 3078 var Nproto = YAHOO.widget.Node.prototype; 3079 3080 /** 3081 * Signals if the label is editable. (Ignored on TextNodes with href set.) 3082 * @property editable 3083 * @type boolean 3084 * @for YAHOO.widget.Node 3085 */ 3086 Nproto.editable = false; 3087 3088 /** 3089 * pops up the contents editor, if there is one and the node is declared editable 3090 * @method editNode 3091 * @for YAHOO.widget.Node 3092 */ 3093 3094 Nproto.editNode = function () { 3095 this.tree._nodeEditing(this); 3096 }; 3097 3098 3099 3100 3101 /** Placeholder for a function that should provide the inline node label editor. 3102 * Leaving it set to null will indicate that this node type is not editable. 3103 * It should be overridden by nodes that provide inline editing. 3104 * The Node-specific editing element (input box, textarea or whatever) should be inserted into editorData.inputContainer. 3105 * @method fillEditorContainer 3106 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information 3107 * @return void 3108 * @for YAHOO.widget.Node 3109 */ 3110 Nproto.fillEditorContainer = null; 3111 3112 3113 /** 3114 * Node-specific destroy function to empty the contents of the inline editor panel 3115 * This function is the worst case alternative that will purge all possible events and remove the editor contents 3116 * Method Event.purgeElement is somewhat costly so if it can be replaced by specifc Event.removeListeners, it is better to do so. 3117 * @method destroyEditorContents 3118 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information 3119 * @for YAHOO.widget.Node 3120 */ 3121 Nproto.destroyEditorContents = function (editorData) { 3122 // In the worst case, if the input editor (such as the Calendar) has no destroy method 3123 // we can only try to remove all possible events on it. 3124 Event.purgeElement(editorData.inputContainer,true); 3125 editorData.inputContainer.innerHTML = ''; 3126 }; 3127 3128 /** 3129 * Saves the value entered into the editor. 3130 * Should be overridden by each node type 3131 * @method saveEditorValue 3132 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information 3133 * @for YAHOO.widget.Node 3134 */ 3135 Nproto.saveEditorValue = function (editorData) { 3136 }; 3137 3138 var TNproto = YAHOO.widget.TextNode.prototype; 3139 3140 3141 3142 /** 3143 * Places an <input> textbox in the input container and loads the label text into it 3144 * @method fillEditorContainer 3145 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information 3146 * @return void 3147 * @for YAHOO.widget.TextNode 3148 */ 3149 TNproto.fillEditorContainer = function (editorData) { 3150 3151 var input; 3152 // If last node edited is not of the same type as this one, delete it and fill it with our editor 3153 if (editorData.nodeType != this._type) { 3154 editorData.nodeType = this._type; 3155 editorData.saveOnEnter = true; 3156 editorData.node.destroyEditorContents(editorData); 3157 3158 editorData.inputElement = input = editorData.inputContainer.appendChild(document.createElement('input')); 3159 3160 } else { 3161 // if the last node edited was of the same time, reuse the input element. 3162 input = editorData.inputElement; 3163 } 3164 3165 input.value = this.label; 3166 input.focus(); 3167 input.select(); 3168 }; 3169 3170 /** 3171 * Saves the value entered in the editor into the TextNode label property and displays it 3172 * Overrides Node.saveEditorValue 3173 * @method saveEditorValue 3174 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information 3175 * @for YAHOO.widget.TextNode 3176 */ 3177 TNproto.saveEditorValue = function (editorData) { 3178 var node = editorData.node, value = editorData.inputElement.value; 3179 node.label = value; 3180 node.data.label = value; 3181 node.getLabelEl().innerHTML = value; 3182 }; 3183 3184 /** 3185 * Destroys the contents of the inline editor panel 3186 * Overrides Node.destroyEditorContent 3187 * Since we didn't set any event listeners on this inline editor, it is more efficient to avoid the generic method in Node 3188 * @method destroyEditorContents 3189 * @param editorData {YAHOO.widget.TreeView.editorData} a shortcut to the static object holding editing information 3190 * @for YAHOO.widget.TextNode 3191 */ 3192 TNproto.destroyEditorContents = function (editorData) { 3193 editorData.inputContainer.innerHTML = ''; 3194 }; 3195 })(); 3196 /** 3197 * A static factory class for tree view expand/collapse animations 3198 * @class TVAnim 3199 * @static 3200 */ 3201 YAHOO.widget.TVAnim = function() { 3202 return { 3203 /** 3204 * Constant for the fade in animation 3205 * @property FADE_IN 3206 * @type string 3207 * @static 3208 */ 3209 FADE_IN: "TVFadeIn", 3210 3211 /** 3212 * Constant for the fade out animation 3213 * @property FADE_OUT 3214 * @type string 3215 * @static 3216 */ 3217 FADE_OUT: "TVFadeOut", 3218 3219 /** 3220 * Returns a ygAnim instance of the given type 3221 * @method getAnim 3222 * @param type {string} the type of animation 3223 * @param el {HTMLElement} the element to element (probably the children div) 3224 * @param callback {function} function to invoke when the animation is done. 3225 * @return {YAHOO.util.Animation} the animation instance 3226 * @static 3227 */ 3228 getAnim: function(type, el, callback) { 3229 if (YAHOO.widget[type]) { 3230 return new YAHOO.widget[type](el, callback); 3231 } else { 3232 return null; 3233 } 3234 }, 3235 3236 /** 3237 * Returns true if the specified animation class is available 3238 * @method isValid 3239 * @param type {string} the type of animation 3240 * @return {boolean} true if valid, false if not 3241 * @static 3242 */ 3243 isValid: function(type) { 3244 return (YAHOO.widget[type]); 3245 } 3246 }; 3247 } (); 3248 /** 3249 * A 1/2 second fade-in animation. 3250 * @class TVFadeIn 3251 * @constructor 3252 * @param el {HTMLElement} the element to animate 3253 * @param callback {function} function to invoke when the animation is finished 3254 */ 3255 YAHOO.widget.TVFadeIn = function(el, callback) { 3256 /** 3257 * The element to animate 3258 * @property el 3259 * @type HTMLElement 3260 */ 3261 this.el = el; 3262 3263 /** 3264 * the callback to invoke when the animation is complete 3265 * @property callback 3266 * @type function 3267 */ 3268 this.callback = callback; 3269 3270 }; 3271 3272 YAHOO.widget.TVFadeIn.prototype = { 3273 /** 3274 * Performs the animation 3275 * @method animate 3276 */ 3277 animate: function() { 3278 var tvanim = this; 3279 3280 var s = this.el.style; 3281 s.opacity = 0.1; 3282 s.filter = "alpha(opacity=10)"; 3283 s.display = ""; 3284 3285 var dur = 0.4; 3286 var a = new YAHOO.util.Anim(this.el, {opacity: {from: 0.1, to: 1, unit:""}}, dur); 3287 a.onComplete.subscribe( function() { tvanim.onComplete(); } ); 3288 a.animate(); 3289 }, 3290 3291 /** 3292 * Clean up and invoke callback 3293 * @method onComplete 3294 */ 3295 onComplete: function() { 3296 this.callback(); 3297 }, 3298 3299 /** 3300 * toString 3301 * @method toString 3302 * @return {string} the string representation of the instance 3303 */ 3304 toString: function() { 3305 return "TVFadeIn"; 3306 } 3307 }; 3308 /** 3309 * A 1/2 second fade out animation. 3310 * @class TVFadeOut 3311 * @constructor 3312 * @param el {HTMLElement} the element to animate 3313 * @param callback {Function} function to invoke when the animation is finished 3314 */ 3315 YAHOO.widget.TVFadeOut = function(el, callback) { 3316 /** 3317 * The element to animate 3318 * @property el 3319 * @type HTMLElement 3320 */ 3321 this.el = el; 3322 3323 /** 3324 * the callback to invoke when the animation is complete 3325 * @property callback 3326 * @type function 3327 */ 3328 this.callback = callback; 3329 3330 }; 3331 3332 YAHOO.widget.TVFadeOut.prototype = { 3333 /** 3334 * Performs the animation 3335 * @method animate 3336 */ 3337 animate: function() { 3338 var tvanim = this; 3339 var dur = 0.4; 3340 var a = new YAHOO.util.Anim(this.el, {opacity: {from: 1, to: 0.1, unit:""}}, dur); 3341 a.onComplete.subscribe( function() { tvanim.onComplete(); } ); 3342 a.animate(); 3343 }, 3344 3345 /** 3346 * Clean up and invoke callback 3347 * @method onComplete 3348 */ 3349 onComplete: function() { 3350 var s = this.el.style; 3351 s.display = "none"; 3352 // s.opacity = 1; 3353 s.filter = "alpha(opacity=100)"; 3354 this.callback(); 3355 }, 3356 3357 /** 3358 * toString 3359 * @method toString 3360 * @return {string} the string representation of the instance 3361 */ 3362 toString: function() { 3363 return "TVFadeOut"; 3364 } 3365 }; 3366 YAHOO.register("treeview", YAHOO.widget.TreeView, {version: "2.6.0", build: "1321"});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 14 11:33:29 2009 | Cross-referenced by PHPXref 0.7 |