Upgrade to ExtJS 3.1.0 - Released 12/16/2009
[extjs.git] / src / widgets / tree / TreePanel.js
1 /*!
2  * Ext JS Library 3.1.0
3  * Copyright(c) 2006-2009 Ext JS, LLC
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**\r
8  * @class Ext.tree.TreePanel\r
9  * @extends Ext.Panel\r
10  * <p>The TreePanel provides tree-structured UI representation of tree-structured data.</p>\r
11  * <p>{@link Ext.tree.TreeNode TreeNode}s added to the TreePanel may each contain metadata\r
12  * used by your application in their {@link Ext.tree.TreeNode#attributes attributes} property.</p>\r
13  * <p><b>A TreePanel must have a {@link #root} node before it is rendered.</b> This may either be\r
14  * specified using the {@link #root} config option, or using the {@link #setRootNode} method.\r
15  * <p>An example of tree rendered to an existing div:</p><pre><code>\r
16 var tree = new Ext.tree.TreePanel({\r
17     renderTo: 'tree-div',\r
18     useArrows: true,\r
19     autoScroll: true,\r
20     animate: true,\r
21     enableDD: true,\r
22     containerScroll: true,\r
23     border: false,\r
24     // auto create TreeLoader\r
25     dataUrl: 'get-nodes.php',\r
26 \r
27     root: {\r
28         nodeType: 'async',\r
29         text: 'Ext JS',\r
30         draggable: false,\r
31         id: 'source'\r
32     }\r
33 });\r
34 \r
35 tree.getRootNode().expand();\r
36  * </code></pre>\r
37  * <p>The example above would work with a data packet similar to this:</p><pre><code>\r
38 [{\r
39     "text": "adapter",\r
40     "id": "source\/adapter",\r
41     "cls": "folder"\r
42 }, {\r
43     "text": "dd",\r
44     "id": "source\/dd",\r
45     "cls": "folder"\r
46 }, {\r
47     "text": "debug.js",\r
48     "id": "source\/debug.js",\r
49     "leaf": true,\r
50     "cls": "file"\r
51 }]\r
52  * </code></pre>\r
53  * <p>An example of tree within a Viewport:</p><pre><code>\r
54 new Ext.Viewport({\r
55     layout: 'border',\r
56     items: [{\r
57         region: 'west',\r
58         collapsible: true,\r
59         title: 'Navigation',\r
60         xtype: 'treepanel',\r
61         width: 200,\r
62         autoScroll: true,\r
63         split: true,\r
64         loader: new Ext.tree.TreeLoader(),\r
65         root: new Ext.tree.AsyncTreeNode({\r
66             expanded: true,\r
67             children: [{\r
68                 text: 'Menu Option 1',\r
69                 leaf: true\r
70             }, {\r
71                 text: 'Menu Option 2',\r
72                 leaf: true\r
73             }, {\r
74                 text: 'Menu Option 3',\r
75                 leaf: true\r
76             }]\r
77         }),\r
78         rootVisible: false,\r
79         listeners: {\r
80             click: function(n) {\r
81                 Ext.Msg.alert('Navigation Tree Click', 'You clicked: "' + n.attributes.text + '"');\r
82             }\r
83         }\r
84     }, {\r
85         region: 'center',\r
86         xtype: 'tabpanel',\r
87         // remaining code not shown ...\r
88     }]\r
89 });\r
90 </code></pre>\r
91  *\r
92  * @cfg {Ext.tree.TreeNode} root The root node for the tree.\r
93  * @cfg {Boolean} rootVisible <tt>false</tt> to hide the root node (defaults to <tt>true</tt>)\r
94  * @cfg {Boolean} lines <tt>false</tt> to disable tree lines (defaults to <tt>true</tt>)\r
95  * @cfg {Boolean} enableDD <tt>true</tt> to enable drag and drop\r
96  * @cfg {Boolean} enableDrag <tt>true</tt> to enable just drag\r
97  * @cfg {Boolean} enableDrop <tt>true</tt> to enable just drop\r
98  * @cfg {Object} dragConfig Custom config to pass to the {@link Ext.tree.TreeDragZone} instance\r
99  * @cfg {Object} dropConfig Custom config to pass to the {@link Ext.tree.TreeDropZone} instance\r
100  * @cfg {String} ddGroup The DD group this TreePanel belongs to\r
101  * @cfg {Boolean} ddAppendOnly <tt>true</tt> if the tree should only allow append drops (use for trees which are sorted)\r
102  * @cfg {Boolean} ddScroll <tt>true</tt> to enable body scrolling\r
103  * @cfg {Boolean} containerScroll <tt>true</tt> to register this container with ScrollManager\r
104  * @cfg {Boolean} hlDrop <tt>false</tt> to disable node highlight on drop (defaults to the value of {@link Ext#enableFx})\r
105  * @cfg {String} hlColor The color of the node highlight (defaults to <tt>'C3DAF9'</tt>)\r
106  * @cfg {Boolean} animate <tt>true</tt> to enable animated expand/collapse (defaults to the value of {@link Ext#enableFx})\r
107  * @cfg {Boolean} singleExpand <tt>true</tt> if only 1 node per branch may be expanded\r
108  * @cfg {Object} selModel A tree selection model to use with this TreePanel (defaults to an {@link Ext.tree.DefaultSelectionModel})\r
109  * @cfg {Boolean} trackMouseOver <tt>false</tt> to disable mouse over highlighting\r
110  * @cfg {Ext.tree.TreeLoader} loader A {@link Ext.tree.TreeLoader} for use with this TreePanel\r
111  * @cfg {String} pathSeparator The token used to separate sub-paths in path strings (defaults to <tt>'/'</tt>)\r
112  * @cfg {Boolean} useArrows <tt>true</tt> to use Vista-style arrows in the tree (defaults to <tt>false</tt>)\r
113  * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).\r
114  *\r
115  * @constructor\r
116  * @param {Object} config\r
117  * @xtype treepanel\r
118  */\r
119 Ext.tree.TreePanel = Ext.extend(Ext.Panel, {\r
120     rootVisible : true,\r
121     animate : Ext.enableFx,\r
122     lines : true,\r
123     enableDD : false,\r
124     hlDrop : Ext.enableFx,\r
125     pathSeparator : '/',\r
126 \r
127     /**\r
128      * @cfg {Array} bubbleEvents\r
129      * <p>An array of events that, when fired, should be bubbled to any parent container.\r
130      * See {@link Ext.util.Observable#enableBubble}.\r
131      * Defaults to <tt>[]</tt>.\r
132      */\r
133     bubbleEvents : [],\r
134 \r
135     initComponent : function(){\r
136         Ext.tree.TreePanel.superclass.initComponent.call(this);\r
137 \r
138         if(!this.eventModel){\r
139             this.eventModel = new Ext.tree.TreeEventModel(this);\r
140         }\r
141 \r
142         // initialize the loader\r
143         var l = this.loader;\r
144         if(!l){\r
145             l = new Ext.tree.TreeLoader({\r
146                 dataUrl: this.dataUrl,\r
147                 requestMethod: this.requestMethod\r
148             });\r
149         }else if(Ext.isObject(l) && !l.load){\r
150             l = new Ext.tree.TreeLoader(l);\r
151         }\r
152         this.loader = l;\r
153 \r
154         this.nodeHash = {};\r
155 \r
156         /**\r
157         * The root node of this tree.\r
158         * @type Ext.tree.TreeNode\r
159         * @property root\r
160         */\r
161         if(this.root){\r
162             var r = this.root;\r
163             delete this.root;\r
164             this.setRootNode(r);\r
165         }\r
166 \r
167 \r
168         this.addEvents(\r
169 \r
170             /**\r
171             * @event append\r
172             * Fires when a new child node is appended to a node in this tree.\r
173             * @param {Tree} tree The owner tree\r
174             * @param {Node} parent The parent node\r
175             * @param {Node} node The newly appended node\r
176             * @param {Number} index The index of the newly appended node\r
177             */\r
178            'append',\r
179            /**\r
180             * @event remove\r
181             * Fires when a child node is removed from a node in this tree.\r
182             * @param {Tree} tree The owner tree\r
183             * @param {Node} parent The parent node\r
184             * @param {Node} node The child node removed\r
185             */\r
186            'remove',\r
187            /**\r
188             * @event movenode\r
189             * Fires when a node is moved to a new location in the tree\r
190             * @param {Tree} tree The owner tree\r
191             * @param {Node} node The node moved\r
192             * @param {Node} oldParent The old parent of this node\r
193             * @param {Node} newParent The new parent of this node\r
194             * @param {Number} index The index it was moved to\r
195             */\r
196            'movenode',\r
197            /**\r
198             * @event insert\r
199             * Fires when a new child node is inserted in a node in this tree.\r
200             * @param {Tree} tree The owner tree\r
201             * @param {Node} parent The parent node\r
202             * @param {Node} node The child node inserted\r
203             * @param {Node} refNode The child node the node was inserted before\r
204             */\r
205            'insert',\r
206            /**\r
207             * @event beforeappend\r
208             * Fires before a new child is appended to a node in this tree, return false to cancel the append.\r
209             * @param {Tree} tree The owner tree\r
210             * @param {Node} parent The parent node\r
211             * @param {Node} node The child node to be appended\r
212             */\r
213            'beforeappend',\r
214            /**\r
215             * @event beforeremove\r
216             * Fires before a child is removed from a node in this tree, return false to cancel the remove.\r
217             * @param {Tree} tree The owner tree\r
218             * @param {Node} parent The parent node\r
219             * @param {Node} node The child node to be removed\r
220             */\r
221            'beforeremove',\r
222            /**\r
223             * @event beforemovenode\r
224             * Fires before a node is moved to a new location in the tree. Return false to cancel the move.\r
225             * @param {Tree} tree The owner tree\r
226             * @param {Node} node The node being moved\r
227             * @param {Node} oldParent The parent of the node\r
228             * @param {Node} newParent The new parent the node is moving to\r
229             * @param {Number} index The index it is being moved to\r
230             */\r
231            'beforemovenode',\r
232            /**\r
233             * @event beforeinsert\r
234             * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.\r
235             * @param {Tree} tree The owner tree\r
236             * @param {Node} parent The parent node\r
237             * @param {Node} node The child node to be inserted\r
238             * @param {Node} refNode The child node the node is being inserted before\r
239             */\r
240             'beforeinsert',\r
241 \r
242             /**\r
243             * @event beforeload\r
244             * Fires before a node is loaded, return false to cancel\r
245             * @param {Node} node The node being loaded\r
246             */\r
247             'beforeload',\r
248             /**\r
249             * @event load\r
250             * Fires when a node is loaded\r
251             * @param {Node} node The node that was loaded\r
252             */\r
253             'load',\r
254             /**\r
255             * @event textchange\r
256             * Fires when the text for a node is changed\r
257             * @param {Node} node The node\r
258             * @param {String} text The new text\r
259             * @param {String} oldText The old text\r
260             */\r
261             'textchange',\r
262             /**\r
263             * @event beforeexpandnode\r
264             * Fires before a node is expanded, return false to cancel.\r
265             * @param {Node} node The node\r
266             * @param {Boolean} deep\r
267             * @param {Boolean} anim\r
268             */\r
269             'beforeexpandnode',\r
270             /**\r
271             * @event beforecollapsenode\r
272             * Fires before a node is collapsed, return false to cancel.\r
273             * @param {Node} node The node\r
274             * @param {Boolean} deep\r
275             * @param {Boolean} anim\r
276             */\r
277             'beforecollapsenode',\r
278             /**\r
279             * @event expandnode\r
280             * Fires when a node is expanded\r
281             * @param {Node} node The node\r
282             */\r
283             'expandnode',\r
284             /**\r
285             * @event disabledchange\r
286             * Fires when the disabled status of a node changes\r
287             * @param {Node} node The node\r
288             * @param {Boolean} disabled\r
289             */\r
290             'disabledchange',\r
291             /**\r
292             * @event collapsenode\r
293             * Fires when a node is collapsed\r
294             * @param {Node} node The node\r
295             */\r
296             'collapsenode',\r
297             /**\r
298             * @event beforeclick\r
299             * Fires before click processing on a node. Return false to cancel the default action.\r
300             * @param {Node} node The node\r
301             * @param {Ext.EventObject} e The event object\r
302             */\r
303             'beforeclick',\r
304             /**\r
305             * @event click\r
306             * Fires when a node is clicked\r
307             * @param {Node} node The node\r
308             * @param {Ext.EventObject} e The event object\r
309             */\r
310             'click',\r
311             /**\r
312             * @event containerclick\r
313             * Fires when the tree container is clicked\r
314             * @param {Tree} this\r
315             * @param {Ext.EventObject} e The event object\r
316             */\r
317             'containerclick',\r
318             /**\r
319             * @event checkchange\r
320             * Fires when a node with a checkbox's checked property changes\r
321             * @param {Node} this This node\r
322             * @param {Boolean} checked\r
323             */\r
324             'checkchange',\r
325             /**\r
326             * @event beforedblclick\r
327             * Fires before double click processing on a node. Return false to cancel the default action.\r
328             * @param {Node} node The node\r
329             * @param {Ext.EventObject} e The event object\r
330             */\r
331             'beforedblclick',\r
332             /**\r
333             * @event dblclick\r
334             * Fires when a node is double clicked\r
335             * @param {Node} node The node\r
336             * @param {Ext.EventObject} e The event object\r
337             */\r
338             'dblclick',\r
339             /**\r
340             * @event containerdblclick\r
341             * Fires when the tree container is double clicked\r
342             * @param {Tree} this\r
343             * @param {Ext.EventObject} e The event object\r
344             */\r
345             'containerdblclick',\r
346             /**\r
347             * @event contextmenu\r
348             * Fires when a node is right clicked. To display a context menu in response to this\r
349             * event, first create a Menu object (see {@link Ext.menu.Menu} for details), then add\r
350             * a handler for this event:<pre><code>\r
351 new Ext.tree.TreePanel({\r
352     title: 'My TreePanel',\r
353     root: new Ext.tree.AsyncTreeNode({\r
354         text: 'The Root',\r
355         children: [\r
356             { text: 'Child node 1', leaf: true },\r
357             { text: 'Child node 2', leaf: true }\r
358         ]\r
359     }),\r
360     contextMenu: new Ext.menu.Menu({\r
361         items: [{\r
362             id: 'delete-node',\r
363             text: 'Delete Node'\r
364         }],\r
365         listeners: {\r
366             itemclick: function(item) {\r
367                 switch (item.id) {\r
368                     case 'delete-node':\r
369                         var n = item.parentMenu.contextNode;\r
370                         if (n.parentNode) {\r
371                             n.remove();\r
372                         }\r
373                         break;\r
374                 }\r
375             }\r
376         }\r
377     }),\r
378     listeners: {\r
379         contextmenu: function(node, e) {\r
380 //          Register the context node with the menu so that a Menu Item's handler function can access\r
381 //          it via its {@link Ext.menu.BaseItem#parentMenu parentMenu} property.\r
382             node.select();\r
383             var c = node.getOwnerTree().contextMenu;\r
384             c.contextNode = node;\r
385             c.showAt(e.getXY());\r
386         }\r
387     }\r
388 });\r
389 </code></pre>\r
390             * @param {Node} node The node\r
391             * @param {Ext.EventObject} e The event object\r
392             */\r
393             'contextmenu',\r
394             /**\r
395             * @event containercontextmenu\r
396             * Fires when the tree container is right clicked\r
397             * @param {Tree} this\r
398             * @param {Ext.EventObject} e The event object\r
399             */\r
400             'containercontextmenu',\r
401             /**\r
402             * @event beforechildrenrendered\r
403             * Fires right before the child nodes for a node are rendered\r
404             * @param {Node} node The node\r
405             */\r
406             'beforechildrenrendered',\r
407            /**\r
408              * @event startdrag\r
409              * Fires when a node starts being dragged\r
410              * @param {Ext.tree.TreePanel} this\r
411              * @param {Ext.tree.TreeNode} node\r
412              * @param {event} e The raw browser event\r
413              */\r
414             'startdrag',\r
415             /**\r
416              * @event enddrag\r
417              * Fires when a drag operation is complete\r
418              * @param {Ext.tree.TreePanel} this\r
419              * @param {Ext.tree.TreeNode} node\r
420              * @param {event} e The raw browser event\r
421              */\r
422             'enddrag',\r
423             /**\r
424              * @event dragdrop\r
425              * Fires when a dragged node is dropped on a valid DD target\r
426              * @param {Ext.tree.TreePanel} this\r
427              * @param {Ext.tree.TreeNode} node\r
428              * @param {DD} dd The dd it was dropped on\r
429              * @param {event} e The raw browser event\r
430              */\r
431             'dragdrop',\r
432             /**\r
433              * @event beforenodedrop\r
434              * Fires when a DD object is dropped on a node in this tree for preprocessing. Return false to cancel the drop. The dropEvent\r
435              * passed to handlers has the following properties:<br />\r
436              * <ul style="padding:5px;padding-left:16px;">\r
437              * <li>tree - The TreePanel</li>\r
438              * <li>target - The node being targeted for the drop</li>\r
439              * <li>data - The drag data from the drag source</li>\r
440              * <li>point - The point of the drop - append, above or below</li>\r
441              * <li>source - The drag source</li>\r
442              * <li>rawEvent - Raw mouse event</li>\r
443              * <li>dropNode - Drop node(s) provided by the source <b>OR</b> you can supply node(s)\r
444              * to be inserted by setting them on this object.</li>\r
445              * <li>cancel - Set this to true to cancel the drop.</li>\r
446              * <li>dropStatus - If the default drop action is cancelled but the drop is valid, setting this to true\r
447              * will prevent the animated 'repair' from appearing.</li>\r
448              * </ul>\r
449              * @param {Object} dropEvent\r
450              */\r
451             'beforenodedrop',\r
452             /**\r
453              * @event nodedrop\r
454              * Fires after a DD object is dropped on a node in this tree. The dropEvent\r
455              * passed to handlers has the following properties:<br />\r
456              * <ul style="padding:5px;padding-left:16px;">\r
457              * <li>tree - The TreePanel</li>\r
458              * <li>target - The node being targeted for the drop</li>\r
459              * <li>data - The drag data from the drag source</li>\r
460              * <li>point - The point of the drop - append, above or below</li>\r
461              * <li>source - The drag source</li>\r
462              * <li>rawEvent - Raw mouse event</li>\r
463              * <li>dropNode - Dropped node(s).</li>\r
464              * </ul>\r
465              * @param {Object} dropEvent\r
466              */\r
467             'nodedrop',\r
468              /**\r
469              * @event nodedragover\r
470              * Fires when a tree node is being targeted for a drag drop, return false to signal drop not allowed. The dragOverEvent\r
471              * passed to handlers has the following properties:<br />\r
472              * <ul style="padding:5px;padding-left:16px;">\r
473              * <li>tree - The TreePanel</li>\r
474              * <li>target - The node being targeted for the drop</li>\r
475              * <li>data - The drag data from the drag source</li>\r
476              * <li>point - The point of the drop - append, above or below</li>\r
477              * <li>source - The drag source</li>\r
478              * <li>rawEvent - Raw mouse event</li>\r
479              * <li>dropNode - Drop node(s) provided by the source.</li>\r
480              * <li>cancel - Set this to true to signal drop not allowed.</li>\r
481              * </ul>\r
482              * @param {Object} dragOverEvent\r
483              */\r
484             'nodedragover'\r
485         );\r
486         if(this.singleExpand){\r
487             this.on('beforeexpandnode', this.restrictExpand, this);\r
488         }\r
489     },\r
490 \r
491     // private\r
492     proxyNodeEvent : function(ename, a1, a2, a3, a4, a5, a6){\r
493         if(ename == 'collapse' || ename == 'expand' || ename == 'beforecollapse' || ename == 'beforeexpand' || ename == 'move' || ename == 'beforemove'){\r
494             ename = ename+'node';\r
495         }\r
496         // args inline for performance while bubbling events\r
497         return this.fireEvent(ename, a1, a2, a3, a4, a5, a6);\r
498     },\r
499 \r
500 \r
501     /**\r
502      * Returns this root node for this tree\r
503      * @return {Node}\r
504      */\r
505     getRootNode : function(){\r
506         return this.root;\r
507     },\r
508 \r
509     /**\r
510      * Sets the root node for this tree. If the TreePanel has already rendered a root node, the\r
511      * previous root node (and all of its descendants) are destroyed before the new root node is rendered.\r
512      * @param {Node} node\r
513      * @return {Node}\r
514      */\r
515     setRootNode : function(node){\r
516         Ext.destroy(this.root);\r
517         if(!node.render){ // attributes passed\r
518             node = this.loader.createNode(node);\r
519         }\r
520         this.root = node;\r
521         node.ownerTree = this;\r
522         node.isRoot = true;\r
523         this.registerNode(node);\r
524         if(!this.rootVisible){\r
525             var uiP = node.attributes.uiProvider;\r
526             node.ui = uiP ? new uiP(node) : new Ext.tree.RootTreeNodeUI(node);\r
527         }\r
528         if (this.innerCt) {\r
529             this.innerCt.update('');\r
530             this.afterRender();\r
531         }\r
532         return node;\r
533     },\r
534 \r
535     /**\r
536      * Gets a node in this tree by its id\r
537      * @param {String} id\r
538      * @return {Node}\r
539      */\r
540     getNodeById : function(id){\r
541         return this.nodeHash[id];\r
542     },\r
543 \r
544     // private\r
545     registerNode : function(node){\r
546         this.nodeHash[node.id] = node;\r
547     },\r
548 \r
549     // private\r
550     unregisterNode : function(node){\r
551         delete this.nodeHash[node.id];\r
552     },\r
553 \r
554     // private\r
555     toString : function(){\r
556         return '[Tree'+(this.id?' '+this.id:'')+']';\r
557     },\r
558 \r
559     // private\r
560     restrictExpand : function(node){\r
561         var p = node.parentNode;\r
562         if(p){\r
563             if(p.expandedChild && p.expandedChild.parentNode == p){\r
564                 p.expandedChild.collapse();\r
565             }\r
566             p.expandedChild = node;\r
567         }\r
568     },\r
569 \r
570     /**\r
571      * Retrieve an array of checked nodes, or an array of a specific attribute of checked nodes (e.g. 'id')\r
572      * @param {String} attribute (optional) Defaults to null (return the actual nodes)\r
573      * @param {TreeNode} startNode (optional) The node to start from, defaults to the root\r
574      * @return {Array}\r
575      */\r
576     getChecked : function(a, startNode){\r
577         startNode = startNode || this.root;\r
578         var r = [];\r
579         var f = function(){\r
580             if(this.attributes.checked){\r
581                 r.push(!a ? this : (a == 'id' ? this.id : this.attributes[a]));\r
582             }\r
583         };\r
584         startNode.cascade(f);\r
585         return r;\r
586     },\r
587 \r
588     /**\r
589      * Returns the default {@link Ext.tree.TreeLoader} for this TreePanel.\r
590      * @return {Ext.tree.TreeLoader} The TreeLoader for this TreePanel.\r
591      */\r
592     getLoader : function(){\r
593         return this.loader;\r
594     },\r
595 \r
596     /**\r
597      * Expand all nodes\r
598      */\r
599     expandAll : function(){\r
600         this.root.expand(true);\r
601     },\r
602 \r
603     /**\r
604      * Collapse all nodes\r
605      */\r
606     collapseAll : function(){\r
607         this.root.collapse(true);\r
608     },\r
609 \r
610     /**\r
611      * Returns the selection model used by this TreePanel.\r
612      * @return {TreeSelectionModel} The selection model used by this TreePanel\r
613      */\r
614     getSelectionModel : function(){\r
615         if(!this.selModel){\r
616             this.selModel = new Ext.tree.DefaultSelectionModel();\r
617         }\r
618         return this.selModel;\r
619     },\r
620 \r
621     /**\r
622      * Expands a specified path in this TreePanel. A path can be retrieved from a node with {@link Ext.data.Node#getPath}\r
623      * @param {String} path\r
624      * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)\r
625      * @param {Function} callback (optional) The callback to call when the expand is complete. The callback will be called with\r
626      * (bSuccess, oLastNode) where bSuccess is if the expand was successful and oLastNode is the last node that was expanded.\r
627      */\r
628     expandPath : function(path, attr, callback){\r
629         attr = attr || 'id';\r
630         var keys = path.split(this.pathSeparator);\r
631         var curNode = this.root;\r
632         if(curNode.attributes[attr] != keys[1]){ // invalid root\r
633             if(callback){\r
634                 callback(false, null);\r
635             }\r
636             return;\r
637         }\r
638         var index = 1;\r
639         var f = function(){\r
640             if(++index == keys.length){\r
641                 if(callback){\r
642                     callback(true, curNode);\r
643                 }\r
644                 return;\r
645             }\r
646             var c = curNode.findChild(attr, keys[index]);\r
647             if(!c){\r
648                 if(callback){\r
649                     callback(false, curNode);\r
650                 }\r
651                 return;\r
652             }\r
653             curNode = c;\r
654             c.expand(false, false, f);\r
655         };\r
656         curNode.expand(false, false, f);\r
657     },\r
658 \r
659     /**\r
660      * Selects the node in this tree at the specified path. A path can be retrieved from a node with {@link Ext.data.Node#getPath}\r
661      * @param {String} path\r
662      * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)\r
663      * @param {Function} callback (optional) The callback to call when the selection is complete. The callback will be called with\r
664      * (bSuccess, oSelNode) where bSuccess is if the selection was successful and oSelNode is the selected node.\r
665      */\r
666     selectPath : function(path, attr, callback){\r
667         attr = attr || 'id';\r
668         var keys = path.split(this.pathSeparator),\r
669             v = keys.pop();\r
670         if(keys.length > 1){\r
671             var f = function(success, node){\r
672                 if(success && node){\r
673                     var n = node.findChild(attr, v);\r
674                     if(n){\r
675                         n.select();\r
676                         if(callback){\r
677                             callback(true, n);\r
678                         }\r
679                     }else if(callback){\r
680                         callback(false, n);\r
681                     }\r
682                 }else{\r
683                     if(callback){\r
684                         callback(false, n);\r
685                     }\r
686                 }\r
687             };\r
688             this.expandPath(keys.join(this.pathSeparator), attr, f);\r
689         }else{\r
690             this.root.select();\r
691             if(callback){\r
692                 callback(true, this.root);\r
693             }\r
694         }\r
695     },\r
696 \r
697     /**\r
698      * Returns the underlying Element for this tree\r
699      * @return {Ext.Element} The Element\r
700      */\r
701     getTreeEl : function(){\r
702         return this.body;\r
703     },\r
704 \r
705     // private\r
706     onRender : function(ct, position){\r
707         Ext.tree.TreePanel.superclass.onRender.call(this, ct, position);\r
708         this.el.addClass('x-tree');\r
709         this.innerCt = this.body.createChild({tag:'ul',\r
710                cls:'x-tree-root-ct ' +\r
711                (this.useArrows ? 'x-tree-arrows' : this.lines ? 'x-tree-lines' : 'x-tree-no-lines')});\r
712     },\r
713 \r
714     // private\r
715     initEvents : function(){\r
716         Ext.tree.TreePanel.superclass.initEvents.call(this);\r
717 \r
718         if(this.containerScroll){\r
719             Ext.dd.ScrollManager.register(this.body);\r
720         }\r
721         if((this.enableDD || this.enableDrop) && !this.dropZone){\r
722            /**\r
723             * The dropZone used by this tree if drop is enabled (see {@link #enableDD} or {@link #enableDrop})\r
724             * @property dropZone\r
725             * @type Ext.tree.TreeDropZone\r
726             */\r
727              this.dropZone = new Ext.tree.TreeDropZone(this, this.dropConfig || {\r
728                ddGroup: this.ddGroup || 'TreeDD', appendOnly: this.ddAppendOnly === true\r
729            });\r
730         }\r
731         if((this.enableDD || this.enableDrag) && !this.dragZone){\r
732            /**\r
733             * The dragZone used by this tree if drag is enabled (see {@link #enableDD} or {@link #enableDrag})\r
734             * @property dragZone\r
735             * @type Ext.tree.TreeDragZone\r
736             */\r
737             this.dragZone = new Ext.tree.TreeDragZone(this, this.dragConfig || {\r
738                ddGroup: this.ddGroup || 'TreeDD',\r
739                scroll: this.ddScroll\r
740            });\r
741         }\r
742         this.getSelectionModel().init(this);\r
743     },\r
744 \r
745     // private\r
746     afterRender : function(){\r
747         Ext.tree.TreePanel.superclass.afterRender.call(this);\r
748         this.root.render();\r
749         if(!this.rootVisible){\r
750             this.root.renderChildren();\r
751         }\r
752     },\r
753 \r
754     beforeDestroy : function(){\r
755         if(this.rendered){\r
756             Ext.dd.ScrollManager.unregister(this.body);\r
757             Ext.destroy(this.dropZone, this.dragZone);\r
758         }\r
759         Ext.destroy(this.root, this.loader);\r
760         this.nodeHash = this.root = this.loader = null;\r
761         Ext.tree.TreePanel.superclass.beforeDestroy.call(this);\r
762     }\r
763 \r
764     /**\r
765      * @cfg {String/Number} activeItem\r
766      * @hide\r
767      */\r
768     /**\r
769      * @cfg {Boolean} autoDestroy\r
770      * @hide\r
771      */\r
772     /**\r
773      * @cfg {Object/String/Function} autoLoad\r
774      * @hide\r
775      */\r
776     /**\r
777      * @cfg {Boolean} autoWidth\r
778      * @hide\r
779      */\r
780     /**\r
781      * @cfg {Boolean/Number} bufferResize\r
782      * @hide\r
783      */\r
784     /**\r
785      * @cfg {String} defaultType\r
786      * @hide\r
787      */\r
788     /**\r
789      * @cfg {Object} defaults\r
790      * @hide\r
791      */\r
792     /**\r
793      * @cfg {Boolean} hideBorders\r
794      * @hide\r
795      */\r
796     /**\r
797      * @cfg {Mixed} items\r
798      * @hide\r
799      */\r
800     /**\r
801      * @cfg {String} layout\r
802      * @hide\r
803      */\r
804     /**\r
805      * @cfg {Object} layoutConfig\r
806      * @hide\r
807      */\r
808     /**\r
809      * @cfg {Boolean} monitorResize\r
810      * @hide\r
811      */\r
812     /**\r
813      * @property items\r
814      * @hide\r
815      */\r
816     /**\r
817      * @method cascade\r
818      * @hide\r
819      */\r
820     /**\r
821      * @method doLayout\r
822      * @hide\r
823      */\r
824     /**\r
825      * @method find\r
826      * @hide\r
827      */\r
828     /**\r
829      * @method findBy\r
830      * @hide\r
831      */\r
832     /**\r
833      * @method findById\r
834      * @hide\r
835      */\r
836     /**\r
837      * @method findByType\r
838      * @hide\r
839      */\r
840     /**\r
841      * @method getComponent\r
842      * @hide\r
843      */\r
844     /**\r
845      * @method getLayout\r
846      * @hide\r
847      */\r
848     /**\r
849      * @method getUpdater\r
850      * @hide\r
851      */\r
852     /**\r
853      * @method insert\r
854      * @hide\r
855      */\r
856     /**\r
857      * @method load\r
858      * @hide\r
859      */\r
860     /**\r
861      * @method remove\r
862      * @hide\r
863      */\r
864     /**\r
865      * @event add\r
866      * @hide\r
867      */\r
868     /**\r
869      * @method removeAll\r
870      * @hide\r
871      */\r
872     /**\r
873      * @event afterLayout\r
874      * @hide\r
875      */\r
876     /**\r
877      * @event beforeadd\r
878      * @hide\r
879      */\r
880     /**\r
881      * @event beforeremove\r
882      * @hide\r
883      */\r
884     /**\r
885      * @event remove\r
886      * @hide\r
887      */\r
888 \r
889 \r
890 \r
891     /**\r
892      * @cfg {String} allowDomMove  @hide\r
893      */\r
894     /**\r
895      * @cfg {String} autoEl @hide\r
896      */\r
897     /**\r
898      * @cfg {String} applyTo  @hide\r
899      */\r
900     /**\r
901      * @cfg {String} contentEl  @hide\r
902      */\r
903     /**\r
904      * @cfg {String} disabledClass  @hide\r
905      */\r
906     /**\r
907      * @cfg {String} elements  @hide\r
908      */\r
909     /**\r
910      * @cfg {String} html  @hide\r
911      */\r
912     /**\r
913      * @cfg {Boolean} preventBodyReset\r
914      * @hide\r
915      */\r
916     /**\r
917      * @property disabled\r
918      * @hide\r
919      */\r
920     /**\r
921      * @method applyToMarkup\r
922      * @hide\r
923      */\r
924     /**\r
925      * @method enable\r
926      * @hide\r
927      */\r
928     /**\r
929      * @method disable\r
930      * @hide\r
931      */\r
932     /**\r
933      * @method setDisabled\r
934      * @hide\r
935      */\r
936 });\r
937 \r
938 Ext.tree.TreePanel.nodeTypes = {};\r
939 \r
940 Ext.reg('treepanel', Ext.tree.TreePanel);