NOBUG: Fixed file access permissions
[moodle.git] / lib / yuilib / 3.13.0 / tree / tree-debug.js
blob0cbbad573fd7246aa293b77f5e1b1ab431bb0ea9
1 /*
2 YUI 3.13.0 (build 508226d)
3 Copyright 2013 Yahoo! Inc. All rights reserved.
4 Licensed under the BSD License.
5 http://yuilibrary.com/license/
6 */
8 YUI.add('tree', function (Y, NAME) {
10 /*jshint boss:true, expr:true, onevar:false */
12 /**
13 Provides a generic tree data structure and related functionality.
15 A tree has a root node, which may contain any number of child nodes, which may
16 themselves contain child nodes, ad infinitum.
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.
22 @module tree
23 @main tree
24 **/
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.
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.
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 **/
46 var Lang = Y.Lang,
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.).
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',
62     /**
63     Fired when this Tree is cleared.
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',
73     /**
74     Fired when a node is removed from this Tree.
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';
86 var Tree = Y.Base.create('tree', Y.Base, [], {
87     // -- Public Properties ----------------------------------------------------
89     /**
90     Reference to the `children` array of this Tree's `rootNode`.
92     This is a convenience property to allow you to type `tree.children` instead
93     of `tree.rootNode.children`.
95     @property {Tree.Node[]} children
96     @readOnly
97     **/
99     /**
100     The `Tree.Node` class or subclass that should be used for nodes created by
101     this tree.
103     You may specify an actual class reference or a string that resolves to a
104     class reference at runtime.
106     @property {String|Tree.Node} nodeClass
107     @default Y.Tree.Node
108     **/
109     nodeClass: Y.Tree.Node,
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.
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.
121     @property {Array} nodeExtensions
122     @default []
123     **/
124     nodeExtensions: [],
126     /**
127     Root node of this Tree.
129     @property {Tree.Node} rootNode
130     @readOnly
131     **/
133     // -- Protected Properties -------------------------------------------------
135     /**
136     Simple way to type-check that this is a Tree instance.
138     @property {Boolean} _isYUITree
139     @default true
140     @protected
141     **/
142     _isYUITree: true,
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`.
149     @property {Tree.Node} _nodeClass
150     @protected
151     **/
153     /**
154     Mapping of node ids to node instances for nodes in this tree.
156     @property {Object} _nodeMap
157     @protected
158     **/
160     /**
161     Default config object for the root node.
163     @property {Object} _rootNodeConfig
164     @protected
165     **/
166     _rootNodeConfig: {canHaveChildren: true},
168     // -- Lifecycle ------------------------------------------------------------
169     initializer: function (config) {
170         config || (config = {});
172         if (config.nodeClass) {
173             this.nodeClass = config.nodeClass;
174         }
176         if (config.nodeExtensions) {
177             this.nodeExtensions = this.nodeExtensions.concat(config.nodeExtensions);
178         }
180         /**
181         Hash of published custom events.
183         @property {Object} _published
184         @default {}
185         @protected
186         **/
187         this._published || (this._published = {});
188         this._nodeMap = {};
190         // Allow all extensions to initialize, then finish up.
191         this.onceAfter('initializedChange', function () {
192             this._composeNodeClass();
194             this.clear(config.rootNode, {silent: true});
196             if (config.nodes) {
197                 this.insertNode(this.rootNode, config.nodes, {silent: true});
198             }
199         });
200     },
202     destructor: function () {
203         this.destroyNode(this.rootNode, {silent: true});
205         this.children   = null;
206         this.rootNode   = null;
207         this._nodeClass = null;
208         this._nodeMap   = null;
209         this._published = null;
210     },
212     // -- Public Methods -------------------------------------------------------
214     /**
215     Appends a node or array of nodes as the last child of the specified parent
216     node.
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.
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     },
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.
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     },
267     /**
268     Creates and returns a new `Tree.Node` instance associated with (but not
269     yet appended to) this tree.
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 = {});
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             }
290             this._adoptNode(config);
291             return config;
292         }
294         // First, create nodes for any children of this node.
295         if (config.children) {
296             var children = [];
298             for (var i = 0, len = config.children.length; i < len; i++) {
299                 children.push(this.createNode(config.children[i]));
300             }
302             config = Y.merge(config, {children: children});
303         }
305         var node = new this._nodeClass(this, config);
307         return this._nodeMap[node.id] = node;
308     },
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.
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;
329         options || (options = {});
331         for (i = 0, len = node.children.length; i < len; i++) {
332             child = node.children[i];
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;
339             // Destroy the child.
340             this.destroyNode(child, options);
341         }
343         if (node.parent) {
344             this.removeNode(node, options);
345         }
347         node.children  = [];
348         node.data      = {};
349         node.state     = {destroyed: true};
350         node.tree      = null;
351         node._indexMap = {};
353         delete this._nodeMap[node.id];
355         return this;
356     },
358     /**
359     Removes all children from the specified node. The removed children will
360     still be reusable unless the `destroy` option is truthy.
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  = [];
380         for (var i = children.length - 1; i > -1; --i) {
381             removed[i] = this.removeNode(children[i], options);
382         }
384         return removed;
385     },
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.
392     Traversal will stop as soon as a truthy value is returned from the callback.
394     See `traverseNode()` for more details on how depth-first traversal works.
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.
406         @param {Tree.Node} callback.node Node being traversed.
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;
416         // Allow callback as second argument.
417         if (typeof options === 'function') {
418             thisObj  = callback;
419             callback = options;
420             options  = {};
421         }
423         this.traverseNode(node, options, function (descendant) {
424             if (callback.call(thisObj, descendant)) {
425                 match = descendant;
426                 return Tree.STOP_TRAVERSAL;
427             }
428         });
430         return match;
431     },
433     /**
434     Returns the tree node with the specified id, or `undefined` if the node
435     doesn't exist in this tree.
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     },
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.
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.
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.
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.
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);
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;
493             for (var i = 0, len = node.length; i < len; i++) {
494                 insertedNode = this.insertNode(parent, node[i], options);
496                 if (insertedNode) {
497                     insertedNodes.push(insertedNode);
499                     if (hasIndex) {
500                         options.index += 1;
501                     }
502                 }
503             }
505             return insertedNodes;
506         }
508         node = this.createNode(node);
510         if (node) {
511             var index = options.index;
513             if (typeof index === 'undefined') {
514                 index = this._getDefaultNodeIndex(parent, node, options);
515             }
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         }
528         return node;
529     },
531     /**
532     Prepends a node or array of nodes at the beginning of the specified parent
533     node.
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.
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     },
557     /**
558     Removes the specified node from its parent node. The removed node will still
559     be reusable unless the `destroy` option is truthy.
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 = {});
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         });
588         return node;
589     },
591     /**
592     Returns the total number of nodes in this tree, at all levels.
594     Use `rootNode.children.length` to get only the number of top-level nodes.
596     @method size
597     @return {Number} Total number of nodes in this tree.
598     **/
599     size: function () {
600         return this.rootNode.size() + 1;
601     },
603     /**
604     Serializes this tree to an object suitable for use in JSON.
606     @method toJSON
607     @return {Object} Serialized tree object.
608     **/
609     toJSON: function () {
610         return this.rootNode.toJSON();
611     },
613     /**
614     Performs a depth-first traversal of _node_, passing it and each of its
615     descendants to the specified _callback_.
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.
622     Since traversal is depth-first, that means nodes are traversed like this:
624                 1
625               / | \
626              2  8  9
627             / \     \
628            3   7    10
629          / | \      / \
630         4  5  6    11 12
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.
641         @param {Tree.Node} callback.node Node being traversed.
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         }
653         // Allow callback as second argument.
654         if (typeof options === 'function') {
655             thisObj  = callback;
656             callback = options;
657             options  = {};
658         }
660         options || (options = {});
662         var stop      = Tree.STOP_TRAVERSAL,
663             unlimited = typeof options.depth === 'undefined';
665         if (callback.call(thisObj, node) === stop) {
666             return stop;
667         }
669         var children = node.children;
671         if (unlimited || options.depth > 0) {
672             var childOptions = unlimited ? options : {depth: options.depth - 1};
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     },
682     // -- Protected Methods ----------------------------------------------------
684     /**
685     Moves the specified node and all its children from another tree to this
686     tree.
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;
696         if (oldTree === this) {
697             return;
698         }
700         for (var i = 0, len = node.children.length; i < len; i++) {
701             this._adoptNode(node.children[i], {silent: true});
702         }
704         oldTree.removeNode(node, options);
705         delete oldTree._nodeMap[node.id];
707         // If this node isn't an instance of this tree's composed _nodeClass,
708         // then we need to recreate it to avoid potentially breaking things in
709         // really weird ways.
710         if (!(node instanceof this._nodeClass)
711                 || oldTree._nodeClass !== this._nodeClass) {
713             node = this.createNode(node.toJSON());
714         }
716         node.tree = this;
717         this._nodeMap[node.id] = node;
718     },
720     /**
721     Composes a custom late-bound tree node class (if necessary) based on the
722     classes specified in this Tree's `nodeClass` and `nodeExtensions`
723     properties.
725     The composed class is stored in this Tree's `_nodeClass` property. If
726     composition wasn't necessary, then `_nodeClass` will just be a reference to
727     `nodeClass`.
729     @method _composeNodeClass
730     @protected
731     **/
732     _composeNodeClass: function () {
733         var nodeClass      = this.nodeClass,
734             nodeExtensions = this.nodeExtensions,
735             composedClass;
737         if (typeof nodeClass === 'string') {
738             // Look for a namespaced node class on `Y`.
739             nodeClass = Y.Object.getValue(Y, nodeClass.split('.'));
741             if (nodeClass) {
742                 this.nodeClass = nodeClass;
743             } else {
744                 Y.error('Node class not found: ' + nodeClass, null, 'tree');
745                 return;
746             }
747         }
749         if (!nodeExtensions.length) {
750             this._nodeClass = nodeClass;
751             return;
752         }
754         // Compose a new class by mixing extensions into nodeClass.
755         composedClass = function () {
756             var extensions = composedClass._nodeExtensions;
758             nodeClass.apply(this, arguments);
760             for (var i = 0, len = extensions.length; i < len; i++) {
761                 extensions[i].apply(this, arguments);
762             }
763         };
765         Y.extend(composedClass, nodeClass);
767         for (var i = 0, len = nodeExtensions.length; i < len; i++) {
768             Y.mix(composedClass.prototype, nodeExtensions[i].prototype, true);
769         }
771         composedClass._nodeExtensions = nodeExtensions;
772         this._nodeClass = composedClass;
773     },
775     /**
776     Utility method for lazily publishing and firing events.
778     @method _fireTreeEvent
779     @param {String} name Event name to fire.
780     @param {Object} facade Event facade.
781     @param {Object} [options] Options.
782         @param {Function} [options.defaultFn] Default handler for this event.
783         @param {Boolean} [options.silent=false] Whether the default handler
784             should be executed directly without actually firing the event.
785     @chainable
786     @protected
787     **/
788     _fireTreeEvent: function (name, facade, options) {
789         if (options && options.silent) {
790             if (options.defaultFn) {
791                 facade.silent = true; // intentionally modifying the facade
792                 options.defaultFn.call(this, facade);
793             }
794         } else {
795             if (options && options.defaultFn && !this._published[name]) {
796                 this._published[name] = this.publish(name, {
797                     defaultFn: options.defaultFn
798                 });
799             }
801             this.fire(name, facade);
802         }
804         return this;
805     },
807     /**
808     Returns the default insertion index that should be used when _node_ is
809     inserted as a child of _parent_ without an explicit index.
811     The primary purpose of this method is to serve as a hook point for
812     extensions and plugins that need to customize insertion order.
814     @method _getDefaultNodeIndex
815     @param {Tree.Node} parent Parent node.
816     @param {Tree.Node} node Node being inserted.
817     @param {Object} [options] Options passed to `insertNode()`.
818     @return {Number} Index at which _node_ should be inserted into _parent_'s
819         `children` array.
820     @protected
821     **/
822     _getDefaultNodeIndex: function (parent/*, node, options*/) {
823         return parent.children.length;
824     },
826     /**
827     Removes the specified node from its parent node if it has one.
829     @method _removeNodeFromParent
830     @param {Tree.Node} node Node to remove.
831     @protected
832     **/
833     _removeNodeFromParent: function (node) {
834         var parent = node.parent,
835             index;
837         if (parent) {
838             index = parent.indexOf(node);
840             if (index > -1) {
841                 var children = parent.children;
843                 if (index === children.length - 1) {
844                     children.pop();
845                 } else {
846                     children.splice(index, 1);
847                     parent._isIndexStale = true;
848                 }
850                 node.parent = null;
851             }
852         }
853     },
855     // -- Default Event Handlers -----------------------------------------------
856     _defAddFn: function (e) {
857         var index  = e.index,
858             node   = e.node,
859             parent = e.parent,
860             oldIndex;
862         // Remove the node from its existing parent if it has one.
863         if (node.parent) {
864             // If the node's existing parent is the same parent it's being
865             // inserted into, adjust the index to avoid an off-by-one error.
866             if (node.parent === parent) {
867                 oldIndex = parent.indexOf(node);
869                 if (oldIndex === index) {
870                     // Old index is the same as the new index, so just don't do
871                     // anything.
872                     return;
873                 } else if (oldIndex < index) {
874                     // Removing the node from its old index will affect the new
875                     // index, so decrement the new index by one.
876                     index -= 1;
877                 }
878             }
880             this.removeNode(node, {
881                 silent: e.silent,
882                 src   : 'add'
883             });
884         }
886         // Add the node to its new parent at the desired index.
887         node.parent = parent;
888         parent.children.splice(index, 0, node);
890         parent.canHaveChildren = true;
891         parent._isIndexStale   = true;
892     },
894     _defClearFn: function (e) {
895         var newRootNode = e.rootNode;
897         if (this.rootNode) {
898             this.destroyNode(this.rootNode, {silent: true});
899         }
901         this._nodeMap = {};
902         this._nodeMap[newRootNode.id] = newRootNode;
904         this.rootNode = newRootNode;
905         this.children = newRootNode.children;
906     },
908     _defRemoveFn: function (e) {
909         var node = e.node;
911         if (e.destroy) {
912             this.destroyNode(node, {silent: true});
913         } else if (e.parent) {
914             this._removeNodeFromParent(node);
915         } else if (this.rootNode === node) {
916             // Guess we'll need a new root node!
917             this.rootNode = this.createNode(this._rootNodeConfig);
918             this.children = this.rootNode.children;
919         }
920     }
921 }, {
922     /**
923     Return this value from a `Tree#traverseNode()` or `Tree.Node#traverse()`
924     callback to immediately stop traversal.
926     @property STOP_TRAVERSAL
927     @static
928     **/
929     STOP_TRAVERSAL: {}
932 Y.Tree = Y.mix(Tree, Y.Tree);
935 }, '3.13.0', {"requires": ["base-build", "tree-node"]});