[ Index ]

PHP Cross Reference of Unnamed Project

title

Body

[close]

/lib/yuilib/3.17.2/tree/ -> tree-debug.js (source)

   1  /*
   2  YUI 3.17.2 (build 9c3c78e)
   3  Copyright 2014 Yahoo! Inc. All rights reserved.
   4  Licensed under the BSD License.
   5  http://yuilibrary.com/license/
   6  */
   7  
   8  YUI.add('tree', function (Y, NAME) {
   9  
  10  /*jshint boss:true, expr:true, onevar:false */
  11  
  12  /**
  13  Provides a generic tree data structure and related functionality.
  14  
  15  A tree has a root node, which may contain any number of child nodes, which may
  16  themselves contain child nodes, ad infinitum.
  17  
  18  Child nodes are lightweight function instances which delegate to the tree for
  19  all significant functionality, so trees remain performant and memory-efficient
  20  even with thousands and thousands of nodes.
  21  
  22  @module tree
  23  @main tree
  24  **/
  25  
  26  /**
  27  The `Tree` class represents a generic tree data structure. A tree has a root
  28  node, which may contain any number of child nodes, which may themselves contain
  29  child nodes, ad infinitum.
  30  
  31  This class doesn't expose any UI, but is intended to be used as a data structure
  32  or base class for other components. For example, the SmugMug TreeView gallery
  33  module extends Tree and provides a TreeView UI.
  34  
  35  @class Tree
  36  @param {Object} [config] Config options.
  37      @param {Object[]|Tree.Node[]} [config.nodes] Array of tree node config
  38          objects or `Tree.Node` instances to add to this tree at initialization
  39          time.
  40      @param {Object|Tree.Node} [config.rootNode] Node to use as the root node of
  41          this tree.
  42  @constructor
  43  @extends Base
  44  **/
  45  
  46  var Lang = Y.Lang,
  47  
  48      /**
  49      Fired when a node is added to this Tree. The `src` property will indicate
  50      how the node was added ("append", "insert", "prepend", etc.).
  51  
  52      @event add
  53      @param {Number} index Index at which the node will be added.
  54      @param {Tree.Node} node Node being added.
  55      @param {Tree.Node} parent Parent node to which the node will be added.
  56      @param {String} src Source of the event ("append", "insert", "prepend",
  57          etc.).
  58      @preventable _defAddFn
  59      **/
  60      EVT_ADD = 'add',
  61  
  62      /**
  63      Fired when this Tree is cleared.
  64  
  65      @event clear
  66      @param {Tree.Node} rootNode New root node of this tree (the old root node is
  67          always destroyed when a tree is cleared).
  68      @param {String} src Source of the event.
  69      @preventable _defClearFn
  70      **/
  71      EVT_CLEAR = 'clear',
  72  
  73      /**
  74      Fired when a node is removed from this Tree.
  75  
  76      @event remove
  77      @param {Boolean} destroy Whether or not the node will be destroyed after
  78          being removed from this tree.
  79      @param {Tree.Node} node Node being removed.
  80      @param {Tree.Node} parent Parent node from which the node will be removed.
  81      @param {String} src Source of the event.
  82      @preventable _defRemoveFn
  83      **/
  84      EVT_REMOVE = 'remove';
  85  
  86  var Tree = Y.Base.create('tree', Y.Base, [], {
  87      // -- Public Properties ----------------------------------------------------
  88  
  89      /**
  90      Reference to the `children` array of this Tree's `rootNode`.
  91  
  92      This is a convenience property to allow you to type `tree.children` instead
  93      of `tree.rootNode.children`.
  94  
  95      @property {Tree.Node[]} children
  96      @readOnly
  97      **/
  98  
  99      /**
 100      The `Tree.Node` class or subclass that should be used for nodes created by
 101      this tree.
 102  
 103      You may specify an actual class reference or a string that resolves to a
 104      class reference at runtime.
 105  
 106      @property {String|Tree.Node} nodeClass
 107      @default Y.Tree.Node
 108      **/
 109      nodeClass: Y.Tree.Node,
 110  
 111      /**
 112      Optional array containing one or more extension classes that should be mixed
 113      into the `nodeClass` when this Tree is instantiated. The resulting composed
 114      node class will be unique to this Tree instance and will not affect any
 115      other instances, nor will it modify the defined `nodeClass` itself.
 116  
 117      This provides a late-binding extension mechanism for nodes that doesn't
 118      require them to extend `Y.Base`, which would incur a significant performance
 119      hit.
 120  
 121      @property {Array} nodeExtensions
 122      @default []
 123      **/
 124      nodeExtensions: [],
 125  
 126      /**
 127      Root node of this Tree.
 128  
 129      @property {Tree.Node} rootNode
 130      @readOnly
 131      **/
 132  
 133      // -- Protected Properties -------------------------------------------------
 134  
 135      /**
 136      Simple way to type-check that this is a Tree instance.
 137  
 138      @property {Boolean} _isYUITree
 139      @default true
 140      @protected
 141      **/
 142      _isYUITree: true,
 143  
 144      /**
 145      Composed node class based on `nodeClass` that mixes in any extensions
 146      specified in `nodeExtensions`. If there are no extensions, this will just be
 147      a reference to `nodeClass`.
 148  
 149      @property {Tree.Node} _nodeClass
 150      @protected
 151      **/
 152  
 153      /**
 154      Mapping of node ids to node instances for nodes in this tree.
 155  
 156      @property {Object} _nodeMap
 157      @protected
 158      **/
 159  
 160      /**
 161      Default config object for the root node.
 162  
 163      @property {Object} _rootNodeConfig
 164      @protected
 165      **/
 166      _rootNodeConfig: {canHaveChildren: true},
 167  
 168      // -- Lifecycle ------------------------------------------------------------
 169      initializer: function (config) {
 170          config || (config = {});
 171  
 172          if (config.nodeClass) {
 173              this.nodeClass = config.nodeClass;
 174          }
 175  
 176          if (config.nodeExtensions) {
 177              this.nodeExtensions = this.nodeExtensions.concat(config.nodeExtensions);
 178          }
 179  
 180          /**
 181          Hash of published custom events.
 182  
 183          @property {Object} _published
 184          @default {}
 185          @protected
 186          **/
 187          this._published || (this._published = {});
 188          this._nodeMap = {};
 189  
 190          // Allow all extensions to initialize, then finish up.
 191          this.onceAfter('initializedChange', function () {
 192              this._composeNodeClass();
 193  
 194              this.clear(config.rootNode, {silent: true});
 195  
 196              if (config.nodes) {
 197                  this.insertNode(this.rootNode, config.nodes, {silent: true});
 198              }
 199          });
 200      },
 201  
 202      destructor: function () {
 203          this.destroyNode(this.rootNode, {silent: true});
 204  
 205          this.children   = null;
 206          this.rootNode   = null;
 207          this._nodeClass = null;
 208          this._nodeMap   = null;
 209          this._published = null;
 210      },
 211  
 212      // -- Public Methods -------------------------------------------------------
 213  
 214      /**
 215      Appends a node or array of nodes as the last child of the specified parent
 216      node.
 217  
 218      If a node being appended is from another tree, it and all its children will
 219      be removed from that tree and moved to this one.
 220  
 221      @method appendNode
 222      @param {Tree.Node} parent Parent node.
 223      @param {Object|Object[]|Tree.Node|Tree.Node[]} node Child node, node config
 224          object, array of child nodes, or array of node config objects to append
 225          to the given parent. Node config objects will automatically be converted
 226          into node instances.
 227      @param {Object} [options] Options.
 228          @param {Boolean} [options.silent=false] If `true`, the `add` event will
 229              be suppressed.
 230      @return {Tree.Node|Tree.Node[]} Node or array of nodes that were
 231          appended.
 232      **/
 233      appendNode: function (parent, node, options) {
 234          return this.insertNode(parent, node, Y.merge(options, {
 235              index: parent.children.length,
 236              src  : 'append'
 237          }));
 238      },
 239  
 240      /**
 241      Clears this tree by destroying the root node and all its children. If a
 242      `rootNode` argument is provided, that node will become the root node of this
 243      tree; otherwise, a new root node will be created.
 244  
 245      @method clear
 246      @param {Object|Tree.Node} [rootNode] If specified, this node will be used as
 247          the new root node.
 248      @param {Object} [options] Options.
 249          @param {Boolean} [options.silent=false] If `true`, the `clear` event
 250              will be suppressed.
 251          @param {String} [options.src] Source of the change, to be passed along
 252              to the event facade of the resulting event. This can be used to
 253              distinguish between changes triggered by a user and changes
 254              triggered programmatically, for example.
 255      @chainable
 256      **/
 257      clear: function (rootNode, options) {
 258          return this._fireTreeEvent(EVT_CLEAR, {
 259              rootNode: this.createNode(rootNode || this._rootNodeConfig),
 260              src     : options && options.src
 261          }, {
 262              defaultFn: this._defClearFn,
 263              silent   : options && options.silent
 264          });
 265      },
 266  
 267      /**
 268      Creates and returns a new `Tree.Node` instance associated with (but not
 269      yet appended to) this tree.
 270  
 271      @method createNode
 272      @param {Object|Tree.Node} [config] Node configuration. If a `Tree.Node`
 273          instance is specified instead of a config object, that node will be
 274          adopted into this tree (if it doesn't already belong to this tree) and
 275          removed from any other tree to which it belongs.
 276      @return {Tree.Node|null} New node, or `null` if a node could not be created
 277          from the given _config_.
 278      **/
 279      createNode: function (config) {
 280          config || (config = {});
 281  
 282          // If `config` is already a node, just ensure it hasn't been destroyed
 283          // and is in the node map, then return it.
 284          if (config._isYUITreeNode) {
 285              if (config.state.destroyed) {
 286                  Y.error('Cannot insert a node that has already been destroyed.', null, 'tree');
 287                  return null;
 288              }
 289  
 290              this._adoptNode(config);
 291              return config;
 292          }
 293  
 294          // First, create nodes for any children of this node.
 295          if (config.children) {
 296              var children = [];
 297  
 298              for (var i = 0, len = config.children.length; i < len; i++) {
 299                  children.push(this.createNode(config.children[i]));
 300              }
 301  
 302              config = Y.merge(config, {children: children});
 303          }
 304  
 305          var node = new this._nodeClass(this, config);
 306  
 307          return this._nodeMap[node.id] = node;
 308      },
 309  
 310      /**
 311      Removes and destroys a node and all its child nodes. Once destroyed, a node
 312      is eligible for garbage collection and cannot be reused or re-added to the
 313      tree.
 314  
 315      @method destroyNode
 316      @param {Tree.Node} node Node to destroy.
 317      @param {Object} [options] Options.
 318          @param {Boolean} [options.silent=false] If `true`, `remove` events will
 319              be suppressed.
 320          @param {String} [options.src] Source of the change, to be passed along
 321              to the event facade of the resulting events. This can be used to
 322              distinguish between changes triggered by a user and changes
 323              triggered programmatically, for example.
 324      @chainable
 325      **/
 326      destroyNode: function (node, options) {
 327          var child, i, len;
 328  
 329          options || (options = {});
 330  
 331          for (i = 0, len = node.children.length; i < len; i++) {
 332              child = node.children[i];
 333  
 334              // Manually remove the child from its parent; this makes destroying
 335              // all children of the parent much faster since there's no splicing
 336              // involved.
 337              child.parent = null;
 338  
 339              // Destroy the child.
 340              this.destroyNode(child, options);
 341          }
 342  
 343          if (node.parent) {
 344              this.removeNode(node, options);
 345          }
 346  
 347          node.children  = [];
 348          node.data      = {};
 349          node.state     = {destroyed: true};
 350          node.tree      = null;
 351          node._indexMap = {};
 352  
 353          delete this._nodeMap[node.id];
 354  
 355          return this;
 356      },
 357  
 358      /**
 359      Removes all children from the specified node. The removed children will
 360      still be reusable unless the `destroy` option is truthy.
 361  
 362      @method emptyNode
 363      @param {Tree.Node} node Node to empty.
 364      @param {Object} [options] Options.
 365          @param {Boolean} [options.destroy=false] If `true`, the children will
 366              also be destroyed, which makes them available for garbage collection
 367              and means they can't be reused.
 368          @param {Boolean} [options.silent=false] If `true`, `remove` events will
 369              be suppressed.
 370          @param {String} [options.src] Source of the change, to be passed along
 371              to the event facade of the resulting events. This can be used to
 372              distinguish between changes triggered by a user and changes
 373              triggered programmatically, for example.
 374      @return {Tree.Node[]} Array of removed child nodes.
 375      **/
 376      emptyNode: function (node, options) {
 377          var children = node.children,
 378              removed  = [];
 379  
 380          for (var i = children.length - 1; i > -1; --i) {
 381              removed[i] = this.removeNode(children[i], options);
 382          }
 383  
 384          return removed;
 385      },
 386  
 387      /**
 388      Performs a depth-first traversal of _node_, passing it and each of its
 389      descendants to the specified _callback_, and returning the first node for
 390      which the callback returns a truthy value.
 391  
 392      Traversal will stop as soon as a truthy value is returned from the callback.
 393  
 394      See `traverseNode()` for more details on how depth-first traversal works.
 395  
 396      @method findNode
 397      @param {Tree.Node} node Node to traverse.
 398      @param {Object} [options] Options.
 399          @param {Number} [options.depth] Depth limit. If specified, descendants
 400              will only be traversed to this depth before backtracking and moving
 401              on.
 402      @param {Function} callback Callback function to call with the traversed
 403          node and each of its descendants. If this function returns a truthy
 404          value, traversal will be stopped and the current node will be returned.
 405  
 406          @param {Tree.Node} callback.node Node being traversed.
 407  
 408      @param {Object} [thisObj] `this` object to use when executing _callback_.
 409      @return {Tree.Node|null} Returns the first node for which the _callback_
 410          returns a truthy value, or `null` if the callback never returns a truthy
 411          value.
 412      **/
 413      findNode: function (node, options, callback, thisObj) {
 414          var match = null;
 415  
 416          // Allow callback as second argument.
 417          if (typeof options === 'function') {
 418              thisObj  = callback;
 419              callback = options;
 420              options  = {};
 421          }
 422  
 423          this.traverseNode(node, options, function (descendant) {
 424              if (callback.call(thisObj, descendant)) {
 425                  match = descendant;
 426                  return Tree.STOP_TRAVERSAL;
 427              }
 428          });
 429  
 430          return match;
 431      },
 432  
 433      /**
 434      Returns the tree node with the specified id, or `undefined` if the node
 435      doesn't exist in this tree.
 436  
 437      @method getNodeById
 438      @param {String} id Node id.
 439      @return {Tree.Node} Node, or `undefined` if not found.
 440      **/
 441      getNodeById: function (id) {
 442          return this._nodeMap[id];
 443      },
 444  
 445      /**
 446      Inserts a node or array of nodes at the specified index under the given
 447      parent node, or appends them to the parent if no index is specified.
 448  
 449      If a node being inserted is from another tree, it and all its children will
 450      be removed from that tree and moved to this one.
 451  
 452      @method insertNode
 453      @param {Tree.Node} parent Parent node.
 454      @param {Object|Object[]|Tree.Node|Tree.Node[]} node Child node, node config
 455          object, array of child nodes, or array of node config objects to insert
 456          under the given parent. Node config objects will automatically be
 457          converted into node instances.
 458  
 459      @param {Object} [options] Options.
 460          @param {Number} [options.index] Index at which to insert the child node.
 461              If not specified, the node will be appended as the last child of the
 462              parent.
 463          @param {Boolean} [options.silent=false] If `true`, the `add` event will
 464              be suppressed.
 465          @param {String} [options.src='insert'] Source of the change, to be
 466              passed along to the event facade of the resulting event. This can be
 467              used to distinguish between changes triggered by a user and changes
 468              triggered programmatically, for example.
 469  
 470      @return {Tree.Node|Tree.Node[]} Node or array of nodes that were inserted.
 471      **/
 472      insertNode: function (parent, node, options) {
 473          options || (options = {});
 474          parent  || (parent = this.rootNode);
 475  
 476          // If `node` is an array, recurse to insert each node it contains.
 477          //
 478          // Note: If you're getting an exception here because `node` is null when
 479          // you've passed in a reference to some other node's `children` array,
 480          // that's happening because nodes must be removed from their current
 481          // parent before being added to the new one, and the `children` array is
 482          // being modified while the nodes are inserted.
 483          //
 484          // Solution: pass a copy of the other node's `children` array instead of
 485          // the original. Doing the copy operation here would have a negative
 486          // impact on performance, so you're on your own since this is such a
 487          // rare edge case.
 488          if ('length' in node && Lang.isArray(node)) {
 489              var hasIndex      = 'index' in options,
 490                  insertedNodes = [],
 491                  insertedNode;
 492  
 493              for (var i = 0, len = node.length; i < len; i++) {
 494                  insertedNode = this.insertNode(parent, node[i], options);
 495  
 496                  if (insertedNode) {
 497                      insertedNodes.push(insertedNode);
 498  
 499                      if (hasIndex) {
 500                          options.index += 1;
 501                      }
 502                  }
 503              }
 504  
 505              return insertedNodes;
 506          }
 507  
 508          node = this.createNode(node);
 509  
 510          if (node) {
 511              var index = options.index;
 512  
 513              if (typeof index === 'undefined') {
 514                  index = this._getDefaultNodeIndex(parent, node, options);
 515              }
 516  
 517              this._fireTreeEvent(EVT_ADD, {
 518                  index : index,
 519                  node  : node,
 520                  parent: parent,
 521                  src   : options.src || 'insert'
 522              }, {
 523                  defaultFn: this._defAddFn,
 524                  silent   : options.silent
 525              });
 526          }
 527  
 528          return node;
 529      },
 530  
 531      /**
 532      Prepends a node or array of nodes at the beginning of the specified parent
 533      node.
 534  
 535      If a node being prepended is from another tree, it and all its children will
 536      be removed from that tree and moved to this one.
 537  
 538      @method prependNode
 539      @param {Tree.Node} parent Parent node.
 540      @param {Object|Object[]|Tree.Node|Tree.Node[]} node Child node,
 541          node config object, array of child nodes, or array of node config
 542          objects to prepend to the given parent. Node config objects will
 543          automatically be converted into node instances.
 544      @param {Object} [options] Options.
 545          @param {Boolean} [options.silent=false] If `true`, the `add` event will
 546              be suppressed.
 547      @return {Tree.Node|Tree.Node[]} Node or array of nodes that were
 548          prepended.
 549      **/
 550      prependNode: function (parent, node, options) {
 551          return this.insertNode(parent, node, Y.merge(options, {
 552              index: 0,
 553              src  : 'prepend'
 554          }));
 555      },
 556  
 557      /**
 558      Removes the specified node from its parent node. The removed node will still
 559      be reusable unless the `destroy` option is truthy.
 560  
 561      @method removeNode
 562      @param {Tree.Node} node Node to remove.
 563      @param {Object} [options] Options.
 564          @param {Boolean} [options.destroy=false] If `true`, the node and all its
 565              children will also be destroyed, which makes them available for
 566              garbage collection and means they can't be reused.
 567          @param {Boolean} [options.silent=false] If `true`, the `remove` event
 568              will be suppressed.
 569          @param {String} [options.src] Source of the change, to be passed along
 570              to the event facade of the resulting event. This can be used to
 571              distinguish between changes triggered by a user and changes
 572              triggered programmatically, for example.
 573      @return {Tree.Node} Node that was removed.
 574      **/
 575      removeNode: function (node, options) {
 576          options || (options = {});
 577  
 578          this._fireTreeEvent(EVT_REMOVE, {
 579              destroy: !!options.destroy,
 580              node   : node,
 581              parent : node.parent,
 582              src    : options.src || 'remove'
 583          }, {
 584              defaultFn: this._defRemoveFn,
 585              silent   : options.silent
 586          });
 587  
 588          return node;
 589      },
 590  
 591      /**
 592      Returns the total number of nodes in this tree, at all levels.
 593  
 594      Use `rootNode.children.length` to get only the number of top-level nodes.
 595  
 596      @method size
 597      @return {Number} Total number of nodes in this tree.
 598      **/
 599      size: function () {
 600          return this.rootNode.size() + 1;
 601      },
 602  
 603      /**
 604      Serializes this tree to an object suitable for use in JSON.
 605  
 606      @method toJSON
 607      @return {Object} Serialized tree object.
 608      **/
 609      toJSON: function () {
 610          return this.rootNode.toJSON();
 611      },
 612  
 613      /**
 614      Performs a depth-first traversal of _node_, passing it and each of its
 615      descendants to the specified _callback_.
 616  
 617      If the callback function returns `Tree.STOP_TRAVERSAL`, traversal will be
 618      stopped immediately. Otherwise, it will continue until the deepest
 619      descendant of _node_ has been traversed, or until each branch has been
 620      traversed to the optional maximum depth limit.
 621  
 622      Since traversal is depth-first, that means nodes are traversed like this:
 623  
 624                  1
 625                / | \
 626               2  8  9
 627              / \     \
 628             3   7    10
 629           / | \      / \
 630          4  5  6    11 12
 631  
 632      @method traverseNode
 633      @param {Tree.Node} node Node to traverse.
 634      @param {Object} [options] Options.
 635          @param {Number} [options.depth] Depth limit. If specified, descendants
 636              will only be traversed to this depth before backtracking and moving
 637              on.
 638      @param {Function} callback Callback function to call with the traversed
 639          node and each of its descendants.
 640  
 641          @param {Tree.Node} callback.node Node being traversed.
 642  
 643      @param {Object} [thisObj] `this` object to use when executing _callback_.
 644      @return {Mixed} Returns `Tree.STOP_TRAVERSAL` if traversal was stopped;
 645          otherwise returns `undefined`.
 646      **/
 647      traverseNode: function (node, options, callback, thisObj) {
 648          if (node.state.destroyed) {
 649              Y.error('Cannot traverse a node that has been destroyed.', null, 'tree');
 650              return;
 651          }
 652  
 653          // Allow callback as second argument.
 654          if (typeof options === 'function') {
 655              thisObj  = callback;
 656              callback = options;
 657              options  = {};
 658          }
 659  
 660          options || (options = {});
 661  
 662          var stop      = Tree.STOP_TRAVERSAL,
 663              unlimited = typeof options.depth === 'undefined';
 664  
 665          if (callback.call(thisObj, node) === stop) {
 666              return stop;
 667          }
 668  
 669          var children = node.children;
 670  
 671          if (unlimited || options.depth > 0) {
 672              var childOptions = unlimited ? options : {depth: options.depth - 1};
 673  
 674              for (var i = 0, len = children.length; i < len; i++) {
 675                  if (this.traverseNode(children[i], childOptions, callback, thisObj) === stop) {
 676                      return stop;
 677                  }
 678              }
 679          }
 680      },
 681  
 682      // -- Protected Methods ----------------------------------------------------
 683  
 684      /**
 685      Moves the specified node and all its children from another tree to this
 686      tree.
 687  
 688      @method _adoptNode
 689      @param {Tree.Node} node Node to adopt.
 690      @param {Object} [options] Options to pass along to `removeNode()`.
 691      @protected
 692      **/
 693      _adoptNode: function (node, options) {
 694          var oldTree = node.tree,
 695              child;
 696  
 697          if (oldTree === this) {
 698              return;
 699          }
 700  
 701          for (var i = 0, len = node.children.length; i < len; i++) {
 702              child = node.children[i];
 703  
 704              child.parent = null; // Prevents the child from being removed from
 705                                   // its parent during the adoption.
 706  
 707              this._adoptNode(child, {silent: true});
 708              child.parent = node;
 709          }
 710  
 711          if (node.parent) {
 712              oldTree.removeNode(node, options);
 713          }
 714  
 715          delete oldTree._nodeMap[node.id];
 716  
 717          // If this node isn't an instance of this tree's composed _nodeClass,
 718          // then we need to recreate it to avoid potentially breaking things in
 719          // really weird ways.
 720          if (!(node instanceof this._nodeClass)
 721                  || oldTree._nodeClass !== this._nodeClass) {
 722  
 723              node = this.createNode(node.toJSON());
 724          }
 725  
 726          node.tree = this;
 727          node._isIndexStale = true;
 728  
 729          this._nodeMap[node.id] = node;
 730      },
 731  
 732      /**
 733      Composes a custom late-bound tree node class (if necessary) based on the
 734      classes specified in this Tree's `nodeClass` and `nodeExtensions`
 735      properties.
 736  
 737      The composed class is stored in this Tree's `_nodeClass` property. If
 738      composition wasn't necessary, then `_nodeClass` will just be a reference to
 739      `nodeClass`.
 740  
 741      @method _composeNodeClass
 742      @protected
 743      **/
 744      _composeNodeClass: function () {
 745          var nodeClass      = this.nodeClass,
 746              nodeExtensions = this.nodeExtensions,
 747              composedClass;
 748  
 749          if (typeof nodeClass === 'string') {
 750              // Look for a namespaced node class on `Y`.
 751              nodeClass = Y.Object.getValue(Y, nodeClass.split('.'));
 752  
 753              if (nodeClass) {
 754                  this.nodeClass = nodeClass;
 755              } else {
 756                  Y.error('Node class not found: ' + nodeClass, null, 'tree');
 757                  return;
 758              }
 759          }
 760  
 761          if (!nodeExtensions.length) {
 762              this._nodeClass = nodeClass;
 763              return;
 764          }
 765  
 766          // Compose a new class by mixing extensions into nodeClass.
 767          composedClass = function () {
 768              var extensions = composedClass._nodeExtensions;
 769  
 770              nodeClass.apply(this, arguments);
 771  
 772              for (var i = 0, len = extensions.length; i < len; i++) {
 773                  extensions[i].apply(this, arguments);
 774              }
 775          };
 776  
 777          Y.extend(composedClass, nodeClass);
 778  
 779          for (var i = 0, len = nodeExtensions.length; i < len; i++) {
 780              Y.mix(composedClass.prototype, nodeExtensions[i].prototype, true);
 781          }
 782  
 783          composedClass._nodeExtensions = nodeExtensions;
 784          this._nodeClass = composedClass;
 785      },
 786  
 787      /**
 788      Utility method for lazily publishing and firing events.
 789  
 790      @method _fireTreeEvent
 791      @param {String} name Event name to fire.
 792      @param {Object} facade Event facade.
 793      @param {Object} [options] Options.
 794          @param {Function} [options.defaultFn] Default handler for this event.
 795          @param {Boolean} [options.silent=false] Whether the default handler
 796              should be executed directly without actually firing the event.
 797      @chainable
 798      @protected
 799      **/
 800      _fireTreeEvent: function (name, facade, options) {
 801          if (options && options.silent) {
 802              if (options.defaultFn) {
 803                  facade.silent = true; // intentionally modifying the facade
 804                  options.defaultFn.call(this, facade);
 805              }
 806          } else {
 807              if (options && options.defaultFn && !this._published[name]) {
 808                  this._published[name] = this.publish(name, {
 809                      defaultFn: options.defaultFn
 810                  });
 811              }
 812  
 813              this.fire(name, facade);
 814          }
 815  
 816          return this;
 817      },
 818  
 819      /**
 820      Returns the default insertion index that should be used when _node_ is
 821      inserted as a child of _parent_ without an explicit index.
 822  
 823      The primary purpose of this method is to serve as a hook point for
 824      extensions and plugins that need to customize insertion order.
 825  
 826      @method _getDefaultNodeIndex
 827      @param {Tree.Node} parent Parent node.
 828      @param {Tree.Node} node Node being inserted.
 829      @param {Object} [options] Options passed to `insertNode()`.
 830      @return {Number} Index at which _node_ should be inserted into _parent_'s
 831          `children` array.
 832      @protected
 833      **/
 834      _getDefaultNodeIndex: function (parent/*, node, options*/) {
 835          return parent.children.length;
 836      },
 837  
 838      /**
 839      Removes the specified node from its parent node if it has one.
 840  
 841      @method _removeNodeFromParent
 842      @param {Tree.Node} node Node to remove.
 843      @protected
 844      **/
 845      _removeNodeFromParent: function (node) {
 846          var parent = node.parent,
 847              index;
 848  
 849          if (parent) {
 850              index = parent.indexOf(node);
 851  
 852              if (index > -1) {
 853                  var children = parent.children;
 854  
 855                  if (index === children.length - 1) {
 856                      children.pop();
 857                  } else {
 858                      children.splice(index, 1);
 859                      parent._isIndexStale = true;
 860                  }
 861  
 862                  node.parent = null;
 863              }
 864          }
 865      },
 866  
 867      // -- Default Event Handlers -----------------------------------------------
 868      _defAddFn: function (e) {
 869          var index  = e.index,
 870              node   = e.node,
 871              parent = e.parent,
 872              oldIndex;
 873  
 874          // Remove the node from its existing parent if it has one.
 875          if (node.parent) {
 876              // If the node's existing parent is the same parent it's being
 877              // inserted into, adjust the index to avoid an off-by-one error.
 878              if (node.parent === parent) {
 879                  oldIndex = parent.indexOf(node);
 880  
 881                  if (oldIndex === index) {
 882                      // Old index is the same as the new index, so just don't do
 883                      // anything.
 884                      return;
 885                  } else if (oldIndex < index) {
 886                      // Removing the node from its old index will affect the new
 887                      // index, so decrement the new index by one.
 888                      index -= 1;
 889                  }
 890              }
 891  
 892              this.removeNode(node, {
 893                  silent: e.silent,
 894                  src   : 'add'
 895              });
 896          }
 897  
 898          // Add the node to its new parent at the desired index.
 899          node.parent = parent;
 900          parent.children.splice(index, 0, node);
 901  
 902          parent.canHaveChildren = true;
 903          parent._isIndexStale   = true;
 904      },
 905  
 906      _defClearFn: function (e) {
 907          var newRootNode = e.rootNode;
 908  
 909          if (this.rootNode) {
 910              this.destroyNode(this.rootNode, {silent: true});
 911          }
 912  
 913          this._nodeMap = {};
 914          this._nodeMap[newRootNode.id] = newRootNode;
 915  
 916          this.rootNode = newRootNode;
 917          this.children = newRootNode.children;
 918      },
 919  
 920      _defRemoveFn: function (e) {
 921          var node = e.node;
 922  
 923          if (e.destroy) {
 924              this.destroyNode(node, {silent: true});
 925          } else if (e.parent) {
 926              this._removeNodeFromParent(node);
 927          } else if (this.rootNode === node) {
 928              // Guess we'll need a new root node!
 929              this.rootNode = this.createNode(this._rootNodeConfig);
 930              this.children = this.rootNode.children;
 931          }
 932      }
 933  }, {
 934      /**
 935      Return this value from a `Tree#traverseNode()` or `Tree.Node#traverse()`
 936      callback to immediately stop traversal.
 937  
 938      @property STOP_TRAVERSAL
 939      @static
 940      **/
 941      STOP_TRAVERSAL: {}
 942  });
 943  
 944  Y.Tree = Y.mix(Tree, Y.Tree);
 945  
 946  
 947  }, '3.17.2', {"requires": ["base-build", "tree-node"]});


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