3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.data.NodeInterface
17 * This class is meant to be used as a set of methods that are applied to the prototype of a
18 * Record to decorate it with a Node API. This means that models used in conjunction with a tree
19 * will have all of the tree related methods available on the model. In general this class will
20 * not be used directly by the developer.
22 Ext.define('Ext.data.NodeInterface', {
23 requires: ['Ext.data.Field'],
27 * This method allows you to decorate a Record's prototype to implement the NodeInterface.
28 * This adds a set of methods, new events, new properties and new fields on every Record
29 * with the same Model as the passed Record.
30 * @param {Ext.data.Record} record The Record you want to decorate the prototype of.
33 decorate: function(record) {
35 // Apply the methods and fields to the prototype
36 // @TODO: clean this up to use proper class system stuff
37 var mgr = Ext.ModelManager,
38 modelName = record.modelName,
39 modelClass = mgr.getModel(modelName),
40 idName = modelClass.prototype.idProperty,
44 // Start by adding the NodeInterface methods to the Model's prototype
45 modelClass.override(this.getPrototypeBody());
46 newFields = this.applyFields(modelClass, [
47 {name: idName, type: 'string', defaultValue: null},
48 {name: 'parentId', type: 'string', defaultValue: null},
49 {name: 'index', type: 'int', defaultValue: null},
50 {name: 'depth', type: 'int', defaultValue: 0},
51 {name: 'expanded', type: 'bool', defaultValue: false, persist: false},
52 {name: 'expandable', type: 'bool', defaultValue: true, persist: false},
53 {name: 'checked', type: 'auto', defaultValue: null},
54 {name: 'leaf', type: 'bool', defaultValue: false, persist: false},
55 {name: 'cls', type: 'string', defaultValue: null, persist: false},
56 {name: 'iconCls', type: 'string', defaultValue: null, persist: false},
57 {name: 'root', type: 'boolean', defaultValue: false, persist: false},
58 {name: 'isLast', type: 'boolean', defaultValue: false, persist: false},
59 {name: 'isFirst', type: 'boolean', defaultValue: false, persist: false},
60 {name: 'allowDrop', type: 'boolean', defaultValue: true, persist: false},
61 {name: 'allowDrag', type: 'boolean', defaultValue: true, persist: false},
62 {name: 'loaded', type: 'boolean', defaultValue: false, persist: false},
63 {name: 'loading', type: 'boolean', defaultValue: false, persist: false},
64 {name: 'href', type: 'string', defaultValue: null, persist: false},
65 {name: 'hrefTarget', type: 'string', defaultValue: null, persist: false},
66 {name: 'qtip', type: 'string', defaultValue: null, persist: false},
67 {name: 'qtitle', type: 'string', defaultValue: null, persist: false}
70 len = newFields.length;
72 for (i = 0; i < len; ++i) {
73 newField = newFields[i];
74 if (record.get(newField.name) === undefined) {
75 record.data[newField.name] = newField.defaultValue;
84 previousSibling: null,
88 // Commit any fields so the record doesn't show as dirty initially
94 * Fires when a new child node is appended
95 * @param {Node} this This node
96 * @param {Node} node The newly appended node
97 * @param {Number} index The index of the newly appended node
103 * Fires when a child node is removed
104 * @param {Node} this This node
105 * @param {Node} node The removed node
111 * Fires when this node is moved to a new location in the tree
112 * @param {Node} this This node
113 * @param {Node} oldParent The old parent of this node
114 * @param {Node} newParent The new parent of this node
115 * @param {Number} index The index it was moved to
121 * Fires when a new child node is inserted.
122 * @param {Node} this This node
123 * @param {Node} node The child node inserted
124 * @param {Node} refNode The child node the node was inserted before
129 * @event beforeappend
130 * Fires before a new child is appended, return false to cancel the append.
131 * @param {Node} this This node
132 * @param {Node} node The child node to be appended
137 * @event beforeremove
138 * Fires before a child is removed, return false to cancel the remove.
139 * @param {Node} this This node
140 * @param {Node} node The child node to be removed
146 * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
147 * @param {Node} this This node
148 * @param {Node} oldParent The parent of this node
149 * @param {Node} newParent The new parent this node is moving to
150 * @param {Number} index The index it is being moved to
155 * @event beforeinsert
156 * Fires before a new child is inserted, return false to cancel the insert.
157 * @param {Node} this This node
158 * @param {Node} node The child node to be inserted
159 * @param {Node} refNode The child node the node is being inserted before
165 * Fires when this node is expanded.
166 * @param {Node} this The expanding node
172 * Fires when this node is collapsed.
173 * @param {Node} this The collapsing node
178 * @event beforeexpand
179 * Fires before this node is expanded.
180 * @param {Node} this The expanding node
185 * @event beforecollapse
186 * Fires before this node is collapsed.
187 * @param {Node} this The collapsing node
193 * Fires when this node's childNodes are sorted.
194 * @param {Node} this This node.
195 * @param {Array} The childNodes of this node.
203 applyFields: function(modelClass, addFields) {
204 var modelPrototype = modelClass.prototype,
205 fields = modelPrototype.fields,
207 ln = addFields.length,
211 for (i = 0; i < ln; i++) {
212 addField = addFields[i];
213 if (!Ext.Array.contains(keys, addField.name)) {
214 addField = Ext.create('data.field', addField);
216 newFields.push(addField);
217 fields.add(addField);
224 getPrototypeBody: function() {
229 * Ensures that the passed object is an instance of a Record with the NodeInterface applied
232 createNode: function(node) {
233 if (Ext.isObject(node) && !node.isModel) {
234 node = Ext.ModelManager.create(node, this.modelName);
236 // Make sure the node implements the node interface
237 return Ext.data.NodeInterface.decorate(node);
241 * Returns true if this node is a leaf
244 isLeaf : function() {
245 return this.get('leaf') === true;
249 * Sets the first child of this node
251 * @param {Ext.data.NodeInterface} node
253 setFirstChild : function(node) {
254 this.firstChild = node;
258 * Sets the last child of this node
260 * @param {Ext.data.NodeInterface} node
262 setLastChild : function(node) {
263 this.lastChild = node;
267 * Updates general data of this node like isFirst, isLast, depth. This
268 * method is internally called after a node is moved. This shouldn't
269 * have to be called by the developer unless they are creating custom
273 updateInfo: function(silent) {
275 isRoot = me.isRoot(),
276 parentNode = me.parentNode,
277 isFirst = (!parentNode ? true : parentNode.firstChild == me),
278 isLast = (!parentNode ? true : parentNode.lastChild == me),
281 children = me.childNodes,
282 len = children.length,
285 while (parent.parentNode) {
287 parent = parent.parentNode;
295 index: parentNode ? parentNode.indexOf(me) : 0,
296 parentId: parentNode ? parentNode.getId() : null
303 for (i = 0; i < len; i++) {
304 children[i].updateInfo(silent);
309 * Returns true if this node is the last child of its parent
312 isLast : function() {
313 return this.get('isLast');
317 * Returns true if this node is the first child of its parent
320 isFirst : function() {
321 return this.get('isFirst');
325 * Returns true if this node has one or more child nodes, else false.
328 hasChildNodes : function() {
329 return !this.isLeaf() && this.childNodes.length > 0;
333 * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
334 * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
337 isExpandable : function() {
340 if (me.get('expandable')) {
341 return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
347 * <p>Insert node(s) as the last child node of this node.</p>
348 * <p>If the node was previously a child node of another parent node, it will be removed from that node first.</p>
349 * @param {Node/Array} node The node or Array of nodes to append
350 * @return {Node} The appended node if single append, or null if an array was passed
352 appendChild : function(node, suppressEvents, suppressNodeUpdate) {
359 // if passed an array or multiple args do them one by one
360 if (Ext.isArray(node)) {
361 for (i = 0, ln = node.length; i < ln; i++) {
362 me.appendChild(node[i]);
365 // Make sure it is a record
366 node = me.createNode(node);
368 if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
372 index = me.childNodes.length;
373 oldParent = node.parentNode;
375 // it's a move, make sure we move it cleanly
377 if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
380 oldParent.removeChild(node, null, false, true);
383 index = me.childNodes.length;
385 me.setFirstChild(node);
388 me.childNodes.push(node);
389 node.parentNode = me;
390 node.nextSibling = null;
392 me.setLastChild(node);
394 ps = me.childNodes[index - 1];
396 node.previousSibling = ps;
397 ps.nextSibling = node;
398 ps.updateInfo(suppressNodeUpdate);
400 node.previousSibling = null;
403 node.updateInfo(suppressNodeUpdate);
405 // As soon as we append a child to this node, we are loaded
406 if (!me.isLoaded()) {
407 me.set('loaded', true);
409 // If this node didnt have any childnodes before, update myself
410 else if (me.childNodes.length === 1) {
411 me.set('loaded', me.isLoaded());
414 if (suppressEvents !== true) {
415 me.fireEvent("append", me, node, index);
418 node.fireEvent("move", node, oldParent, me, index);
427 * Returns the bubble target for this node
429 * @return {Object} The bubble target
431 getBubbleTarget: function() {
432 return this.parentNode;
436 * Removes a child node from this node.
437 * @param {Node} node The node to remove
438 * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
439 * @return {Node} The removed node
441 removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
443 index = me.indexOf(node);
445 if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
449 // remove it from childNodes collection
450 Ext.Array.erase(me.childNodes, index, 1);
453 if (me.firstChild == node) {
454 me.setFirstChild(node.nextSibling);
456 if (me.lastChild == node) {
457 me.setLastChild(node.previousSibling);
461 if (node.previousSibling) {
462 node.previousSibling.nextSibling = node.nextSibling;
463 node.previousSibling.updateInfo(suppressNodeUpdate);
465 if (node.nextSibling) {
466 node.nextSibling.previousSibling = node.previousSibling;
467 node.nextSibling.updateInfo(suppressNodeUpdate);
470 if (suppressEvents !== true) {
471 me.fireEvent("remove", me, node);
475 // If this node suddenly doesnt have childnodes anymore, update myself
476 if (!me.childNodes.length) {
477 me.set('loaded', me.isLoaded());
490 * Creates a copy (clone) of this Node.
491 * @param {String} id (optional) A new id, defaults to this Node's id. See <code>{@link #id}</code>.
492 * @param {Boolean} deep (optional) <p>If passed as <code>true</code>, all child Nodes are recursively copied into the new Node.</p>
493 * <p>If omitted or false, the copy will have no child Nodes.</p>
494 * @return {Node} A copy of this Node.
496 copy: function(newId, deep) {
498 result = me.callOverridden(arguments),
499 len = me.childNodes ? me.childNodes.length : 0,
502 // Move child nodes across to the copy if required
504 for (i = 0; i < len; i++) {
505 result.appendChild(me.childNodes[i].copy(true));
514 * @param {Boolean} destroy True to destroy the node.
516 clear : function(destroy) {
519 // clear any references from the node
520 me.parentNode = me.previousSibling = me.nextSibling = null;
522 me.firstChild = me.lastChild = null;
529 destroy : function(silent) {
531 * Silent is to be used in a number of cases
532 * 1) When setRoot is called.
533 * 2) When destroy on the tree is called
534 * 3) For destroying child nodes on a node
537 options = me.destroyOptions;
539 if (silent === true) {
541 Ext.each(me.childNodes, function(n) {
544 me.childNodes = null;
545 delete me.destroyOptions;
546 me.callOverridden([options]);
548 me.destroyOptions = silent;
549 // overridden method will be called, since remove will end up calling destroy(true);
555 * Inserts the first node before the second node in this nodes childNodes collection.
556 * @param {Node} node The node to insert
557 * @param {Node} refNode The node to insert before (if null the node is appended)
558 * @return {Node} The inserted node
560 insertBefore : function(node, refNode, suppressEvents) {
562 index = me.indexOf(refNode),
563 oldParent = node.parentNode,
567 if (!refNode) { // like standard Dom, refNode can be null for append
568 return me.appendChild(node);
572 if (node == refNode) {
576 // Make sure it is a record with the NodeInterface
577 node = me.createNode(node);
579 if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
583 // when moving internally, indexes will change after remove
584 if (oldParent == me && me.indexOf(node) < index) {
588 // it's a move, make sure we move it cleanly
590 if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
593 oldParent.removeChild(node);
596 if (refIndex === 0) {
597 me.setFirstChild(node);
600 Ext.Array.splice(me.childNodes, refIndex, 0, node);
601 node.parentNode = me;
603 node.nextSibling = refNode;
604 refNode.previousSibling = node;
606 ps = me.childNodes[refIndex - 1];
608 node.previousSibling = ps;
609 ps.nextSibling = node;
612 node.previousSibling = null;
617 if (!me.isLoaded()) {
618 me.set('loaded', true);
620 // If this node didnt have any childnodes before, update myself
621 else if (me.childNodes.length === 1) {
622 me.set('loaded', me.isLoaded());
625 if (suppressEvents !== true) {
626 me.fireEvent("insert", me, node, refNode);
629 node.fireEvent("move", node, oldParent, me, refIndex, refNode);
637 * Insert a node into this node
638 * @param {Number} index The zero-based index to insert the node at
639 * @param {Ext.data.Model} node The node to insert
640 * @return {Ext.data.Record} The record you just inserted
642 insertChild: function(index, node) {
643 var sibling = this.childNodes[index];
645 return this.insertBefore(node, sibling);
648 return this.appendChild(node);
653 * Removes this node from its parent
654 * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
655 * @return {Node} this
657 remove : function(destroy, suppressEvents) {
658 var parentNode = this.parentNode;
661 parentNode.removeChild(this, destroy, suppressEvents, true);
667 * Removes all child nodes from this node.
668 * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
669 * @return {Node} this
671 removeAll : function(destroy, suppressEvents) {
672 var cn = this.childNodes,
675 while ((n = cn[0])) {
676 this.removeChild(n, destroy, suppressEvents);
682 * Returns the child node at the specified index.
683 * @param {Number} index
686 getChildAt : function(index) {
687 return this.childNodes[index];
691 * Replaces one child node in this node with another.
692 * @param {Node} newChild The replacement node
693 * @param {Node} oldChild The node to replace
694 * @return {Node} The replaced node
696 replaceChild : function(newChild, oldChild, suppressEvents) {
697 var s = oldChild ? oldChild.nextSibling : null;
699 this.removeChild(oldChild, suppressEvents);
700 this.insertBefore(newChild, s, suppressEvents);
705 * Returns the index of a child node
707 * @return {Number} The index of the node or -1 if it was not found
709 indexOf : function(child) {
710 return Ext.Array.indexOf(this.childNodes, child);
714 * Returns depth of this node (the root node has a depth of 0)
717 getDepth : function() {
718 return this.get('depth');
722 * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
723 * will be the args provided or the current node. If the function returns false at any point,
724 * the bubble is stopped.
725 * @param {Function} fn The function to call
726 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
727 * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
729 bubble : function(fn, scope, args) {
732 if (fn.apply(scope || p, args || [p]) === false) {
739 //<deprecated since=0.99>
740 cascade: function() {
741 if (Ext.isDefined(Ext.global.console)) {
742 Ext.global.console.warn('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');
744 return this.cascadeBy.apply(this, arguments);
749 * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
750 * will be the args provided or the current node. If the function returns false at any point,
751 * the cascade is stopped on that branch.
752 * @param {Function} fn The function to call
753 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
754 * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
756 cascadeBy : function(fn, scope, args) {
757 if (fn.apply(scope || this, args || [this]) !== false) {
758 var childNodes = this.childNodes,
759 length = childNodes.length,
762 for (i = 0; i < length; i++) {
763 childNodes[i].cascadeBy(fn, scope, args);
769 * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
770 * will be the args provided or the current node. If the function returns false at any point,
771 * the iteration stops.
772 * @param {Function} fn The function to call
773 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node in the iteration.
774 * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
776 eachChild : function(fn, scope, args) {
777 var childNodes = this.childNodes,
778 length = childNodes.length,
781 for (i = 0; i < length; i++) {
782 if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
789 * Finds the first child that has the attribute with the specified value.
790 * @param {String} attribute The attribute name
791 * @param {Mixed} value The value to search for
792 * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
793 * @return {Node} The found child or null if none was found
795 findChild : function(attribute, value, deep) {
796 return this.findChildBy(function() {
797 return this.get(attribute) == value;
802 * Finds the first child by a custom function. The child matches if the function passed returns <code>true</code>.
803 * @param {Function} fn A function which must return <code>true</code> if the passed Node is the required Node.
804 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Node being tested.
805 * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
806 * @return {Node} The found child or null if none was found
808 findChildBy : function(fn, scope, deep) {
809 var cs = this.childNodes,
813 for (; i < len; i++) {
815 if (fn.call(scope || n, n) === true) {
819 res = n.findChildBy(fn, scope, deep);
830 * Returns true if this node is an ancestor (at any point) of the passed node.
834 contains : function(node) {
835 return node.isAncestor(this);
839 * Returns true if the passed node is an ancestor (at any point) of this node.
843 isAncestor : function(node) {
844 var p = this.parentNode;
855 * Sorts this nodes children using the supplied sort function.
856 * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
857 * @param {Boolean} recursive Whether or not to apply this sort recursively
858 * @param {Boolean} suppressEvent Set to true to not fire a sort event.
860 sort : function(sortFn, recursive, suppressEvent) {
861 var cs = this.childNodes,
866 Ext.Array.sort(cs, sortFn);
867 for (i = 0; i < ln; i++) {
869 n.previousSibling = cs[i-1];
870 n.nextSibling = cs[i+1];
873 this.setFirstChild(n);
877 this.setLastChild(n);
880 if (recursive && !n.isLeaf()) {
881 n.sort(sortFn, true, true);
885 if (suppressEvent !== true) {
886 this.fireEvent('sort', this, cs);
892 * Returns true if this node is expaned
895 isExpanded: function() {
896 return this.get('expanded');
900 * Returns true if this node is loaded
903 isLoaded: function() {
904 return this.get('loaded');
908 * Returns true if this node is loading
911 isLoading: function() {
912 return this.get('loading');
916 * Returns true if this node is the root node
920 return !this.parentNode;
924 * Returns true if this node is visible
927 isVisible: function() {
928 var parent = this.parentNode;
930 if (!parent.isExpanded()) {
933 parent = parent.parentNode;
940 * @param {Function} recursive (Optional) True to recursively expand all the children
941 * @param {Function} callback (Optional) The function to execute once the expand completes
942 * @param {Object} scope (Optional) The scope to run the callback in
944 expand: function(recursive, callback, scope) {
947 // all paths must call the callback (eventually) or things like
950 // First we start by checking if this node is a parent
952 // Now we check if this record is already expanding or expanded
953 if (!me.isLoading() && !me.isExpanded()) {
954 // The TreeStore actually listens for the beforeexpand method and checks
955 // whether we have to asynchronously load the children from the server
956 // first. Thats why we pass a callback function to the event that the
957 // store can call once it has loaded and parsed all the children.
958 me.fireEvent('beforeexpand', me, function() {
959 me.set('expanded', true);
960 me.fireEvent('expand', me, me.childNodes, false);
962 // Call the expandChildren method if recursive was set to true
964 me.expandChildren(true, callback, scope);
967 Ext.callback(callback, scope || me, [me.childNodes]);
971 // If it is is already expanded but we want to recursively expand then call expandChildren
972 else if (recursive) {
973 me.expandChildren(true, callback, scope);
976 Ext.callback(callback, scope || me, [me.childNodes]);
979 // TODO - if the node isLoading, we probably need to defer the
980 // callback until it is loaded (e.g., selectPath would need us
981 // to not make the callback until the childNodes exist).
983 // If it's not then we fire the callback right away
985 Ext.callback(callback, scope || me); // leaf = no childNodes
990 * Expand all the children of this node.
991 * @param {Function} recursive (Optional) True to recursively expand all the children
992 * @param {Function} callback (Optional) The function to execute once all the children are expanded
993 * @param {Object} scope (Optional) The scope to run the callback in
995 expandChildren: function(recursive, callback, scope) {
998 nodes = me.childNodes,
1003 for (; i < ln; ++i) {
1005 if (!node.isLeaf() && !node.isExpanded()) {
1007 nodes[i].expand(recursive, function () {
1009 if (callback && !expanding) {
1010 Ext.callback(callback, scope || me, [me.childNodes]);
1016 if (!expanding && callback) {
1017 Ext.callback(callback, scope || me, [me.childNodes]); }
1021 * Collapse this node.
1022 * @param {Function} recursive (Optional) True to recursively collapse all the children
1023 * @param {Function} callback (Optional) The function to execute once the collapse completes
1024 * @param {Object} scope (Optional) The scope to run the callback in
1026 collapse: function(recursive, callback, scope) {
1029 // First we start by checking if this node is a parent
1031 // Now we check if this record is already collapsing or collapsed
1032 if (!me.collapsing && me.isExpanded()) {
1033 me.fireEvent('beforecollapse', me, function() {
1034 me.set('expanded', false);
1035 me.fireEvent('collapse', me, me.childNodes, false);
1037 // Call the collapseChildren method if recursive was set to true
1039 me.collapseChildren(true, callback, scope);
1042 Ext.callback(callback, scope || me, [me.childNodes]);
1046 // If it is is already collapsed but we want to recursively collapse then call collapseChildren
1047 else if (recursive) {
1048 me.collapseChildren(true, callback, scope);
1051 // If it's not then we fire the callback right away
1053 Ext.callback(callback, scope || me, [me.childNodes]);
1058 * Collapse all the children of this node.
1059 * @param {Function} recursive (Optional) True to recursively collapse all the children
1060 * @param {Function} callback (Optional) The function to execute once all the children are collapsed
1061 * @param {Object} scope (Optional) The scope to run the callback in
1063 collapseChildren: function(recursive, callback, scope) {
1066 nodes = me.childNodes,
1071 for (; i < ln; ++i) {
1073 if (!node.isLeaf() && node.isExpanded()) {
1075 nodes[i].collapse(recursive, function () {
1077 if (callback && !collapsing) {
1078 Ext.callback(callback, scope || me, [me.childNodes]);
1084 if (!collapsing && callback) {
1085 Ext.callback(callback, scope || me, [me.childNodes]);