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