[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/2in3/2.9.0/build/yui2-treeview/ -> yui2-treeview-debug.js (source)

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


Generated: Thu Aug 11 10:00:09 2016 Cross-referenced by PHPXref 0.7.1