Upgrade to ExtJS 3.0.3 - Released 10/11/2009
[extjs.git] / pkgs / pkg-tree-debug.js
1 /*!
2  * Ext JS Library 3.0.3
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      * Defaults to <tt>['add', 'remove']</tt>.\r
131      */\r
132     bubbleEvents: [],\r
133 \r
134     initComponent : function(){\r
135         Ext.tree.TreePanel.superclass.initComponent.call(this);\r
136 \r
137         if(!this.eventModel){\r
138             this.eventModel = new Ext.tree.TreeEventModel(this);\r
139         }\r
140 \r
141         // initialize the loader\r
142         var l = this.loader;\r
143         if(!l){\r
144             l = new Ext.tree.TreeLoader({\r
145                 dataUrl: this.dataUrl,\r
146                 requestMethod: this.requestMethod\r
147             });\r
148         }else if(typeof l == 'object' && !l.load){\r
149             l = new Ext.tree.TreeLoader(l);\r
150         }\r
151         this.loader = l;\r
152 \r
153         this.nodeHash = {};\r
154 \r
155         /**\r
156         * The root node of this tree.\r
157         * @type Ext.tree.TreeNode\r
158         * @property root\r
159         */\r
160         if(this.root){\r
161             var r = this.root;\r
162             delete this.root;\r
163             this.setRootNode(r);\r
164         }\r
165 \r
166 \r
167         this.addEvents(\r
168 \r
169             /**\r
170             * @event append\r
171             * Fires when a new child node is appended to a node in this tree.\r
172             * @param {Tree} tree The owner tree\r
173             * @param {Node} parent The parent node\r
174             * @param {Node} node The newly appended node\r
175             * @param {Number} index The index of the newly appended node\r
176             */\r
177            "append",\r
178            /**\r
179             * @event remove\r
180             * Fires when a child node is removed from a node in this tree.\r
181             * @param {Tree} tree The owner tree\r
182             * @param {Node} parent The parent node\r
183             * @param {Node} node The child node removed\r
184             */\r
185            "remove",\r
186            /**\r
187             * @event movenode\r
188             * Fires when a node is moved to a new location in the tree\r
189             * @param {Tree} tree The owner tree\r
190             * @param {Node} node The node moved\r
191             * @param {Node} oldParent The old parent of this node\r
192             * @param {Node} newParent The new parent of this node\r
193             * @param {Number} index The index it was moved to\r
194             */\r
195            "movenode",\r
196            /**\r
197             * @event insert\r
198             * Fires when a new child node is inserted in a node in this tree.\r
199             * @param {Tree} tree The owner tree\r
200             * @param {Node} parent The parent node\r
201             * @param {Node} node The child node inserted\r
202             * @param {Node} refNode The child node the node was inserted before\r
203             */\r
204            "insert",\r
205            /**\r
206             * @event beforeappend\r
207             * Fires before a new child is appended to a node in this tree, return false to cancel the append.\r
208             * @param {Tree} tree The owner tree\r
209             * @param {Node} parent The parent node\r
210             * @param {Node} node The child node to be appended\r
211             */\r
212            "beforeappend",\r
213            /**\r
214             * @event beforeremove\r
215             * Fires before a child is removed from a node in this tree, return false to cancel the remove.\r
216             * @param {Tree} tree The owner tree\r
217             * @param {Node} parent The parent node\r
218             * @param {Node} node The child node to be removed\r
219             */\r
220            "beforeremove",\r
221            /**\r
222             * @event beforemovenode\r
223             * Fires before a node is moved to a new location in the tree. Return false to cancel the move.\r
224             * @param {Tree} tree The owner tree\r
225             * @param {Node} node The node being moved\r
226             * @param {Node} oldParent The parent of the node\r
227             * @param {Node} newParent The new parent the node is moving to\r
228             * @param {Number} index The index it is being moved to\r
229             */\r
230            "beforemovenode",\r
231            /**\r
232             * @event beforeinsert\r
233             * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.\r
234             * @param {Tree} tree The owner tree\r
235             * @param {Node} parent The parent node\r
236             * @param {Node} node The child node to be inserted\r
237             * @param {Node} refNode The child node the node is being inserted before\r
238             */\r
239             "beforeinsert",\r
240 \r
241             /**\r
242             * @event beforeload\r
243             * Fires before a node is loaded, return false to cancel\r
244             * @param {Node} node The node being loaded\r
245             */\r
246             "beforeload",\r
247             /**\r
248             * @event load\r
249             * Fires when a node is loaded\r
250             * @param {Node} node The node that was loaded\r
251             */\r
252             "load",\r
253             /**\r
254             * @event textchange\r
255             * Fires when the text for a node is changed\r
256             * @param {Node} node The node\r
257             * @param {String} text The new text\r
258             * @param {String} oldText The old text\r
259             */\r
260             "textchange",\r
261             /**\r
262             * @event beforeexpandnode\r
263             * Fires before a node is expanded, return false to cancel.\r
264             * @param {Node} node The node\r
265             * @param {Boolean} deep\r
266             * @param {Boolean} anim\r
267             */\r
268             "beforeexpandnode",\r
269             /**\r
270             * @event beforecollapsenode\r
271             * Fires before a node is collapsed, return false to cancel.\r
272             * @param {Node} node The node\r
273             * @param {Boolean} deep\r
274             * @param {Boolean} anim\r
275             */\r
276             "beforecollapsenode",\r
277             /**\r
278             * @event expandnode\r
279             * Fires when a node is expanded\r
280             * @param {Node} node The node\r
281             */\r
282             "expandnode",\r
283             /**\r
284             * @event disabledchange\r
285             * Fires when the disabled status of a node changes\r
286             * @param {Node} node The node\r
287             * @param {Boolean} disabled\r
288             */\r
289             "disabledchange",\r
290             /**\r
291             * @event collapsenode\r
292             * Fires when a node is collapsed\r
293             * @param {Node} node The node\r
294             */\r
295             "collapsenode",\r
296             /**\r
297             * @event beforeclick\r
298             * Fires before click processing on a node. Return false to cancel the default action.\r
299             * @param {Node} node The node\r
300             * @param {Ext.EventObject} e The event object\r
301             */\r
302             "beforeclick",\r
303             /**\r
304             * @event click\r
305             * Fires when a node is clicked\r
306             * @param {Node} node The node\r
307             * @param {Ext.EventObject} e The event object\r
308             */\r
309             "click",\r
310             /**\r
311             * @event checkchange\r
312             * Fires when a node with a checkbox's checked property changes\r
313             * @param {Node} this This node\r
314             * @param {Boolean} checked\r
315             */\r
316             "checkchange",\r
317             /**\r
318             * @event beforedblclick\r
319             * Fires before double click processing on a node. Return false to cancel the default action.\r
320             * @param {Node} node The node\r
321             * @param {Ext.EventObject} e The event object\r
322             */\r
323             "beforedblclick",\r
324             /**\r
325             * @event dblclick\r
326             * Fires when a node is double clicked\r
327             * @param {Node} node The node\r
328             * @param {Ext.EventObject} e The event object\r
329             */\r
330             "dblclick",\r
331             /**\r
332             * @event contextmenu\r
333             * Fires when a node is right clicked. To display a context menu in response to this\r
334             * event, first create a Menu object (see {@link Ext.menu.Menu} for details), then add\r
335             * a handler for this event:<pre><code>\r
336 new Ext.tree.TreePanel({\r
337     title: 'My TreePanel',\r
338     root: new Ext.tree.AsyncTreeNode({\r
339         text: 'The Root',\r
340         children: [\r
341             { text: 'Child node 1', leaf: true },\r
342             { text: 'Child node 2', leaf: true }\r
343         ]\r
344     }),\r
345     contextMenu: new Ext.menu.Menu({\r
346         items: [{\r
347             id: 'delete-node',\r
348             text: 'Delete Node'\r
349         }],\r
350         listeners: {\r
351             itemclick: function(item) {\r
352                 switch (item.id) {\r
353                     case 'delete-node':\r
354                         var n = item.parentMenu.contextNode;\r
355                         if (n.parentNode) {\r
356                             n.remove();\r
357                         }\r
358                         break;\r
359                 }\r
360             }\r
361         }\r
362     }),\r
363     listeners: {\r
364         contextmenu: function(node, e) {\r
365 //          Register the context node with the menu so that a Menu Item's handler function can access\r
366 //          it via its {@link Ext.menu.BaseItem#parentMenu parentMenu} property.\r
367             node.select();\r
368             var c = node.getOwnerTree().contextMenu;\r
369             c.contextNode = node;\r
370             c.showAt(e.getXY());\r
371         }\r
372     }\r
373 });\r
374 </code></pre>\r
375             * @param {Node} node The node\r
376             * @param {Ext.EventObject} e The event object\r
377             */\r
378             "contextmenu",\r
379             /**\r
380             * @event beforechildrenrendered\r
381             * Fires right before the child nodes for a node are rendered\r
382             * @param {Node} node The node\r
383             */\r
384             "beforechildrenrendered",\r
385            /**\r
386              * @event startdrag\r
387              * Fires when a node starts being dragged\r
388              * @param {Ext.tree.TreePanel} this\r
389              * @param {Ext.tree.TreeNode} node\r
390              * @param {event} e The raw browser event\r
391              */\r
392             "startdrag",\r
393             /**\r
394              * @event enddrag\r
395              * Fires when a drag operation is complete\r
396              * @param {Ext.tree.TreePanel} this\r
397              * @param {Ext.tree.TreeNode} node\r
398              * @param {event} e The raw browser event\r
399              */\r
400             "enddrag",\r
401             /**\r
402              * @event dragdrop\r
403              * Fires when a dragged node is dropped on a valid DD target\r
404              * @param {Ext.tree.TreePanel} this\r
405              * @param {Ext.tree.TreeNode} node\r
406              * @param {DD} dd The dd it was dropped on\r
407              * @param {event} e The raw browser event\r
408              */\r
409             "dragdrop",\r
410             /**\r
411              * @event beforenodedrop\r
412              * Fires when a DD object is dropped on a node in this tree for preprocessing. Return false to cancel the drop. The dropEvent\r
413              * passed to handlers has the following properties:<br />\r
414              * <ul style="padding:5px;padding-left:16px;">\r
415              * <li>tree - The TreePanel</li>\r
416              * <li>target - The node being targeted for the drop</li>\r
417              * <li>data - The drag data from the drag source</li>\r
418              * <li>point - The point of the drop - append, above or below</li>\r
419              * <li>source - The drag source</li>\r
420              * <li>rawEvent - Raw mouse event</li>\r
421              * <li>dropNode - Drop node(s) provided by the source <b>OR</b> you can supply node(s)\r
422              * to be inserted by setting them on this object.</li>\r
423              * <li>cancel - Set this to true to cancel the drop.</li>\r
424              * <li>dropStatus - If the default drop action is cancelled but the drop is valid, setting this to true\r
425              * will prevent the animated "repair" from appearing.</li>\r
426              * </ul>\r
427              * @param {Object} dropEvent\r
428              */\r
429             "beforenodedrop",\r
430             /**\r
431              * @event nodedrop\r
432              * Fires after a DD object is dropped on a node in this tree. The dropEvent\r
433              * passed to handlers has the following properties:<br />\r
434              * <ul style="padding:5px;padding-left:16px;">\r
435              * <li>tree - The TreePanel</li>\r
436              * <li>target - The node being targeted for the drop</li>\r
437              * <li>data - The drag data from the drag source</li>\r
438              * <li>point - The point of the drop - append, above or below</li>\r
439              * <li>source - The drag source</li>\r
440              * <li>rawEvent - Raw mouse event</li>\r
441              * <li>dropNode - Dropped node(s).</li>\r
442              * </ul>\r
443              * @param {Object} dropEvent\r
444              */\r
445             "nodedrop",\r
446              /**\r
447              * @event nodedragover\r
448              * Fires when a tree node is being targeted for a drag drop, return false to signal drop not allowed. The dragOverEvent\r
449              * passed to handlers has the following properties:<br />\r
450              * <ul style="padding:5px;padding-left:16px;">\r
451              * <li>tree - The TreePanel</li>\r
452              * <li>target - The node being targeted for the drop</li>\r
453              * <li>data - The drag data from the drag source</li>\r
454              * <li>point - The point of the drop - append, above or below</li>\r
455              * <li>source - The drag source</li>\r
456              * <li>rawEvent - Raw mouse event</li>\r
457              * <li>dropNode - Drop node(s) provided by the source.</li>\r
458              * <li>cancel - Set this to true to signal drop not allowed.</li>\r
459              * </ul>\r
460              * @param {Object} dragOverEvent\r
461              */\r
462             "nodedragover"\r
463         );\r
464         if(this.singleExpand){\r
465             this.on("beforeexpandnode", this.restrictExpand, this);\r
466         }\r
467     },\r
468 \r
469     // private\r
470     proxyNodeEvent : function(ename, a1, a2, a3, a4, a5, a6){\r
471         if(ename == 'collapse' || ename == 'expand' || ename == 'beforecollapse' || ename == 'beforeexpand' || ename == 'move' || ename == 'beforemove'){\r
472             ename = ename+'node';\r
473         }\r
474         // args inline for performance while bubbling events\r
475         return this.fireEvent(ename, a1, a2, a3, a4, a5, a6);\r
476     },\r
477 \r
478 \r
479     /**\r
480      * Returns this root node for this tree\r
481      * @return {Node}\r
482      */\r
483     getRootNode : function(){\r
484         return this.root;\r
485     },\r
486 \r
487     /**\r
488      * Sets the root node for this tree. If the TreePanel has already rendered a root node, the\r
489      * previous root node (and all of its descendants) are destroyed before the new root node is rendered.\r
490      * @param {Node} node\r
491      * @return {Node}\r
492      */\r
493     setRootNode : function(node){\r
494         Ext.destroy(this.root);\r
495         if(!node.render){ // attributes passed\r
496             node = this.loader.createNode(node);\r
497         }\r
498         this.root = node;\r
499         node.ownerTree = this;\r
500         node.isRoot = true;\r
501         this.registerNode(node);\r
502         if(!this.rootVisible){\r
503             var uiP = node.attributes.uiProvider;\r
504             node.ui = uiP ? new uiP(node) : new Ext.tree.RootTreeNodeUI(node);\r
505         }\r
506         if (this.innerCt) {\r
507             this.innerCt.update('');\r
508             this.afterRender();\r
509         }\r
510         return node;\r
511     },\r
512 \r
513     /**\r
514      * Gets a node in this tree by its id\r
515      * @param {String} id\r
516      * @return {Node}\r
517      */\r
518     getNodeById : function(id){\r
519         return this.nodeHash[id];\r
520     },\r
521 \r
522     // private\r
523     registerNode : function(node){\r
524         this.nodeHash[node.id] = node;\r
525     },\r
526 \r
527     // private\r
528     unregisterNode : function(node){\r
529         delete this.nodeHash[node.id];\r
530     },\r
531 \r
532     // private\r
533     toString : function(){\r
534         return "[Tree"+(this.id?" "+this.id:"")+"]";\r
535     },\r
536 \r
537     // private\r
538     restrictExpand : function(node){\r
539         var p = node.parentNode;\r
540         if(p){\r
541             if(p.expandedChild && p.expandedChild.parentNode == p){\r
542                 p.expandedChild.collapse();\r
543             }\r
544             p.expandedChild = node;\r
545         }\r
546     },\r
547 \r
548     /**\r
549      * Retrieve an array of checked nodes, or an array of a specific attribute of checked nodes (e.g. "id")\r
550      * @param {String} attribute (optional) Defaults to null (return the actual nodes)\r
551      * @param {TreeNode} startNode (optional) The node to start from, defaults to the root\r
552      * @return {Array}\r
553      */\r
554     getChecked : function(a, startNode){\r
555         startNode = startNode || this.root;\r
556         var r = [];\r
557         var f = function(){\r
558             if(this.attributes.checked){\r
559                 r.push(!a ? this : (a == 'id' ? this.id : this.attributes[a]));\r
560             }\r
561         };\r
562         startNode.cascade(f);\r
563         return r;\r
564     },\r
565 \r
566     /**\r
567      * Returns the container element for this TreePanel.\r
568      * @return {Element} The container element for this TreePanel.\r
569      */\r
570     getEl : function(){\r
571         return this.el;\r
572     },\r
573 \r
574     /**\r
575      * Returns the default {@link Ext.tree.TreeLoader} for this TreePanel.\r
576      * @return {Ext.tree.TreeLoader} The TreeLoader for this TreePanel.\r
577      */\r
578     getLoader : function(){\r
579         return this.loader;\r
580     },\r
581 \r
582     /**\r
583      * Expand all nodes\r
584      */\r
585     expandAll : function(){\r
586         this.root.expand(true);\r
587     },\r
588 \r
589     /**\r
590      * Collapse all nodes\r
591      */\r
592     collapseAll : function(){\r
593         this.root.collapse(true);\r
594     },\r
595 \r
596     /**\r
597      * Returns the selection model used by this TreePanel.\r
598      * @return {TreeSelectionModel} The selection model used by this TreePanel\r
599      */\r
600     getSelectionModel : function(){\r
601         if(!this.selModel){\r
602             this.selModel = new Ext.tree.DefaultSelectionModel();\r
603         }\r
604         return this.selModel;\r
605     },\r
606 \r
607     /**\r
608      * Expands a specified path in this TreePanel. A path can be retrieved from a node with {@link Ext.data.Node#getPath}\r
609      * @param {String} path\r
610      * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)\r
611      * @param {Function} callback (optional) The callback to call when the expand is complete. The callback will be called with\r
612      * (bSuccess, oLastNode) where bSuccess is if the expand was successful and oLastNode is the last node that was expanded.\r
613      */\r
614     expandPath : function(path, attr, callback){\r
615         attr = attr || "id";\r
616         var keys = path.split(this.pathSeparator);\r
617         var curNode = this.root;\r
618         if(curNode.attributes[attr] != keys[1]){ // invalid root\r
619             if(callback){\r
620                 callback(false, null);\r
621             }\r
622             return;\r
623         }\r
624         var index = 1;\r
625         var f = function(){\r
626             if(++index == keys.length){\r
627                 if(callback){\r
628                     callback(true, curNode);\r
629                 }\r
630                 return;\r
631             }\r
632             var c = curNode.findChild(attr, keys[index]);\r
633             if(!c){\r
634                 if(callback){\r
635                     callback(false, curNode);\r
636                 }\r
637                 return;\r
638             }\r
639             curNode = c;\r
640             c.expand(false, false, f);\r
641         };\r
642         curNode.expand(false, false, f);\r
643     },\r
644 \r
645     /**\r
646      * 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
647      * @param {String} path\r
648      * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)\r
649      * @param {Function} callback (optional) The callback to call when the selection is complete. The callback will be called with\r
650      * (bSuccess, oSelNode) where bSuccess is if the selection was successful and oSelNode is the selected node.\r
651      */\r
652     selectPath : function(path, attr, callback){\r
653         attr = attr || "id";\r
654         var keys = path.split(this.pathSeparator),\r
655             v = keys.pop();\r
656         if(keys.length > 1){\r
657             var f = function(success, node){\r
658                 if(success && node){\r
659                     var n = node.findChild(attr, v);\r
660                     if(n){\r
661                         n.select();\r
662                         if(callback){\r
663                             callback(true, n);\r
664                         }\r
665                     }else if(callback){\r
666                         callback(false, n);\r
667                     }\r
668                 }else{\r
669                     if(callback){\r
670                         callback(false, n);\r
671                     }\r
672                 }\r
673             };\r
674             this.expandPath(keys.join(this.pathSeparator), attr, f);\r
675         }else{\r
676             this.root.select();\r
677             if(callback){\r
678                 callback(true, this.root);\r
679             }\r
680         }\r
681     },\r
682 \r
683     /**\r
684      * Returns the underlying Element for this tree\r
685      * @return {Ext.Element} The Element\r
686      */\r
687     getTreeEl : function(){\r
688         return this.body;\r
689     },\r
690 \r
691     // private\r
692     onRender : function(ct, position){\r
693         Ext.tree.TreePanel.superclass.onRender.call(this, ct, position);\r
694         this.el.addClass('x-tree');\r
695         this.innerCt = this.body.createChild({tag:"ul",\r
696                cls:"x-tree-root-ct " +\r
697                (this.useArrows ? 'x-tree-arrows' : this.lines ? "x-tree-lines" : "x-tree-no-lines")});\r
698     },\r
699 \r
700     // private\r
701     initEvents : function(){\r
702         Ext.tree.TreePanel.superclass.initEvents.call(this);\r
703 \r
704         if(this.containerScroll){\r
705             Ext.dd.ScrollManager.register(this.body);\r
706         }\r
707         if((this.enableDD || this.enableDrop) && !this.dropZone){\r
708            /**\r
709             * The dropZone used by this tree if drop is enabled (see {@link #enableDD} or {@link #enableDrop})\r
710             * @property dropZone\r
711             * @type Ext.tree.TreeDropZone\r
712             */\r
713              this.dropZone = new Ext.tree.TreeDropZone(this, this.dropConfig || {\r
714                ddGroup: this.ddGroup || "TreeDD", appendOnly: this.ddAppendOnly === true\r
715            });\r
716         }\r
717         if((this.enableDD || this.enableDrag) && !this.dragZone){\r
718            /**\r
719             * The dragZone used by this tree if drag is enabled (see {@link #enableDD} or {@link #enableDrag})\r
720             * @property dragZone\r
721             * @type Ext.tree.TreeDragZone\r
722             */\r
723             this.dragZone = new Ext.tree.TreeDragZone(this, this.dragConfig || {\r
724                ddGroup: this.ddGroup || "TreeDD",\r
725                scroll: this.ddScroll\r
726            });\r
727         }\r
728         this.getSelectionModel().init(this);\r
729     },\r
730 \r
731     // private\r
732     afterRender : function(){\r
733         Ext.tree.TreePanel.superclass.afterRender.call(this);\r
734         this.root.render();\r
735         if(!this.rootVisible){\r
736             this.root.renderChildren();\r
737         }\r
738     },\r
739 \r
740     onDestroy : function(){\r
741         if(this.rendered){\r
742             this.body.removeAllListeners();\r
743             Ext.dd.ScrollManager.unregister(this.body);\r
744             if(this.dropZone){\r
745                 this.dropZone.unreg();\r
746             }\r
747             if(this.dragZone){\r
748                this.dragZone.unreg();\r
749             }\r
750         }\r
751         this.root.destroy();\r
752         this.nodeHash = null;\r
753         Ext.tree.TreePanel.superclass.onDestroy.call(this);\r
754     }\r
755 \r
756     /**\r
757      * @cfg {String/Number} activeItem\r
758      * @hide\r
759      */\r
760     /**\r
761      * @cfg {Boolean} autoDestroy\r
762      * @hide\r
763      */\r
764     /**\r
765      * @cfg {Object/String/Function} autoLoad\r
766      * @hide\r
767      */\r
768     /**\r
769      * @cfg {Boolean} autoWidth\r
770      * @hide\r
771      */\r
772     /**\r
773      * @cfg {Boolean/Number} bufferResize\r
774      * @hide\r
775      */\r
776     /**\r
777      * @cfg {String} defaultType\r
778      * @hide\r
779      */\r
780     /**\r
781      * @cfg {Object} defaults\r
782      * @hide\r
783      */\r
784     /**\r
785      * @cfg {Boolean} hideBorders\r
786      * @hide\r
787      */\r
788     /**\r
789      * @cfg {Mixed} items\r
790      * @hide\r
791      */\r
792     /**\r
793      * @cfg {String} layout\r
794      * @hide\r
795      */\r
796     /**\r
797      * @cfg {Object} layoutConfig\r
798      * @hide\r
799      */\r
800     /**\r
801      * @cfg {Boolean} monitorResize\r
802      * @hide\r
803      */\r
804     /**\r
805      * @property items\r
806      * @hide\r
807      */\r
808     /**\r
809      * @method cascade\r
810      * @hide\r
811      */\r
812     /**\r
813      * @method doLayout\r
814      * @hide\r
815      */\r
816     /**\r
817      * @method find\r
818      * @hide\r
819      */\r
820     /**\r
821      * @method findBy\r
822      * @hide\r
823      */\r
824     /**\r
825      * @method findById\r
826      * @hide\r
827      */\r
828     /**\r
829      * @method findByType\r
830      * @hide\r
831      */\r
832     /**\r
833      * @method getComponent\r
834      * @hide\r
835      */\r
836     /**\r
837      * @method getLayout\r
838      * @hide\r
839      */\r
840     /**\r
841      * @method getUpdater\r
842      * @hide\r
843      */\r
844     /**\r
845      * @method insert\r
846      * @hide\r
847      */\r
848     /**\r
849      * @method load\r
850      * @hide\r
851      */\r
852     /**\r
853      * @method remove\r
854      * @hide\r
855      */\r
856     /**\r
857      * @event add\r
858      * @hide\r
859      */\r
860     /**\r
861      * @method removeAll\r
862      * @hide\r
863      */\r
864     /**\r
865      * @event afterLayout\r
866      * @hide\r
867      */\r
868     /**\r
869      * @event beforeadd\r
870      * @hide\r
871      */\r
872     /**\r
873      * @event beforeremove\r
874      * @hide\r
875      */\r
876     /**\r
877      * @event remove\r
878      * @hide\r
879      */\r
880 \r
881 \r
882 \r
883     /**\r
884      * @cfg {String} allowDomMove  @hide\r
885      */\r
886     /**\r
887      * @cfg {String} autoEl @hide\r
888      */\r
889     /**\r
890      * @cfg {String} applyTo  @hide\r
891      */\r
892     /**\r
893      * @cfg {String} contentEl  @hide\r
894      */\r
895     /**\r
896      * @cfg {String} disabledClass  @hide\r
897      */\r
898     /**\r
899      * @cfg {String} elements  @hide\r
900      */\r
901     /**\r
902      * @cfg {String} html  @hide\r
903      */\r
904     /**\r
905      * @cfg {Boolean} preventBodyReset\r
906      * @hide\r
907      */\r
908     /**\r
909      * @property disabled\r
910      * @hide\r
911      */\r
912     /**\r
913      * @method applyToMarkup\r
914      * @hide\r
915      */\r
916     /**\r
917      * @method enable\r
918      * @hide\r
919      */\r
920     /**\r
921      * @method disable\r
922      * @hide\r
923      */\r
924     /**\r
925      * @method setDisabled\r
926      * @hide\r
927      */\r
928 });\r
929 \r
930 Ext.tree.TreePanel.nodeTypes = {};\r
931 \r
932 Ext.reg('treepanel', Ext.tree.TreePanel);Ext.tree.TreeEventModel = function(tree){\r
933     this.tree = tree;\r
934     this.tree.on('render', this.initEvents, this);\r
935 }\r
936 \r
937 Ext.tree.TreeEventModel.prototype = {\r
938     initEvents : function(){\r
939         var t = this.tree;\r
940             \r
941         if(t.trackMouseOver !== false){\r
942             t.mon(t.innerCt, {\r
943                 scope: this,\r
944                 mouseover: this.delegateOver,\r
945                 mouseout: this.delegateOut\r
946             });\r
947         }\r
948         t.mon(t.getTreeEl(), {\r
949             scope: this,\r
950             click: this.delegateClick,\r
951             dblclick: this.delegateDblClick,\r
952             contextmenu: this.delegateContextMenu\r
953         });\r
954     },\r
955 \r
956     getNode : function(e){\r
957         var t;\r
958         if(t = e.getTarget('.x-tree-node-el', 10)){\r
959             var id = Ext.fly(t, '_treeEvents').getAttribute('tree-node-id', 'ext');\r
960             if(id){\r
961                 return this.tree.getNodeById(id);\r
962             }\r
963         }\r
964         return null;\r
965     },\r
966 \r
967     getNodeTarget : function(e){\r
968         var t = e.getTarget('.x-tree-node-icon', 1);\r
969         if(!t){\r
970             t = e.getTarget('.x-tree-node-el', 6);\r
971         }\r
972         return t;\r
973     },\r
974 \r
975     delegateOut : function(e, t){\r
976         if(!this.beforeEvent(e)){\r
977             return;\r
978         }\r
979         if(e.getTarget('.x-tree-ec-icon', 1)){\r
980             var n = this.getNode(e);\r
981             this.onIconOut(e, n);\r
982             if(n == this.lastEcOver){\r
983                 delete this.lastEcOver;\r
984             }\r
985         }\r
986         if((t = this.getNodeTarget(e)) && !e.within(t, true)){\r
987             this.onNodeOut(e, this.getNode(e));\r
988         }\r
989     },\r
990 \r
991     delegateOver : function(e, t){\r
992         if(!this.beforeEvent(e)){\r
993             return;\r
994         }\r
995         if(Ext.isGecko && !this.trackingDoc){ // prevent hanging in FF\r
996             Ext.getBody().on('mouseover', this.trackExit, this);\r
997             this.trackingDoc = true;\r
998         }\r
999         if(this.lastEcOver){ // prevent hung highlight\r
1000             this.onIconOut(e, this.lastEcOver);\r
1001             delete this.lastEcOver;\r
1002         }\r
1003         if(e.getTarget('.x-tree-ec-icon', 1)){\r
1004             this.lastEcOver = this.getNode(e);\r
1005             this.onIconOver(e, this.lastEcOver);\r
1006         }\r
1007         if(t = this.getNodeTarget(e)){\r
1008             this.onNodeOver(e, this.getNode(e));\r
1009         }\r
1010     },\r
1011 \r
1012     trackExit : function(e){\r
1013         if(this.lastOverNode && !e.within(this.lastOverNode.ui.getEl())){\r
1014             this.onNodeOut(e, this.lastOverNode);\r
1015             delete this.lastOverNode;\r
1016             Ext.getBody().un('mouseover', this.trackExit, this);\r
1017             this.trackingDoc = false;\r
1018         }\r
1019     },\r
1020 \r
1021     delegateClick : function(e, t){\r
1022         if(!this.beforeEvent(e)){\r
1023             return;\r
1024         }\r
1025 \r
1026         if(e.getTarget('input[type=checkbox]', 1)){\r
1027             this.onCheckboxClick(e, this.getNode(e));\r
1028         }\r
1029         else if(e.getTarget('.x-tree-ec-icon', 1)){\r
1030             this.onIconClick(e, this.getNode(e));\r
1031         }\r
1032         else if(this.getNodeTarget(e)){\r
1033             this.onNodeClick(e, this.getNode(e));\r
1034         }\r
1035     },\r
1036 \r
1037     delegateDblClick : function(e, t){\r
1038         if(this.beforeEvent(e) && this.getNodeTarget(e)){\r
1039             this.onNodeDblClick(e, this.getNode(e));\r
1040         }\r
1041     },\r
1042 \r
1043     delegateContextMenu : function(e, t){\r
1044         if(this.beforeEvent(e) && this.getNodeTarget(e)){\r
1045             this.onNodeContextMenu(e, this.getNode(e));\r
1046         }\r
1047     },\r
1048 \r
1049     onNodeClick : function(e, node){\r
1050         node.ui.onClick(e);\r
1051     },\r
1052 \r
1053     onNodeOver : function(e, node){\r
1054         this.lastOverNode = node;\r
1055         node.ui.onOver(e);\r
1056     },\r
1057 \r
1058     onNodeOut : function(e, node){\r
1059         node.ui.onOut(e);\r
1060     },\r
1061 \r
1062     onIconOver : function(e, node){\r
1063         node.ui.addClass('x-tree-ec-over');\r
1064     },\r
1065 \r
1066     onIconOut : function(e, node){\r
1067         node.ui.removeClass('x-tree-ec-over');\r
1068     },\r
1069 \r
1070     onIconClick : function(e, node){\r
1071         node.ui.ecClick(e);\r
1072     },\r
1073 \r
1074     onCheckboxClick : function(e, node){\r
1075         node.ui.onCheckChange(e);\r
1076     },\r
1077 \r
1078     onNodeDblClick : function(e, node){\r
1079         node.ui.onDblClick(e);\r
1080     },\r
1081 \r
1082     onNodeContextMenu : function(e, node){\r
1083         node.ui.onContextMenu(e);\r
1084     },\r
1085 \r
1086     beforeEvent : function(e){\r
1087         if(this.disabled){\r
1088             e.stopEvent();\r
1089             return false;\r
1090         }\r
1091         return true;\r
1092     },\r
1093 \r
1094     disable: function(){\r
1095         this.disabled = true;\r
1096     },\r
1097 \r
1098     enable: function(){\r
1099         this.disabled = false;\r
1100     }\r
1101 };/**\r
1102  * @class Ext.tree.DefaultSelectionModel\r
1103  * @extends Ext.util.Observable\r
1104  * The default single selection for a TreePanel.\r
1105  */\r
1106 Ext.tree.DefaultSelectionModel = function(config){\r
1107    this.selNode = null;\r
1108    \r
1109    this.addEvents(\r
1110        /**\r
1111         * @event selectionchange\r
1112         * Fires when the selected node changes\r
1113         * @param {DefaultSelectionModel} this\r
1114         * @param {TreeNode} node the new selection\r
1115         */\r
1116        "selectionchange",\r
1117 \r
1118        /**\r
1119         * @event beforeselect\r
1120         * Fires before the selected node changes, return false to cancel the change\r
1121         * @param {DefaultSelectionModel} this\r
1122         * @param {TreeNode} node the new selection\r
1123         * @param {TreeNode} node the old selection\r
1124         */\r
1125        "beforeselect"\r
1126    );\r
1127 \r
1128     Ext.apply(this, config);\r
1129     Ext.tree.DefaultSelectionModel.superclass.constructor.call(this);\r
1130 };\r
1131 \r
1132 Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, {\r
1133     init : function(tree){\r
1134         this.tree = tree;\r
1135         tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);\r
1136         tree.on("click", this.onNodeClick, this);\r
1137     },\r
1138     \r
1139     onNodeClick : function(node, e){\r
1140         this.select(node);\r
1141     },\r
1142     \r
1143     /**\r
1144      * Select a node.\r
1145      * @param {TreeNode} node The node to select\r
1146      * @return {TreeNode} The selected node\r
1147      */\r
1148     select : function(node, /* private*/ selectNextNode){\r
1149         // If node is hidden, select the next node in whatever direction was being moved in.\r
1150         if (!Ext.fly(node.ui.wrap).isVisible() && selectNextNode) {\r
1151             return selectNextNode.call(this, node);\r
1152         }\r
1153         var last = this.selNode;\r
1154         if(node == last){\r
1155             node.ui.onSelectedChange(true);\r
1156         }else if(this.fireEvent('beforeselect', this, node, last) !== false){\r
1157             if(last){\r
1158                 last.ui.onSelectedChange(false);\r
1159             }\r
1160             this.selNode = node;\r
1161             node.ui.onSelectedChange(true);\r
1162             this.fireEvent("selectionchange", this, node, last);\r
1163         }\r
1164         return node;\r
1165     },\r
1166     \r
1167     /**\r
1168      * Deselect a node.\r
1169      * @param {TreeNode} node The node to unselect\r
1170      */\r
1171     unselect : function(node){\r
1172         if(this.selNode == node){\r
1173             this.clearSelections();\r
1174         }    \r
1175     },\r
1176     \r
1177     /**\r
1178      * Clear all selections\r
1179      */\r
1180     clearSelections : function(){\r
1181         var n = this.selNode;\r
1182         if(n){\r
1183             n.ui.onSelectedChange(false);\r
1184             this.selNode = null;\r
1185             this.fireEvent("selectionchange", this, null);\r
1186         }\r
1187         return n;\r
1188     },\r
1189     \r
1190     /**\r
1191      * Get the selected node\r
1192      * @return {TreeNode} The selected node\r
1193      */\r
1194     getSelectedNode : function(){\r
1195         return this.selNode;    \r
1196     },\r
1197     \r
1198     /**\r
1199      * Returns true if the node is selected\r
1200      * @param {TreeNode} node The node to check\r
1201      * @return {Boolean}\r
1202      */\r
1203     isSelected : function(node){\r
1204         return this.selNode == node;  \r
1205     },\r
1206 \r
1207     /**\r
1208      * Selects the node above the selected node in the tree, intelligently walking the nodes\r
1209      * @return TreeNode The new selection\r
1210      */\r
1211     selectPrevious : function(/* private */ s){\r
1212         if(!(s = s || this.selNode || this.lastSelNode)){\r
1213             return null;\r
1214         }\r
1215         // Here we pass in the current function to select to indicate the direction we're moving\r
1216         var ps = s.previousSibling;\r
1217         if(ps){\r
1218             if(!ps.isExpanded() || ps.childNodes.length < 1){\r
1219                 return this.select(ps, this.selectPrevious);\r
1220             } else{\r
1221                 var lc = ps.lastChild;\r
1222                 while(lc && lc.isExpanded() && Ext.fly(lc.ui.wrap).isVisible() && lc.childNodes.length > 0){\r
1223                     lc = lc.lastChild;\r
1224                 }\r
1225                 return this.select(lc, this.selectPrevious);\r
1226             }\r
1227         } else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){\r
1228             return this.select(s.parentNode, this.selectPrevious);\r
1229         }\r
1230         return null;\r
1231     },\r
1232 \r
1233     /**\r
1234      * Selects the node above the selected node in the tree, intelligently walking the nodes\r
1235      * @return TreeNode The new selection\r
1236      */\r
1237     selectNext : function(/* private */ s){\r
1238         if(!(s = s || this.selNode || this.lastSelNode)){\r
1239             return null;\r
1240         }\r
1241         // Here we pass in the current function to select to indicate the direction we're moving\r
1242         if(s.firstChild && s.isExpanded() && Ext.fly(s.ui.wrap).isVisible()){\r
1243              return this.select(s.firstChild, this.selectNext);\r
1244          }else if(s.nextSibling){\r
1245              return this.select(s.nextSibling, this.selectNext);\r
1246          }else if(s.parentNode){\r
1247             var newS = null;\r
1248             s.parentNode.bubble(function(){\r
1249                 if(this.nextSibling){\r
1250                     newS = this.getOwnerTree().selModel.select(this.nextSibling, this.selectNext);\r
1251                     return false;\r
1252                 }\r
1253             });\r
1254             return newS;\r
1255          }\r
1256         return null;\r
1257     },\r
1258 \r
1259     onKeyDown : function(e){\r
1260         var s = this.selNode || this.lastSelNode;\r
1261         // undesirable, but required\r
1262         var sm = this;\r
1263         if(!s){\r
1264             return;\r
1265         }\r
1266         var k = e.getKey();\r
1267         switch(k){\r
1268              case e.DOWN:\r
1269                  e.stopEvent();\r
1270                  this.selectNext();\r
1271              break;\r
1272              case e.UP:\r
1273                  e.stopEvent();\r
1274                  this.selectPrevious();\r
1275              break;\r
1276              case e.RIGHT:\r
1277                  e.preventDefault();\r
1278                  if(s.hasChildNodes()){\r
1279                      if(!s.isExpanded()){\r
1280                          s.expand();\r
1281                      }else if(s.firstChild){\r
1282                          this.select(s.firstChild, e);\r
1283                      }\r
1284                  }\r
1285              break;\r
1286              case e.LEFT:\r
1287                  e.preventDefault();\r
1288                  if(s.hasChildNodes() && s.isExpanded()){\r
1289                      s.collapse();\r
1290                  }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){\r
1291                      this.select(s.parentNode, e);\r
1292                  }\r
1293              break;\r
1294         };\r
1295     }\r
1296 });\r
1297 \r
1298 /**\r
1299  * @class Ext.tree.MultiSelectionModel\r
1300  * @extends Ext.util.Observable\r
1301  * Multi selection for a TreePanel.\r
1302  */\r
1303 Ext.tree.MultiSelectionModel = function(config){\r
1304    this.selNodes = [];\r
1305    this.selMap = {};\r
1306    this.addEvents(\r
1307        /**\r
1308         * @event selectionchange\r
1309         * Fires when the selected nodes change\r
1310         * @param {MultiSelectionModel} this\r
1311         * @param {Array} nodes Array of the selected nodes\r
1312         */\r
1313        "selectionchange"\r
1314    );\r
1315     Ext.apply(this, config);\r
1316     Ext.tree.MultiSelectionModel.superclass.constructor.call(this);\r
1317 };\r
1318 \r
1319 Ext.extend(Ext.tree.MultiSelectionModel, Ext.util.Observable, {\r
1320     init : function(tree){\r
1321         this.tree = tree;\r
1322         tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);\r
1323         tree.on("click", this.onNodeClick, this);\r
1324     },\r
1325     \r
1326     onNodeClick : function(node, e){\r
1327         if(e.ctrlKey && this.isSelected(node)){\r
1328             this.unselect(node);\r
1329         }else{\r
1330             this.select(node, e, e.ctrlKey);\r
1331         }\r
1332     },\r
1333     \r
1334     /**\r
1335      * Select a node.\r
1336      * @param {TreeNode} node The node to select\r
1337      * @param {EventObject} e (optional) An event associated with the selection\r
1338      * @param {Boolean} keepExisting True to retain existing selections\r
1339      * @return {TreeNode} The selected node\r
1340      */\r
1341     select : function(node, e, keepExisting){\r
1342         if(keepExisting !== true){\r
1343             this.clearSelections(true);\r
1344         }\r
1345         if(this.isSelected(node)){\r
1346             this.lastSelNode = node;\r
1347             return node;\r
1348         }\r
1349         this.selNodes.push(node);\r
1350         this.selMap[node.id] = node;\r
1351         this.lastSelNode = node;\r
1352         node.ui.onSelectedChange(true);\r
1353         this.fireEvent("selectionchange", this, this.selNodes);\r
1354         return node;\r
1355     },\r
1356     \r
1357     /**\r
1358      * Deselect a node.\r
1359      * @param {TreeNode} node The node to unselect\r
1360      */\r
1361     unselect : function(node){\r
1362         if(this.selMap[node.id]){\r
1363             node.ui.onSelectedChange(false);\r
1364             var sn = this.selNodes;\r
1365             var index = sn.indexOf(node);\r
1366             if(index != -1){\r
1367                 this.selNodes.splice(index, 1);\r
1368             }\r
1369             delete this.selMap[node.id];\r
1370             this.fireEvent("selectionchange", this, this.selNodes);\r
1371         }\r
1372     },\r
1373     \r
1374     /**\r
1375      * Clear all selections\r
1376      */\r
1377     clearSelections : function(suppressEvent){\r
1378         var sn = this.selNodes;\r
1379         if(sn.length > 0){\r
1380             for(var i = 0, len = sn.length; i < len; i++){\r
1381                 sn[i].ui.onSelectedChange(false);\r
1382             }\r
1383             this.selNodes = [];\r
1384             this.selMap = {};\r
1385             if(suppressEvent !== true){\r
1386                 this.fireEvent("selectionchange", this, this.selNodes);\r
1387             }\r
1388         }\r
1389     },\r
1390     \r
1391     /**\r
1392      * Returns true if the node is selected\r
1393      * @param {TreeNode} node The node to check\r
1394      * @return {Boolean}\r
1395      */\r
1396     isSelected : function(node){\r
1397         return this.selMap[node.id] ? true : false;  \r
1398     },\r
1399     \r
1400     /**\r
1401      * Returns an array of the selected nodes\r
1402      * @return {Array}\r
1403      */\r
1404     getSelectedNodes : function(){\r
1405         return this.selNodes;    \r
1406     },\r
1407 \r
1408     onKeyDown : Ext.tree.DefaultSelectionModel.prototype.onKeyDown,\r
1409 \r
1410     selectNext : Ext.tree.DefaultSelectionModel.prototype.selectNext,\r
1411 \r
1412     selectPrevious : Ext.tree.DefaultSelectionModel.prototype.selectPrevious\r
1413 });/**\r
1414  * @class Ext.data.Tree\r
1415  * @extends Ext.util.Observable\r
1416  * Represents a tree data structure and bubbles all the events for its nodes. The nodes\r
1417  * in the tree have most standard DOM functionality.\r
1418  * @constructor\r
1419  * @param {Node} root (optional) The root node\r
1420  */\r
1421 Ext.data.Tree = function(root){\r
1422    this.nodeHash = {};\r
1423    /**\r
1424     * The root node for this tree\r
1425     * @type Node\r
1426     */\r
1427    this.root = null;\r
1428    if(root){\r
1429        this.setRootNode(root);\r
1430    }\r
1431    this.addEvents(\r
1432        /**\r
1433         * @event append\r
1434         * Fires when a new child node is appended to a node in this tree.\r
1435         * @param {Tree} tree The owner tree\r
1436         * @param {Node} parent The parent node\r
1437         * @param {Node} node The newly appended node\r
1438         * @param {Number} index The index of the newly appended node\r
1439         */\r
1440        "append",\r
1441        /**\r
1442         * @event remove\r
1443         * Fires when a child node is removed from a node in this tree.\r
1444         * @param {Tree} tree The owner tree\r
1445         * @param {Node} parent The parent node\r
1446         * @param {Node} node The child node removed\r
1447         */\r
1448        "remove",\r
1449        /**\r
1450         * @event move\r
1451         * Fires when a node is moved to a new location in the tree\r
1452         * @param {Tree} tree The owner tree\r
1453         * @param {Node} node The node moved\r
1454         * @param {Node} oldParent The old parent of this node\r
1455         * @param {Node} newParent The new parent of this node\r
1456         * @param {Number} index The index it was moved to\r
1457         */\r
1458        "move",\r
1459        /**\r
1460         * @event insert\r
1461         * Fires when a new child node is inserted in a node in this tree.\r
1462         * @param {Tree} tree The owner tree\r
1463         * @param {Node} parent The parent node\r
1464         * @param {Node} node The child node inserted\r
1465         * @param {Node} refNode The child node the node was inserted before\r
1466         */\r
1467        "insert",\r
1468        /**\r
1469         * @event beforeappend\r
1470         * Fires before a new child is appended to a node in this tree, return false to cancel the append.\r
1471         * @param {Tree} tree The owner tree\r
1472         * @param {Node} parent The parent node\r
1473         * @param {Node} node The child node to be appended\r
1474         */\r
1475        "beforeappend",\r
1476        /**\r
1477         * @event beforeremove\r
1478         * Fires before a child is removed from a node in this tree, return false to cancel the remove.\r
1479         * @param {Tree} tree The owner tree\r
1480         * @param {Node} parent The parent node\r
1481         * @param {Node} node The child node to be removed\r
1482         */\r
1483        "beforeremove",\r
1484        /**\r
1485         * @event beforemove\r
1486         * Fires before a node is moved to a new location in the tree. Return false to cancel the move.\r
1487         * @param {Tree} tree The owner tree\r
1488         * @param {Node} node The node being moved\r
1489         * @param {Node} oldParent The parent of the node\r
1490         * @param {Node} newParent The new parent the node is moving to\r
1491         * @param {Number} index The index it is being moved to\r
1492         */\r
1493        "beforemove",\r
1494        /**\r
1495         * @event beforeinsert\r
1496         * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.\r
1497         * @param {Tree} tree The owner tree\r
1498         * @param {Node} parent The parent node\r
1499         * @param {Node} node The child node to be inserted\r
1500         * @param {Node} refNode The child node the node is being inserted before\r
1501         */\r
1502        "beforeinsert"\r
1503    );\r
1504 \r
1505     Ext.data.Tree.superclass.constructor.call(this);\r
1506 };\r
1507 \r
1508 Ext.extend(Ext.data.Tree, Ext.util.Observable, {\r
1509     /**\r
1510      * @cfg {String} pathSeparator\r
1511      * The token used to separate paths in node ids (defaults to '/').\r
1512      */\r
1513     pathSeparator: "/",\r
1514 \r
1515     // private\r
1516     proxyNodeEvent : function(){\r
1517         return this.fireEvent.apply(this, arguments);\r
1518     },\r
1519 \r
1520     /**\r
1521      * Returns the root node for this tree.\r
1522      * @return {Node}\r
1523      */\r
1524     getRootNode : function(){\r
1525         return this.root;\r
1526     },\r
1527 \r
1528     /**\r
1529      * Sets the root node for this tree.\r
1530      * @param {Node} node\r
1531      * @return {Node}\r
1532      */\r
1533     setRootNode : function(node){\r
1534         this.root = node;\r
1535         node.ownerTree = this;\r
1536         node.isRoot = true;\r
1537         this.registerNode(node);\r
1538         return node;\r
1539     },\r
1540 \r
1541     /**\r
1542      * Gets a node in this tree by its id.\r
1543      * @param {String} id\r
1544      * @return {Node}\r
1545      */\r
1546     getNodeById : function(id){\r
1547         return this.nodeHash[id];\r
1548     },\r
1549 \r
1550     // private\r
1551     registerNode : function(node){\r
1552         this.nodeHash[node.id] = node;\r
1553     },\r
1554 \r
1555     // private\r
1556     unregisterNode : function(node){\r
1557         delete this.nodeHash[node.id];\r
1558     },\r
1559 \r
1560     toString : function(){\r
1561         return "[Tree"+(this.id?" "+this.id:"")+"]";\r
1562     }\r
1563 });\r
1564 \r
1565 /**\r
1566  * @class Ext.data.Node\r
1567  * @extends Ext.util.Observable\r
1568  * @cfg {Boolean} leaf true if this node is a leaf and does not have children\r
1569  * @cfg {String} id The id for this node. If one is not specified, one is generated.\r
1570  * @constructor\r
1571  * @param {Object} attributes The attributes/config for the node\r
1572  */\r
1573 Ext.data.Node = function(attributes){\r
1574     /**\r
1575      * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.\r
1576      * @type {Object}\r
1577      */\r
1578     this.attributes = attributes || {};\r
1579     this.leaf = this.attributes.leaf;\r
1580     /**\r
1581      * The node id. @type String\r
1582      */\r
1583     this.id = this.attributes.id;\r
1584     if(!this.id){\r
1585         this.id = Ext.id(null, "xnode-");\r
1586         this.attributes.id = this.id;\r
1587     }\r
1588     /**\r
1589      * All child nodes of this node. @type Array\r
1590      */\r
1591     this.childNodes = [];\r
1592     if(!this.childNodes.indexOf){ // indexOf is a must\r
1593         this.childNodes.indexOf = function(o){\r
1594             for(var i = 0, len = this.length; i < len; i++){\r
1595                 if(this[i] == o){\r
1596                     return i;\r
1597                 }\r
1598             }\r
1599             return -1;\r
1600         };\r
1601     }\r
1602     /**\r
1603      * The parent node for this node. @type Node\r
1604      */\r
1605     this.parentNode = null;\r
1606     /**\r
1607      * The first direct child node of this node, or null if this node has no child nodes. @type Node\r
1608      */\r
1609     this.firstChild = null;\r
1610     /**\r
1611      * The last direct child node of this node, or null if this node has no child nodes. @type Node\r
1612      */\r
1613     this.lastChild = null;\r
1614     /**\r
1615      * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node\r
1616      */\r
1617     this.previousSibling = null;\r
1618     /**\r
1619      * The node immediately following this node in the tree, or null if there is no sibling node. @type Node\r
1620      */\r
1621     this.nextSibling = null;\r
1622 \r
1623     this.addEvents({\r
1624        /**\r
1625         * @event append\r
1626         * Fires when a new child node is appended\r
1627         * @param {Tree} tree The owner tree\r
1628         * @param {Node} this This node\r
1629         * @param {Node} node The newly appended node\r
1630         * @param {Number} index The index of the newly appended node\r
1631         */\r
1632        "append" : true,\r
1633        /**\r
1634         * @event remove\r
1635         * Fires when a child node is removed\r
1636         * @param {Tree} tree The owner tree\r
1637         * @param {Node} this This node\r
1638         * @param {Node} node The removed node\r
1639         */\r
1640        "remove" : true,\r
1641        /**\r
1642         * @event move\r
1643         * Fires when this node is moved to a new location in the tree\r
1644         * @param {Tree} tree The owner tree\r
1645         * @param {Node} this This node\r
1646         * @param {Node} oldParent The old parent of this node\r
1647         * @param {Node} newParent The new parent of this node\r
1648         * @param {Number} index The index it was moved to\r
1649         */\r
1650        "move" : true,\r
1651        /**\r
1652         * @event insert\r
1653         * Fires when a new child node is inserted.\r
1654         * @param {Tree} tree The owner tree\r
1655         * @param {Node} this This node\r
1656         * @param {Node} node The child node inserted\r
1657         * @param {Node} refNode The child node the node was inserted before\r
1658         */\r
1659        "insert" : true,\r
1660        /**\r
1661         * @event beforeappend\r
1662         * Fires before a new child is appended, return false to cancel the append.\r
1663         * @param {Tree} tree The owner tree\r
1664         * @param {Node} this This node\r
1665         * @param {Node} node The child node to be appended\r
1666         */\r
1667        "beforeappend" : true,\r
1668        /**\r
1669         * @event beforeremove\r
1670         * Fires before a child is removed, return false to cancel the remove.\r
1671         * @param {Tree} tree The owner tree\r
1672         * @param {Node} this This node\r
1673         * @param {Node} node The child node to be removed\r
1674         */\r
1675        "beforeremove" : true,\r
1676        /**\r
1677         * @event beforemove\r
1678         * Fires before this node is moved to a new location in the tree. Return false to cancel the move.\r
1679         * @param {Tree} tree The owner tree\r
1680         * @param {Node} this This node\r
1681         * @param {Node} oldParent The parent of this node\r
1682         * @param {Node} newParent The new parent this node is moving to\r
1683         * @param {Number} index The index it is being moved to\r
1684         */\r
1685        "beforemove" : true,\r
1686        /**\r
1687         * @event beforeinsert\r
1688         * Fires before a new child is inserted, return false to cancel the insert.\r
1689         * @param {Tree} tree The owner tree\r
1690         * @param {Node} this This node\r
1691         * @param {Node} node The child node to be inserted\r
1692         * @param {Node} refNode The child node the node is being inserted before\r
1693         */\r
1694        "beforeinsert" : true\r
1695    });\r
1696     this.listeners = this.attributes.listeners;\r
1697     Ext.data.Node.superclass.constructor.call(this);\r
1698 };\r
1699 \r
1700 Ext.extend(Ext.data.Node, Ext.util.Observable, {\r
1701     // private\r
1702     fireEvent : function(evtName){\r
1703         // first do standard event for this node\r
1704         if(Ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){\r
1705             return false;\r
1706         }\r
1707         // then bubble it up to the tree if the event wasn't cancelled\r
1708         var ot = this.getOwnerTree();\r
1709         if(ot){\r
1710             if(ot.proxyNodeEvent.apply(ot, arguments) === false){\r
1711                 return false;\r
1712             }\r
1713         }\r
1714         return true;\r
1715     },\r
1716 \r
1717     /**\r
1718      * Returns true if this node is a leaf\r
1719      * @return {Boolean}\r
1720      */\r
1721     isLeaf : function(){\r
1722         return this.leaf === true;\r
1723     },\r
1724 \r
1725     // private\r
1726     setFirstChild : function(node){\r
1727         this.firstChild = node;\r
1728     },\r
1729 \r
1730     //private\r
1731     setLastChild : function(node){\r
1732         this.lastChild = node;\r
1733     },\r
1734 \r
1735 \r
1736     /**\r
1737      * Returns true if this node is the last child of its parent\r
1738      * @return {Boolean}\r
1739      */\r
1740     isLast : function(){\r
1741        return (!this.parentNode ? true : this.parentNode.lastChild == this);\r
1742     },\r
1743 \r
1744     /**\r
1745      * Returns true if this node is the first child of its parent\r
1746      * @return {Boolean}\r
1747      */\r
1748     isFirst : function(){\r
1749        return (!this.parentNode ? true : this.parentNode.firstChild == this);\r
1750     },\r
1751 \r
1752     /**\r
1753      * Returns true if this node has one or more child nodes, else false.\r
1754      * @return {Boolean}\r
1755      */\r
1756     hasChildNodes : function(){\r
1757         return !this.isLeaf() && this.childNodes.length > 0;\r
1758     },\r
1759     \r
1760     /**\r
1761      * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>\r
1762      * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.\r
1763      * @return {Boolean}\r
1764      */\r
1765     isExpandable : function(){\r
1766         return this.attributes.expandable || this.hasChildNodes();\r
1767     },\r
1768 \r
1769     /**\r
1770      * Insert node(s) as the last child node of this node.\r
1771      * @param {Node/Array} node The node or Array of nodes to append\r
1772      * @return {Node} The appended node if single append, or null if an array was passed\r
1773      */\r
1774     appendChild : function(node){\r
1775         var multi = false;\r
1776         if(Ext.isArray(node)){\r
1777             multi = node;\r
1778         }else if(arguments.length > 1){\r
1779             multi = arguments;\r
1780         }\r
1781         // if passed an array or multiple args do them one by one\r
1782         if(multi){\r
1783             for(var i = 0, len = multi.length; i < len; i++) {\r
1784                 this.appendChild(multi[i]);\r
1785             }\r
1786         }else{\r
1787             if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){\r
1788                 return false;\r
1789             }\r
1790             var index = this.childNodes.length;\r
1791             var oldParent = node.parentNode;\r
1792             // it's a move, make sure we move it cleanly\r
1793             if(oldParent){\r
1794                 if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){\r
1795                     return false;\r
1796                 }\r
1797                 oldParent.removeChild(node);\r
1798             }\r
1799             index = this.childNodes.length;\r
1800             if(index === 0){\r
1801                 this.setFirstChild(node);\r
1802             }\r
1803             this.childNodes.push(node);\r
1804             node.parentNode = this;\r
1805             var ps = this.childNodes[index-1];\r
1806             if(ps){\r
1807                 node.previousSibling = ps;\r
1808                 ps.nextSibling = node;\r
1809             }else{\r
1810                 node.previousSibling = null;\r
1811             }\r
1812             node.nextSibling = null;\r
1813             this.setLastChild(node);\r
1814             node.setOwnerTree(this.getOwnerTree());\r
1815             this.fireEvent("append", this.ownerTree, this, node, index);\r
1816             if(oldParent){\r
1817                 node.fireEvent("move", this.ownerTree, node, oldParent, this, index);\r
1818             }\r
1819             return node;\r
1820         }\r
1821     },\r
1822 \r
1823     /**\r
1824      * Removes a child node from this node.\r
1825      * @param {Node} node The node to remove\r
1826      * @return {Node} The removed node\r
1827      */\r
1828     removeChild : function(node){\r
1829         var index = this.childNodes.indexOf(node);\r
1830         if(index == -1){\r
1831             return false;\r
1832         }\r
1833         if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){\r
1834             return false;\r
1835         }\r
1836 \r
1837         // remove it from childNodes collection\r
1838         this.childNodes.splice(index, 1);\r
1839 \r
1840         // update siblings\r
1841         if(node.previousSibling){\r
1842             node.previousSibling.nextSibling = node.nextSibling;\r
1843         }\r
1844         if(node.nextSibling){\r
1845             node.nextSibling.previousSibling = node.previousSibling;\r
1846         }\r
1847 \r
1848         // update child refs\r
1849         if(this.firstChild == node){\r
1850             this.setFirstChild(node.nextSibling);\r
1851         }\r
1852         if(this.lastChild == node){\r
1853             this.setLastChild(node.previousSibling);\r
1854         }\r
1855 \r
1856         node.setOwnerTree(null);\r
1857         // clear any references from the node\r
1858         node.parentNode = null;\r
1859         node.previousSibling = null;\r
1860         node.nextSibling = null;\r
1861         this.fireEvent("remove", this.ownerTree, this, node);\r
1862         return node;\r
1863     },\r
1864 \r
1865     /**\r
1866      * Inserts the first node before the second node in this nodes childNodes collection.\r
1867      * @param {Node} node The node to insert\r
1868      * @param {Node} refNode The node to insert before (if null the node is appended)\r
1869      * @return {Node} The inserted node\r
1870      */\r
1871     insertBefore : function(node, refNode){\r
1872         if(!refNode){ // like standard Dom, refNode can be null for append\r
1873             return this.appendChild(node);\r
1874         }\r
1875         // nothing to do\r
1876         if(node == refNode){\r
1877             return false;\r
1878         }\r
1879 \r
1880         if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){\r
1881             return false;\r
1882         }\r
1883         var index = this.childNodes.indexOf(refNode);\r
1884         var oldParent = node.parentNode;\r
1885         var refIndex = index;\r
1886 \r
1887         // when moving internally, indexes will change after remove\r
1888         if(oldParent == this && this.childNodes.indexOf(node) < index){\r
1889             refIndex--;\r
1890         }\r
1891 \r
1892         // it's a move, make sure we move it cleanly\r
1893         if(oldParent){\r
1894             if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){\r
1895                 return false;\r
1896             }\r
1897             oldParent.removeChild(node);\r
1898         }\r
1899         if(refIndex === 0){\r
1900             this.setFirstChild(node);\r
1901         }\r
1902         this.childNodes.splice(refIndex, 0, node);\r
1903         node.parentNode = this;\r
1904         var ps = this.childNodes[refIndex-1];\r
1905         if(ps){\r
1906             node.previousSibling = ps;\r
1907             ps.nextSibling = node;\r
1908         }else{\r
1909             node.previousSibling = null;\r
1910         }\r
1911         node.nextSibling = refNode;\r
1912         refNode.previousSibling = node;\r
1913         node.setOwnerTree(this.getOwnerTree());\r
1914         this.fireEvent("insert", this.ownerTree, this, node, refNode);\r
1915         if(oldParent){\r
1916             node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);\r
1917         }\r
1918         return node;\r
1919     },\r
1920 \r
1921     /**\r
1922      * Removes this node from its parent\r
1923      * @return {Node} this\r
1924      */\r
1925     remove : function(){\r
1926         this.parentNode.removeChild(this);\r
1927         return this;\r
1928     },\r
1929 \r
1930     /**\r
1931      * Returns the child node at the specified index.\r
1932      * @param {Number} index\r
1933      * @return {Node}\r
1934      */\r
1935     item : function(index){\r
1936         return this.childNodes[index];\r
1937     },\r
1938 \r
1939     /**\r
1940      * Replaces one child node in this node with another.\r
1941      * @param {Node} newChild The replacement node\r
1942      * @param {Node} oldChild The node to replace\r
1943      * @return {Node} The replaced node\r
1944      */\r
1945     replaceChild : function(newChild, oldChild){\r
1946         var s = oldChild ? oldChild.nextSibling : null;\r
1947         this.removeChild(oldChild);\r
1948         this.insertBefore(newChild, s);\r
1949         return oldChild;\r
1950     },\r
1951 \r
1952     /**\r
1953      * Returns the index of a child node\r
1954      * @param {Node} node\r
1955      * @return {Number} The index of the node or -1 if it was not found\r
1956      */\r
1957     indexOf : function(child){\r
1958         return this.childNodes.indexOf(child);\r
1959     },\r
1960 \r
1961     /**\r
1962      * Returns the tree this node is in.\r
1963      * @return {Tree}\r
1964      */\r
1965     getOwnerTree : function(){\r
1966         // if it doesn't have one, look for one\r
1967         if(!this.ownerTree){\r
1968             var p = this;\r
1969             while(p){\r
1970                 if(p.ownerTree){\r
1971                     this.ownerTree = p.ownerTree;\r
1972                     break;\r
1973                 }\r
1974                 p = p.parentNode;\r
1975             }\r
1976         }\r
1977         return this.ownerTree;\r
1978     },\r
1979 \r
1980     /**\r
1981      * Returns depth of this node (the root node has a depth of 0)\r
1982      * @return {Number}\r
1983      */\r
1984     getDepth : function(){\r
1985         var depth = 0;\r
1986         var p = this;\r
1987         while(p.parentNode){\r
1988             ++depth;\r
1989             p = p.parentNode;\r
1990         }\r
1991         return depth;\r
1992     },\r
1993 \r
1994     // private\r
1995     setOwnerTree : function(tree){\r
1996         // if it is a move, we need to update everyone\r
1997         if(tree != this.ownerTree){\r
1998             if(this.ownerTree){\r
1999                 this.ownerTree.unregisterNode(this);\r
2000             }\r
2001             this.ownerTree = tree;\r
2002             var cs = this.childNodes;\r
2003             for(var i = 0, len = cs.length; i < len; i++) {\r
2004                 cs[i].setOwnerTree(tree);\r
2005             }\r
2006             if(tree){\r
2007                 tree.registerNode(this);\r
2008             }\r
2009         }\r
2010     },\r
2011     \r
2012     /**\r
2013      * Changes the id of this node.\r
2014      * @param {String} id The new id for the node.\r
2015      */\r
2016     setId: function(id){\r
2017         if(id !== this.id){\r
2018             var t = this.ownerTree;\r
2019             if(t){\r
2020                 t.unregisterNode(this);\r
2021             }\r
2022             this.id = this.attributes.id = id;\r
2023             if(t){\r
2024                 t.registerNode(this);\r
2025             }\r
2026             this.onIdChange(id);\r
2027         }\r
2028     },\r
2029     \r
2030     // private\r
2031     onIdChange: Ext.emptyFn,\r
2032 \r
2033     /**\r
2034      * Returns the path for this node. The path can be used to expand or select this node programmatically.\r
2035      * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)\r
2036      * @return {String} The path\r
2037      */\r
2038     getPath : function(attr){\r
2039         attr = attr || "id";\r
2040         var p = this.parentNode;\r
2041         var b = [this.attributes[attr]];\r
2042         while(p){\r
2043             b.unshift(p.attributes[attr]);\r
2044             p = p.parentNode;\r
2045         }\r
2046         var sep = this.getOwnerTree().pathSeparator;\r
2047         return sep + b.join(sep);\r
2048     },\r
2049 \r
2050     /**\r
2051      * Bubbles up the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of\r
2052      * function call will be the scope provided or the current node. The arguments to the function\r
2053      * will be the args provided or the current node. If the function returns false at any point,\r
2054      * the bubble is stopped.\r
2055      * @param {Function} fn The function to call\r
2056      * @param {Object} scope (optional) The scope of the function (defaults to current node)\r
2057      * @param {Array} args (optional) The args to call the function with (default to passing the current node)\r
2058      */\r
2059     bubble : function(fn, scope, args){\r
2060         var p = this;\r
2061         while(p){\r
2062             if(fn.apply(scope || p, args || [p]) === false){\r
2063                 break;\r
2064             }\r
2065             p = p.parentNode;\r
2066         }\r
2067     },\r
2068 \r
2069     /**\r
2070      * Cascades down the tree from this node, calling the specified function with each node. The scope (<i>this</i>) of\r
2071      * function call will be the scope provided or the current node. The arguments to the function\r
2072      * will be the args provided or the current node. If the function returns false at any point,\r
2073      * the cascade is stopped on that branch.\r
2074      * @param {Function} fn The function to call\r
2075      * @param {Object} scope (optional) The scope of the function (defaults to current node)\r
2076      * @param {Array} args (optional) The args to call the function with (default to passing the current node)\r
2077      */\r
2078     cascade : function(fn, scope, args){\r
2079         if(fn.apply(scope || this, args || [this]) !== false){\r
2080             var cs = this.childNodes;\r
2081             for(var i = 0, len = cs.length; i < len; i++) {\r
2082                 cs[i].cascade(fn, scope, args);\r
2083             }\r
2084         }\r
2085     },\r
2086 \r
2087     /**\r
2088      * Interates the child nodes of this node, calling the specified function with each node. The scope (<i>this</i>) of\r
2089      * function call will be the scope provided or the current node. The arguments to the function\r
2090      * will be the args provided or the current node. If the function returns false at any point,\r
2091      * the iteration stops.\r
2092      * @param {Function} fn The function to call\r
2093      * @param {Object} scope (optional) The scope of the function (defaults to current node)\r
2094      * @param {Array} args (optional) The args to call the function with (default to passing the current node)\r
2095      */\r
2096     eachChild : function(fn, scope, args){\r
2097         var cs = this.childNodes;\r
2098         for(var i = 0, len = cs.length; i < len; i++) {\r
2099                 if(fn.apply(scope || this, args || [cs[i]]) === false){\r
2100                     break;\r
2101                 }\r
2102         }\r
2103     },\r
2104 \r
2105     /**\r
2106      * Finds the first child that has the attribute with the specified value.\r
2107      * @param {String} attribute The attribute name\r
2108      * @param {Mixed} value The value to search for\r
2109      * @return {Node} The found child or null if none was found\r
2110      */\r
2111     findChild : function(attribute, value){\r
2112         var cs = this.childNodes;\r
2113         for(var i = 0, len = cs.length; i < len; i++) {\r
2114                 if(cs[i].attributes[attribute] == value){\r
2115                     return cs[i];\r
2116                 }\r
2117         }\r
2118         return null;\r
2119     },\r
2120 \r
2121     /**\r
2122      * Finds the first child by a custom function. The child matches if the function passed\r
2123      * returns true.\r
2124      * @param {Function} fn\r
2125      * @param {Object} scope (optional)\r
2126      * @return {Node} The found child or null if none was found\r
2127      */\r
2128     findChildBy : function(fn, scope){\r
2129         var cs = this.childNodes;\r
2130         for(var i = 0, len = cs.length; i < len; i++) {\r
2131                 if(fn.call(scope||cs[i], cs[i]) === true){\r
2132                     return cs[i];\r
2133                 }\r
2134         }\r
2135         return null;\r
2136     },\r
2137 \r
2138     /**\r
2139      * Sorts this nodes children using the supplied sort function\r
2140      * @param {Function} fn\r
2141      * @param {Object} scope (optional)\r
2142      */\r
2143     sort : function(fn, scope){\r
2144         var cs = this.childNodes;\r
2145         var len = cs.length;\r
2146         if(len > 0){\r
2147             var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;\r
2148             cs.sort(sortFn);\r
2149             for(var i = 0; i < len; i++){\r
2150                 var n = cs[i];\r
2151                 n.previousSibling = cs[i-1];\r
2152                 n.nextSibling = cs[i+1];\r
2153                 if(i === 0){\r
2154                     this.setFirstChild(n);\r
2155                 }\r
2156                 if(i == len-1){\r
2157                     this.setLastChild(n);\r
2158                 }\r
2159             }\r
2160         }\r
2161     },\r
2162 \r
2163     /**\r
2164      * Returns true if this node is an ancestor (at any point) of the passed node.\r
2165      * @param {Node} node\r
2166      * @return {Boolean}\r
2167      */\r
2168     contains : function(node){\r
2169         return node.isAncestor(this);\r
2170     },\r
2171 \r
2172     /**\r
2173      * Returns true if the passed node is an ancestor (at any point) of this node.\r
2174      * @param {Node} node\r
2175      * @return {Boolean}\r
2176      */\r
2177     isAncestor : function(node){\r
2178         var p = this.parentNode;\r
2179         while(p){\r
2180             if(p == node){\r
2181                 return true;\r
2182             }\r
2183             p = p.parentNode;\r
2184         }\r
2185         return false;\r
2186     },\r
2187 \r
2188     toString : function(){\r
2189         return "[Node"+(this.id?" "+this.id:"")+"]";\r
2190     }\r
2191 });/**\r
2192  * @class Ext.tree.TreeNode\r
2193  * @extends Ext.data.Node\r
2194  * @cfg {String} text The text for this node\r
2195  * @cfg {Boolean} expanded true to start the node expanded\r
2196  * @cfg {Boolean} allowDrag False to make this node undraggable if {@link #draggable} = true (defaults to true)\r
2197  * @cfg {Boolean} allowDrop False if this node cannot have child nodes dropped on it (defaults to true)\r
2198  * @cfg {Boolean} disabled true to start the node disabled\r
2199  * @cfg {String} icon The path to an icon for the node. The preferred way to do this\r
2200  * is to use the cls or iconCls attributes and add the icon via a CSS background image.\r
2201  * @cfg {String} cls A css class to be added to the node\r
2202  * @cfg {String} iconCls A css class to be added to the nodes icon element for applying css background images\r
2203  * @cfg {String} href URL of the link used for the node (defaults to #)\r
2204  * @cfg {String} hrefTarget target frame for the link\r
2205  * @cfg {Boolean} hidden True to render hidden. (Defaults to false).\r
2206  * @cfg {String} qtip An Ext QuickTip for the node\r
2207  * @cfg {Boolean} expandable If set to true, the node will always show a plus/minus icon, even when empty\r
2208  * @cfg {String} qtipCfg An Ext QuickTip config for the node (used instead of qtip)\r
2209  * @cfg {Boolean} singleClickExpand True for single click expand on this node\r
2210  * @cfg {Function} uiProvider A UI <b>class</b> to use for this node (defaults to Ext.tree.TreeNodeUI)\r
2211  * @cfg {Boolean} checked True to render a checked checkbox for this node, false to render an unchecked checkbox\r
2212  * (defaults to undefined with no checkbox rendered)\r
2213  * @cfg {Boolean} draggable True to make this node draggable (defaults to false)\r
2214  * @cfg {Boolean} isTarget False to not allow this node to act as a drop target (defaults to true)\r
2215  * @cfg {Boolean} allowChildren False to not allow this node to have child nodes (defaults to true)\r
2216  * @cfg {Boolean} editable False to not allow this node to be edited by an (@link Ext.tree.TreeEditor} (defaults to true)\r
2217  * @constructor\r
2218  * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node\r
2219  */\r
2220 Ext.tree.TreeNode = function(attributes){\r
2221     attributes = attributes || {};\r
2222     if(typeof attributes == 'string'){\r
2223         attributes = {text: attributes};\r
2224     }\r
2225     this.childrenRendered = false;\r
2226     this.rendered = false;\r
2227     Ext.tree.TreeNode.superclass.constructor.call(this, attributes);\r
2228     this.expanded = attributes.expanded === true;\r
2229     this.isTarget = attributes.isTarget !== false;\r
2230     this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;\r
2231     this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;\r
2232 \r
2233     /**\r
2234      * Read-only. The text for this node. To change it use <code>{@link #setText}</code>.\r
2235      * @type String\r
2236      */\r
2237     this.text = attributes.text;\r
2238     /**\r
2239      * True if this node is disabled.\r
2240      * @type Boolean\r
2241      */\r
2242     this.disabled = attributes.disabled === true;\r
2243     /**\r
2244      * True if this node is hidden.\r
2245      * @type Boolean\r
2246      */\r
2247     this.hidden = attributes.hidden === true;\r
2248 \r
2249     this.addEvents(\r
2250         /**\r
2251         * @event textchange\r
2252         * Fires when the text for this node is changed\r
2253         * @param {Node} this This node\r
2254         * @param {String} text The new text\r
2255         * @param {String} oldText The old text\r
2256         */\r
2257         'textchange',\r
2258         /**\r
2259         * @event beforeexpand\r
2260         * Fires before this node is expanded, return false to cancel.\r
2261         * @param {Node} this This node\r
2262         * @param {Boolean} deep\r
2263         * @param {Boolean} anim\r
2264         */\r
2265         'beforeexpand',\r
2266         /**\r
2267         * @event beforecollapse\r
2268         * Fires before this node is collapsed, return false to cancel.\r
2269         * @param {Node} this This node\r
2270         * @param {Boolean} deep\r
2271         * @param {Boolean} anim\r
2272         */\r
2273         'beforecollapse',\r
2274         /**\r
2275         * @event expand\r
2276         * Fires when this node is expanded\r
2277         * @param {Node} this This node\r
2278         */\r
2279         'expand',\r
2280         /**\r
2281         * @event disabledchange\r
2282         * Fires when the disabled status of this node changes\r
2283         * @param {Node} this This node\r
2284         * @param {Boolean} disabled\r
2285         */\r
2286         'disabledchange',\r
2287         /**\r
2288         * @event collapse\r
2289         * Fires when this node is collapsed\r
2290         * @param {Node} this This node\r
2291         */\r
2292         'collapse',\r
2293         /**\r
2294         * @event beforeclick\r
2295         * Fires before click processing. Return false to cancel the default action.\r
2296         * @param {Node} this This node\r
2297         * @param {Ext.EventObject} e The event object\r
2298         */\r
2299         'beforeclick',\r
2300         /**\r
2301         * @event click\r
2302         * Fires when this node is clicked\r
2303         * @param {Node} this This node\r
2304         * @param {Ext.EventObject} e The event object\r
2305         */\r
2306         'click',\r
2307         /**\r
2308         * @event checkchange\r
2309         * Fires when a node with a checkbox's checked property changes\r
2310         * @param {Node} this This node\r
2311         * @param {Boolean} checked\r
2312         */\r
2313         'checkchange',\r
2314         /**\r
2315         * @event beforedblclick\r
2316         * Fires before double click processing. Return false to cancel the default action.\r
2317         * @param {Node} this This node\r
2318         * @param {Ext.EventObject} e The event object\r
2319         */\r
2320         'beforedblclick',\r
2321         /**\r
2322         * @event dblclick\r
2323         * Fires when this node is double clicked\r
2324         * @param {Node} this This node\r
2325         * @param {Ext.EventObject} e The event object\r
2326         */\r
2327         'dblclick',\r
2328         /**\r
2329         * @event contextmenu\r
2330         * Fires when this node is right clicked\r
2331         * @param {Node} this This node\r
2332         * @param {Ext.EventObject} e The event object\r
2333         */\r
2334         'contextmenu',\r
2335         /**\r
2336         * @event beforechildrenrendered\r
2337         * Fires right before the child nodes for this node are rendered\r
2338         * @param {Node} this This node\r
2339         */\r
2340         'beforechildrenrendered'\r
2341     );\r
2342 \r
2343     var uiClass = this.attributes.uiProvider || this.defaultUI || Ext.tree.TreeNodeUI;\r
2344 \r
2345     /**\r
2346      * Read-only. The UI for this node\r
2347      * @type TreeNodeUI\r
2348      */\r
2349     this.ui = new uiClass(this);\r
2350 };\r
2351 Ext.extend(Ext.tree.TreeNode, Ext.data.Node, {\r
2352     preventHScroll : true,\r
2353     /**\r
2354      * Returns true if this node is expanded\r
2355      * @return {Boolean}\r
2356      */\r
2357     isExpanded : function(){\r
2358         return this.expanded;\r
2359     },\r
2360 \r
2361 /**\r
2362  * Returns the UI object for this node.\r
2363  * @return {TreeNodeUI} The object which is providing the user interface for this tree\r
2364  * node. Unless otherwise specified in the {@link #uiProvider}, this will be an instance\r
2365  * of {@link Ext.tree.TreeNodeUI}\r
2366  */\r
2367     getUI : function(){\r
2368         return this.ui;\r
2369     },\r
2370 \r
2371     getLoader : function(){\r
2372         var owner;\r
2373         return this.loader || ((owner = this.getOwnerTree()) && owner.loader ? owner.loader : new Ext.tree.TreeLoader());\r
2374     },\r
2375 \r
2376     // private override\r
2377     setFirstChild : function(node){\r
2378         var of = this.firstChild;\r
2379         Ext.tree.TreeNode.superclass.setFirstChild.call(this, node);\r
2380         if(this.childrenRendered && of && node != of){\r
2381             of.renderIndent(true, true);\r
2382         }\r
2383         if(this.rendered){\r
2384             this.renderIndent(true, true);\r
2385         }\r
2386     },\r
2387 \r
2388     // private override\r
2389     setLastChild : function(node){\r
2390         var ol = this.lastChild;\r
2391         Ext.tree.TreeNode.superclass.setLastChild.call(this, node);\r
2392         if(this.childrenRendered && ol && node != ol){\r
2393             ol.renderIndent(true, true);\r
2394         }\r
2395         if(this.rendered){\r
2396             this.renderIndent(true, true);\r
2397         }\r
2398     },\r
2399 \r
2400     // these methods are overridden to provide lazy rendering support\r
2401     // private override\r
2402     appendChild : function(n){\r
2403         if(!n.render && !Ext.isArray(n)){\r
2404             n = this.getLoader().createNode(n);\r
2405         }\r
2406         var node = Ext.tree.TreeNode.superclass.appendChild.call(this, n);\r
2407         if(node && this.childrenRendered){\r
2408             node.render();\r
2409         }\r
2410         this.ui.updateExpandIcon();\r
2411         return node;\r
2412     },\r
2413 \r
2414     // private override\r
2415     removeChild : function(node){\r
2416         this.ownerTree.getSelectionModel().unselect(node);\r
2417         Ext.tree.TreeNode.superclass.removeChild.apply(this, arguments);\r
2418         // if it's been rendered remove dom node\r
2419         if(this.childrenRendered){\r
2420             node.ui.remove();\r
2421         }\r
2422         if(this.childNodes.length < 1){\r
2423             this.collapse(false, false);\r
2424         }else{\r
2425             this.ui.updateExpandIcon();\r
2426         }\r
2427         if(!this.firstChild && !this.isHiddenRoot()) {\r
2428             this.childrenRendered = false;\r
2429         }\r
2430         return node;\r
2431     },\r
2432 \r
2433     // private override\r
2434     insertBefore : function(node, refNode){\r
2435         if(!node.render){\r
2436             node = this.getLoader().createNode(node);\r
2437         }\r
2438         var newNode = Ext.tree.TreeNode.superclass.insertBefore.call(this, node, refNode);\r
2439         if(newNode && refNode && this.childrenRendered){\r
2440             node.render();\r
2441         }\r
2442         this.ui.updateExpandIcon();\r
2443         return newNode;\r
2444     },\r
2445 \r
2446     /**\r
2447      * Sets the text for this node\r
2448      * @param {String} text\r
2449      */\r
2450     setText : function(text){\r
2451         var oldText = this.text;\r
2452         this.text = text;\r
2453         this.attributes.text = text;\r
2454         if(this.rendered){ // event without subscribing\r
2455             this.ui.onTextChange(this, text, oldText);\r
2456         }\r
2457         this.fireEvent('textchange', this, text, oldText);\r
2458     },\r
2459 \r
2460     /**\r
2461      * Triggers selection of this node\r
2462      */\r
2463     select : function(){\r
2464         this.getOwnerTree().getSelectionModel().select(this);\r
2465     },\r
2466 \r
2467     /**\r
2468      * Triggers deselection of this node\r
2469      */\r
2470     unselect : function(){\r
2471         this.getOwnerTree().getSelectionModel().unselect(this);\r
2472     },\r
2473 \r
2474     /**\r
2475      * Returns true if this node is selected\r
2476      * @return {Boolean}\r
2477      */\r
2478     isSelected : function(){\r
2479         return this.getOwnerTree().getSelectionModel().isSelected(this);\r
2480     },\r
2481 \r
2482     /**\r
2483      * Expand this node.\r
2484      * @param {Boolean} deep (optional) True to expand all children as well\r
2485      * @param {Boolean} anim (optional) false to cancel the default animation\r
2486      * @param {Function} callback (optional) A callback to be called when\r
2487      * expanding this node completes (does not wait for deep expand to complete).\r
2488      * Called with 1 parameter, this node.\r
2489      * @param {Object} scope (optional) The scope in which to execute the callback.\r
2490      */\r
2491     expand : function(deep, anim, callback, scope){\r
2492         if(!this.expanded){\r
2493             if(this.fireEvent('beforeexpand', this, deep, anim) === false){\r
2494                 return;\r
2495             }\r
2496             if(!this.childrenRendered){\r
2497                 this.renderChildren();\r
2498             }\r
2499             this.expanded = true;\r
2500             if(!this.isHiddenRoot() && (this.getOwnerTree().animate && anim !== false) || anim){\r
2501                 this.ui.animExpand(function(){\r
2502                     this.fireEvent('expand', this);\r
2503                     this.runCallback(callback, scope || this, [this]);\r
2504                     if(deep === true){\r
2505                         this.expandChildNodes(true);\r
2506                     }\r
2507                 }.createDelegate(this));\r
2508                 return;\r
2509             }else{\r
2510                 this.ui.expand();\r
2511                 this.fireEvent('expand', this);\r
2512                 this.runCallback(callback, scope || this, [this]);\r
2513             }\r
2514         }else{\r
2515            this.runCallback(callback, scope || this, [this]);\r
2516         }\r
2517         if(deep === true){\r
2518             this.expandChildNodes(true);\r
2519         }\r
2520     },\r
2521 \r
2522     runCallback : function(cb, scope, args){\r
2523         if(Ext.isFunction(cb)){\r
2524             cb.apply(scope, args);\r
2525         }\r
2526     },\r
2527 \r
2528     isHiddenRoot : function(){\r
2529         return this.isRoot && !this.getOwnerTree().rootVisible;\r
2530     },\r
2531 \r
2532     /**\r
2533      * Collapse this node.\r
2534      * @param {Boolean} deep (optional) True to collapse all children as well\r
2535      * @param {Boolean} anim (optional) false to cancel the default animation\r
2536      * @param {Function} callback (optional) A callback to be called when\r
2537      * expanding this node completes (does not wait for deep expand to complete).\r
2538      * Called with 1 parameter, this node.\r
2539      * @param {Object} scope (optional) The scope in which to execute the callback.\r
2540      */\r
2541     collapse : function(deep, anim, callback, scope){\r
2542         if(this.expanded && !this.isHiddenRoot()){\r
2543             if(this.fireEvent('beforecollapse', this, deep, anim) === false){\r
2544                 return;\r
2545             }\r
2546             this.expanded = false;\r
2547             if((this.getOwnerTree().animate && anim !== false) || anim){\r
2548                 this.ui.animCollapse(function(){\r
2549                     this.fireEvent('collapse', this);\r
2550                     this.runCallback(callback, scope || this, [this]);\r
2551                     if(deep === true){\r
2552                         this.collapseChildNodes(true);\r
2553                     }\r
2554                 }.createDelegate(this));\r
2555                 return;\r
2556             }else{\r
2557                 this.ui.collapse();\r
2558                 this.fireEvent('collapse', this);\r
2559                 this.runCallback(callback, scope || this, [this]);\r
2560             }\r
2561         }else if(!this.expanded){\r
2562             this.runCallback(callback, scope || this, [this]);\r
2563         }\r
2564         if(deep === true){\r
2565             var cs = this.childNodes;\r
2566             for(var i = 0, len = cs.length; i < len; i++) {\r
2567                 cs[i].collapse(true, false);\r
2568             }\r
2569         }\r
2570     },\r
2571 \r
2572     // private\r
2573     delayedExpand : function(delay){\r
2574         if(!this.expandProcId){\r
2575             this.expandProcId = this.expand.defer(delay, this);\r
2576         }\r
2577     },\r
2578 \r
2579     // private\r
2580     cancelExpand : function(){\r
2581         if(this.expandProcId){\r
2582             clearTimeout(this.expandProcId);\r
2583         }\r
2584         this.expandProcId = false;\r
2585     },\r
2586 \r
2587     /**\r
2588      * Toggles expanded/collapsed state of the node\r
2589      */\r
2590     toggle : function(){\r
2591         if(this.expanded){\r
2592             this.collapse();\r
2593         }else{\r
2594             this.expand();\r
2595         }\r
2596     },\r
2597 \r
2598     /**\r
2599      * Ensures all parent nodes are expanded, and if necessary, scrolls\r
2600      * the node into view.\r
2601      * @param {Function} callback (optional) A function to call when the node has been made visible.\r
2602      * @param {Object} scope (optional) The scope in which to execute the callback.\r
2603      */\r
2604     ensureVisible : function(callback, scope){\r
2605         var tree = this.getOwnerTree();\r
2606         tree.expandPath(this.parentNode ? this.parentNode.getPath() : this.getPath(), false, function(){\r
2607             var node = tree.getNodeById(this.id);  // Somehow if we don't do this, we lose changes that happened to node in the meantime\r
2608             tree.getTreeEl().scrollChildIntoView(node.ui.anchor);\r
2609             this.runCallback(callback, scope || this, [this]);\r
2610         }.createDelegate(this));\r
2611     },\r
2612 \r
2613     /**\r
2614      * Expand all child nodes\r
2615      * @param {Boolean} deep (optional) true if the child nodes should also expand their child nodes\r
2616      */\r
2617     expandChildNodes : function(deep){\r
2618         var cs = this.childNodes;\r
2619         for(var i = 0, len = cs.length; i < len; i++) {\r
2620                 cs[i].expand(deep);\r
2621         }\r
2622     },\r
2623 \r
2624     /**\r
2625      * Collapse all child nodes\r
2626      * @param {Boolean} deep (optional) true if the child nodes should also collapse their child nodes\r
2627      */\r
2628     collapseChildNodes : function(deep){\r
2629         var cs = this.childNodes;\r
2630         for(var i = 0, len = cs.length; i < len; i++) {\r
2631                 cs[i].collapse(deep);\r
2632         }\r
2633     },\r
2634 \r
2635     /**\r
2636      * Disables this node\r
2637      */\r
2638     disable : function(){\r
2639         this.disabled = true;\r
2640         this.unselect();\r
2641         if(this.rendered && this.ui.onDisableChange){ // event without subscribing\r
2642             this.ui.onDisableChange(this, true);\r
2643         }\r
2644         this.fireEvent('disabledchange', this, true);\r
2645     },\r
2646 \r
2647     /**\r
2648      * Enables this node\r
2649      */\r
2650     enable : function(){\r
2651         this.disabled = false;\r
2652         if(this.rendered && this.ui.onDisableChange){ // event without subscribing\r
2653             this.ui.onDisableChange(this, false);\r
2654         }\r
2655         this.fireEvent('disabledchange', this, false);\r
2656     },\r
2657 \r
2658     // private\r
2659     renderChildren : function(suppressEvent){\r
2660         if(suppressEvent !== false){\r
2661             this.fireEvent('beforechildrenrendered', this);\r
2662         }\r
2663         var cs = this.childNodes;\r
2664         for(var i = 0, len = cs.length; i < len; i++){\r
2665             cs[i].render(true);\r
2666         }\r
2667         this.childrenRendered = true;\r
2668     },\r
2669 \r
2670     // private\r
2671     sort : function(fn, scope){\r
2672         Ext.tree.TreeNode.superclass.sort.apply(this, arguments);\r
2673         if(this.childrenRendered){\r
2674             var cs = this.childNodes;\r
2675             for(var i = 0, len = cs.length; i < len; i++){\r
2676                 cs[i].render(true);\r
2677             }\r
2678         }\r
2679     },\r
2680 \r
2681     // private\r
2682     render : function(bulkRender){\r
2683         this.ui.render(bulkRender);\r
2684         if(!this.rendered){\r
2685             // make sure it is registered\r
2686             this.getOwnerTree().registerNode(this);\r
2687             this.rendered = true;\r
2688             if(this.expanded){\r
2689                 this.expanded = false;\r
2690                 this.expand(false, false);\r
2691             }\r
2692         }\r
2693     },\r
2694 \r
2695     // private\r
2696     renderIndent : function(deep, refresh){\r
2697         if(refresh){\r
2698             this.ui.childIndent = null;\r
2699         }\r
2700         this.ui.renderIndent();\r
2701         if(deep === true && this.childrenRendered){\r
2702             var cs = this.childNodes;\r
2703             for(var i = 0, len = cs.length; i < len; i++){\r
2704                 cs[i].renderIndent(true, refresh);\r
2705             }\r
2706         }\r
2707     },\r
2708 \r
2709     beginUpdate : function(){\r
2710         this.childrenRendered = false;\r
2711     },\r
2712 \r
2713     endUpdate : function(){\r
2714         if(this.expanded && this.rendered){\r
2715             this.renderChildren();\r
2716         }\r
2717     },\r
2718 \r
2719     destroy : function(){\r
2720         if(this.childNodes){\r
2721             for(var i = 0,l = this.childNodes.length; i < l; i++){\r
2722                 this.childNodes[i].destroy();\r
2723             }\r
2724             this.childNodes = null;\r
2725         }\r
2726         if(this.ui.destroy){\r
2727             this.ui.destroy();\r
2728         }\r
2729     },\r
2730 \r
2731     // private\r
2732     onIdChange : function(id){\r
2733         this.ui.onIdChange(id);\r
2734     }\r
2735 });\r
2736 \r
2737 Ext.tree.TreePanel.nodeTypes.node = Ext.tree.TreeNode;/**\r
2738  * @class Ext.tree.AsyncTreeNode\r
2739  * @extends Ext.tree.TreeNode\r
2740  * @cfg {TreeLoader} loader A TreeLoader to be used by this node (defaults to the loader defined on the tree)\r
2741  * @constructor\r
2742  * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node \r
2743  */\r
2744  Ext.tree.AsyncTreeNode = function(config){\r
2745     this.loaded = config && config.loaded === true;\r
2746     this.loading = false;\r
2747     Ext.tree.AsyncTreeNode.superclass.constructor.apply(this, arguments);\r
2748     /**\r
2749     * @event beforeload\r
2750     * Fires before this node is loaded, return false to cancel\r
2751     * @param {Node} this This node\r
2752     */\r
2753     this.addEvents('beforeload', 'load');\r
2754     /**\r
2755     * @event load\r
2756     * Fires when this node is loaded\r
2757     * @param {Node} this This node\r
2758     */\r
2759     /**\r
2760      * The loader used by this node (defaults to using the tree's defined loader)\r
2761      * @type TreeLoader\r
2762      * @property loader\r
2763      */\r
2764 };\r
2765 Ext.extend(Ext.tree.AsyncTreeNode, Ext.tree.TreeNode, {\r
2766     expand : function(deep, anim, callback, scope){\r
2767         if(this.loading){ // if an async load is already running, waiting til it's done\r
2768             var timer;\r
2769             var f = function(){\r
2770                 if(!this.loading){ // done loading\r
2771                     clearInterval(timer);\r
2772                     this.expand(deep, anim, callback, scope);\r
2773                 }\r
2774             }.createDelegate(this);\r
2775             timer = setInterval(f, 200);\r
2776             return;\r
2777         }\r
2778         if(!this.loaded){\r
2779             if(this.fireEvent("beforeload", this) === false){\r
2780                 return;\r
2781             }\r
2782             this.loading = true;\r
2783             this.ui.beforeLoad(this);\r
2784             var loader = this.loader || this.attributes.loader || this.getOwnerTree().getLoader();\r
2785             if(loader){\r
2786                 loader.load(this, this.loadComplete.createDelegate(this, [deep, anim, callback, scope]), this);\r
2787                 return;\r
2788             }\r
2789         }\r
2790         Ext.tree.AsyncTreeNode.superclass.expand.call(this, deep, anim, callback, scope);\r
2791     },\r
2792     \r
2793     /**\r
2794      * Returns true if this node is currently loading\r
2795      * @return {Boolean}\r
2796      */\r
2797     isLoading : function(){\r
2798         return this.loading;  \r
2799     },\r
2800     \r
2801     loadComplete : function(deep, anim, callback, scope){\r
2802         this.loading = false;\r
2803         this.loaded = true;\r
2804         this.ui.afterLoad(this);\r
2805         this.fireEvent("load", this);\r
2806         this.expand(deep, anim, callback, scope);\r
2807     },\r
2808     \r
2809     /**\r
2810      * Returns true if this node has been loaded\r
2811      * @return {Boolean}\r
2812      */\r
2813     isLoaded : function(){\r
2814         return this.loaded;\r
2815     },\r
2816     \r
2817     hasChildNodes : function(){\r
2818         if(!this.isLeaf() && !this.loaded){\r
2819             return true;\r
2820         }else{\r
2821             return Ext.tree.AsyncTreeNode.superclass.hasChildNodes.call(this);\r
2822         }\r
2823     },\r
2824 \r
2825     /**\r
2826      * Trigger a reload for this node\r
2827      * @param {Function} callback\r
2828      * @param {Object} scope (optional) The scope in which to execute the callback.\r
2829      */\r
2830     reload : function(callback, scope){\r
2831         this.collapse(false, false);\r
2832         while(this.firstChild){\r
2833             this.removeChild(this.firstChild).destroy();\r
2834         }\r
2835         this.childrenRendered = false;\r
2836         this.loaded = false;\r
2837         if(this.isHiddenRoot()){\r
2838             this.expanded = false;\r
2839         }\r
2840         this.expand(false, false, callback, scope);\r
2841     }\r
2842 });\r
2843 \r
2844 Ext.tree.TreePanel.nodeTypes.async = Ext.tree.AsyncTreeNode;/**\r
2845  * @class Ext.tree.TreeNodeUI\r
2846  * This class provides the default UI implementation for Ext TreeNodes.\r
2847  * The TreeNode UI implementation is separate from the\r
2848  * tree implementation, and allows customizing of the appearance of\r
2849  * tree nodes.<br>\r
2850  * <p>\r
2851  * If you are customizing the Tree's user interface, you\r
2852  * may need to extend this class, but you should never need to instantiate this class.<br>\r
2853  * <p>\r
2854  * This class provides access to the user interface components of an Ext TreeNode, through\r
2855  * {@link Ext.tree.TreeNode#getUI}\r
2856  */\r
2857 Ext.tree.TreeNodeUI = function(node){\r
2858     this.node = node;\r
2859     this.rendered = false;\r
2860     this.animating = false;\r
2861     this.wasLeaf = true;\r
2862     this.ecc = 'x-tree-ec-icon x-tree-elbow';\r
2863     this.emptyIcon = Ext.BLANK_IMAGE_URL;\r
2864 };\r
2865 \r
2866 Ext.tree.TreeNodeUI.prototype = {\r
2867     // private\r
2868     removeChild : function(node){\r
2869         if(this.rendered){\r
2870             this.ctNode.removeChild(node.ui.getEl());\r
2871         } \r
2872     },\r
2873 \r
2874     // private\r
2875     beforeLoad : function(){\r
2876          this.addClass("x-tree-node-loading");\r
2877     },\r
2878 \r
2879     // private\r
2880     afterLoad : function(){\r
2881          this.removeClass("x-tree-node-loading");\r
2882     },\r
2883 \r
2884     // private\r
2885     onTextChange : function(node, text, oldText){\r
2886         if(this.rendered){\r
2887             this.textNode.innerHTML = text;\r
2888         }\r
2889     },\r
2890 \r
2891     // private\r
2892     onDisableChange : function(node, state){\r
2893         this.disabled = state;\r
2894                 if (this.checkbox) {\r
2895                         this.checkbox.disabled = state;\r
2896                 }        \r
2897         if(state){\r
2898             this.addClass("x-tree-node-disabled");\r
2899         }else{\r
2900             this.removeClass("x-tree-node-disabled");\r
2901         } \r
2902     },\r
2903 \r
2904     // private\r
2905     onSelectedChange : function(state){\r
2906         if(state){\r
2907             this.focus();\r
2908             this.addClass("x-tree-selected");\r
2909         }else{\r
2910             //this.blur();\r
2911             this.removeClass("x-tree-selected");\r
2912         }\r
2913     },\r
2914 \r
2915     // private\r
2916     onMove : function(tree, node, oldParent, newParent, index, refNode){\r
2917         this.childIndent = null;\r
2918         if(this.rendered){\r
2919             var targetNode = newParent.ui.getContainer();\r
2920             if(!targetNode){//target not rendered\r
2921                 this.holder = document.createElement("div");\r
2922                 this.holder.appendChild(this.wrap);\r
2923                 return;\r
2924             }\r
2925             var insertBefore = refNode ? refNode.ui.getEl() : null;\r
2926             if(insertBefore){\r
2927                 targetNode.insertBefore(this.wrap, insertBefore);\r
2928             }else{\r
2929                 targetNode.appendChild(this.wrap);\r
2930             }\r
2931             this.node.renderIndent(true, oldParent != newParent);\r
2932         }\r
2933     },\r
2934 \r
2935 /**\r
2936  * Adds one or more CSS classes to the node's UI element.\r
2937  * Duplicate classes are automatically filtered out.\r
2938  * @param {String/Array} className The CSS class to add, or an array of classes\r
2939  */\r
2940     addClass : function(cls){\r
2941         if(this.elNode){\r
2942             Ext.fly(this.elNode).addClass(cls);\r
2943         }\r
2944     },\r
2945 \r
2946 /**\r
2947  * Removes one or more CSS classes from the node's UI element.\r
2948  * @param {String/Array} className The CSS class to remove, or an array of classes\r
2949  */\r
2950     removeClass : function(cls){\r
2951         if(this.elNode){\r
2952             Ext.fly(this.elNode).removeClass(cls);  \r
2953         }\r
2954     },\r
2955 \r
2956     // private\r
2957     remove : function(){\r
2958         if(this.rendered){\r
2959             this.holder = document.createElement("div");\r
2960             this.holder.appendChild(this.wrap);\r
2961         }  \r
2962     },\r
2963 \r
2964     // private\r
2965     fireEvent : function(){\r
2966         return this.node.fireEvent.apply(this.node, arguments);  \r
2967     },\r
2968 \r
2969     // private\r
2970     initEvents : function(){\r
2971         this.node.on("move", this.onMove, this);\r
2972 \r
2973         if(this.node.disabled){\r
2974             this.onDisableChange(this.node, true);            \r
2975         }\r
2976         if(this.node.hidden){\r
2977             this.hide();\r
2978         }\r
2979         var ot = this.node.getOwnerTree();\r
2980         var dd = ot.enableDD || ot.enableDrag || ot.enableDrop;\r
2981         if(dd && (!this.node.isRoot || ot.rootVisible)){\r
2982             Ext.dd.Registry.register(this.elNode, {\r
2983                 node: this.node,\r
2984                 handles: this.getDDHandles(),\r
2985                 isHandle: false\r
2986             });\r
2987         }\r
2988     },\r
2989 \r
2990     // private\r
2991     getDDHandles : function(){\r
2992         return [this.iconNode, this.textNode, this.elNode];\r
2993     },\r
2994 \r
2995 /**\r
2996  * Hides this node.\r
2997  */\r
2998     hide : function(){\r
2999         this.node.hidden = true;\r
3000         if(this.wrap){\r
3001             this.wrap.style.display = "none";\r
3002         }\r
3003     },\r
3004 \r
3005 /**\r
3006  * Shows this node.\r
3007  */\r
3008     show : function(){\r
3009         this.node.hidden = false;\r
3010         if(this.wrap){\r
3011             this.wrap.style.display = "";\r
3012         } \r
3013     },\r
3014 \r
3015     // private\r
3016     onContextMenu : function(e){\r
3017         if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) {\r
3018             e.preventDefault();\r
3019             this.focus();\r
3020             this.fireEvent("contextmenu", this.node, e);\r
3021         }\r
3022     },\r
3023 \r
3024     // private\r
3025     onClick : function(e){\r
3026         if(this.dropping){\r
3027             e.stopEvent();\r
3028             return;\r
3029         }\r
3030         if(this.fireEvent("beforeclick", this.node, e) !== false){\r
3031             var a = e.getTarget('a');\r
3032             if(!this.disabled && this.node.attributes.href && a){\r
3033                 this.fireEvent("click", this.node, e);\r
3034                 return;\r
3035             }else if(a && e.ctrlKey){\r
3036                 e.stopEvent();\r
3037             }\r
3038             e.preventDefault();\r
3039             if(this.disabled){\r
3040                 return;\r
3041             }\r
3042 \r
3043             if(this.node.attributes.singleClickExpand && !this.animating && this.node.isExpandable()){\r
3044                 this.node.toggle();\r
3045             }\r
3046 \r
3047             this.fireEvent("click", this.node, e);\r
3048         }else{\r
3049             e.stopEvent();\r
3050         }\r
3051     },\r
3052 \r
3053     // private\r
3054     onDblClick : function(e){\r
3055         e.preventDefault();\r
3056         if(this.disabled){\r
3057             return;\r
3058         }\r
3059         if(this.fireEvent("beforedblclick", this.node, e) !== false){\r
3060             if(this.checkbox){\r
3061                 this.toggleCheck();\r
3062             }\r
3063             if(!this.animating && this.node.isExpandable()){\r
3064                 this.node.toggle();\r
3065             }\r
3066             this.fireEvent("dblclick", this.node, e);\r
3067         }\r
3068     },\r
3069 \r
3070     onOver : function(e){\r
3071         this.addClass('x-tree-node-over');\r
3072     },\r
3073 \r
3074     onOut : function(e){\r
3075         this.removeClass('x-tree-node-over');\r
3076     },\r
3077 \r
3078     // private\r
3079     onCheckChange : function(){\r
3080         var checked = this.checkbox.checked;\r
3081                 // fix for IE6\r
3082                 this.checkbox.defaultChecked = checked;         \r
3083         this.node.attributes.checked = checked;\r
3084         this.fireEvent('checkchange', this.node, checked);\r
3085     },\r
3086 \r
3087     // private\r
3088     ecClick : function(e){\r
3089         if(!this.animating && this.node.isExpandable()){\r
3090             this.node.toggle();\r
3091         }\r
3092     },\r
3093 \r
3094     // private\r
3095     startDrop : function(){\r
3096         this.dropping = true;\r
3097     },\r
3098     \r
3099     // delayed drop so the click event doesn't get fired on a drop\r
3100     endDrop : function(){ \r
3101        setTimeout(function(){\r
3102            this.dropping = false;\r
3103        }.createDelegate(this), 50); \r
3104     },\r
3105 \r
3106     // private\r
3107     expand : function(){\r
3108         this.updateExpandIcon();\r
3109         this.ctNode.style.display = "";\r
3110     },\r
3111 \r
3112     // private\r
3113     focus : function(){\r
3114         if(!this.node.preventHScroll){\r
3115             try{this.anchor.focus();\r
3116             }catch(e){}\r
3117         }else{\r
3118             try{\r
3119                 var noscroll = this.node.getOwnerTree().getTreeEl().dom;\r
3120                 var l = noscroll.scrollLeft;\r
3121                 this.anchor.focus();\r
3122                 noscroll.scrollLeft = l;\r
3123             }catch(e){}\r
3124         }\r
3125     },\r
3126 \r
3127 /**\r
3128  * Sets the checked status of the tree node to the passed value, or, if no value was passed,\r
3129  * toggles the checked status. If the node was rendered with no checkbox, this has no effect.\r
3130  * @param {Boolean} (optional) The new checked status.\r
3131  */\r
3132     toggleCheck : function(value){\r
3133         var cb = this.checkbox;\r
3134         if(cb){\r
3135             cb.checked = (value === undefined ? !cb.checked : value);\r
3136             this.onCheckChange();\r
3137         }\r
3138     },\r
3139 \r
3140     // private\r
3141     blur : function(){\r
3142         try{\r
3143             this.anchor.blur();\r
3144         }catch(e){} \r
3145     },\r
3146 \r
3147     // private\r
3148     animExpand : function(callback){\r
3149         var ct = Ext.get(this.ctNode);\r
3150         ct.stopFx();\r
3151         if(!this.node.isExpandable()){\r
3152             this.updateExpandIcon();\r
3153             this.ctNode.style.display = "";\r
3154             Ext.callback(callback);\r
3155             return;\r
3156         }\r
3157         this.animating = true;\r
3158         this.updateExpandIcon();\r
3159         \r
3160         ct.slideIn('t', {\r
3161            callback : function(){\r
3162                this.animating = false;\r
3163                Ext.callback(callback);\r
3164             },\r
3165             scope: this,\r
3166             duration: this.node.ownerTree.duration || .25\r
3167         });\r
3168     },\r
3169 \r
3170     // private\r
3171     highlight : function(){\r
3172         var tree = this.node.getOwnerTree();\r
3173         Ext.fly(this.wrap).highlight(\r
3174             tree.hlColor || "C3DAF9",\r
3175             {endColor: tree.hlBaseColor}\r
3176         );\r
3177     },\r
3178 \r
3179     // private\r
3180     collapse : function(){\r
3181         this.updateExpandIcon();\r
3182         this.ctNode.style.display = "none";\r
3183     },\r
3184 \r
3185     // private\r
3186     animCollapse : function(callback){\r
3187         var ct = Ext.get(this.ctNode);\r
3188         ct.enableDisplayMode('block');\r
3189         ct.stopFx();\r
3190 \r
3191         this.animating = true;\r
3192         this.updateExpandIcon();\r
3193 \r
3194         ct.slideOut('t', {\r
3195             callback : function(){\r
3196                this.animating = false;\r
3197                Ext.callback(callback);\r
3198             },\r
3199             scope: this,\r
3200             duration: this.node.ownerTree.duration || .25\r
3201         });\r
3202     },\r
3203 \r
3204     // private\r
3205     getContainer : function(){\r
3206         return this.ctNode;  \r
3207     },\r
3208 \r
3209     // private\r
3210     getEl : function(){\r
3211         return this.wrap;  \r
3212     },\r
3213 \r
3214     // private\r
3215     appendDDGhost : function(ghostNode){\r
3216         ghostNode.appendChild(this.elNode.cloneNode(true));\r
3217     },\r
3218 \r
3219     // private\r
3220     getDDRepairXY : function(){\r
3221         return Ext.lib.Dom.getXY(this.iconNode);\r
3222     },\r
3223 \r
3224     // private\r
3225     onRender : function(){\r
3226         this.render();    \r
3227     },\r
3228 \r
3229     // private\r
3230     render : function(bulkRender){\r
3231         var n = this.node, a = n.attributes;\r
3232         var targetNode = n.parentNode ? \r
3233               n.parentNode.ui.getContainer() : n.ownerTree.innerCt.dom;\r
3234         \r
3235         if(!this.rendered){\r
3236             this.rendered = true;\r
3237 \r
3238             this.renderElements(n, a, targetNode, bulkRender);\r
3239 \r
3240             if(a.qtip){\r
3241                if(this.textNode.setAttributeNS){\r
3242                    this.textNode.setAttributeNS("ext", "qtip", a.qtip);\r
3243                    if(a.qtipTitle){\r
3244                        this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle);\r
3245                    }\r
3246                }else{\r
3247                    this.textNode.setAttribute("ext:qtip", a.qtip);\r
3248                    if(a.qtipTitle){\r
3249                        this.textNode.setAttribute("ext:qtitle", a.qtipTitle);\r
3250                    }\r
3251                } \r
3252             }else if(a.qtipCfg){\r
3253                 a.qtipCfg.target = Ext.id(this.textNode);\r
3254                 Ext.QuickTips.register(a.qtipCfg);\r
3255             }\r
3256             this.initEvents();\r
3257             if(!this.node.expanded){\r
3258                 this.updateExpandIcon(true);\r
3259             }\r
3260         }else{\r
3261             if(bulkRender === true) {\r
3262                 targetNode.appendChild(this.wrap);\r
3263             }\r
3264         }\r
3265     },\r
3266 \r
3267     // private\r
3268     renderElements : function(n, a, targetNode, bulkRender){\r
3269         // add some indent caching, this helps performance when rendering a large tree\r
3270         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';\r
3271 \r
3272         var cb = typeof a.checked == 'boolean';\r
3273 \r
3274         var href = a.href ? a.href : Ext.isGecko ? "" : "#";\r
3275         var buf = ['<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">',\r
3276             '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",\r
3277             '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',\r
3278             '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',\r
3279             cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',\r
3280             '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',\r
3281              a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',n.text,"</span></a></div>",\r
3282             '<ul class="x-tree-node-ct" style="display:none;"></ul>',\r
3283             "</li>"].join('');\r
3284 \r
3285         var nel;\r
3286         if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){\r
3287             this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);\r
3288         }else{\r
3289             this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);\r
3290         }\r
3291         \r
3292         this.elNode = this.wrap.childNodes[0];\r
3293         this.ctNode = this.wrap.childNodes[1];\r
3294         var cs = this.elNode.childNodes;\r
3295         this.indentNode = cs[0];\r
3296         this.ecNode = cs[1];\r
3297         this.iconNode = cs[2];\r
3298         var index = 3;\r
3299         if(cb){\r
3300             this.checkbox = cs[3];\r
3301                         // fix for IE6\r
3302                         this.checkbox.defaultChecked = this.checkbox.checked;                                           \r
3303             index++;\r
3304         }\r
3305         this.anchor = cs[index];\r
3306         this.textNode = cs[index].firstChild;\r
3307     },\r
3308 \r
3309 /**\r
3310  * Returns the &lt;a> element that provides focus for the node's UI.\r
3311  * @return {HtmlElement} The DOM anchor element.\r
3312  */\r
3313     getAnchor : function(){\r
3314         return this.anchor;\r
3315     },\r
3316     \r
3317 /**\r
3318  * Returns the text node.\r
3319  * @return {HtmlNode} The DOM text node.\r
3320  */\r
3321     getTextEl : function(){\r
3322         return this.textNode;\r
3323     },\r
3324     \r
3325 /**\r
3326  * Returns the icon &lt;img> element.\r
3327  * @return {HtmlElement} The DOM image element.\r
3328  */\r
3329     getIconEl : function(){\r
3330         return this.iconNode;\r
3331     },\r
3332 \r
3333 /**\r
3334  * Returns the checked status of the node. If the node was rendered with no\r
3335  * checkbox, it returns false.\r
3336  * @return {Boolean} The checked flag.\r
3337  */\r
3338     isChecked : function(){\r
3339         return this.checkbox ? this.checkbox.checked : false; \r
3340     },\r
3341 \r
3342     // private\r
3343     updateExpandIcon : function(){\r
3344         if(this.rendered){\r
3345             var n = this.node, c1, c2;\r
3346             var cls = n.isLast() ? "x-tree-elbow-end" : "x-tree-elbow";\r
3347             var hasChild = n.hasChildNodes();\r
3348             if(hasChild || n.attributes.expandable){\r
3349                 if(n.expanded){\r
3350                     cls += "-minus";\r
3351                     c1 = "x-tree-node-collapsed";\r
3352                     c2 = "x-tree-node-expanded";\r
3353                 }else{\r
3354                     cls += "-plus";\r
3355                     c1 = "x-tree-node-expanded";\r
3356                     c2 = "x-tree-node-collapsed";\r
3357                 }\r
3358                 if(this.wasLeaf){\r
3359                     this.removeClass("x-tree-node-leaf");\r
3360                     this.wasLeaf = false;\r
3361                 }\r
3362                 if(this.c1 != c1 || this.c2 != c2){\r
3363                     Ext.fly(this.elNode).replaceClass(c1, c2);\r
3364                     this.c1 = c1; this.c2 = c2;\r
3365                 }\r
3366             }else{\r
3367                 if(!this.wasLeaf){\r
3368                     Ext.fly(this.elNode).replaceClass("x-tree-node-expanded", "x-tree-node-leaf");\r
3369                     delete this.c1;\r
3370                     delete this.c2;\r
3371                     this.wasLeaf = true;\r
3372                 }\r
3373             }\r
3374             var ecc = "x-tree-ec-icon "+cls;\r
3375             if(this.ecc != ecc){\r
3376                 this.ecNode.className = ecc;\r
3377                 this.ecc = ecc;\r
3378             }\r
3379         }\r
3380     },\r
3381     \r
3382     // private\r
3383     onIdChange: function(id){\r
3384         if(this.rendered){\r
3385             this.elNode.setAttribute('ext:tree-node-id', id);\r
3386         }\r
3387     },\r
3388 \r
3389     // private\r
3390     getChildIndent : function(){\r
3391         if(!this.childIndent){\r
3392             var buf = [];\r
3393             var p = this.node;\r
3394             while(p){\r
3395                 if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){\r
3396                     if(!p.isLast()) {\r
3397                         buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-elbow-line" />');\r
3398                     } else {\r
3399                         buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-icon" />');\r
3400                     }\r
3401                 }\r
3402                 p = p.parentNode;\r
3403             }\r
3404             this.childIndent = buf.join("");\r
3405         }\r
3406         return this.childIndent;\r
3407     },\r
3408 \r
3409     // private\r
3410     renderIndent : function(){\r
3411         if(this.rendered){\r
3412             var indent = "";\r
3413             var p = this.node.parentNode;\r
3414             if(p){\r
3415                 indent = p.ui.getChildIndent();\r
3416             }\r
3417             if(this.indentMarkup != indent){ // don't rerender if not required\r
3418                 this.indentNode.innerHTML = indent;\r
3419                 this.indentMarkup = indent;\r
3420             }\r
3421             this.updateExpandIcon();\r
3422         }\r
3423     },\r
3424 \r
3425     destroy : function(){\r
3426         if(this.elNode){\r
3427             Ext.dd.Registry.unregister(this.elNode.id);\r
3428         }\r
3429         delete this.elNode;\r
3430         delete this.ctNode;\r
3431         delete this.indentNode;\r
3432         delete this.ecNode;\r
3433         delete this.iconNode;\r
3434         delete this.checkbox;\r
3435         delete this.anchor;\r
3436         delete this.textNode;\r
3437         \r
3438         if (this.holder){\r
3439              delete this.wrap;\r
3440              Ext.removeNode(this.holder);\r
3441              delete this.holder;\r
3442         }else{\r
3443             Ext.removeNode(this.wrap);\r
3444             delete this.wrap;\r
3445         }\r
3446     }\r
3447 };\r
3448 \r
3449 /**\r
3450  * @class Ext.tree.RootTreeNodeUI\r
3451  * This class provides the default UI implementation for <b>root</b> Ext TreeNodes.\r
3452  * The RootTreeNode UI implementation allows customizing the appearance of the root tree node.<br>\r
3453  * <p>\r
3454  * If you are customizing the Tree's user interface, you\r
3455  * may need to extend this class, but you should never need to instantiate this class.<br>\r
3456  */\r
3457 Ext.tree.RootTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {\r
3458     // private\r
3459     render : function(){\r
3460         if(!this.rendered){\r
3461             var targetNode = this.node.ownerTree.innerCt.dom;\r
3462             this.node.expanded = true;\r
3463             targetNode.innerHTML = '<div class="x-tree-root-node"></div>';\r
3464             this.wrap = this.ctNode = targetNode.firstChild;\r
3465         }\r
3466     },\r
3467     collapse : Ext.emptyFn,\r
3468     expand : Ext.emptyFn\r
3469 });/**\r
3470  * @class Ext.tree.TreeLoader\r
3471  * @extends Ext.util.Observable\r
3472  * A TreeLoader provides for lazy loading of an {@link Ext.tree.TreeNode}'s child\r
3473  * nodes from a specified URL. The response must be a JavaScript Array definition\r
3474  * whose elements are node definition objects. e.g.:\r
3475  * <pre><code>\r
3476     [{\r
3477         id: 1,\r
3478         text: 'A leaf Node',\r
3479         leaf: true\r
3480     },{\r
3481         id: 2,\r
3482         text: 'A folder Node',\r
3483         children: [{\r
3484             id: 3,\r
3485             text: 'A child Node',\r
3486             leaf: true\r
3487         }]\r
3488    }]\r
3489 </code></pre>\r
3490  * <br><br>\r
3491  * A server request is sent, and child nodes are loaded only when a node is expanded.\r
3492  * The loading node's id is passed to the server under the parameter name "node" to\r
3493  * enable the server to produce the correct child nodes.\r
3494  * <br><br>\r
3495  * To pass extra parameters, an event handler may be attached to the "beforeload"\r
3496  * event, and the parameters specified in the TreeLoader's baseParams property:\r
3497  * <pre><code>\r
3498     myTreeLoader.on("beforeload", function(treeLoader, node) {\r
3499         this.baseParams.category = node.attributes.category;\r
3500     }, this);\r
3501 </code></pre>\r
3502  * This would pass an HTTP parameter called "category" to the server containing\r
3503  * the value of the Node's "category" attribute.\r
3504  * @constructor\r
3505  * Creates a new Treeloader.\r
3506  * @param {Object} config A config object containing config properties.\r
3507  */\r
3508 Ext.tree.TreeLoader = function(config){\r
3509     this.baseParams = {};\r
3510     Ext.apply(this, config);\r
3511 \r
3512     this.addEvents(\r
3513         /**\r
3514          * @event beforeload\r
3515          * Fires before a network request is made to retrieve the Json text which specifies a node's children.\r
3516          * @param {Object} This TreeLoader object.\r
3517          * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.\r
3518          * @param {Object} callback The callback function specified in the {@link #load} call.\r
3519          */\r
3520         "beforeload",\r
3521         /**\r
3522          * @event load\r
3523          * Fires when the node has been successfuly loaded.\r
3524          * @param {Object} This TreeLoader object.\r
3525          * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.\r
3526          * @param {Object} response The response object containing the data from the server.\r
3527          */\r
3528         "load",\r
3529         /**\r
3530          * @event loadexception\r
3531          * Fires if the network request failed.\r
3532          * @param {Object} This TreeLoader object.\r
3533          * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.\r
3534          * @param {Object} response The response object containing the data from the server.\r
3535          */\r
3536         "loadexception"\r
3537     );\r
3538     Ext.tree.TreeLoader.superclass.constructor.call(this);\r
3539     if(typeof this.paramOrder == 'string'){\r
3540         this.paramOrder = this.paramOrder.split(/[\s,|]/);\r
3541     }\r
3542 };\r
3543 \r
3544 Ext.extend(Ext.tree.TreeLoader, Ext.util.Observable, {\r
3545     /**\r
3546     * @cfg {String} dataUrl The URL from which to request a Json string which\r
3547     * specifies an array of node definition objects representing the child nodes\r
3548     * to be loaded.\r
3549     */\r
3550     /**\r
3551      * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).\r
3552      */\r
3553     /**\r
3554      * @cfg {String} url Equivalent to {@link #dataUrl}.\r
3555      */\r
3556     /**\r
3557      * @cfg {Boolean} preloadChildren If set to true, the loader recursively loads "children" attributes when doing the first load on nodes.\r
3558      */\r
3559     /**\r
3560     * @cfg {Object} baseParams (optional) An object containing properties which\r
3561     * specify HTTP parameters to be passed to each request for child nodes.\r
3562     */\r
3563     /**\r
3564     * @cfg {Object} baseAttrs (optional) An object containing attributes to be added to all nodes\r
3565     * created by this loader. If the attributes sent by the server have an attribute in this object,\r
3566     * they take priority.\r
3567     */\r
3568     /**\r
3569     * @cfg {Object} uiProviders (optional) An object containing properties which\r
3570     * specify custom {@link Ext.tree.TreeNodeUI} implementations. If the optional\r
3571     * <i>uiProvider</i> attribute of a returned child node is a string rather\r
3572     * than a reference to a TreeNodeUI implementation, then that string value\r
3573     * is used as a property name in the uiProviders object.\r
3574     */\r
3575     uiProviders : {},\r
3576 \r
3577     /**\r
3578     * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing\r
3579     * child nodes before loading.\r
3580     */\r
3581     clearOnLoad : true,\r
3582 \r
3583     /**\r
3584      * @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. Only used when using directFn.\r
3585      * A list of params to be executed\r
3586      * server side.  Specify the params in the order in which they must be executed on the server-side\r
3587      * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,\r
3588      * comma, or pipe. For example,\r
3589      * any of the following would be acceptable:<pre><code>\r
3590 paramOrder: ['param1','param2','param3']\r
3591 paramOrder: 'param1 param2 param3'\r
3592 paramOrder: 'param1,param2,param3'\r
3593 paramOrder: 'param1|param2|param'\r
3594      </code></pre>\r
3595      */\r
3596     paramOrder: undefined,\r
3597 \r
3598     /**\r
3599      * @cfg {Boolean} paramsAsHash Only used when using directFn.\r
3600      * Send parameters as a collection of named arguments (defaults to <tt>false</tt>). Providing a\r
3601      * <tt>{@link #paramOrder}</tt> nullifies this configuration.\r
3602      */\r
3603     paramsAsHash: false,\r
3604 \r
3605     /**\r
3606      * @cfg {Function} directFn\r
3607      * Function to call when executing a request.\r
3608      */\r
3609     directFn : undefined,\r
3610 \r
3611     /**\r
3612      * Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor.\r
3613      * This is called automatically when a node is expanded, but may be used to reload\r
3614      * a node (or append new children if the {@link #clearOnLoad} option is false.)\r
3615      * @param {Ext.tree.TreeNode} node\r
3616      * @param {Function} callback\r
3617      * @param (Object) scope\r
3618      */\r
3619     load : function(node, callback, scope){\r
3620         if(this.clearOnLoad){\r
3621             while(node.firstChild){\r
3622                 node.removeChild(node.firstChild);\r
3623             }\r
3624         }\r
3625         if(this.doPreload(node)){ // preloaded json children\r
3626             this.runCallback(callback, scope || node, [node]);\r
3627         }else if(this.directFn || this.dataUrl || this.url){\r
3628             this.requestData(node, callback, scope || node);\r
3629         }\r
3630     },\r
3631 \r
3632     doPreload : function(node){\r
3633         if(node.attributes.children){\r
3634             if(node.childNodes.length < 1){ // preloaded?\r
3635                 var cs = node.attributes.children;\r
3636                 node.beginUpdate();\r
3637                 for(var i = 0, len = cs.length; i < len; i++){\r
3638                     var cn = node.appendChild(this.createNode(cs[i]));\r
3639                     if(this.preloadChildren){\r
3640                         this.doPreload(cn);\r
3641                     }\r
3642                 }\r
3643                 node.endUpdate();\r
3644             }\r
3645             return true;\r
3646         }\r
3647         return false;\r
3648     },\r
3649 \r
3650     getParams: function(node){\r
3651         var buf = [], bp = this.baseParams;\r
3652         if(this.directFn){\r
3653             buf.push(node.id);\r
3654             if(bp){\r
3655                 if(this.paramOrder){\r
3656                     for(var i = 0, len = this.paramOrder.length; i < len; i++){\r
3657                         buf.push(bp[this.paramOrder[i]]);\r
3658                     }\r
3659                 }else if(this.paramsAsHash){\r
3660                     buf.push(bp);\r
3661                 }\r
3662             }\r
3663             return buf;\r
3664         }else{\r
3665             for(var key in bp){\r
3666                 if(!Ext.isFunction(bp[key])){\r
3667                     buf.push(encodeURIComponent(key), "=", encodeURIComponent(bp[key]), "&");\r
3668                 }\r
3669             }\r
3670             buf.push("node=", encodeURIComponent(node.id));\r
3671             return buf.join("");\r
3672         }\r
3673     },\r
3674 \r
3675     requestData : function(node, callback, scope){\r
3676         if(this.fireEvent("beforeload", this, node, callback) !== false){\r
3677             if(this.directFn){\r
3678                 var args = this.getParams(node);\r
3679                 args.push(this.processDirectResponse.createDelegate(this, [{callback: callback, node: node, scope: scope}], true));\r
3680                 this.directFn.apply(window, args);\r
3681             }else{\r
3682                 this.transId = Ext.Ajax.request({\r
3683                     method:this.requestMethod,\r
3684                     url: this.dataUrl||this.url,\r
3685                     success: this.handleResponse,\r
3686                     failure: this.handleFailure,\r
3687                     scope: this,\r
3688                     argument: {callback: callback, node: node, scope: scope},\r
3689                     params: this.getParams(node)\r
3690                 });\r
3691             }\r
3692         }else{\r
3693             // if the load is cancelled, make sure we notify\r
3694             // the node that we are done\r
3695             this.runCallback(callback, scope || node, []);\r
3696         }\r
3697     },\r
3698 \r
3699     processDirectResponse: function(result, response, args){\r
3700         if(response.status){\r
3701             this.handleResponse({\r
3702                 responseData: Ext.isArray(result) ? result : null,\r
3703                 responseText: result,\r
3704                 argument: args\r
3705             });\r
3706         }else{\r
3707             this.handleFailure({\r
3708                 argument: args\r
3709             });\r
3710         }\r
3711     },\r
3712 \r
3713     // private\r
3714     runCallback: function(cb, scope, args){\r
3715         if(Ext.isFunction(cb)){\r
3716             cb.apply(scope, args);\r
3717         }\r
3718     },\r
3719 \r
3720     isLoading : function(){\r
3721         return !!this.transId;\r
3722     },\r
3723 \r
3724     abort : function(){\r
3725         if(this.isLoading()){\r
3726             Ext.Ajax.abort(this.transId);\r
3727         }\r
3728     },\r
3729 \r
3730     /**\r
3731     * <p>Override this function for custom TreeNode node implementation, or to\r
3732     * modify the attributes at creation time.</p>\r
3733     * Example:<pre><code>\r
3734 new Ext.tree.TreePanel({\r
3735     ...\r
3736     loader: new Ext.tree.TreeLoader({\r
3737         url: 'dataUrl',\r
3738         createNode: function(attr) {\r
3739 //          Allow consolidation consignments to have\r
3740 //          consignments dropped into them.\r
3741             if (attr.isConsolidation) {\r
3742                 attr.iconCls = 'x-consol',\r
3743                 attr.allowDrop = true;\r
3744             }\r
3745             return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);\r
3746         }\r
3747     }),\r
3748     ...\r
3749 });\r
3750 </code></pre>\r
3751     * @param attr {Object} The attributes from which to create the new node.\r
3752     */\r
3753     createNode : function(attr){\r
3754         // apply baseAttrs, nice idea Corey!\r
3755         if(this.baseAttrs){\r
3756             Ext.applyIf(attr, this.baseAttrs);\r
3757         }\r
3758         if(this.applyLoader !== false && !attr.loader){\r
3759             attr.loader = this;\r
3760         }\r
3761         if(typeof attr.uiProvider == 'string'){\r
3762            attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);\r
3763         }\r
3764         if(attr.nodeType){\r
3765             return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);\r
3766         }else{\r
3767             return attr.leaf ?\r
3768                         new Ext.tree.TreeNode(attr) :\r
3769                         new Ext.tree.AsyncTreeNode(attr);\r
3770         }\r
3771     },\r
3772 \r
3773     processResponse : function(response, node, callback, scope){\r
3774         var json = response.responseText;\r
3775         try {\r
3776             var o = response.responseData || Ext.decode(json);\r
3777             node.beginUpdate();\r
3778             for(var i = 0, len = o.length; i < len; i++){\r
3779                 var n = this.createNode(o[i]);\r
3780                 if(n){\r
3781                     node.appendChild(n);\r
3782                 }\r
3783             }\r
3784             node.endUpdate();\r
3785             this.runCallback(callback, scope || node, [node]);\r
3786         }catch(e){\r
3787             this.handleFailure(response);\r
3788         }\r
3789     },\r
3790 \r
3791     handleResponse : function(response){\r
3792         this.transId = false;\r
3793         var a = response.argument;\r
3794         this.processResponse(response, a.node, a.callback, a.scope);\r
3795         this.fireEvent("load", this, a.node, response);\r
3796     },\r
3797 \r
3798     handleFailure : function(response){\r
3799         this.transId = false;\r
3800         var a = response.argument;\r
3801         this.fireEvent("loadexception", this, a.node, response);\r
3802         this.runCallback(a.callback, a.scope || a.node, [a.node]);\r
3803     }\r
3804 });/**
3805  * @class Ext.tree.TreeFilter
3806  * Note this class is experimental and doesn't update the indent (lines) or expand collapse icons of the nodes
3807  * @param {TreePanel} tree
3808  * @param {Object} config (optional)
3809  */
3810 Ext.tree.TreeFilter = function(tree, config){
3811     this.tree = tree;
3812     this.filtered = {};
3813     Ext.apply(this, config);
3814 };
3815
3816 Ext.tree.TreeFilter.prototype = {
3817     clearBlank:false,
3818     reverse:false,
3819     autoClear:false,
3820     remove:false,
3821
3822      /**
3823      * Filter the data by a specific attribute.
3824      * @param {String/RegExp} value Either string that the attribute value
3825      * should start with or a RegExp to test against the attribute
3826      * @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
3827      * @param {TreeNode} startNode (optional) The node to start the filter at.
3828      */
3829     filter : function(value, attr, startNode){
3830         attr = attr || "text";
3831         var f;
3832         if(typeof value == "string"){
3833             var vlen = value.length;
3834             // auto clear empty filter
3835             if(vlen == 0 && this.clearBlank){
3836                 this.clear();
3837                 return;
3838             }
3839             value = value.toLowerCase();
3840             f = function(n){
3841                 return n.attributes[attr].substr(0, vlen).toLowerCase() == value;
3842             };
3843         }else if(value.exec){ // regex?
3844             f = function(n){
3845                 return value.test(n.attributes[attr]);
3846             };
3847         }else{
3848             throw 'Illegal filter type, must be string or regex';
3849         }
3850         this.filterBy(f, null, startNode);
3851         },
3852
3853     /**
3854      * Filter by a function. The passed function will be called with each
3855      * node in the tree (or from the startNode). If the function returns true, the node is kept
3856      * otherwise it is filtered. If a node is filtered, its children are also filtered.
3857      * @param {Function} fn The filter function
3858      * @param {Object} scope (optional) The scope of the function (defaults to the current node)
3859      */
3860     filterBy : function(fn, scope, startNode){
3861         startNode = startNode || this.tree.root;
3862         if(this.autoClear){
3863             this.clear();
3864         }
3865         var af = this.filtered, rv = this.reverse;
3866         var f = function(n){
3867             if(n == startNode){
3868                 return true;
3869             }
3870             if(af[n.id]){
3871                 return false;
3872             }
3873             var m = fn.call(scope || n, n);
3874             if(!m || rv){
3875                 af[n.id] = n;
3876                 n.ui.hide();
3877                 return false;
3878             }
3879             return true;
3880         };
3881         startNode.cascade(f);
3882         if(this.remove){
3883            for(var id in af){
3884                if(typeof id != "function"){
3885                    var n = af[id];
3886                    if(n && n.parentNode){
3887                        n.parentNode.removeChild(n);
3888                    }
3889                }
3890            }
3891         }
3892     },
3893
3894     /**
3895      * Clears the current filter. Note: with the "remove" option
3896      * set a filter cannot be cleared.
3897      */
3898     clear : function(){
3899         var t = this.tree;
3900         var af = this.filtered;
3901         for(var id in af){
3902             if(typeof id != "function"){
3903                 var n = af[id];
3904                 if(n){
3905                     n.ui.show();
3906                 }
3907             }
3908         }
3909         this.filtered = {};
3910     }
3911 };
3912 /**\r
3913  * @class Ext.tree.TreeSorter\r
3914  * Provides sorting of nodes in a {@link Ext.tree.TreePanel}.  The TreeSorter automatically monitors events on the \r
3915  * associated TreePanel that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange).\r
3916  * Example usage:<br />\r
3917  * <pre><code>\r
3918 new Ext.tree.TreeSorter(myTree, {\r
3919     folderSort: true,\r
3920     dir: "desc",\r
3921     sortType: function(node) {\r
3922         // sort by a custom, typed attribute:\r
3923         return parseInt(node.id, 10);\r
3924     }\r
3925 });\r
3926 </code></pre>\r
3927  * @constructor\r
3928  * @param {TreePanel} tree\r
3929  * @param {Object} config\r
3930  */\r
3931 Ext.tree.TreeSorter = function(tree, config){\r
3932     /**\r
3933      * @cfg {Boolean} folderSort True to sort leaf nodes under non-leaf nodes (defaults to false)\r
3934      */\r
3935     /** \r
3936      * @cfg {String} property The named attribute on the node to sort by (defaults to "text").  Note that this \r
3937      * property is only used if no {@link #sortType} function is specified, otherwise it is ignored.\r
3938      */\r
3939     /** \r
3940      * @cfg {String} dir The direction to sort ("asc" or "desc," case-insensitive, defaults to "asc")\r
3941      */\r
3942     /** \r
3943      * @cfg {String} leafAttr The attribute used to determine leaf nodes when {@link #folderSort} = true (defaults to "leaf")\r
3944      */\r
3945     /** \r
3946      * @cfg {Boolean} caseSensitive true for case-sensitive sort (defaults to false)\r
3947      */\r
3948     /** \r
3949      * @cfg {Function} sortType A custom "casting" function used to convert node values before sorting.  The function\r
3950      * will be called with a single parameter (the {@link Ext.tree.TreeNode} being evaluated) and is expected to return\r
3951      * the node's sort value cast to the specific data type required for sorting.  This could be used, for example, when\r
3952      * a node's text (or other attribute) should be sorted as a date or numeric value.  See the class description for \r
3953      * example usage.  Note that if a sortType is specified, any {@link #property} config will be ignored.\r
3954      */\r
3955     \r
3956     Ext.apply(this, config);\r
3957     tree.on("beforechildrenrendered", this.doSort, this);\r
3958     tree.on("append", this.updateSort, this);\r
3959     tree.on("insert", this.updateSort, this);\r
3960     tree.on("textchange", this.updateSortParent, this);\r
3961     \r
3962     var dsc = this.dir && this.dir.toLowerCase() == "desc";\r
3963     var p = this.property || "text";\r
3964     var sortType = this.sortType;\r
3965     var fs = this.folderSort;\r
3966     var cs = this.caseSensitive === true;\r
3967     var leafAttr = this.leafAttr || 'leaf';\r
3968 \r
3969     this.sortFn = function(n1, n2){\r
3970         if(fs){\r
3971             if(n1.attributes[leafAttr] && !n2.attributes[leafAttr]){\r
3972                 return 1;\r
3973             }\r
3974             if(!n1.attributes[leafAttr] && n2.attributes[leafAttr]){\r
3975                 return -1;\r
3976             }\r
3977         }\r
3978         var v1 = sortType ? sortType(n1) : (cs ? n1.attributes[p] : n1.attributes[p].toUpperCase());\r
3979         var v2 = sortType ? sortType(n2) : (cs ? n2.attributes[p] : n2.attributes[p].toUpperCase());\r
3980         if(v1 < v2){\r
3981                         return dsc ? +1 : -1;\r
3982                 }else if(v1 > v2){\r
3983                         return dsc ? -1 : +1;\r
3984         }else{\r
3985                 return 0;\r
3986         }\r
3987     };\r
3988 };\r
3989 \r
3990 Ext.tree.TreeSorter.prototype = {\r
3991     doSort : function(node){\r
3992         node.sort(this.sortFn);\r
3993     },\r
3994     \r
3995     compareNodes : function(n1, n2){\r
3996         return (n1.text.toUpperCase() > n2.text.toUpperCase() ? 1 : -1);\r
3997     },\r
3998     \r
3999     updateSort : function(tree, node){\r
4000         if(node.childrenRendered){\r
4001             this.doSort.defer(1, this, [node]);\r
4002         }\r
4003     },\r
4004     \r
4005     updateSortParent : function(node){\r
4006                 var p = node.parentNode;\r
4007                 if(p && p.childrenRendered){\r
4008             this.doSort.defer(1, this, [p]);\r
4009         }\r
4010     }\r
4011 };/**\r
4012  * @class Ext.tree.TreeDropZone\r
4013  * @extends Ext.dd.DropZone\r
4014  * @constructor\r
4015  * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dropping\r
4016  * @param {Object} config\r
4017  */\r
4018 if(Ext.dd.DropZone){\r
4019     \r
4020 Ext.tree.TreeDropZone = function(tree, config){\r
4021     /**\r
4022      * @cfg {Boolean} allowParentInsert\r
4023      * Allow inserting a dragged node between an expanded parent node and its first child that will become a\r
4024      * sibling of the parent when dropped (defaults to false)\r
4025      */\r
4026     this.allowParentInsert = config.allowParentInsert || false;\r
4027     /**\r
4028      * @cfg {String} allowContainerDrop\r
4029      * True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false)\r
4030      */\r
4031     this.allowContainerDrop = config.allowContainerDrop || false;\r
4032     /**\r
4033      * @cfg {String} appendOnly\r
4034      * True if the tree should only allow append drops (use for trees which are sorted, defaults to false)\r
4035      */\r
4036     this.appendOnly = config.appendOnly || false;\r
4037 \r
4038     Ext.tree.TreeDropZone.superclass.constructor.call(this, tree.getTreeEl(), config);\r
4039     /**\r
4040     * The TreePanel for this drop zone\r
4041     * @type Ext.tree.TreePanel\r
4042     * @property\r
4043     */\r
4044     this.tree = tree;\r
4045     /**\r
4046     * Arbitrary data that can be associated with this tree and will be included in the event object that gets\r
4047     * passed to any nodedragover event handler (defaults to {})\r
4048     * @type Ext.tree.TreePanel\r
4049     * @property\r
4050     */\r
4051     this.dragOverData = {};\r
4052     // private\r
4053     this.lastInsertClass = "x-tree-no-status";\r
4054 };\r
4055 \r
4056 Ext.extend(Ext.tree.TreeDropZone, Ext.dd.DropZone, {\r
4057     /**\r
4058      * @cfg {String} ddGroup\r
4059      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only\r
4060      * interact with other drag drop objects in the same group (defaults to 'TreeDD').\r
4061      */\r
4062     ddGroup : "TreeDD",\r
4063 \r
4064     /**\r
4065      * @cfg {String} expandDelay\r
4066      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node\r
4067      * over the target (defaults to 1000)\r
4068      */\r
4069     expandDelay : 1000,\r
4070 \r
4071     // private\r
4072     expandNode : function(node){\r
4073         if(node.hasChildNodes() && !node.isExpanded()){\r
4074             node.expand(false, null, this.triggerCacheRefresh.createDelegate(this));\r
4075         }\r
4076     },\r
4077 \r
4078     // private\r
4079     queueExpand : function(node){\r
4080         this.expandProcId = this.expandNode.defer(this.expandDelay, this, [node]);\r
4081     },\r
4082 \r
4083     // private\r
4084     cancelExpand : function(){\r
4085         if(this.expandProcId){\r
4086             clearTimeout(this.expandProcId);\r
4087             this.expandProcId = false;\r
4088         }\r
4089     },\r
4090 \r
4091     // private\r
4092     isValidDropPoint : function(n, pt, dd, e, data){\r
4093         if(!n || !data){ return false; }\r
4094         var targetNode = n.node;\r
4095         var dropNode = data.node;\r
4096         // default drop rules\r
4097         if(!(targetNode && targetNode.isTarget && pt)){\r
4098             return false;\r
4099         }\r
4100         if(pt == "append" && targetNode.allowChildren === false){\r
4101             return false;\r
4102         }\r
4103         if((pt == "above" || pt == "below") && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){\r
4104             return false;\r
4105         }\r
4106         if(dropNode && (targetNode == dropNode || dropNode.contains(targetNode))){\r
4107             return false;\r
4108         }\r
4109         // reuse the object\r
4110         var overEvent = this.dragOverData;\r
4111         overEvent.tree = this.tree;\r
4112         overEvent.target = targetNode;\r
4113         overEvent.data = data;\r
4114         overEvent.point = pt;\r
4115         overEvent.source = dd;\r
4116         overEvent.rawEvent = e;\r
4117         overEvent.dropNode = dropNode;\r
4118         overEvent.cancel = false;  \r
4119         var result = this.tree.fireEvent("nodedragover", overEvent);\r
4120         return overEvent.cancel === false && result !== false;\r
4121     },\r
4122 \r
4123     // private\r
4124     getDropPoint : function(e, n, dd){\r
4125         var tn = n.node;\r
4126         if(tn.isRoot){\r
4127             return tn.allowChildren !== false ? "append" : false; // always append for root\r
4128         }\r
4129         var dragEl = n.ddel;\r
4130         var t = Ext.lib.Dom.getY(dragEl), b = t + dragEl.offsetHeight;\r
4131         var y = Ext.lib.Event.getPageY(e);\r
4132         var noAppend = tn.allowChildren === false || tn.isLeaf();\r
4133         if(this.appendOnly || tn.parentNode.allowChildren === false){\r
4134             return noAppend ? false : "append";\r
4135         }\r
4136         var noBelow = false;\r
4137         if(!this.allowParentInsert){\r
4138             noBelow = tn.hasChildNodes() && tn.isExpanded();\r
4139         }\r
4140         var q = (b - t) / (noAppend ? 2 : 3);\r
4141         if(y >= t && y < (t + q)){\r
4142             return "above";\r
4143         }else if(!noBelow && (noAppend || y >= b-q && y <= b)){\r
4144             return "below";\r
4145         }else{\r
4146             return "append";\r
4147         }\r
4148     },\r
4149 \r
4150     // private\r
4151     onNodeEnter : function(n, dd, e, data){\r
4152         this.cancelExpand();\r
4153     },\r
4154     \r
4155     onContainerOver : function(dd, e, data) {\r
4156         if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {\r
4157             return this.dropAllowed;\r
4158         }\r
4159         return this.dropNotAllowed;\r
4160     },\r
4161 \r
4162     // private\r
4163     onNodeOver : function(n, dd, e, data){\r
4164         var pt = this.getDropPoint(e, n, dd);\r
4165         var node = n.node;\r
4166         \r
4167         // auto node expand check\r
4168         if(!this.expandProcId && pt == "append" && node.hasChildNodes() && !n.node.isExpanded()){\r
4169             this.queueExpand(node);\r
4170         }else if(pt != "append"){\r
4171             this.cancelExpand();\r
4172         }\r
4173         \r
4174         // set the insert point style on the target node\r
4175         var returnCls = this.dropNotAllowed;\r
4176         if(this.isValidDropPoint(n, pt, dd, e, data)){\r
4177            if(pt){\r
4178                var el = n.ddel;\r
4179                var cls;\r
4180                if(pt == "above"){\r
4181                    returnCls = n.node.isFirst() ? "x-tree-drop-ok-above" : "x-tree-drop-ok-between";\r
4182                    cls = "x-tree-drag-insert-above";\r
4183                }else if(pt == "below"){\r
4184                    returnCls = n.node.isLast() ? "x-tree-drop-ok-below" : "x-tree-drop-ok-between";\r
4185                    cls = "x-tree-drag-insert-below";\r
4186                }else{\r
4187                    returnCls = "x-tree-drop-ok-append";\r
4188                    cls = "x-tree-drag-append";\r
4189                }\r
4190                if(this.lastInsertClass != cls){\r
4191                    Ext.fly(el).replaceClass(this.lastInsertClass, cls);\r
4192                    this.lastInsertClass = cls;\r
4193                }\r
4194            }\r
4195        }\r
4196        return returnCls;\r
4197     },\r
4198 \r
4199     // private\r
4200     onNodeOut : function(n, dd, e, data){\r
4201         this.cancelExpand();\r
4202         this.removeDropIndicators(n);\r
4203     },\r
4204 \r
4205     // private\r
4206     onNodeDrop : function(n, dd, e, data){\r
4207         var point = this.getDropPoint(e, n, dd);\r
4208         var targetNode = n.node;\r
4209         targetNode.ui.startDrop();\r
4210         if(!this.isValidDropPoint(n, point, dd, e, data)){\r
4211             targetNode.ui.endDrop();\r
4212             return false;\r
4213         }\r
4214         // first try to find the drop node\r
4215         var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null);\r
4216         return this.processDrop(targetNode, data, point, dd, e, dropNode);\r
4217     },\r
4218     \r
4219     onContainerDrop : function(dd, e, data){\r
4220         if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {\r
4221             var targetNode = this.tree.getRootNode();       \r
4222             targetNode.ui.startDrop();\r
4223             var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, 'append', e) : null);\r
4224             return this.processDrop(targetNode, data, 'append', dd, e, dropNode);\r
4225         }\r
4226         return false;\r
4227     },\r
4228     \r
4229     // private\r
4230     processDrop: function(target, data, point, dd, e, dropNode){\r
4231         var dropEvent = {\r
4232             tree : this.tree,\r
4233             target: target,\r
4234             data: data,\r
4235             point: point,\r
4236             source: dd,\r
4237             rawEvent: e,\r
4238             dropNode: dropNode,\r
4239             cancel: !dropNode,\r
4240             dropStatus: false\r
4241         };\r
4242         var retval = this.tree.fireEvent("beforenodedrop", dropEvent);\r
4243         if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){\r
4244             target.ui.endDrop();\r
4245             return dropEvent.dropStatus;\r
4246         }\r
4247     \r
4248         target = dropEvent.target;\r
4249         if(point == 'append' && !target.isExpanded()){\r
4250             target.expand(false, null, function(){\r
4251                 this.completeDrop(dropEvent);\r
4252             }.createDelegate(this));\r
4253         }else{\r
4254             this.completeDrop(dropEvent);\r
4255         }\r
4256         return true;\r
4257     },\r
4258 \r
4259     // private\r
4260     completeDrop : function(de){\r
4261         var ns = de.dropNode, p = de.point, t = de.target;\r
4262         if(!Ext.isArray(ns)){\r
4263             ns = [ns];\r
4264         }\r
4265         var n;\r
4266         for(var i = 0, len = ns.length; i < len; i++){\r
4267             n = ns[i];\r
4268             if(p == "above"){\r
4269                 t.parentNode.insertBefore(n, t);\r
4270             }else if(p == "below"){\r
4271                 t.parentNode.insertBefore(n, t.nextSibling);\r
4272             }else{\r
4273                 t.appendChild(n);\r
4274             }\r
4275         }\r
4276         n.ui.focus();\r
4277         if(Ext.enableFx && this.tree.hlDrop){\r
4278             n.ui.highlight();\r
4279         }\r
4280         t.ui.endDrop();\r
4281         this.tree.fireEvent("nodedrop", de);\r
4282     },\r
4283 \r
4284     // private\r
4285     afterNodeMoved : function(dd, data, e, targetNode, dropNode){\r
4286         if(Ext.enableFx && this.tree.hlDrop){\r
4287             dropNode.ui.focus();\r
4288             dropNode.ui.highlight();\r
4289         }\r
4290         this.tree.fireEvent("nodedrop", this.tree, targetNode, data, dd, e);\r
4291     },\r
4292 \r
4293     // private\r
4294     getTree : function(){\r
4295         return this.tree;\r
4296     },\r
4297 \r
4298     // private\r
4299     removeDropIndicators : function(n){\r
4300         if(n && n.ddel){\r
4301             var el = n.ddel;\r
4302             Ext.fly(el).removeClass([\r
4303                     "x-tree-drag-insert-above",\r
4304                     "x-tree-drag-insert-below",\r
4305                     "x-tree-drag-append"]);\r
4306             this.lastInsertClass = "_noclass";\r
4307         }\r
4308     },\r
4309 \r
4310     // private\r
4311     beforeDragDrop : function(target, e, id){\r
4312         this.cancelExpand();\r
4313         return true;\r
4314     },\r
4315 \r
4316     // private\r
4317     afterRepair : function(data){\r
4318         if(data && Ext.enableFx){\r
4319             data.node.ui.highlight();\r
4320         }\r
4321         this.hideProxy();\r
4322     }    \r
4323 });\r
4324 \r
4325 }/**\r
4326  * @class Ext.tree.TreeDragZone\r
4327  * @extends Ext.dd.DragZone\r
4328  * @constructor\r
4329  * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dragging\r
4330  * @param {Object} config\r
4331  */\r
4332 if(Ext.dd.DragZone){\r
4333 Ext.tree.TreeDragZone = function(tree, config){\r
4334     Ext.tree.TreeDragZone.superclass.constructor.call(this, tree.innerCt, config);\r
4335     /**\r
4336     * The TreePanel for this drag zone\r
4337     * @type Ext.tree.TreePanel\r
4338     * @property\r
4339     */\r
4340     this.tree = tree;\r
4341 };\r
4342 \r
4343 Ext.extend(Ext.tree.TreeDragZone, Ext.dd.DragZone, {\r
4344     /**\r
4345      * @cfg {String} ddGroup\r
4346      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only\r
4347      * interact with other drag drop objects in the same group (defaults to 'TreeDD').\r
4348      */\r
4349     ddGroup : "TreeDD",\r
4350 \r
4351     // private\r
4352     onBeforeDrag : function(data, e){\r
4353         var n = data.node;\r
4354         return n && n.draggable && !n.disabled;\r
4355     },\r
4356 \r
4357     // private\r
4358     onInitDrag : function(e){\r
4359         var data = this.dragData;\r
4360         this.tree.getSelectionModel().select(data.node);\r
4361         this.tree.eventModel.disable();\r
4362         this.proxy.update("");\r
4363         data.node.ui.appendDDGhost(this.proxy.ghost.dom);\r
4364         this.tree.fireEvent("startdrag", this.tree, data.node, e);\r
4365     },\r
4366 \r
4367     // private\r
4368     getRepairXY : function(e, data){\r
4369         return data.node.ui.getDDRepairXY();\r
4370     },\r
4371 \r
4372     // private\r
4373     onEndDrag : function(data, e){\r
4374         this.tree.eventModel.enable.defer(100, this.tree.eventModel);\r
4375         this.tree.fireEvent("enddrag", this.tree, data.node, e);\r
4376     },\r
4377 \r
4378     // private\r
4379     onValidDrop : function(dd, e, id){\r
4380         this.tree.fireEvent("dragdrop", this.tree, this.dragData.node, dd, e);\r
4381         this.hideProxy();\r
4382     },\r
4383 \r
4384     // private\r
4385     beforeInvalidDrop : function(e, id){\r
4386         // this scrolls the original position back into view\r
4387         var sm = this.tree.getSelectionModel();\r
4388         sm.clearSelections();\r
4389         sm.select(this.dragData.node);\r
4390     },\r
4391     \r
4392     // private\r
4393     afterRepair : function(){\r
4394         if (Ext.enableFx && this.tree.hlDrop) {\r
4395             Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || "c3daf9");\r
4396         }\r
4397         this.dragging = false;\r
4398     }\r
4399 });\r
4400 }/**
4401  * @class Ext.tree.TreeEditor
4402  * @extends Ext.Editor
4403  * Provides editor functionality for inline tree node editing.  Any valid {@link Ext.form.Field} subclass can be used
4404  * as the editor field.
4405  * @constructor
4406  * @param {TreePanel} tree
4407  * @param {Object} fieldConfig (optional) Either a prebuilt {@link Ext.form.Field} instance or a Field config object
4408  * that will be applied to the default field instance (defaults to a {@link Ext.form.TextField}).
4409  * @param {Object} config (optional) A TreeEditor config object
4410  */
4411 Ext.tree.TreeEditor = function(tree, fc, config){
4412     fc = fc || {};
4413     var field = fc.events ? fc : new Ext.form.TextField(fc);
4414     Ext.tree.TreeEditor.superclass.constructor.call(this, field, config);
4415
4416     this.tree = tree;
4417
4418     if(!tree.rendered){
4419         tree.on('render', this.initEditor, this);
4420     }else{
4421         this.initEditor(tree);
4422     }
4423 };
4424
4425 Ext.extend(Ext.tree.TreeEditor, Ext.Editor, {
4426     /**
4427      * @cfg {String} alignment
4428      * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "l-l").
4429      */
4430     alignment: "l-l",
4431     // inherit
4432     autoSize: false,
4433     /**
4434      * @cfg {Boolean} hideEl
4435      * True to hide the bound element while the editor is displayed (defaults to false)
4436      */
4437     hideEl : false,
4438     /**
4439      * @cfg {String} cls
4440      * CSS class to apply to the editor (defaults to "x-small-editor x-tree-editor")
4441      */
4442     cls: "x-small-editor x-tree-editor",
4443     /**
4444      * @cfg {Boolean} shim
4445      * True to shim the editor if selects/iframes could be displayed beneath it (defaults to false)
4446      */
4447     shim:false,
4448     // inherit
4449     shadow:"frame",
4450     /**
4451      * @cfg {Number} maxWidth
4452      * The maximum width in pixels of the editor field (defaults to 250).  Note that if the maxWidth would exceed
4453      * the containing tree element's size, it will be automatically limited for you to the container width, taking
4454      * scroll and client offsets into account prior to each edit.
4455      */
4456     maxWidth: 250,
4457     /**
4458      * @cfg {Number} editDelay The number of milliseconds between clicks to register a double-click that will trigger
4459      * editing on the current node (defaults to 350).  If two clicks occur on the same node within this time span,
4460      * the editor for the node will display, otherwise it will be processed as a regular click.
4461      */
4462     editDelay : 350,
4463
4464     initEditor : function(tree){
4465         tree.on('beforeclick', this.beforeNodeClick, this);
4466         tree.on('dblclick', this.onNodeDblClick, this);
4467         this.on('complete', this.updateNode, this);
4468         this.on('beforestartedit', this.fitToTree, this);
4469         this.on('startedit', this.bindScroll, this, {delay:10});
4470         this.on('specialkey', this.onSpecialKey, this);
4471     },
4472
4473     // private
4474     fitToTree : function(ed, el){
4475         var td = this.tree.getTreeEl().dom, nd = el.dom;
4476         if(td.scrollLeft >  nd.offsetLeft){ // ensure the node left point is visible
4477             td.scrollLeft = nd.offsetLeft;
4478         }
4479         var w = Math.min(
4480                 this.maxWidth,
4481                 (td.clientWidth > 20 ? td.clientWidth : td.offsetWidth) - Math.max(0, nd.offsetLeft-td.scrollLeft) - /*cushion*/5);
4482         this.setSize(w, '');
4483     },
4484
4485     /**
4486      * Edit the text of the passed {@link Ext.tree.TreeNode TreeNode}.
4487      * @param node {Ext.tree.TreeNode} The TreeNode to edit. The TreeNode must be {@link Ext.tree.TreeNode#editable editable}.
4488      */
4489     triggerEdit : function(node, defer){
4490         this.completeEdit();
4491                 if(node.attributes.editable !== false){
4492            /**
4493             * The {@link Ext.tree.TreeNode TreeNode} this editor is bound to. Read-only.
4494             * @type Ext.tree.TreeNode
4495             * @property editNode
4496             */
4497                         this.editNode = node;
4498             if(this.tree.autoScroll){
4499                 Ext.fly(node.ui.getEl()).scrollIntoView(this.tree.body);
4500             }
4501             var value = node.text || '';
4502             if (!Ext.isGecko && Ext.isEmpty(node.text)){
4503                 node.setText('&#160;');
4504             }
4505             this.autoEditTimer = this.startEdit.defer(this.editDelay, this, [node.ui.textNode, value]);
4506             return false;
4507         }
4508     },
4509
4510     // private
4511     bindScroll : function(){
4512         this.tree.getTreeEl().on('scroll', this.cancelEdit, this);
4513     },
4514
4515     // private
4516     beforeNodeClick : function(node, e){
4517         clearTimeout(this.autoEditTimer);
4518         if(this.tree.getSelectionModel().isSelected(node)){
4519             e.stopEvent();
4520             return this.triggerEdit(node);
4521         }
4522     },
4523
4524     onNodeDblClick : function(node, e){
4525         clearTimeout(this.autoEditTimer);
4526     },
4527
4528     // private
4529     updateNode : function(ed, value){
4530         this.tree.getTreeEl().un('scroll', this.cancelEdit, this);
4531         this.editNode.setText(value);
4532     },
4533
4534     // private
4535     onHide : function(){
4536         Ext.tree.TreeEditor.superclass.onHide.call(this);
4537         if(this.editNode){
4538             this.editNode.ui.focus.defer(50, this.editNode.ui);
4539         }
4540     },
4541
4542     // private
4543     onSpecialKey : function(field, e){
4544         var k = e.getKey();
4545         if(k == e.ESC){
4546             e.stopEvent();
4547             this.cancelEdit();
4548         }else if(k == e.ENTER && !e.hasModifier()){
4549             e.stopEvent();
4550             this.completeEdit();
4551         }
4552     }
4553 });