[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
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"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |