[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/lib/yui/treeview/ -> treeview.js (source)

   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 &lt;UL&gt; or &lt;OL&gt; elements, possibly nested.  
 417       * Depending what the &lt;LI&gt; 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] + '">&#160;</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">&nbsp;</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 &lt;input&gt;  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 (&lt;div&gt;)} element holding the in-line editor</li>
2922      * <li>inputContainer {HTMLelement (&lt;div&gt;)} element which will hold the type-specific input element(s) to be filled by the fillEditorContainer method</li>
2923      * <li>buttonsContainer {HTMLelement (&lt;div&gt;)} element which holds the &lt;button&gt; 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 &lt;input&gt;  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"});


Generated: Wed Jan 14 11:33:29 2009 Cross-referenced by PHPXref 0.7