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 * This class is used as a set of methods that are applied to the prototype of a
17 * Model to decorate it with a Node API. This means that models used in conjunction with a tree
18 * will have all of the tree related methods available on the model. In general this class will
19 * not be used directly by the developer. This class also creates extra fields on the model if
20 * they do not exist, to help maintain the tree state and UI. These fields are documented as
23 Ext.define('Ext.data.NodeInterface', {
24 requires: ['Ext.data.Field'],
27 * @cfg {String} parentId
33 * The position of the node inside its parent. When parent has 4 children and the node is third amongst them,
39 * The number of parents this node has. A root node has depth 0, a child of it depth 1, and so on...
43 * @cfg {Boolean} [expanded=false]
44 * True if the node is expanded.
48 * @cfg {Boolean} [expandable=false]
49 * Set to true to allow for expanding/collapsing of this node.
53 * @cfg {Boolean} [checked=null]
54 * Set to true or false to show a checkbox alongside this node.
58 * @cfg {Boolean} [leaf=false]
59 * Set to true to indicate that this child can have no children. The expand icon/arrow will then not be
60 * rendered for this node.
65 * CSS class to apply for this node.
69 * @cfg {String} iconCls
70 * CSS class to apply for this node's icon.
75 * URL for this node's icon.
80 * True if this is the root node.
84 * @cfg {Boolean} isLast
85 * True if this is the last node.
89 * @cfg {Boolean} isFirst
90 * True if this is the first node.
94 * @cfg {Boolean} [allowDrop=true]
95 * Set to false to deny dropping on this node.
99 * @cfg {Boolean} [allowDrag=true]
100 * Set to false to deny dragging of this node.
104 * @cfg {Boolean} [loaded=false]
105 * True if the node has finished loading.
109 * @cfg {Boolean} [loading=false]
110 * True if the node is currently loading.
115 * An URL for a link that's created when this config is specified.
119 * @cfg {String} hrefTarget
120 * Target for link. Only applicable when {@link #href} also specified.
125 * Tooltip text to show on this node.
129 * @cfg {String} qtitle
135 * The text for to show on node label.
139 * @cfg {Ext.data.NodeInterface[]} children
140 * Array of child nodes.
145 * @property nextSibling
146 * A reference to this node's next sibling node. `null` if this node does not have a next sibling.
150 * @property previousSibling
151 * A reference to this node's previous sibling node. `null` if this node does not have a previous sibling.
155 * @property parentNode
156 * A reference to this node's parent node. `null` if this node is the root node.
160 * @property lastChild
161 * A reference to this node's last child node. `null` if this node has no children.
165 * @property firstChild
166 * A reference to this node's first child node. `null` if this node has no children.
170 * @property childNodes
171 * An array of this nodes children. Array will be empty if this node has no chidren.
176 * This method allows you to decorate a Record's prototype to implement the NodeInterface.
177 * This adds a set of methods, new events, new properties and new fields on every Record
178 * with the same Model as the passed Record.
179 * @param {Ext.data.Model} record The Record you want to decorate the prototype of.
182 decorate: function(record) {
183 if (!record.isNode) {
184 // Apply the methods and fields to the prototype
185 // @TODO: clean this up to use proper class system stuff
186 var mgr = Ext.ModelManager,
187 modelName = record.modelName,
188 modelClass = mgr.getModel(modelName),
189 idName = modelClass.prototype.idProperty,
193 // Start by adding the NodeInterface methods to the Model's prototype
194 modelClass.override(this.getPrototypeBody());
195 newFields = this.applyFields(modelClass, [
196 {name: idName, type: 'string', defaultValue: null},
197 {name: 'parentId', type: 'string', defaultValue: null},
198 {name: 'index', type: 'int', defaultValue: null},
199 {name: 'depth', type: 'int', defaultValue: 0},
200 {name: 'expanded', type: 'bool', defaultValue: false, persist: false},
201 {name: 'expandable', type: 'bool', defaultValue: true, persist: false},
202 {name: 'checked', type: 'auto', defaultValue: null},
203 {name: 'leaf', type: 'bool', defaultValue: false, persist: false},
204 {name: 'cls', type: 'string', defaultValue: null, persist: false},
205 {name: 'iconCls', type: 'string', defaultValue: null, persist: false},
206 {name: 'icon', type: 'string', defaultValue: null, persist: false},
207 {name: 'root', type: 'boolean', defaultValue: false, persist: false},
208 {name: 'isLast', type: 'boolean', defaultValue: false, persist: false},
209 {name: 'isFirst', type: 'boolean', defaultValue: false, persist: false},
210 {name: 'allowDrop', type: 'boolean', defaultValue: true, persist: false},
211 {name: 'allowDrag', type: 'boolean', defaultValue: true, persist: false},
212 {name: 'loaded', type: 'boolean', defaultValue: false, persist: false},
213 {name: 'loading', type: 'boolean', defaultValue: false, persist: false},
214 {name: 'href', type: 'string', defaultValue: null, persist: false},
215 {name: 'hrefTarget', type: 'string', defaultValue: null, persist: false},
216 {name: 'qtip', type: 'string', defaultValue: null, persist: false},
217 {name: 'qtitle', type: 'string', defaultValue: null, persist: false}
220 len = newFields.length;
221 // Set default values
222 for (i = 0; i < len; ++i) {
223 newField = newFields[i];
224 if (record.get(newField.name) === undefined) {
225 record.data[newField.name] = newField.defaultValue;
230 Ext.applyIf(record, {
234 previousSibling: null,
238 // Commit any fields so the record doesn't show as dirty initially
241 record.enableBubble([
244 * Fires when a new child node is appended
245 * @param {Ext.data.NodeInterface} this This node
246 * @param {Ext.data.NodeInterface} node The newly appended node
247 * @param {Number} index The index of the newly appended node
253 * Fires when a child node is removed
254 * @param {Ext.data.NodeInterface} this This node
255 * @param {Ext.data.NodeInterface} node The removed node
261 * Fires when this node is moved to a new location in the tree
262 * @param {Ext.data.NodeInterface} this This node
263 * @param {Ext.data.NodeInterface} oldParent The old parent of this node
264 * @param {Ext.data.NodeInterface} newParent The new parent of this node
265 * @param {Number} index The index it was moved to
271 * Fires when a new child node is inserted.
272 * @param {Ext.data.NodeInterface} this This node
273 * @param {Ext.data.NodeInterface} node The child node inserted
274 * @param {Ext.data.NodeInterface} refNode The child node the node was inserted before
279 * @event beforeappend
280 * Fires before a new child is appended, return false to cancel the append.
281 * @param {Ext.data.NodeInterface} this This node
282 * @param {Ext.data.NodeInterface} node The child node to be appended
287 * @event beforeremove
288 * Fires before a child is removed, return false to cancel the remove.
289 * @param {Ext.data.NodeInterface} this This node
290 * @param {Ext.data.NodeInterface} node The child node to be removed
296 * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
297 * @param {Ext.data.NodeInterface} this This node
298 * @param {Ext.data.NodeInterface} oldParent The parent of this node
299 * @param {Ext.data.NodeInterface} newParent The new parent this node is moving to
300 * @param {Number} index The index it is being moved to
305 * @event beforeinsert
306 * Fires before a new child is inserted, return false to cancel the insert.
307 * @param {Ext.data.NodeInterface} this This node
308 * @param {Ext.data.NodeInterface} node The child node to be inserted
309 * @param {Ext.data.NodeInterface} refNode The child node the node is being inserted before
315 * Fires when this node is expanded.
316 * @param {Ext.data.NodeInterface} this The expanding node
322 * Fires when this node is collapsed.
323 * @param {Ext.data.NodeInterface} this The collapsing node
328 * @event beforeexpand
329 * Fires before this node is expanded.
330 * @param {Ext.data.NodeInterface} this The expanding node
335 * @event beforecollapse
336 * Fires before this node is collapsed.
337 * @param {Ext.data.NodeInterface} this The collapsing node
343 * Fires when this node's childNodes are sorted.
344 * @param {Ext.data.NodeInterface} this This node.
345 * @param {Ext.data.NodeInterface[]} childNodes The childNodes of this node.
353 applyFields: function(modelClass, addFields) {
354 var modelPrototype = modelClass.prototype,
355 fields = modelPrototype.fields,
357 ln = addFields.length,
361 for (i = 0; i < ln; i++) {
362 addField = addFields[i];
363 if (!Ext.Array.contains(keys, addField.name)) {
364 addField = Ext.create('data.field', addField);
366 newFields.push(addField);
367 fields.add(addField);
374 getPrototypeBody: function() {
379 * Ensures that the passed object is an instance of a Record with the NodeInterface applied
382 createNode: function(node) {
383 if (Ext.isObject(node) && !node.isModel) {
384 node = Ext.ModelManager.create(node, this.modelName);
386 // Make sure the node implements the node interface
387 return Ext.data.NodeInterface.decorate(node);
391 * Returns true if this node is a leaf
394 isLeaf : function() {
395 return this.get('leaf') === true;
399 * Sets the first child of this node
401 * @param {Ext.data.NodeInterface} node
403 setFirstChild : function(node) {
404 this.firstChild = node;
408 * Sets the last child of this node
410 * @param {Ext.data.NodeInterface} node
412 setLastChild : function(node) {
413 this.lastChild = node;
417 * Updates general data of this node like isFirst, isLast, depth. This
418 * method is internally called after a node is moved. This shouldn't
419 * have to be called by the developer unless they are creating custom
423 updateInfo: function(silent) {
425 isRoot = me.isRoot(),
426 parentNode = me.parentNode,
427 isFirst = (!parentNode ? true : parentNode.firstChild == me),
428 isLast = (!parentNode ? true : parentNode.lastChild == me),
431 children = me.childNodes,
432 len = children.length,
435 while (parent.parentNode) {
437 parent = parent.parentNode;
445 index: parentNode ? parentNode.indexOf(me) : 0,
446 parentId: parentNode ? parentNode.getId() : null
453 for (i = 0; i < len; i++) {
454 children[i].updateInfo(silent);
459 * Returns true if this node is the last child of its parent
462 isLast : function() {
463 return this.get('isLast');
467 * Returns true if this node is the first child of its parent
470 isFirst : function() {
471 return this.get('isFirst');
475 * Returns true if this node has one or more child nodes, else false.
478 hasChildNodes : function() {
479 return !this.isLeaf() && this.childNodes.length > 0;
483 * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
484 * node attribute is explicitly specified as true, otherwise returns false.
487 isExpandable : function() {
490 if (me.get('expandable')) {
491 return !(me.isLeaf() || (me.isLoaded() && !me.hasChildNodes()));
497 * Inserts node(s) as the last child node of this node.
499 * If the node was previously a child node of another parent node, it will be removed from that node first.
501 * @param {Ext.data.NodeInterface/Ext.data.NodeInterface[]} node The node or Array of nodes to append
502 * @return {Ext.data.NodeInterface} The appended node if single append, or null if an array was passed
504 appendChild : function(node, suppressEvents, suppressNodeUpdate) {
511 // if passed an array or multiple args do them one by one
512 if (Ext.isArray(node)) {
513 for (i = 0, ln = node.length; i < ln; i++) {
514 me.appendChild(node[i]);
517 // Make sure it is a record
518 node = me.createNode(node);
520 if (suppressEvents !== true && me.fireEvent("beforeappend", me, node) === false) {
524 index = me.childNodes.length;
525 oldParent = node.parentNode;
527 // it's a move, make sure we move it cleanly
529 if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index) === false) {
532 oldParent.removeChild(node, null, false, true);
535 index = me.childNodes.length;
537 me.setFirstChild(node);
540 me.childNodes.push(node);
541 node.parentNode = me;
542 node.nextSibling = null;
544 me.setLastChild(node);
546 ps = me.childNodes[index - 1];
548 node.previousSibling = ps;
549 ps.nextSibling = node;
550 ps.updateInfo(suppressNodeUpdate);
552 node.previousSibling = null;
555 node.updateInfo(suppressNodeUpdate);
557 // As soon as we append a child to this node, we are loaded
558 if (!me.isLoaded()) {
559 me.set('loaded', true);
561 // If this node didnt have any childnodes before, update myself
562 else if (me.childNodes.length === 1) {
563 me.set('loaded', me.isLoaded());
566 if (suppressEvents !== true) {
567 me.fireEvent("append", me, node, index);
570 node.fireEvent("move", node, oldParent, me, index);
579 * Returns the bubble target for this node
581 * @return {Object} The bubble target
583 getBubbleTarget: function() {
584 return this.parentNode;
588 * Removes a child node from this node.
589 * @param {Ext.data.NodeInterface} node The node to remove
590 * @param {Boolean} [destroy=false] True to destroy the node upon removal.
591 * @return {Ext.data.NodeInterface} The removed node
593 removeChild : function(node, destroy, suppressEvents, suppressNodeUpdate) {
595 index = me.indexOf(node);
597 if (index == -1 || (suppressEvents !== true && me.fireEvent("beforeremove", me, node) === false)) {
601 // remove it from childNodes collection
602 Ext.Array.erase(me.childNodes, index, 1);
605 if (me.firstChild == node) {
606 me.setFirstChild(node.nextSibling);
608 if (me.lastChild == node) {
609 me.setLastChild(node.previousSibling);
613 if (node.previousSibling) {
614 node.previousSibling.nextSibling = node.nextSibling;
615 node.previousSibling.updateInfo(suppressNodeUpdate);
617 if (node.nextSibling) {
618 node.nextSibling.previousSibling = node.previousSibling;
619 node.nextSibling.updateInfo(suppressNodeUpdate);
622 if (suppressEvents !== true) {
623 me.fireEvent("remove", me, node);
627 // If this node suddenly doesnt have childnodes anymore, update myself
628 if (!me.childNodes.length) {
629 me.set('loaded', me.isLoaded());
642 * Creates a copy (clone) of this Node.
643 * @param {String} [id] A new id, defaults to this Node's id.
644 * @param {Boolean} [deep=false] True to recursively copy all child Nodes into the new Node.
645 * False to copy without child Nodes.
646 * @return {Ext.data.NodeInterface} A copy of this Node.
648 copy: function(newId, deep) {
650 result = me.callOverridden(arguments),
651 len = me.childNodes ? me.childNodes.length : 0,
654 // Move child nodes across to the copy if required
656 for (i = 0; i < len; i++) {
657 result.appendChild(me.childNodes[i].copy(true));
666 * @param {Boolean} [destroy=false] True to destroy the node.
668 clear : function(destroy) {
671 // clear any references from the node
672 me.parentNode = me.previousSibling = me.nextSibling = null;
674 me.firstChild = me.lastChild = null;
681 destroy : function(silent) {
683 * Silent is to be used in a number of cases
684 * 1) When setRoot is called.
685 * 2) When destroy on the tree is called
686 * 3) For destroying child nodes on a node
689 options = me.destroyOptions;
691 if (silent === true) {
693 Ext.each(me.childNodes, function(n) {
696 me.childNodes = null;
697 delete me.destroyOptions;
698 me.callOverridden([options]);
700 me.destroyOptions = silent;
701 // overridden method will be called, since remove will end up calling destroy(true);
707 * Inserts the first node before the second node in this nodes childNodes collection.
708 * @param {Ext.data.NodeInterface} node The node to insert
709 * @param {Ext.data.NodeInterface} refNode The node to insert before (if null the node is appended)
710 * @return {Ext.data.NodeInterface} The inserted node
712 insertBefore : function(node, refNode, suppressEvents) {
714 index = me.indexOf(refNode),
715 oldParent = node.parentNode,
719 if (!refNode) { // like standard Dom, refNode can be null for append
720 return me.appendChild(node);
724 if (node == refNode) {
728 // Make sure it is a record with the NodeInterface
729 node = me.createNode(node);
731 if (suppressEvents !== true && me.fireEvent("beforeinsert", me, node, refNode) === false) {
735 // when moving internally, indexes will change after remove
736 if (oldParent == me && me.indexOf(node) < index) {
740 // it's a move, make sure we move it cleanly
742 if (suppressEvents !== true && node.fireEvent("beforemove", node, oldParent, me, index, refNode) === false) {
745 oldParent.removeChild(node);
748 if (refIndex === 0) {
749 me.setFirstChild(node);
752 Ext.Array.splice(me.childNodes, refIndex, 0, node);
753 node.parentNode = me;
755 node.nextSibling = refNode;
756 refNode.previousSibling = node;
758 ps = me.childNodes[refIndex - 1];
760 node.previousSibling = ps;
761 ps.nextSibling = node;
764 node.previousSibling = null;
769 if (!me.isLoaded()) {
770 me.set('loaded', true);
772 // If this node didnt have any childnodes before, update myself
773 else if (me.childNodes.length === 1) {
774 me.set('loaded', me.isLoaded());
777 if (suppressEvents !== true) {
778 me.fireEvent("insert", me, node, refNode);
781 node.fireEvent("move", node, oldParent, me, refIndex, refNode);
789 * Insert a node into this node
790 * @param {Number} index The zero-based index to insert the node at
791 * @param {Ext.data.Model} node The node to insert
792 * @return {Ext.data.Model} The record you just inserted
794 insertChild: function(index, node) {
795 var sibling = this.childNodes[index];
797 return this.insertBefore(node, sibling);
800 return this.appendChild(node);
805 * Removes this node from its parent
806 * @param {Boolean} [destroy=false] True to destroy the node upon removal.
807 * @return {Ext.data.NodeInterface} this
809 remove : function(destroy, suppressEvents) {
810 var parentNode = this.parentNode;
813 parentNode.removeChild(this, destroy, suppressEvents, true);
819 * Removes all child nodes from this node.
820 * @param {Boolean} [destroy=false] <True to destroy the node upon removal.
821 * @return {Ext.data.NodeInterface} this
823 removeAll : function(destroy, suppressEvents) {
824 var cn = this.childNodes,
827 while ((n = cn[0])) {
828 this.removeChild(n, destroy, suppressEvents);
834 * Returns the child node at the specified index.
835 * @param {Number} index
836 * @return {Ext.data.NodeInterface}
838 getChildAt : function(index) {
839 return this.childNodes[index];
843 * Replaces one child node in this node with another.
844 * @param {Ext.data.NodeInterface} newChild The replacement node
845 * @param {Ext.data.NodeInterface} oldChild The node to replace
846 * @return {Ext.data.NodeInterface} The replaced node
848 replaceChild : function(newChild, oldChild, suppressEvents) {
849 var s = oldChild ? oldChild.nextSibling : null;
851 this.removeChild(oldChild, suppressEvents);
852 this.insertBefore(newChild, s, suppressEvents);
857 * Returns the index of a child node
858 * @param {Ext.data.NodeInterface} node
859 * @return {Number} The index of the node or -1 if it was not found
861 indexOf : function(child) {
862 return Ext.Array.indexOf(this.childNodes, child);
866 * Gets the hierarchical path from the root of the current node.
867 * @param {String} [field] The field to construct the path from. Defaults to the model idProperty.
868 * @param {String} [separator="/"] A separator to use.
869 * @return {String} The node path
871 getPath: function(field, separator) {
872 field = field || this.idProperty;
873 separator = separator || '/';
875 var path = [this.get(field)],
876 parent = this.parentNode;
879 path.unshift(parent.get(field));
880 parent = parent.parentNode;
882 return separator + path.join(separator);
886 * Returns depth of this node (the root node has a depth of 0)
889 getDepth : function() {
890 return this.get('depth');
894 * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
895 * will be the args provided or the current node. If the function returns false at any point,
896 * the bubble is stopped.
897 * @param {Function} fn The function to call
898 * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
899 * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
901 bubble : function(fn, scope, args) {
904 if (fn.apply(scope || p, args || [p]) === false) {
911 //<deprecated since=0.99>
912 cascade: function() {
913 if (Ext.isDefined(Ext.global.console)) {
914 Ext.global.console.warn('Ext.data.Node: cascade has been deprecated. Please use cascadeBy instead.');
916 return this.cascadeBy.apply(this, arguments);
921 * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
922 * will be the args provided or the current node. If the function returns false at any point,
923 * the cascade is stopped on that branch.
924 * @param {Function} fn The function to call
925 * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node.
926 * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
928 cascadeBy : function(fn, scope, args) {
929 if (fn.apply(scope || this, args || [this]) !== false) {
930 var childNodes = this.childNodes,
931 length = childNodes.length,
934 for (i = 0; i < length; i++) {
935 childNodes[i].cascadeBy(fn, scope, args);
941 * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
942 * will be the args provided or the current node. If the function returns false at any point,
943 * the iteration stops.
944 * @param {Function} fn The function to call
945 * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the current Node in iteration.
946 * @param {Array} [args] The args to call the function with. Defaults to passing the current Node.
948 eachChild : function(fn, scope, args) {
949 var childNodes = this.childNodes,
950 length = childNodes.length,
953 for (i = 0; i < length; i++) {
954 if (fn.apply(scope || this, args || [childNodes[i]]) === false) {
961 * Finds the first child that has the attribute with the specified value.
962 * @param {String} attribute The attribute name
963 * @param {Object} value The value to search for
964 * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
965 * @return {Ext.data.NodeInterface} The found child or null if none was found
967 findChild : function(attribute, value, deep) {
968 return this.findChildBy(function() {
969 return this.get(attribute) == value;
974 * Finds the first child by a custom function. The child matches if the function passed returns true.
975 * @param {Function} fn A function which must return true if the passed Node is the required Node.
976 * @param {Object} [scope] The scope (this reference) in which the function is executed. Defaults to the Node being tested.
977 * @param {Boolean} [deep=false] True to search through nodes deeper than the immediate children
978 * @return {Ext.data.NodeInterface} The found child or null if none was found
980 findChildBy : function(fn, scope, deep) {
981 var cs = this.childNodes,
985 for (; i < len; i++) {
987 if (fn.call(scope || n, n) === true) {
991 res = n.findChildBy(fn, scope, deep);
1002 * Returns true if this node is an ancestor (at any point) of the passed node.
1003 * @param {Ext.data.NodeInterface} node
1006 contains : function(node) {
1007 return node.isAncestor(this);
1011 * Returns true if the passed node is an ancestor (at any point) of this node.
1012 * @param {Ext.data.NodeInterface} node
1015 isAncestor : function(node) {
1016 var p = this.parentNode;
1027 * Sorts this nodes children using the supplied sort function.
1028 * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
1029 * @param {Boolean} [recursive=false] True to apply this sort recursively
1030 * @param {Boolean} [suppressEvent=false] True to not fire a sort event.
1032 sort : function(sortFn, recursive, suppressEvent) {
1033 var cs = this.childNodes,
1038 Ext.Array.sort(cs, sortFn);
1039 for (i = 0; i < ln; i++) {
1041 n.previousSibling = cs[i-1];
1042 n.nextSibling = cs[i+1];
1045 this.setFirstChild(n);
1049 this.setLastChild(n);
1052 if (recursive && !n.isLeaf()) {
1053 n.sort(sortFn, true, true);
1057 if (suppressEvent !== true) {
1058 this.fireEvent('sort', this, cs);
1064 * Returns true if this node is expaned
1067 isExpanded: function() {
1068 return this.get('expanded');
1072 * Returns true if this node is loaded
1075 isLoaded: function() {
1076 return this.get('loaded');
1080 * Returns true if this node is loading
1083 isLoading: function() {
1084 return this.get('loading');
1088 * Returns true if this node is the root node
1091 isRoot: function() {
1092 return !this.parentNode;
1096 * Returns true if this node is visible
1099 isVisible: function() {
1100 var parent = this.parentNode;
1102 if (!parent.isExpanded()) {
1105 parent = parent.parentNode;
1112 * @param {Boolean} [recursive=false] True to recursively expand all the children
1113 * @param {Function} [callback] The function to execute once the expand completes
1114 * @param {Object} [scope] The scope to run the callback in
1116 expand: function(recursive, callback, scope) {
1119 // all paths must call the callback (eventually) or things like
1122 // First we start by checking if this node is a parent
1124 // If it's loaded, wait until it loads before proceeding
1125 if (me.isLoading()) {
1126 me.on('expand', function(){
1127 me.expand(recursive, callback, scope);
1128 }, me, {single: true});
1130 // Now we check if this record is already expanding or expanded
1131 if (!me.isExpanded()) {
1132 // The TreeStore actually listens for the beforeexpand method and checks
1133 // whether we have to asynchronously load the children from the server
1134 // first. Thats why we pass a callback function to the event that the
1135 // store can call once it has loaded and parsed all the children.
1136 me.fireEvent('beforeexpand', me, function(){
1137 me.set('expanded', true);
1138 me.fireEvent('expand', me, me.childNodes, false);
1140 // Call the expandChildren method if recursive was set to true
1142 me.expandChildren(true, callback, scope);
1144 Ext.callback(callback, scope || me, [me.childNodes]);
1147 } else if (recursive) {
1148 // If it is is already expanded but we want to recursively expand then call expandChildren
1149 me.expandChildren(true, callback, scope);
1151 Ext.callback(callback, scope || me, [me.childNodes]);
1155 // If it's not then we fire the callback right away
1156 Ext.callback(callback, scope || me); // leaf = no childNodes
1161 * Expand all the children of this node.
1162 * @param {Boolean} [recursive=false] True to recursively expand all the children
1163 * @param {Function} [callback] The function to execute once all the children are expanded
1164 * @param {Object} [scope] The scope to run the callback in
1166 expandChildren: function(recursive, callback, scope) {
1169 nodes = me.childNodes,
1174 for (; i < ln; ++i) {
1176 if (!node.isLeaf() && !node.isExpanded()) {
1178 nodes[i].expand(recursive, function () {
1180 if (callback && !expanding) {
1181 Ext.callback(callback, scope || me, [me.childNodes]);
1187 if (!expanding && callback) {
1188 Ext.callback(callback, scope || me, [me.childNodes]); }
1192 * Collapse this node.
1193 * @param {Boolean} [recursive=false] True to recursively collapse all the children
1194 * @param {Function} [callback] The function to execute once the collapse completes
1195 * @param {Object} [scope] The scope to run the callback in
1197 collapse: function(recursive, callback, scope) {
1200 // First we start by checking if this node is a parent
1202 // Now we check if this record is already collapsing or collapsed
1203 if (!me.collapsing && me.isExpanded()) {
1204 me.fireEvent('beforecollapse', me, function() {
1205 me.set('expanded', false);
1206 me.fireEvent('collapse', me, me.childNodes, false);
1208 // Call the collapseChildren method if recursive was set to true
1210 me.collapseChildren(true, callback, scope);
1213 Ext.callback(callback, scope || me, [me.childNodes]);
1217 // If it is is already collapsed but we want to recursively collapse then call collapseChildren
1218 else if (recursive) {
1219 me.collapseChildren(true, callback, scope);
1222 // If it's not then we fire the callback right away
1224 Ext.callback(callback, scope || me, [me.childNodes]);
1229 * Collapse all the children of this node.
1230 * @param {Function} [recursive=false] True to recursively collapse all the children
1231 * @param {Function} [callback] The function to execute once all the children are collapsed
1232 * @param {Object} [scope] The scope to run the callback in
1234 collapseChildren: function(recursive, callback, scope) {
1237 nodes = me.childNodes,
1242 for (; i < ln; ++i) {
1244 if (!node.isLeaf() && node.isExpanded()) {
1246 nodes[i].collapse(recursive, function () {
1248 if (callback && !collapsing) {
1249 Ext.callback(callback, scope || me, [me.childNodes]);
1255 if (!collapsing && callback) {
1256 Ext.callback(callback, scope || me, [me.childNodes]);