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