src/cm/media/js/lib/yui/yui_3.10.3/build/tree/tree-debug.js
changeset 525 89ef5ed3c48b
equal deleted inserted replaced
524:322d0feea350 525:89ef5ed3c48b
       
     1 /*
       
     2 YUI 3.10.3 (build 2fb5187)
       
     3 Copyright 2013 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 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} New node.
       
   277     **/
       
   278     createNode: function (config) {
       
   279         /*jshint boss:true */
       
   280         config || (config = {});
       
   281 
       
   282         // If `config` is already a node, just ensure it's in the node map and
       
   283         // return it.
       
   284         if (config._isYUITreeNode) {
       
   285             this._adoptNode(config);
       
   286             return config;
       
   287         }
       
   288 
       
   289         // First, create nodes for any children of this node.
       
   290         if (config.children) {
       
   291             var children = [];
       
   292 
       
   293             for (var i = 0, len = config.children.length; i < len; i++) {
       
   294                 children.push(this.createNode(config.children[i]));
       
   295             }
       
   296 
       
   297             config = Y.merge(config, {children: children});
       
   298         }
       
   299 
       
   300         var node = new this._nodeClass(this, config);
       
   301 
       
   302         return this._nodeMap[node.id] = node;
       
   303     },
       
   304 
       
   305     /**
       
   306     Removes and destroys a node and all its child nodes. Once destroyed, a node
       
   307     is eligible for garbage collection and cannot be reused or re-added to the
       
   308     tree.
       
   309 
       
   310     @method destroyNode
       
   311     @param {Tree.Node} node Node to destroy.
       
   312     @param {Object} [options] Options.
       
   313         @param {Boolean} [options.silent=false] If `true`, `remove` events will
       
   314             be suppressed.
       
   315         @param {String} [options.src] Source of the change, to be passed along
       
   316             to the event facade of the resulting events. This can be used to
       
   317             distinguish between changes triggered by a user and changes
       
   318             triggered programmatically, for example.
       
   319     @chainable
       
   320     **/
       
   321     destroyNode: function (node, options) {
       
   322         var child, i, len;
       
   323 
       
   324         options || (options = {});
       
   325 
       
   326         for (i = 0, len = node.children.length; i < len; i++) {
       
   327             child = node.children[i];
       
   328 
       
   329             // Manually remove the child from its parent; this makes destroying
       
   330             // all children of the parent much faster since there's no splicing
       
   331             // involved.
       
   332             child.parent = null;
       
   333 
       
   334             // Destroy the child.
       
   335             this.destroyNode(child, options);
       
   336         }
       
   337 
       
   338         if (node.parent) {
       
   339             this.removeNode(node, options);
       
   340         }
       
   341 
       
   342         node.children  = null;
       
   343         node.data      = null;
       
   344         node.state     = {destroyed: true};
       
   345         node.tree      = null;
       
   346         node._htmlNode = null;
       
   347         node._indexMap = null;
       
   348 
       
   349         delete this._nodeMap[node.id];
       
   350 
       
   351         return this;
       
   352     },
       
   353 
       
   354     /**
       
   355     Removes all children from the specified node. The removed children will
       
   356     still be reusable unless the `destroy` option is truthy.
       
   357 
       
   358     @method emptyNode
       
   359     @param {Tree.Node} node Node to empty.
       
   360     @param {Object} [options] Options.
       
   361         @param {Boolean} [options.destroy=false] If `true`, the children will
       
   362             also be destroyed, which makes them available for garbage collection
       
   363             and means they can't be reused.
       
   364         @param {Boolean} [options.silent=false] If `true`, `remove` events will
       
   365             be suppressed.
       
   366         @param {String} [options.src] Source of the change, to be passed along
       
   367             to the event facade of the resulting events. This can be used to
       
   368             distinguish between changes triggered by a user and changes
       
   369             triggered programmatically, for example.
       
   370     @return {Tree.Node[]} Array of removed child nodes.
       
   371     **/
       
   372     emptyNode: function (node, options) {
       
   373         var removed = [];
       
   374 
       
   375         while (node.children.length) {
       
   376             removed.push(this.removeNode(node.children[0], options));
       
   377         }
       
   378 
       
   379         return removed;
       
   380     },
       
   381 
       
   382     /**
       
   383     Performs a depth-first traversal of _node_, passing it and each of its
       
   384     descendants to the specified _callback_, and returning the first node for
       
   385     which the callback returns a truthy value.
       
   386 
       
   387     Traversal will stop as soon as a truthy value is returned from the callback.
       
   388 
       
   389     See `traverseNode()` for more details on how depth-first traversal works.
       
   390 
       
   391     @method findNode
       
   392     @param {Tree.Node} node Node to traverse.
       
   393     @param {Object} [options] Options.
       
   394         @param {Number} [options.depth] Depth limit. If specified, descendants
       
   395             will only be traversed to this depth before backtracking and moving
       
   396             on.
       
   397     @param {Function} callback Callback function to call with the traversed
       
   398         node and each of its descendants. If this function returns a truthy
       
   399         value, traversal will be stopped and the current node will be returned.
       
   400 
       
   401         @param {Tree.Node} callback.node Node being traversed.
       
   402 
       
   403     @param {Object} [thisObj] `this` object to use when executing _callback_.
       
   404     @return {Tree.Node|null} Returns the first node for which the _callback_
       
   405         returns a truthy value, or `null` if the callback never returns a truthy
       
   406         value.
       
   407     **/
       
   408     findNode: function (node, options, callback, thisObj) {
       
   409         var match = null;
       
   410 
       
   411         // Allow callback as second argument.
       
   412         if (typeof options === 'function') {
       
   413             thisObj  = callback;
       
   414             callback = options;
       
   415             options  = {};
       
   416         }
       
   417 
       
   418         this.traverseNode(node, options, function (descendant) {
       
   419             if (callback.call(thisObj, descendant)) {
       
   420                 match = descendant;
       
   421                 return Tree.STOP_TRAVERSAL;
       
   422             }
       
   423         });
       
   424 
       
   425         return match;
       
   426     },
       
   427 
       
   428     /**
       
   429     Returns the tree node with the specified id, or `undefined` if the node
       
   430     doesn't exist in this tree.
       
   431 
       
   432     @method getNodeById
       
   433     @param {String} id Node id.
       
   434     @return {Tree.Node} Node, or `undefined` if not found.
       
   435     **/
       
   436     getNodeById: function (id) {
       
   437         return this._nodeMap[id];
       
   438     },
       
   439 
       
   440     /**
       
   441     Inserts a node or array of nodes at the specified index under the given
       
   442     parent node, or appends them to the parent if no index is specified.
       
   443 
       
   444     If a node being inserted is from another tree, it and all its children will
       
   445     be removed from that tree and moved to this one.
       
   446 
       
   447     @method insertNode
       
   448     @param {Tree.Node} parent Parent node.
       
   449     @param {Object|Object[]|Tree.Node|Tree.Node[]} node Child node, node config
       
   450         object, array of child nodes, or array of node config objects to insert
       
   451         under the given parent. Node config objects will automatically be
       
   452         converted into node instances.
       
   453 
       
   454     @param {Object} [options] Options.
       
   455         @param {Number} [options.index] Index at which to insert the child node.
       
   456             If not specified, the node will be appended as the last child of the
       
   457             parent.
       
   458         @param {Boolean} [options.silent=false] If `true`, the `add` event will
       
   459             be suppressed.
       
   460         @param {String} [options.src='insert'] Source of the change, to be
       
   461             passed along to the event facade of the resulting event. This can be
       
   462             used to distinguish between changes triggered by a user and changes
       
   463             triggered programmatically, for example.
       
   464 
       
   465     @return {Tree.Node[]} Node or array of nodes that were inserted.
       
   466     **/
       
   467     insertNode: function (parent, node, options) {
       
   468         options || (options = {});
       
   469         parent  || (parent = this.rootNode);
       
   470 
       
   471         // If `node` is an array, recurse to insert each node it contains.
       
   472         //
       
   473         // Note: If you're getting an exception here because `node` is null when
       
   474         // you've passed in a reference to some other node's `children` array,
       
   475         // that's happening because nodes must be removed from their current
       
   476         // parent before being added to the new one, and the `children` array is
       
   477         // being modified while the nodes are inserted.
       
   478         //
       
   479         // Solution: pass a copy of the other node's `children` array instead of
       
   480         // the original. Doing the copy operation here would have a negative
       
   481         // impact on performance, so you're on your own since this is such a
       
   482         // rare edge case.
       
   483         if ('length' in node && Lang.isArray(node)) {
       
   484             var inserted = [];
       
   485 
       
   486             for (var i = 0, len = node.length; i < len; i++) {
       
   487                 inserted.push(this.insertNode(parent, node[i], options));
       
   488 
       
   489                 if ('index' in options) {
       
   490                     options.index += 1;
       
   491                 }
       
   492             }
       
   493 
       
   494             return inserted;
       
   495         }
       
   496 
       
   497         node = this.createNode(node);
       
   498 
       
   499         var index = options.index;
       
   500 
       
   501         if (typeof index === 'undefined') {
       
   502             index = this._getDefaultNodeIndex(parent, node, options);
       
   503         }
       
   504 
       
   505         this._fireTreeEvent(EVT_ADD, {
       
   506             index : index,
       
   507             node  : node,
       
   508             parent: parent,
       
   509             src   : options.src || 'insert'
       
   510         }, {
       
   511             defaultFn: this._defAddFn,
       
   512             silent   : options.silent
       
   513         });
       
   514 
       
   515         return node;
       
   516     },
       
   517 
       
   518     /**
       
   519     Prepends a node or array of nodes at the beginning of the specified parent
       
   520     node.
       
   521 
       
   522     If a node being prepended is from another tree, it and all its children will
       
   523     be removed from that tree and moved to this one.
       
   524 
       
   525     @method prependNode
       
   526     @param {Tree.Node} parent Parent node.
       
   527     @param {Object|Object[]|Tree.Node|Tree.Node[]} node Child node,
       
   528         node config object, array of child nodes, or array of node config
       
   529         objects to prepend to the given parent. Node config objects will
       
   530         automatically be converted into node instances.
       
   531     @param {Object} [options] Options.
       
   532         @param {Boolean} [options.silent=false] If `true`, the `add` event will
       
   533             be suppressed.
       
   534     @return {Tree.Node|Tree.Node[]} Node or array of nodes that were
       
   535         prepended.
       
   536     **/
       
   537     prependNode: function (parent, node, options) {
       
   538         return this.insertNode(parent, node, Y.merge(options, {
       
   539             index: 0,
       
   540             src  : 'prepend'
       
   541         }));
       
   542     },
       
   543 
       
   544     /**
       
   545     Removes the specified node from its parent node. The removed node will still
       
   546     be reusable unless the `destroy` option is truthy.
       
   547 
       
   548     @method removeNode
       
   549     @param {Tree.Node} node Node to remove.
       
   550     @param {Object} [options] Options.
       
   551         @param {Boolean} [options.destroy=false] If `true`, the node and all its
       
   552             children will also be destroyed, which makes them available for
       
   553             garbage collection and means they can't be reused.
       
   554         @param {Boolean} [options.silent=false] If `true`, the `remove` event
       
   555             will be suppressed.
       
   556         @param {String} [options.src] Source of the change, to be passed along
       
   557             to the event facade of the resulting event. This can be used to
       
   558             distinguish between changes triggered by a user and changes
       
   559             triggered programmatically, for example.
       
   560     @return {Tree.Node} Node that was removed.
       
   561     **/
       
   562     removeNode: function (node, options) {
       
   563         options || (options = {});
       
   564 
       
   565         this._fireTreeEvent(EVT_REMOVE, {
       
   566             destroy: !!options.destroy,
       
   567             node   : node,
       
   568             parent : node.parent,
       
   569             src    : options.src || 'remove'
       
   570         }, {
       
   571             defaultFn: this._defRemoveFn,
       
   572             silent   : options.silent
       
   573         });
       
   574 
       
   575         return node;
       
   576     },
       
   577 
       
   578     /**
       
   579     Returns the total number of nodes in this tree, at all levels.
       
   580 
       
   581     Use `rootNode.children.length` to get only the number of top-level nodes.
       
   582 
       
   583     @method size
       
   584     @return {Number} Total number of nodes in this tree.
       
   585     **/
       
   586     size: function () {
       
   587         return this.rootNode.size() + 1;
       
   588     },
       
   589 
       
   590     /**
       
   591     Serializes this tree to an object suitable for use in JSON.
       
   592 
       
   593     @method toJSON
       
   594     @return {Object} Serialized tree object.
       
   595     **/
       
   596     toJSON: function () {
       
   597         return this.rootNode.toJSON();
       
   598     },
       
   599 
       
   600     /**
       
   601     Performs a depth-first traversal of _node_, passing it and each of its
       
   602     descendants to the specified _callback_.
       
   603 
       
   604     If the callback function returns `Tree.STOP_TRAVERSAL`, traversal will be
       
   605     stopped immediately. Otherwise, it will continue until the deepest
       
   606     descendant of _node_ has been traversed, or until each branch has been
       
   607     traversed to the optional maximum depth limit.
       
   608 
       
   609     Since traversal is depth-first, that means nodes are traversed like this:
       
   610 
       
   611                 1
       
   612               / | \
       
   613              2  8  9
       
   614             / \     \
       
   615            3   7    10
       
   616          / | \      / \
       
   617         4  5  6    11 12
       
   618 
       
   619     @method traverseNode
       
   620     @param {Tree.Node} node Node to traverse.
       
   621     @param {Object} [options] Options.
       
   622         @param {Number} [options.depth] Depth limit. If specified, descendants
       
   623             will only be traversed to this depth before backtracking and moving
       
   624             on.
       
   625     @param {Function} callback Callback function to call with the traversed
       
   626         node and each of its descendants.
       
   627 
       
   628         @param {Tree.Node} callback.node Node being traversed.
       
   629 
       
   630     @param {Object} [thisObj] `this` object to use when executing _callback_.
       
   631     @return {Mixed} Returns `Tree.STOP_TRAVERSAL` if traversal was stopped;
       
   632         otherwise returns `undefined`.
       
   633     **/
       
   634     traverseNode: function (node, options, callback, thisObj) {
       
   635         // Allow callback as second argument.
       
   636         if (typeof options === 'function') {
       
   637             thisObj  = callback;
       
   638             callback = options;
       
   639             options  = {};
       
   640         }
       
   641 
       
   642         options || (options = {});
       
   643 
       
   644         var stop      = Tree.STOP_TRAVERSAL,
       
   645             unlimited = typeof options.depth === 'undefined';
       
   646 
       
   647         if (callback.call(thisObj, node) === stop) {
       
   648             return stop;
       
   649         }
       
   650 
       
   651         var children = node.children;
       
   652 
       
   653         if (unlimited || options.depth > 0) {
       
   654             var childOptions = unlimited ? options : {depth: options.depth - 1};
       
   655 
       
   656             for (var i = 0, len = children.length; i < len; i++) {
       
   657                 if (this.traverseNode(children[i], childOptions, callback, thisObj) === stop) {
       
   658                     return stop;
       
   659                 }
       
   660             }
       
   661         }
       
   662     },
       
   663 
       
   664     // -- Protected Methods ----------------------------------------------------
       
   665 
       
   666     /**
       
   667     Moves the specified node and all its children from another tree to this
       
   668     tree.
       
   669 
       
   670     @method _adoptNode
       
   671     @param {Tree.Node} node Node to adopt.
       
   672     @param {Object} [options] Options to pass along to `removeNode()`.
       
   673     @protected
       
   674     **/
       
   675     _adoptNode: function (node, options) {
       
   676         var oldTree = node.tree;
       
   677 
       
   678         if (oldTree === this) {
       
   679             return;
       
   680         }
       
   681 
       
   682         for (var i = 0, len = node.children.length; i < len; i++) {
       
   683             this._adoptNode(node.children[i], {silent: true});
       
   684         }
       
   685 
       
   686         oldTree.removeNode(node, options);
       
   687         delete oldTree._nodeMap[node.id];
       
   688 
       
   689         // If this node isn't an instance of this tree's composed _nodeClass,
       
   690         // then we need to recreate it to avoid potentially breaking things in
       
   691         // really weird ways.
       
   692         if (!(node instanceof this._nodeClass)
       
   693                 || oldTree._nodeClass !== this._nodeClass) {
       
   694 
       
   695             node = this.createNode(node.toJSON());
       
   696         }
       
   697 
       
   698         node.tree = this;
       
   699         this._nodeMap[node.id] = node;
       
   700     },
       
   701 
       
   702     /**
       
   703     Composes a custom late-bound tree node class (if necessary) based on the
       
   704     classes specified in this Tree's `nodeClass` and `nodeExtensions`
       
   705     properties.
       
   706 
       
   707     The composed class is stored in this Tree's `_nodeClass` property. If
       
   708     composition wasn't necessary, then `_nodeClass` will just be a reference to
       
   709     `nodeClass`.
       
   710 
       
   711     @method _composeNodeClass
       
   712     @protected
       
   713     **/
       
   714     _composeNodeClass: function () {
       
   715         var nodeClass      = this.nodeClass,
       
   716             nodeExtensions = this.nodeExtensions,
       
   717             composedClass;
       
   718 
       
   719         if (typeof nodeClass === 'string') {
       
   720             // Look for a namespaced node class on `Y`.
       
   721             nodeClass = Y.Object.getValue(Y, nodeClass.split('.'));
       
   722 
       
   723             if (nodeClass) {
       
   724                 this.nodeClass = nodeClass;
       
   725             } else {
       
   726                 Y.error('Tree: Node class not found: ' + nodeClass);
       
   727                 return;
       
   728             }
       
   729         }
       
   730 
       
   731         if (!nodeExtensions.length) {
       
   732             this._nodeClass = nodeClass;
       
   733             return;
       
   734         }
       
   735 
       
   736         // Compose a new class by mixing extensions into nodeClass.
       
   737         composedClass = function () {
       
   738             var extensions = composedClass._nodeExtensions;
       
   739 
       
   740             nodeClass.apply(this, arguments);
       
   741 
       
   742             for (var i = 0, len = extensions.length; i < len; i++) {
       
   743                 extensions[i].apply(this, arguments);
       
   744             }
       
   745         };
       
   746 
       
   747         Y.extend(composedClass, nodeClass);
       
   748 
       
   749         for (var i = 0, len = nodeExtensions.length; i < len; i++) {
       
   750             Y.mix(composedClass.prototype, nodeExtensions[i].prototype, true);
       
   751         }
       
   752 
       
   753         composedClass._nodeExtensions = nodeExtensions;
       
   754         this._nodeClass = composedClass;
       
   755     },
       
   756 
       
   757     /**
       
   758     Utility method for lazily publishing and firing events.
       
   759 
       
   760     @method _fireTreeEvent
       
   761     @param {String} name Event name to fire.
       
   762     @param {Object} facade Event facade.
       
   763     @param {Object} [options] Options.
       
   764         @param {Function} [options.defaultFn] Default handler for this event.
       
   765         @param {Boolean} [options.silent=false] Whether the default handler
       
   766             should be executed directly without actually firing the event.
       
   767     @chainable
       
   768     @protected
       
   769     **/
       
   770     _fireTreeEvent: function (name, facade, options) {
       
   771         if (options && options.silent) {
       
   772             if (options.defaultFn) {
       
   773                 options.defaultFn.call(this, facade);
       
   774             }
       
   775         } else {
       
   776             if (options && options.defaultFn && !this._published[name]) {
       
   777                 this._published[name] = this.publish(name, {
       
   778                     defaultFn: options.defaultFn
       
   779                 });
       
   780             }
       
   781 
       
   782             this.fire(name, facade);
       
   783         }
       
   784 
       
   785         return this;
       
   786     },
       
   787 
       
   788     /**
       
   789     Returns the default insertion index that should be used when _node_ is
       
   790     inserted as a child of _parent_ without an explicit index.
       
   791 
       
   792     The primary purpose of this method is to serve as a hook point for
       
   793     extensions and plugins that need to customize insertion order.
       
   794 
       
   795     @method _getDefaultNodeIndex
       
   796     @param {Tree.Node} parent Parent node.
       
   797     @param {Tree.Node} node Node being inserted.
       
   798     @param {Object} [options] Options passed to `insertNode()`.
       
   799     @return {Number} Index at which _node_ should be inserted into _parent_'s
       
   800         `children` array.
       
   801     @protected
       
   802     **/
       
   803     _getDefaultNodeIndex: function (parent/*, node, options*/) {
       
   804         return parent.children.length;
       
   805     },
       
   806 
       
   807     /**
       
   808     Removes the specified node from its parent node if it has one.
       
   809 
       
   810     @method _removeNodeFromParent
       
   811     @param {Tree.Node} node Node to remove.
       
   812     @protected
       
   813     **/
       
   814     _removeNodeFromParent: function (node) {
       
   815         var parent = node.parent,
       
   816             index;
       
   817 
       
   818         if (parent) {
       
   819             index = parent.indexOf(node);
       
   820 
       
   821             if (index > -1) {
       
   822                 parent.children.splice(index, 1);
       
   823                 parent._isIndexStale = true;
       
   824                 node.parent = null;
       
   825             }
       
   826         }
       
   827     },
       
   828 
       
   829     // -- Default Event Handlers -----------------------------------------------
       
   830     _defAddFn: function (e) {
       
   831         var node   = e.node,
       
   832             parent = e.parent;
       
   833 
       
   834         // Remove the node from its existing parent if it has one.
       
   835         if (node.parent) {
       
   836             this._removeNodeFromParent(node);
       
   837         }
       
   838 
       
   839         // Add the node to its new parent at the desired index.
       
   840         node.parent = parent;
       
   841         parent.children.splice(e.index, 0, node);
       
   842 
       
   843         parent.canHaveChildren = true;
       
   844         parent._isIndexStale   = true;
       
   845     },
       
   846 
       
   847     _defClearFn: function (e) {
       
   848         var newRootNode = e.rootNode;
       
   849 
       
   850         if (this.rootNode) {
       
   851             this.destroyNode(this.rootNode, {silent: true});
       
   852         }
       
   853 
       
   854         this._nodeMap = {};
       
   855         this._nodeMap[newRootNode.id] = newRootNode;
       
   856 
       
   857         this.rootNode = newRootNode;
       
   858         this.children = newRootNode.children;
       
   859     },
       
   860 
       
   861     _defRemoveFn: function (e) {
       
   862         var node = e.node;
       
   863 
       
   864         if (e.destroy) {
       
   865             this.destroyNode(node, {silent: true});
       
   866         } else if (e.parent) {
       
   867             this._removeNodeFromParent(node);
       
   868         } else if (this.rootNode === node) {
       
   869             // Guess we'll need a new root node!
       
   870             this.rootNode = this.createNode(this._rootNodeConfig);
       
   871             this.children = this.rootNode.children;
       
   872         }
       
   873     }
       
   874 }, {
       
   875     /**
       
   876     Return this value from a `Tree#traverseNode()` or `Tree.Node#traverse()`
       
   877     callback to immediately stop traversal.
       
   878 
       
   879     @property STOP_TRAVERSAL
       
   880     @static
       
   881     **/
       
   882     STOP_TRAVERSAL: {}
       
   883 });
       
   884 
       
   885 Y.Tree = Y.mix(Tree, Y.Tree);
       
   886 
       
   887 
       
   888 }, '3.10.3', {"requires": ["base-build", "tree-node"]});