Upgrade to ExtJS 3.2.0 - Released 03/30/2010
[extjs.git] / pkgs / pkg-tree-debug.js
1 /*!
2  * Ext JS Library 3.2.0
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 = function(config){
1166    this.selNode = null;
1167    
1168    this.addEvents(
1169        /**
1170         * @event selectionchange
1171         * Fires when the selected node changes
1172         * @param {DefaultSelectionModel} this
1173         * @param {TreeNode} node the new selection
1174         */
1175        'selectionchange',
1176
1177        /**
1178         * @event beforeselect
1179         * Fires before the selected node changes, return false to cancel the change
1180         * @param {DefaultSelectionModel} this
1181         * @param {TreeNode} node the new selection
1182         * @param {TreeNode} node the old selection
1183         */
1184        'beforeselect'
1185    );
1186
1187     Ext.apply(this, config);
1188     Ext.tree.DefaultSelectionModel.superclass.constructor.call(this);
1189 };
1190
1191 Ext.extend(Ext.tree.DefaultSelectionModel, Ext.util.Observable, {
1192     init : function(tree){
1193         this.tree = tree;
1194         tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);
1195         tree.on('click', this.onNodeClick, this);
1196     },
1197     
1198     onNodeClick : function(node, e){
1199         this.select(node);
1200     },
1201     
1202     /**
1203      * Select a node.
1204      * @param {TreeNode} node The node to select
1205      * @return {TreeNode} The selected node
1206      */
1207     select : function(node, /* private*/ selectNextNode){
1208         // If node is hidden, select the next node in whatever direction was being moved in.
1209         if (!Ext.fly(node.ui.wrap).isVisible() && selectNextNode) {
1210             return selectNextNode.call(this, node);
1211         }
1212         var last = this.selNode;
1213         if(node == last){
1214             node.ui.onSelectedChange(true);
1215         }else if(this.fireEvent('beforeselect', this, node, last) !== false){
1216             if(last && last.ui){
1217                 last.ui.onSelectedChange(false);
1218             }
1219             this.selNode = node;
1220             node.ui.onSelectedChange(true);
1221             this.fireEvent('selectionchange', this, node, last);
1222         }
1223         return node;
1224     },
1225     
1226     /**
1227      * Deselect a node.
1228      * @param {TreeNode} node The node to unselect
1229      * @param {Boolean} silent True to stop the selectionchange event from firing.
1230      */
1231     unselect : function(node, silent){
1232         if(this.selNode == node){
1233             this.clearSelections(silent);
1234         }    
1235     },
1236     
1237     /**
1238      * Clear all selections
1239      * @param {Boolean} silent True to stop the selectionchange event from firing.
1240      */
1241     clearSelections : function(silent){
1242         var n = this.selNode;
1243         if(n){
1244             n.ui.onSelectedChange(false);
1245             this.selNode = null;
1246             if(silent !== true){
1247                 this.fireEvent('selectionchange', this, null);
1248             }
1249         }
1250         return n;
1251     },
1252     
1253     /**
1254      * Get the selected node
1255      * @return {TreeNode} The selected node
1256      */
1257     getSelectedNode : function(){
1258         return this.selNode;    
1259     },
1260     
1261     /**
1262      * Returns true if the node is selected
1263      * @param {TreeNode} node The node to check
1264      * @return {Boolean}
1265      */
1266     isSelected : function(node){
1267         return this.selNode == node;  
1268     },
1269
1270     /**
1271      * Selects the node above the selected node in the tree, intelligently walking the nodes
1272      * @return TreeNode The new selection
1273      */
1274     selectPrevious : function(/* private */ s){
1275         if(!(s = s || this.selNode || this.lastSelNode)){
1276             return null;
1277         }
1278         // Here we pass in the current function to select to indicate the direction we're moving
1279         var ps = s.previousSibling;
1280         if(ps){
1281             if(!ps.isExpanded() || ps.childNodes.length < 1){
1282                 return this.select(ps, this.selectPrevious);
1283             } else{
1284                 var lc = ps.lastChild;
1285                 while(lc && lc.isExpanded() && Ext.fly(lc.ui.wrap).isVisible() && lc.childNodes.length > 0){
1286                     lc = lc.lastChild;
1287                 }
1288                 return this.select(lc, this.selectPrevious);
1289             }
1290         } else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){
1291             return this.select(s.parentNode, this.selectPrevious);
1292         }
1293         return null;
1294     },
1295
1296     /**
1297      * Selects the node above the selected node in the tree, intelligently walking the nodes
1298      * @return TreeNode The new selection
1299      */
1300     selectNext : function(/* private */ s){
1301         if(!(s = s || this.selNode || this.lastSelNode)){
1302             return null;
1303         }
1304         // Here we pass in the current function to select to indicate the direction we're moving
1305         if(s.firstChild && s.isExpanded() && Ext.fly(s.ui.wrap).isVisible()){
1306              return this.select(s.firstChild, this.selectNext);
1307          }else if(s.nextSibling){
1308              return this.select(s.nextSibling, this.selectNext);
1309          }else if(s.parentNode){
1310             var newS = null;
1311             s.parentNode.bubble(function(){
1312                 if(this.nextSibling){
1313                     newS = this.getOwnerTree().selModel.select(this.nextSibling, this.selectNext);
1314                     return false;
1315                 }
1316             });
1317             return newS;
1318          }
1319         return null;
1320     },
1321
1322     onKeyDown : function(e){
1323         var s = this.selNode || this.lastSelNode;
1324         // undesirable, but required
1325         var sm = this;
1326         if(!s){
1327             return;
1328         }
1329         var k = e.getKey();
1330         switch(k){
1331              case e.DOWN:
1332                  e.stopEvent();
1333                  this.selectNext();
1334              break;
1335              case e.UP:
1336                  e.stopEvent();
1337                  this.selectPrevious();
1338              break;
1339              case e.RIGHT:
1340                  e.preventDefault();
1341                  if(s.hasChildNodes()){
1342                      if(!s.isExpanded()){
1343                          s.expand();
1344                      }else if(s.firstChild){
1345                          this.select(s.firstChild, e);
1346                      }
1347                  }
1348              break;
1349              case e.LEFT:
1350                  e.preventDefault();
1351                  if(s.hasChildNodes() && s.isExpanded()){
1352                      s.collapse();
1353                  }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){
1354                      this.select(s.parentNode, e);
1355                  }
1356              break;
1357         };
1358     }
1359 });
1360
1361 /**
1362  * @class Ext.tree.MultiSelectionModel
1363  * @extends Ext.util.Observable
1364  * Multi selection for a TreePanel.
1365  */
1366 Ext.tree.MultiSelectionModel = function(config){
1367    this.selNodes = [];
1368    this.selMap = {};
1369    this.addEvents(
1370        /**
1371         * @event selectionchange
1372         * Fires when the selected nodes change
1373         * @param {MultiSelectionModel} this
1374         * @param {Array} nodes Array of the selected nodes
1375         */
1376        'selectionchange'
1377    );
1378     Ext.apply(this, config);
1379     Ext.tree.MultiSelectionModel.superclass.constructor.call(this);
1380 };
1381
1382 Ext.extend(Ext.tree.MultiSelectionModel, Ext.util.Observable, {
1383     init : function(tree){
1384         this.tree = tree;
1385         tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);
1386         tree.on('click', this.onNodeClick, this);
1387     },
1388     
1389     onNodeClick : function(node, e){
1390         if(e.ctrlKey && this.isSelected(node)){
1391             this.unselect(node);
1392         }else{
1393             this.select(node, e, e.ctrlKey);
1394         }
1395     },
1396     
1397     /**
1398      * Select a node.
1399      * @param {TreeNode} node The node to select
1400      * @param {EventObject} e (optional) An event associated with the selection
1401      * @param {Boolean} keepExisting True to retain existing selections
1402      * @return {TreeNode} The selected node
1403      */
1404     select : function(node, e, keepExisting){
1405         if(keepExisting !== true){
1406             this.clearSelections(true);
1407         }
1408         if(this.isSelected(node)){
1409             this.lastSelNode = node;
1410             return node;
1411         }
1412         this.selNodes.push(node);
1413         this.selMap[node.id] = node;
1414         this.lastSelNode = node;
1415         node.ui.onSelectedChange(true);
1416         this.fireEvent('selectionchange', this, this.selNodes);
1417         return node;
1418     },
1419     
1420     /**
1421      * Deselect a node.
1422      * @param {TreeNode} node The node to unselect
1423      */
1424     unselect : function(node){
1425         if(this.selMap[node.id]){
1426             node.ui.onSelectedChange(false);
1427             var sn = this.selNodes;
1428             var index = sn.indexOf(node);
1429             if(index != -1){
1430                 this.selNodes.splice(index, 1);
1431             }
1432             delete this.selMap[node.id];
1433             this.fireEvent('selectionchange', this, this.selNodes);
1434         }
1435     },
1436     
1437     /**
1438      * Clear all selections
1439      */
1440     clearSelections : function(suppressEvent){
1441         var sn = this.selNodes;
1442         if(sn.length > 0){
1443             for(var i = 0, len = sn.length; i < len; i++){
1444                 sn[i].ui.onSelectedChange(false);
1445             }
1446             this.selNodes = [];
1447             this.selMap = {};
1448             if(suppressEvent !== true){
1449                 this.fireEvent('selectionchange', this, this.selNodes);
1450             }
1451         }
1452     },
1453     
1454     /**
1455      * Returns true if the node is selected
1456      * @param {TreeNode} node The node to check
1457      * @return {Boolean}
1458      */
1459     isSelected : function(node){
1460         return this.selMap[node.id] ? true : false;  
1461     },
1462     
1463     /**
1464      * Returns an array of the selected nodes
1465      * @return {Array}
1466      */
1467     getSelectedNodes : function(){
1468         return this.selNodes;    
1469     },
1470
1471     onKeyDown : Ext.tree.DefaultSelectionModel.prototype.onKeyDown,
1472
1473     selectNext : Ext.tree.DefaultSelectionModel.prototype.selectNext,
1474
1475     selectPrevious : Ext.tree.DefaultSelectionModel.prototype.selectPrevious
1476 });/**
1477  * @class Ext.data.Tree
1478  * @extends Ext.util.Observable
1479  * Represents a tree data structure and bubbles all the events for its nodes. The nodes
1480  * in the tree have most standard DOM functionality.
1481  * @constructor
1482  * @param {Node} root (optional) The root node
1483  */
1484 Ext.data.Tree = function(root){
1485    this.nodeHash = {};
1486    /**
1487     * The root node for this tree
1488     * @type Node
1489     */
1490    this.root = null;
1491    if(root){
1492        this.setRootNode(root);
1493    }
1494    this.addEvents(
1495        /**
1496         * @event append
1497         * Fires when a new child node is appended to a node in this tree.
1498         * @param {Tree} tree The owner tree
1499         * @param {Node} parent The parent node
1500         * @param {Node} node The newly appended node
1501         * @param {Number} index The index of the newly appended node
1502         */
1503        "append",
1504        /**
1505         * @event remove
1506         * Fires when a child node is removed from a node in this tree.
1507         * @param {Tree} tree The owner tree
1508         * @param {Node} parent The parent node
1509         * @param {Node} node The child node removed
1510         */
1511        "remove",
1512        /**
1513         * @event move
1514         * Fires when a node is moved to a new location in the tree
1515         * @param {Tree} tree The owner tree
1516         * @param {Node} node The node moved
1517         * @param {Node} oldParent The old parent of this node
1518         * @param {Node} newParent The new parent of this node
1519         * @param {Number} index The index it was moved to
1520         */
1521        "move",
1522        /**
1523         * @event insert
1524         * Fires when a new child node is inserted in a node in this tree.
1525         * @param {Tree} tree The owner tree
1526         * @param {Node} parent The parent node
1527         * @param {Node} node The child node inserted
1528         * @param {Node} refNode The child node the node was inserted before
1529         */
1530        "insert",
1531        /**
1532         * @event beforeappend
1533         * Fires before a new child is appended to a node in this tree, return false to cancel the append.
1534         * @param {Tree} tree The owner tree
1535         * @param {Node} parent The parent node
1536         * @param {Node} node The child node to be appended
1537         */
1538        "beforeappend",
1539        /**
1540         * @event beforeremove
1541         * Fires before a child is removed from a node in this tree, return false to cancel the remove.
1542         * @param {Tree} tree The owner tree
1543         * @param {Node} parent The parent node
1544         * @param {Node} node The child node to be removed
1545         */
1546        "beforeremove",
1547        /**
1548         * @event beforemove
1549         * Fires before a node is moved to a new location in the tree. Return false to cancel the move.
1550         * @param {Tree} tree The owner tree
1551         * @param {Node} node The node being moved
1552         * @param {Node} oldParent The parent of the node
1553         * @param {Node} newParent The new parent the node is moving to
1554         * @param {Number} index The index it is being moved to
1555         */
1556        "beforemove",
1557        /**
1558         * @event beforeinsert
1559         * Fires before a new child is inserted in a node in this tree, return false to cancel the insert.
1560         * @param {Tree} tree The owner tree
1561         * @param {Node} parent The parent node
1562         * @param {Node} node The child node to be inserted
1563         * @param {Node} refNode The child node the node is being inserted before
1564         */
1565        "beforeinsert"
1566    );
1567
1568     Ext.data.Tree.superclass.constructor.call(this);
1569 };
1570
1571 Ext.extend(Ext.data.Tree, Ext.util.Observable, {
1572     /**
1573      * @cfg {String} pathSeparator
1574      * The token used to separate paths in node ids (defaults to '/').
1575      */
1576     pathSeparator: "/",
1577
1578     // private
1579     proxyNodeEvent : function(){
1580         return this.fireEvent.apply(this, arguments);
1581     },
1582
1583     /**
1584      * Returns the root node for this tree.
1585      * @return {Node}
1586      */
1587     getRootNode : function(){
1588         return this.root;
1589     },
1590
1591     /**
1592      * Sets the root node for this tree.
1593      * @param {Node} node
1594      * @return {Node}
1595      */
1596     setRootNode : function(node){
1597         this.root = node;
1598         node.ownerTree = this;
1599         node.isRoot = true;
1600         this.registerNode(node);
1601         return node;
1602     },
1603
1604     /**
1605      * Gets a node in this tree by its id.
1606      * @param {String} id
1607      * @return {Node}
1608      */
1609     getNodeById : function(id){
1610         return this.nodeHash[id];
1611     },
1612
1613     // private
1614     registerNode : function(node){
1615         this.nodeHash[node.id] = node;
1616     },
1617
1618     // private
1619     unregisterNode : function(node){
1620         delete this.nodeHash[node.id];
1621     },
1622
1623     toString : function(){
1624         return "[Tree"+(this.id?" "+this.id:"")+"]";
1625     }
1626 });
1627
1628 /**
1629  * @class Ext.data.Node
1630  * @extends Ext.util.Observable
1631  * @cfg {Boolean} leaf true if this node is a leaf and does not have children
1632  * @cfg {String} id The id for this node. If one is not specified, one is generated.
1633  * @constructor
1634  * @param {Object} attributes The attributes/config for the node
1635  */
1636 Ext.data.Node = function(attributes){
1637     /**
1638      * The attributes supplied for the node. You can use this property to access any custom attributes you supplied.
1639      * @type {Object}
1640      */
1641     this.attributes = attributes || {};
1642     this.leaf = this.attributes.leaf;
1643     /**
1644      * The node id. @type String
1645      */
1646     this.id = this.attributes.id;
1647     if(!this.id){
1648         this.id = Ext.id(null, "xnode-");
1649         this.attributes.id = this.id;
1650     }
1651     /**
1652      * All child nodes of this node. @type Array
1653      */
1654     this.childNodes = [];
1655     if(!this.childNodes.indexOf){ // indexOf is a must
1656         this.childNodes.indexOf = function(o){
1657             for(var i = 0, len = this.length; i < len; i++){
1658                 if(this[i] == o){
1659                     return i;
1660                 }
1661             }
1662             return -1;
1663         };
1664     }
1665     /**
1666      * The parent node for this node. @type Node
1667      */
1668     this.parentNode = null;
1669     /**
1670      * The first direct child node of this node, or null if this node has no child nodes. @type Node
1671      */
1672     this.firstChild = null;
1673     /**
1674      * The last direct child node of this node, or null if this node has no child nodes. @type Node
1675      */
1676     this.lastChild = null;
1677     /**
1678      * The node immediately preceding this node in the tree, or null if there is no sibling node. @type Node
1679      */
1680     this.previousSibling = null;
1681     /**
1682      * The node immediately following this node in the tree, or null if there is no sibling node. @type Node
1683      */
1684     this.nextSibling = null;
1685
1686     this.addEvents({
1687        /**
1688         * @event append
1689         * Fires when a new child node is appended
1690         * @param {Tree} tree The owner tree
1691         * @param {Node} this This node
1692         * @param {Node} node The newly appended node
1693         * @param {Number} index The index of the newly appended node
1694         */
1695        "append" : true,
1696        /**
1697         * @event remove
1698         * Fires when a child node is removed
1699         * @param {Tree} tree The owner tree
1700         * @param {Node} this This node
1701         * @param {Node} node The removed node
1702         */
1703        "remove" : true,
1704        /**
1705         * @event move
1706         * Fires when this node is moved to a new location in the tree
1707         * @param {Tree} tree The owner tree
1708         * @param {Node} this This node
1709         * @param {Node} oldParent The old parent of this node
1710         * @param {Node} newParent The new parent of this node
1711         * @param {Number} index The index it was moved to
1712         */
1713        "move" : true,
1714        /**
1715         * @event insert
1716         * Fires when a new child node is inserted.
1717         * @param {Tree} tree The owner tree
1718         * @param {Node} this This node
1719         * @param {Node} node The child node inserted
1720         * @param {Node} refNode The child node the node was inserted before
1721         */
1722        "insert" : true,
1723        /**
1724         * @event beforeappend
1725         * Fires before a new child is appended, return false to cancel the append.
1726         * @param {Tree} tree The owner tree
1727         * @param {Node} this This node
1728         * @param {Node} node The child node to be appended
1729         */
1730        "beforeappend" : true,
1731        /**
1732         * @event beforeremove
1733         * Fires before a child is removed, return false to cancel the remove.
1734         * @param {Tree} tree The owner tree
1735         * @param {Node} this This node
1736         * @param {Node} node The child node to be removed
1737         */
1738        "beforeremove" : true,
1739        /**
1740         * @event beforemove
1741         * Fires before this node is moved to a new location in the tree. Return false to cancel the move.
1742         * @param {Tree} tree The owner tree
1743         * @param {Node} this This node
1744         * @param {Node} oldParent The parent of this node
1745         * @param {Node} newParent The new parent this node is moving to
1746         * @param {Number} index The index it is being moved to
1747         */
1748        "beforemove" : true,
1749        /**
1750         * @event beforeinsert
1751         * Fires before a new child is inserted, return false to cancel the insert.
1752         * @param {Tree} tree The owner tree
1753         * @param {Node} this This node
1754         * @param {Node} node The child node to be inserted
1755         * @param {Node} refNode The child node the node is being inserted before
1756         */
1757        "beforeinsert" : true
1758    });
1759     this.listeners = this.attributes.listeners;
1760     Ext.data.Node.superclass.constructor.call(this);
1761 };
1762
1763 Ext.extend(Ext.data.Node, Ext.util.Observable, {
1764     // private
1765     fireEvent : function(evtName){
1766         // first do standard event for this node
1767         if(Ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
1768             return false;
1769         }
1770         // then bubble it up to the tree if the event wasn't cancelled
1771         var ot = this.getOwnerTree();
1772         if(ot){
1773             if(ot.proxyNodeEvent.apply(ot, arguments) === false){
1774                 return false;
1775             }
1776         }
1777         return true;
1778     },
1779
1780     /**
1781      * Returns true if this node is a leaf
1782      * @return {Boolean}
1783      */
1784     isLeaf : function(){
1785         return this.leaf === true;
1786     },
1787
1788     // private
1789     setFirstChild : function(node){
1790         this.firstChild = node;
1791     },
1792
1793     //private
1794     setLastChild : function(node){
1795         this.lastChild = node;
1796     },
1797
1798
1799     /**
1800      * Returns true if this node is the last child of its parent
1801      * @return {Boolean}
1802      */
1803     isLast : function(){
1804        return (!this.parentNode ? true : this.parentNode.lastChild == this);
1805     },
1806
1807     /**
1808      * Returns true if this node is the first child of its parent
1809      * @return {Boolean}
1810      */
1811     isFirst : function(){
1812        return (!this.parentNode ? true : this.parentNode.firstChild == this);
1813     },
1814
1815     /**
1816      * Returns true if this node has one or more child nodes, else false.
1817      * @return {Boolean}
1818      */
1819     hasChildNodes : function(){
1820         return !this.isLeaf() && this.childNodes.length > 0;
1821     },
1822
1823     /**
1824      * Returns true if this node has one or more child nodes, or if the <tt>expandable</tt>
1825      * node attribute is explicitly specified as true (see {@link #attributes}), otherwise returns false.
1826      * @return {Boolean}
1827      */
1828     isExpandable : function(){
1829         return this.attributes.expandable || this.hasChildNodes();
1830     },
1831
1832     /**
1833      * Insert node(s) as the last child node of this node.
1834      * @param {Node/Array} node The node or Array of nodes to append
1835      * @return {Node} The appended node if single append, or null if an array was passed
1836      */
1837     appendChild : function(node){
1838         var multi = false;
1839         if(Ext.isArray(node)){
1840             multi = node;
1841         }else if(arguments.length > 1){
1842             multi = arguments;
1843         }
1844         // if passed an array or multiple args do them one by one
1845         if(multi){
1846             for(var i = 0, len = multi.length; i < len; i++) {
1847                 this.appendChild(multi[i]);
1848             }
1849         }else{
1850             if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
1851                 return false;
1852             }
1853             var index = this.childNodes.length;
1854             var oldParent = node.parentNode;
1855             // it's a move, make sure we move it cleanly
1856             if(oldParent){
1857                 if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
1858                     return false;
1859                 }
1860                 oldParent.removeChild(node);
1861             }
1862             index = this.childNodes.length;
1863             if(index === 0){
1864                 this.setFirstChild(node);
1865             }
1866             this.childNodes.push(node);
1867             node.parentNode = this;
1868             var ps = this.childNodes[index-1];
1869             if(ps){
1870                 node.previousSibling = ps;
1871                 ps.nextSibling = node;
1872             }else{
1873                 node.previousSibling = null;
1874             }
1875             node.nextSibling = null;
1876             this.setLastChild(node);
1877             node.setOwnerTree(this.getOwnerTree());
1878             this.fireEvent("append", this.ownerTree, this, node, index);
1879             if(oldParent){
1880                 node.fireEvent("move", this.ownerTree, node, oldParent, this, index);
1881             }
1882             return node;
1883         }
1884     },
1885
1886     /**
1887      * Removes a child node from this node.
1888      * @param {Node} node The node to remove
1889      * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
1890      * @return {Node} The removed node
1891      */
1892     removeChild : function(node, destroy){
1893         var index = this.childNodes.indexOf(node);
1894         if(index == -1){
1895             return false;
1896         }
1897         if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
1898             return false;
1899         }
1900
1901         // remove it from childNodes collection
1902         this.childNodes.splice(index, 1);
1903
1904         // update siblings
1905         if(node.previousSibling){
1906             node.previousSibling.nextSibling = node.nextSibling;
1907         }
1908         if(node.nextSibling){
1909             node.nextSibling.previousSibling = node.previousSibling;
1910         }
1911
1912         // update child refs
1913         if(this.firstChild == node){
1914             this.setFirstChild(node.nextSibling);
1915         }
1916         if(this.lastChild == node){
1917             this.setLastChild(node.previousSibling);
1918         }
1919
1920         this.fireEvent("remove", this.ownerTree, this, node);
1921         if(destroy){
1922             node.destroy(true);
1923         }else{
1924             node.clear();
1925         }
1926         return node;
1927     },
1928
1929     // private
1930     clear : function(destroy){
1931         // clear any references from the node
1932         this.setOwnerTree(null, destroy);
1933         this.parentNode = this.previousSibling = this.nextSibling = null;
1934         if(destroy){
1935             this.firstChild = this.lastChild = null;
1936         }
1937     },
1938
1939     /**
1940      * Destroys the node.
1941      */
1942     destroy : function(/* private */ silent){
1943         /*
1944          * Silent is to be used in a number of cases
1945          * 1) When setRootNode is called.
1946          * 2) When destroy on the tree is called
1947          * 3) For destroying child nodes on a node
1948          */
1949         if(silent === true){
1950             this.purgeListeners();
1951             this.clear(true);
1952             Ext.each(this.childNodes, function(n){
1953                 n.destroy(true);
1954             });
1955             this.childNodes = null;
1956         }else{
1957             this.remove(true);
1958         }
1959     },
1960
1961     /**
1962      * Inserts the first node before the second node in this nodes childNodes collection.
1963      * @param {Node} node The node to insert
1964      * @param {Node} refNode The node to insert before (if null the node is appended)
1965      * @return {Node} The inserted node
1966      */
1967     insertBefore : function(node, refNode){
1968         if(!refNode){ // like standard Dom, refNode can be null for append
1969             return this.appendChild(node);
1970         }
1971         // nothing to do
1972         if(node == refNode){
1973             return false;
1974         }
1975
1976         if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
1977             return false;
1978         }
1979         var index = this.childNodes.indexOf(refNode);
1980         var oldParent = node.parentNode;
1981         var refIndex = index;
1982
1983         // when moving internally, indexes will change after remove
1984         if(oldParent == this && this.childNodes.indexOf(node) < index){
1985             refIndex--;
1986         }
1987
1988         // it's a move, make sure we move it cleanly
1989         if(oldParent){
1990             if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
1991                 return false;
1992             }
1993             oldParent.removeChild(node);
1994         }
1995         if(refIndex === 0){
1996             this.setFirstChild(node);
1997         }
1998         this.childNodes.splice(refIndex, 0, node);
1999         node.parentNode = this;
2000         var ps = this.childNodes[refIndex-1];
2001         if(ps){
2002             node.previousSibling = ps;
2003             ps.nextSibling = node;
2004         }else{
2005             node.previousSibling = null;
2006         }
2007         node.nextSibling = refNode;
2008         refNode.previousSibling = node;
2009         node.setOwnerTree(this.getOwnerTree());
2010         this.fireEvent("insert", this.ownerTree, this, node, refNode);
2011         if(oldParent){
2012             node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
2013         }
2014         return node;
2015     },
2016
2017     /**
2018      * Removes this node from its parent
2019      * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
2020      * @return {Node} this
2021      */
2022     remove : function(destroy){
2023         if (this.parentNode) {
2024             this.parentNode.removeChild(this, destroy);
2025         }
2026         return this;
2027     },
2028
2029     /**
2030      * Removes all child nodes from this node.
2031      * @param {Boolean} destroy <tt>true</tt> to destroy the node upon removal. Defaults to <tt>false</tt>.
2032      * @return {Node} this
2033      */
2034     removeAll : function(destroy){
2035         var cn = this.childNodes,
2036             n;
2037         while((n = cn[0])){
2038             this.removeChild(n, destroy);
2039         }
2040         return this;
2041     },
2042
2043     /**
2044      * Returns the child node at the specified index.
2045      * @param {Number} index
2046      * @return {Node}
2047      */
2048     item : function(index){
2049         return this.childNodes[index];
2050     },
2051
2052     /**
2053      * Replaces one child node in this node with another.
2054      * @param {Node} newChild The replacement node
2055      * @param {Node} oldChild The node to replace
2056      * @return {Node} The replaced node
2057      */
2058     replaceChild : function(newChild, oldChild){
2059         var s = oldChild ? oldChild.nextSibling : null;
2060         this.removeChild(oldChild);
2061         this.insertBefore(newChild, s);
2062         return oldChild;
2063     },
2064
2065     /**
2066      * Returns the index of a child node
2067      * @param {Node} node
2068      * @return {Number} The index of the node or -1 if it was not found
2069      */
2070     indexOf : function(child){
2071         return this.childNodes.indexOf(child);
2072     },
2073
2074     /**
2075      * Returns the tree this node is in.
2076      * @return {Tree}
2077      */
2078     getOwnerTree : function(){
2079         // if it doesn't have one, look for one
2080         if(!this.ownerTree){
2081             var p = this;
2082             while(p){
2083                 if(p.ownerTree){
2084                     this.ownerTree = p.ownerTree;
2085                     break;
2086                 }
2087                 p = p.parentNode;
2088             }
2089         }
2090         return this.ownerTree;
2091     },
2092
2093     /**
2094      * Returns depth of this node (the root node has a depth of 0)
2095      * @return {Number}
2096      */
2097     getDepth : function(){
2098         var depth = 0;
2099         var p = this;
2100         while(p.parentNode){
2101             ++depth;
2102             p = p.parentNode;
2103         }
2104         return depth;
2105     },
2106
2107     // private
2108     setOwnerTree : function(tree, destroy){
2109         // if it is a move, we need to update everyone
2110         if(tree != this.ownerTree){
2111             if(this.ownerTree){
2112                 this.ownerTree.unregisterNode(this);
2113             }
2114             this.ownerTree = tree;
2115             // If we're destroying, we don't need to recurse since it will be called on each child node
2116             if(destroy !== true){
2117                 Ext.each(this.childNodes, function(n){
2118                     n.setOwnerTree(tree);
2119                 });
2120             }
2121             if(tree){
2122                 tree.registerNode(this);
2123             }
2124         }
2125     },
2126
2127     /**
2128      * Changes the id of this node.
2129      * @param {String} id The new id for the node.
2130      */
2131     setId: function(id){
2132         if(id !== this.id){
2133             var t = this.ownerTree;
2134             if(t){
2135                 t.unregisterNode(this);
2136             }
2137             this.id = this.attributes.id = id;
2138             if(t){
2139                 t.registerNode(this);
2140             }
2141             this.onIdChange(id);
2142         }
2143     },
2144
2145     // private
2146     onIdChange: Ext.emptyFn,
2147
2148     /**
2149      * Returns the path for this node. The path can be used to expand or select this node programmatically.
2150      * @param {String} attr (optional) The attr to use for the path (defaults to the node's id)
2151      * @return {String} The path
2152      */
2153     getPath : function(attr){
2154         attr = attr || "id";
2155         var p = this.parentNode;
2156         var b = [this.attributes[attr]];
2157         while(p){
2158             b.unshift(p.attributes[attr]);
2159             p = p.parentNode;
2160         }
2161         var sep = this.getOwnerTree().pathSeparator;
2162         return sep + b.join(sep);
2163     },
2164
2165     /**
2166      * Bubbles up the tree from this node, calling the specified function with each node. The arguments to the function
2167      * will be the args provided or the current node. If the function returns false at any point,
2168      * the bubble is stopped.
2169      * @param {Function} fn The function to call
2170      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
2171      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
2172      */
2173     bubble : function(fn, scope, args){
2174         var p = this;
2175         while(p){
2176             if(fn.apply(scope || p, args || [p]) === false){
2177                 break;
2178             }
2179             p = p.parentNode;
2180         }
2181     },
2182
2183     /**
2184      * Cascades down the tree from this node, calling the specified function with each node. The arguments to the function
2185      * will be the args provided or the current node. If the function returns false at any point,
2186      * the cascade is stopped on that branch.
2187      * @param {Function} fn The function to call
2188      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
2189      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
2190      */
2191     cascade : function(fn, scope, args){
2192         if(fn.apply(scope || this, args || [this]) !== false){
2193             var cs = this.childNodes;
2194             for(var i = 0, len = cs.length; i < len; i++) {
2195                 cs[i].cascade(fn, scope, args);
2196             }
2197         }
2198     },
2199
2200     /**
2201      * Interates the child nodes of this node, calling the specified function with each node. The arguments to the function
2202      * will be the args provided or the current node. If the function returns false at any point,
2203      * the iteration stops.
2204      * @param {Function} fn The function to call
2205      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node in the iteration.
2206      * @param {Array} args (optional) The args to call the function with (default to passing the current Node)
2207      */
2208     eachChild : function(fn, scope, args){
2209         var cs = this.childNodes;
2210         for(var i = 0, len = cs.length; i < len; i++) {
2211             if(fn.apply(scope || this, args || [cs[i]]) === false){
2212                 break;
2213             }
2214         }
2215     },
2216
2217     /**
2218      * Finds the first child that has the attribute with the specified value.
2219      * @param {String} attribute The attribute name
2220      * @param {Mixed} value The value to search for
2221      * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
2222      * @return {Node} The found child or null if none was found
2223      */
2224     findChild : function(attribute, value, deep){
2225         return this.findChildBy(function(){
2226             return this.attributes[attribute] == value;
2227         }, null, deep);
2228     },
2229
2230     /**
2231      * Finds the first child by a custom function. The child matches if the function passed returns <code>true</code>.
2232      * @param {Function} fn A function which must return <code>true</code> if the passed Node is the required Node.
2233      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the Node being tested.
2234      * @param {Boolean} deep (Optional) True to search through nodes deeper than the immediate children
2235      * @return {Node} The found child or null if none was found
2236      */
2237     findChildBy : function(fn, scope, deep){
2238         var cs = this.childNodes,
2239             len = cs.length,
2240             i = 0,
2241             n,
2242             res;
2243         for(; i < len; i++){
2244             n = cs[i];
2245             if(fn.call(scope || n, n) === true){
2246                 return n;
2247             }else if (deep){
2248                 res = n.findChildBy(fn, scope, deep);
2249                 if(res != null){
2250                     return res;
2251                 }
2252             }
2253             
2254         }
2255         return null;
2256     },
2257
2258     /**
2259      * Sorts this nodes children using the supplied sort function.
2260      * @param {Function} fn A function which, when passed two Nodes, returns -1, 0 or 1 depending upon required sort order.
2261      * @param {Object} scope (optional)The scope (<code>this</code> reference) in which the function is executed. Defaults to the browser window.
2262      */
2263     sort : function(fn, scope){
2264         var cs = this.childNodes;
2265         var len = cs.length;
2266         if(len > 0){
2267             var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
2268             cs.sort(sortFn);
2269             for(var i = 0; i < len; i++){
2270                 var n = cs[i];
2271                 n.previousSibling = cs[i-1];
2272                 n.nextSibling = cs[i+1];
2273                 if(i === 0){
2274                     this.setFirstChild(n);
2275                 }
2276                 if(i == len-1){
2277                     this.setLastChild(n);
2278                 }
2279             }
2280         }
2281     },
2282
2283     /**
2284      * Returns true if this node is an ancestor (at any point) of the passed node.
2285      * @param {Node} node
2286      * @return {Boolean}
2287      */
2288     contains : function(node){
2289         return node.isAncestor(this);
2290     },
2291
2292     /**
2293      * Returns true if the passed node is an ancestor (at any point) of this node.
2294      * @param {Node} node
2295      * @return {Boolean}
2296      */
2297     isAncestor : function(node){
2298         var p = this.parentNode;
2299         while(p){
2300             if(p == node){
2301                 return true;
2302             }
2303             p = p.parentNode;
2304         }
2305         return false;
2306     },
2307
2308     toString : function(){
2309         return "[Node"+(this.id?" "+this.id:"")+"]";
2310     }
2311 });/**
2312  * @class Ext.tree.TreeNode
2313  * @extends Ext.data.Node
2314  * @cfg {String} text The text for this node
2315  * @cfg {Boolean} expanded true to start the node expanded
2316  * @cfg {Boolean} allowDrag False to make this node undraggable if {@link #draggable} = true (defaults to true)
2317  * @cfg {Boolean} allowDrop False if this node cannot have child nodes dropped on it (defaults to true)
2318  * @cfg {Boolean} disabled true to start the node disabled
2319  * @cfg {String} icon The path to an icon for the node. The preferred way to do this
2320  * is to use the cls or iconCls attributes and add the icon via a CSS background image.
2321  * @cfg {String} cls A css class to be added to the node
2322  * @cfg {String} iconCls A css class to be added to the nodes icon element for applying css background images
2323  * @cfg {String} href URL of the link used for the node (defaults to #)
2324  * @cfg {String} hrefTarget target frame for the link
2325  * @cfg {Boolean} hidden True to render hidden. (Defaults to false).
2326  * @cfg {String} qtip An Ext QuickTip for the node
2327  * @cfg {Boolean} expandable If set to true, the node will always show a plus/minus icon, even when empty
2328  * @cfg {String} qtipCfg An Ext QuickTip config for the node (used instead of qtip)
2329  * @cfg {Boolean} singleClickExpand True for single click expand on this node
2330  * @cfg {Function} uiProvider A UI <b>class</b> to use for this node (defaults to Ext.tree.TreeNodeUI)
2331  * @cfg {Boolean} checked True to render a checked checkbox for this node, false to render an unchecked checkbox
2332  * (defaults to undefined with no checkbox rendered)
2333  * @cfg {Boolean} draggable True to make this node draggable (defaults to false)
2334  * @cfg {Boolean} isTarget False to not allow this node to act as a drop target (defaults to true)
2335  * @cfg {Boolean} allowChildren False to not allow this node to have child nodes (defaults to true)
2336  * @cfg {Boolean} editable False to not allow this node to be edited by an {@link Ext.tree.TreeEditor} (defaults to true)
2337  * @constructor
2338  * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node
2339  */
2340 Ext.tree.TreeNode = function(attributes){
2341     attributes = attributes || {};
2342     if(Ext.isString(attributes)){
2343         attributes = {text: attributes};
2344     }
2345     this.childrenRendered = false;
2346     this.rendered = false;
2347     Ext.tree.TreeNode.superclass.constructor.call(this, attributes);
2348     this.expanded = attributes.expanded === true;
2349     this.isTarget = attributes.isTarget !== false;
2350     this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;
2351     this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;
2352
2353     /**
2354      * Read-only. The text for this node. To change it use <code>{@link #setText}</code>.
2355      * @type String
2356      */
2357     this.text = attributes.text;
2358     /**
2359      * True if this node is disabled.
2360      * @type Boolean
2361      */
2362     this.disabled = attributes.disabled === true;
2363     /**
2364      * True if this node is hidden.
2365      * @type Boolean
2366      */
2367     this.hidden = attributes.hidden === true;
2368
2369     this.addEvents(
2370         /**
2371         * @event textchange
2372         * Fires when the text for this node is changed
2373         * @param {Node} this This node
2374         * @param {String} text The new text
2375         * @param {String} oldText The old text
2376         */
2377         'textchange',
2378         /**
2379         * @event beforeexpand
2380         * Fires before this node is expanded, return false to cancel.
2381         * @param {Node} this This node
2382         * @param {Boolean} deep
2383         * @param {Boolean} anim
2384         */
2385         'beforeexpand',
2386         /**
2387         * @event beforecollapse
2388         * Fires before this node is collapsed, return false to cancel.
2389         * @param {Node} this This node
2390         * @param {Boolean} deep
2391         * @param {Boolean} anim
2392         */
2393         'beforecollapse',
2394         /**
2395         * @event expand
2396         * Fires when this node is expanded
2397         * @param {Node} this This node
2398         */
2399         'expand',
2400         /**
2401         * @event disabledchange
2402         * Fires when the disabled status of this node changes
2403         * @param {Node} this This node
2404         * @param {Boolean} disabled
2405         */
2406         'disabledchange',
2407         /**
2408         * @event collapse
2409         * Fires when this node is collapsed
2410         * @param {Node} this This node
2411         */
2412         'collapse',
2413         /**
2414         * @event beforeclick
2415         * Fires before click processing. Return false to cancel the default action.
2416         * @param {Node} this This node
2417         * @param {Ext.EventObject} e The event object
2418         */
2419         'beforeclick',
2420         /**
2421         * @event click
2422         * Fires when this node is clicked
2423         * @param {Node} this This node
2424         * @param {Ext.EventObject} e The event object
2425         */
2426         'click',
2427         /**
2428         * @event checkchange
2429         * Fires when a node with a checkbox's checked property changes
2430         * @param {Node} this This node
2431         * @param {Boolean} checked
2432         */
2433         'checkchange',
2434         /**
2435         * @event beforedblclick
2436         * Fires before double click processing. Return false to cancel the default action.
2437         * @param {Node} this This node
2438         * @param {Ext.EventObject} e The event object
2439         */
2440         'beforedblclick',
2441         /**
2442         * @event dblclick
2443         * Fires when this node is double clicked
2444         * @param {Node} this This node
2445         * @param {Ext.EventObject} e The event object
2446         */
2447         'dblclick',
2448         /**
2449         * @event contextmenu
2450         * Fires when this node is right clicked
2451         * @param {Node} this This node
2452         * @param {Ext.EventObject} e The event object
2453         */
2454         'contextmenu',
2455         /**
2456         * @event beforechildrenrendered
2457         * Fires right before the child nodes for this node are rendered
2458         * @param {Node} this This node
2459         */
2460         'beforechildrenrendered'
2461     );
2462
2463     var uiClass = this.attributes.uiProvider || this.defaultUI || Ext.tree.TreeNodeUI;
2464
2465     /**
2466      * Read-only. The UI for this node
2467      * @type TreeNodeUI
2468      */
2469     this.ui = new uiClass(this);
2470 };
2471 Ext.extend(Ext.tree.TreeNode, Ext.data.Node, {
2472     preventHScroll : true,
2473     /**
2474      * Returns true if this node is expanded
2475      * @return {Boolean}
2476      */
2477     isExpanded : function(){
2478         return this.expanded;
2479     },
2480
2481 /**
2482  * Returns the UI object for this node.
2483  * @return {TreeNodeUI} The object which is providing the user interface for this tree
2484  * node. Unless otherwise specified in the {@link #uiProvider}, this will be an instance
2485  * of {@link Ext.tree.TreeNodeUI}
2486  */
2487     getUI : function(){
2488         return this.ui;
2489     },
2490
2491     getLoader : function(){
2492         var owner;
2493         return this.loader || ((owner = this.getOwnerTree()) && owner.loader ? owner.loader : (this.loader = new Ext.tree.TreeLoader()));
2494     },
2495
2496     // private override
2497     setFirstChild : function(node){
2498         var of = this.firstChild;
2499         Ext.tree.TreeNode.superclass.setFirstChild.call(this, node);
2500         if(this.childrenRendered && of && node != of){
2501             of.renderIndent(true, true);
2502         }
2503         if(this.rendered){
2504             this.renderIndent(true, true);
2505         }
2506     },
2507
2508     // private override
2509     setLastChild : function(node){
2510         var ol = this.lastChild;
2511         Ext.tree.TreeNode.superclass.setLastChild.call(this, node);
2512         if(this.childrenRendered && ol && node != ol){
2513             ol.renderIndent(true, true);
2514         }
2515         if(this.rendered){
2516             this.renderIndent(true, true);
2517         }
2518     },
2519
2520     // these methods are overridden to provide lazy rendering support
2521     // private override
2522     appendChild : function(n){
2523         if(!n.render && !Ext.isArray(n)){
2524             n = this.getLoader().createNode(n);
2525         }
2526         var node = Ext.tree.TreeNode.superclass.appendChild.call(this, n);
2527         if(node && this.childrenRendered){
2528             node.render();
2529         }
2530         this.ui.updateExpandIcon();
2531         return node;
2532     },
2533
2534     // private override
2535     removeChild : function(node, destroy){
2536         this.ownerTree.getSelectionModel().unselect(node);
2537         Ext.tree.TreeNode.superclass.removeChild.apply(this, arguments);
2538         // only update the ui if we're not destroying
2539         if(!destroy){
2540             // if it's been rendered remove dom node
2541             if(node.ui.rendered){
2542                 node.ui.remove();
2543             }
2544             if(this.childNodes.length < 1){
2545                 this.collapse(false, false);
2546             }else{
2547                 this.ui.updateExpandIcon();
2548             }
2549             if(!this.firstChild && !this.isHiddenRoot()){
2550                 this.childrenRendered = false;
2551             }
2552         }
2553         return node;
2554     },
2555
2556     // private override
2557     insertBefore : function(node, refNode){
2558         if(!node.render){
2559             node = this.getLoader().createNode(node);
2560         }
2561         var newNode = Ext.tree.TreeNode.superclass.insertBefore.call(this, node, refNode);
2562         if(newNode && refNode && this.childrenRendered){
2563             node.render();
2564         }
2565         this.ui.updateExpandIcon();
2566         return newNode;
2567     },
2568
2569     /**
2570      * Sets the text for this node
2571      * @param {String} text
2572      */
2573     setText : function(text){
2574         var oldText = this.text;
2575         this.text = this.attributes.text = text;
2576         if(this.rendered){ // event without subscribing
2577             this.ui.onTextChange(this, text, oldText);
2578         }
2579         this.fireEvent('textchange', this, text, oldText);
2580     },
2581
2582     /**
2583      * Triggers selection of this node
2584      */
2585     select : function(){
2586         var t = this.getOwnerTree();
2587         if(t){
2588             t.getSelectionModel().select(this);
2589         }
2590     },
2591
2592     /**
2593      * Triggers deselection of this node
2594      * @param {Boolean} silent (optional) True to stop selection change events from firing.
2595      */
2596     unselect : function(silent){
2597         var t = this.getOwnerTree();
2598         if(t){
2599             t.getSelectionModel().unselect(this, silent);
2600         }
2601     },
2602
2603     /**
2604      * Returns true if this node is selected
2605      * @return {Boolean}
2606      */
2607     isSelected : function(){
2608         var t = this.getOwnerTree();
2609         return t ? t.getSelectionModel().isSelected(this) : false;
2610     },
2611
2612     /**
2613      * Expand this node.
2614      * @param {Boolean} deep (optional) True to expand all children as well
2615      * @param {Boolean} anim (optional) false to cancel the default animation
2616      * @param {Function} callback (optional) A callback to be called when
2617      * expanding this node completes (does not wait for deep expand to complete).
2618      * Called with 1 parameter, this node.
2619      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this TreeNode.
2620      */
2621     expand : function(deep, anim, callback, scope){
2622         if(!this.expanded){
2623             if(this.fireEvent('beforeexpand', this, deep, anim) === false){
2624                 return;
2625             }
2626             if(!this.childrenRendered){
2627                 this.renderChildren();
2628             }
2629             this.expanded = true;
2630             if(!this.isHiddenRoot() && (this.getOwnerTree().animate && anim !== false) || anim){
2631                 this.ui.animExpand(function(){
2632                     this.fireEvent('expand', this);
2633                     this.runCallback(callback, scope || this, [this]);
2634                     if(deep === true){
2635                         this.expandChildNodes(true);
2636                     }
2637                 }.createDelegate(this));
2638                 return;
2639             }else{
2640                 this.ui.expand();
2641                 this.fireEvent('expand', this);
2642                 this.runCallback(callback, scope || this, [this]);
2643             }
2644         }else{
2645            this.runCallback(callback, scope || this, [this]);
2646         }
2647         if(deep === true){
2648             this.expandChildNodes(true);
2649         }
2650     },
2651
2652     runCallback : function(cb, scope, args){
2653         if(Ext.isFunction(cb)){
2654             cb.apply(scope, args);
2655         }
2656     },
2657
2658     isHiddenRoot : function(){
2659         return this.isRoot && !this.getOwnerTree().rootVisible;
2660     },
2661
2662     /**
2663      * Collapse this node.
2664      * @param {Boolean} deep (optional) True to collapse all children as well
2665      * @param {Boolean} anim (optional) false to cancel the default animation
2666      * @param {Function} callback (optional) A callback to be called when
2667      * expanding this node completes (does not wait for deep expand to complete).
2668      * Called with 1 parameter, this node.
2669      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this TreeNode.
2670      */
2671     collapse : function(deep, anim, callback, scope){
2672         if(this.expanded && !this.isHiddenRoot()){
2673             if(this.fireEvent('beforecollapse', this, deep, anim) === false){
2674                 return;
2675             }
2676             this.expanded = false;
2677             if((this.getOwnerTree().animate && anim !== false) || anim){
2678                 this.ui.animCollapse(function(){
2679                     this.fireEvent('collapse', this);
2680                     this.runCallback(callback, scope || this, [this]);
2681                     if(deep === true){
2682                         this.collapseChildNodes(true);
2683                     }
2684                 }.createDelegate(this));
2685                 return;
2686             }else{
2687                 this.ui.collapse();
2688                 this.fireEvent('collapse', this);
2689                 this.runCallback(callback, scope || this, [this]);
2690             }
2691         }else if(!this.expanded){
2692             this.runCallback(callback, scope || this, [this]);
2693         }
2694         if(deep === true){
2695             var cs = this.childNodes;
2696             for(var i = 0, len = cs.length; i < len; i++) {
2697                 cs[i].collapse(true, false);
2698             }
2699         }
2700     },
2701
2702     // private
2703     delayedExpand : function(delay){
2704         if(!this.expandProcId){
2705             this.expandProcId = this.expand.defer(delay, this);
2706         }
2707     },
2708
2709     // private
2710     cancelExpand : function(){
2711         if(this.expandProcId){
2712             clearTimeout(this.expandProcId);
2713         }
2714         this.expandProcId = false;
2715     },
2716
2717     /**
2718      * Toggles expanded/collapsed state of the node
2719      */
2720     toggle : function(){
2721         if(this.expanded){
2722             this.collapse();
2723         }else{
2724             this.expand();
2725         }
2726     },
2727
2728     /**
2729      * Ensures all parent nodes are expanded, and if necessary, scrolls
2730      * the node into view.
2731      * @param {Function} callback (optional) A function to call when the node has been made visible.
2732      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this TreeNode.
2733      */
2734     ensureVisible : function(callback, scope){
2735         var tree = this.getOwnerTree();
2736         tree.expandPath(this.parentNode ? this.parentNode.getPath() : this.getPath(), false, function(){
2737             var node = tree.getNodeById(this.id);  // Somehow if we don't do this, we lose changes that happened to node in the meantime
2738             tree.getTreeEl().scrollChildIntoView(node.ui.anchor);
2739             this.runCallback(callback, scope || this, [this]);
2740         }.createDelegate(this));
2741     },
2742
2743     /**
2744      * Expand all child nodes
2745      * @param {Boolean} deep (optional) true if the child nodes should also expand their child nodes
2746      */
2747     expandChildNodes : function(deep){
2748         var cs = this.childNodes;
2749         for(var i = 0, len = cs.length; i < len; i++) {
2750                 cs[i].expand(deep);
2751         }
2752     },
2753
2754     /**
2755      * Collapse all child nodes
2756      * @param {Boolean} deep (optional) true if the child nodes should also collapse their child nodes
2757      */
2758     collapseChildNodes : function(deep){
2759         var cs = this.childNodes;
2760         for(var i = 0, len = cs.length; i < len; i++) {
2761                 cs[i].collapse(deep);
2762         }
2763     },
2764
2765     /**
2766      * Disables this node
2767      */
2768     disable : function(){
2769         this.disabled = true;
2770         this.unselect();
2771         if(this.rendered && this.ui.onDisableChange){ // event without subscribing
2772             this.ui.onDisableChange(this, true);
2773         }
2774         this.fireEvent('disabledchange', this, true);
2775     },
2776
2777     /**
2778      * Enables this node
2779      */
2780     enable : function(){
2781         this.disabled = false;
2782         if(this.rendered && this.ui.onDisableChange){ // event without subscribing
2783             this.ui.onDisableChange(this, false);
2784         }
2785         this.fireEvent('disabledchange', this, false);
2786     },
2787
2788     // private
2789     renderChildren : function(suppressEvent){
2790         if(suppressEvent !== false){
2791             this.fireEvent('beforechildrenrendered', this);
2792         }
2793         var cs = this.childNodes;
2794         for(var i = 0, len = cs.length; i < len; i++){
2795             cs[i].render(true);
2796         }
2797         this.childrenRendered = true;
2798     },
2799
2800     // private
2801     sort : function(fn, scope){
2802         Ext.tree.TreeNode.superclass.sort.apply(this, arguments);
2803         if(this.childrenRendered){
2804             var cs = this.childNodes;
2805             for(var i = 0, len = cs.length; i < len; i++){
2806                 cs[i].render(true);
2807             }
2808         }
2809     },
2810
2811     // private
2812     render : function(bulkRender){
2813         this.ui.render(bulkRender);
2814         if(!this.rendered){
2815             // make sure it is registered
2816             this.getOwnerTree().registerNode(this);
2817             this.rendered = true;
2818             if(this.expanded){
2819                 this.expanded = false;
2820                 this.expand(false, false);
2821             }
2822         }
2823     },
2824
2825     // private
2826     renderIndent : function(deep, refresh){
2827         if(refresh){
2828             this.ui.childIndent = null;
2829         }
2830         this.ui.renderIndent();
2831         if(deep === true && this.childrenRendered){
2832             var cs = this.childNodes;
2833             for(var i = 0, len = cs.length; i < len; i++){
2834                 cs[i].renderIndent(true, refresh);
2835             }
2836         }
2837     },
2838
2839     beginUpdate : function(){
2840         this.childrenRendered = false;
2841     },
2842
2843     endUpdate : function(){
2844         if(this.expanded && this.rendered){
2845             this.renderChildren();
2846         }
2847     },
2848
2849     //inherit docs
2850     destroy : function(silent){
2851         if(silent === true){
2852             this.unselect(true);
2853         }
2854         Ext.tree.TreeNode.superclass.destroy.call(this, silent);
2855         Ext.destroy(this.ui, this.loader);
2856         this.ui = this.loader = null;
2857     },
2858
2859     // private
2860     onIdChange : function(id){
2861         this.ui.onIdChange(id);
2862     }
2863 });
2864
2865 Ext.tree.TreePanel.nodeTypes.node = Ext.tree.TreeNode;/**
2866  * @class Ext.tree.AsyncTreeNode
2867  * @extends Ext.tree.TreeNode
2868  * @cfg {TreeLoader} loader A TreeLoader to be used by this node (defaults to the loader defined on the tree)
2869  * @constructor
2870  * @param {Object/String} attributes The attributes/config for the node or just a string with the text for the node 
2871  */
2872  Ext.tree.AsyncTreeNode = function(config){
2873     this.loaded = config && config.loaded === true;
2874     this.loading = false;
2875     Ext.tree.AsyncTreeNode.superclass.constructor.apply(this, arguments);
2876     /**
2877     * @event beforeload
2878     * Fires before this node is loaded, return false to cancel
2879     * @param {Node} this This node
2880     */
2881     this.addEvents('beforeload', 'load');
2882     /**
2883     * @event load
2884     * Fires when this node is loaded
2885     * @param {Node} this This node
2886     */
2887     /**
2888      * The loader used by this node (defaults to using the tree's defined loader)
2889      * @type TreeLoader
2890      * @property loader
2891      */
2892 };
2893 Ext.extend(Ext.tree.AsyncTreeNode, Ext.tree.TreeNode, {
2894     expand : function(deep, anim, callback, scope){
2895         if(this.loading){ // if an async load is already running, waiting til it's done
2896             var timer;
2897             var f = function(){
2898                 if(!this.loading){ // done loading
2899                     clearInterval(timer);
2900                     this.expand(deep, anim, callback, scope);
2901                 }
2902             }.createDelegate(this);
2903             timer = setInterval(f, 200);
2904             return;
2905         }
2906         if(!this.loaded){
2907             if(this.fireEvent("beforeload", this) === false){
2908                 return;
2909             }
2910             this.loading = true;
2911             this.ui.beforeLoad(this);
2912             var loader = this.loader || this.attributes.loader || this.getOwnerTree().getLoader();
2913             if(loader){
2914                 loader.load(this, this.loadComplete.createDelegate(this, [deep, anim, callback, scope]), this);
2915                 return;
2916             }
2917         }
2918         Ext.tree.AsyncTreeNode.superclass.expand.call(this, deep, anim, callback, scope);
2919     },
2920     
2921     /**
2922      * Returns true if this node is currently loading
2923      * @return {Boolean}
2924      */
2925     isLoading : function(){
2926         return this.loading;  
2927     },
2928     
2929     loadComplete : function(deep, anim, callback, scope){
2930         this.loading = false;
2931         this.loaded = true;
2932         this.ui.afterLoad(this);
2933         this.fireEvent("load", this);
2934         this.expand(deep, anim, callback, scope);
2935     },
2936     
2937     /**
2938      * Returns true if this node has been loaded
2939      * @return {Boolean}
2940      */
2941     isLoaded : function(){
2942         return this.loaded;
2943     },
2944     
2945     hasChildNodes : function(){
2946         if(!this.isLeaf() && !this.loaded){
2947             return true;
2948         }else{
2949             return Ext.tree.AsyncTreeNode.superclass.hasChildNodes.call(this);
2950         }
2951     },
2952
2953     /**
2954      * Trigger a reload for this node
2955      * @param {Function} callback
2956      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the callback is executed. Defaults to this Node.
2957      */
2958     reload : function(callback, scope){
2959         this.collapse(false, false);
2960         while(this.firstChild){
2961             this.removeChild(this.firstChild).destroy();
2962         }
2963         this.childrenRendered = false;
2964         this.loaded = false;
2965         if(this.isHiddenRoot()){
2966             this.expanded = false;
2967         }
2968         this.expand(false, false, callback, scope);
2969     }
2970 });
2971
2972 Ext.tree.TreePanel.nodeTypes.async = Ext.tree.AsyncTreeNode;/**
2973  * @class Ext.tree.TreeNodeUI
2974  * This class provides the default UI implementation for Ext TreeNodes.
2975  * The TreeNode UI implementation is separate from the
2976  * tree implementation, and allows customizing of the appearance of
2977  * tree nodes.<br>
2978  * <p>
2979  * If you are customizing the Tree's user interface, you
2980  * may need to extend this class, but you should never need to instantiate this class.<br>
2981  * <p>
2982  * This class provides access to the user interface components of an Ext TreeNode, through
2983  * {@link Ext.tree.TreeNode#getUI}
2984  */
2985 Ext.tree.TreeNodeUI = function(node){
2986     this.node = node;
2987     this.rendered = false;
2988     this.animating = false;
2989     this.wasLeaf = true;
2990     this.ecc = 'x-tree-ec-icon x-tree-elbow';
2991     this.emptyIcon = Ext.BLANK_IMAGE_URL;
2992 };
2993
2994 Ext.tree.TreeNodeUI.prototype = {
2995     // private
2996     removeChild : function(node){
2997         if(this.rendered){
2998             this.ctNode.removeChild(node.ui.getEl());
2999         }
3000     },
3001
3002     // private
3003     beforeLoad : function(){
3004          this.addClass("x-tree-node-loading");
3005     },
3006
3007     // private
3008     afterLoad : function(){
3009          this.removeClass("x-tree-node-loading");
3010     },
3011
3012     // private
3013     onTextChange : function(node, text, oldText){
3014         if(this.rendered){
3015             this.textNode.innerHTML = text;
3016         }
3017     },
3018
3019     // private
3020     onDisableChange : function(node, state){
3021         this.disabled = state;
3022         if (this.checkbox) {
3023             this.checkbox.disabled = state;
3024         }
3025         if(state){
3026             this.addClass("x-tree-node-disabled");
3027         }else{
3028             this.removeClass("x-tree-node-disabled");
3029         }
3030     },
3031
3032     // private
3033     onSelectedChange : function(state){
3034         if(state){
3035             this.focus();
3036             this.addClass("x-tree-selected");
3037         }else{
3038             //this.blur();
3039             this.removeClass("x-tree-selected");
3040         }
3041     },
3042
3043     // private
3044     onMove : function(tree, node, oldParent, newParent, index, refNode){
3045         this.childIndent = null;
3046         if(this.rendered){
3047             var targetNode = newParent.ui.getContainer();
3048             if(!targetNode){//target not rendered
3049                 this.holder = document.createElement("div");
3050                 this.holder.appendChild(this.wrap);
3051                 return;
3052             }
3053             var insertBefore = refNode ? refNode.ui.getEl() : null;
3054             if(insertBefore){
3055                 targetNode.insertBefore(this.wrap, insertBefore);
3056             }else{
3057                 targetNode.appendChild(this.wrap);
3058             }
3059             this.node.renderIndent(true, oldParent != newParent);
3060         }
3061     },
3062
3063 /**
3064  * Adds one or more CSS classes to the node's UI element.
3065  * Duplicate classes are automatically filtered out.
3066  * @param {String/Array} className The CSS class to add, or an array of classes
3067  */
3068     addClass : function(cls){
3069         if(this.elNode){
3070             Ext.fly(this.elNode).addClass(cls);
3071         }
3072     },
3073
3074 /**
3075  * Removes one or more CSS classes from the node's UI element.
3076  * @param {String/Array} className The CSS class to remove, or an array of classes
3077  */
3078     removeClass : function(cls){
3079         if(this.elNode){
3080             Ext.fly(this.elNode).removeClass(cls);
3081         }
3082     },
3083
3084     // private
3085     remove : function(){
3086         if(this.rendered){
3087             this.holder = document.createElement("div");
3088             this.holder.appendChild(this.wrap);
3089         }
3090     },
3091
3092     // private
3093     fireEvent : function(){
3094         return this.node.fireEvent.apply(this.node, arguments);
3095     },
3096
3097     // private
3098     initEvents : function(){
3099         this.node.on("move", this.onMove, this);
3100
3101         if(this.node.disabled){
3102             this.onDisableChange(this.node, true);
3103         }
3104         if(this.node.hidden){
3105             this.hide();
3106         }
3107         var ot = this.node.getOwnerTree();
3108         var dd = ot.enableDD || ot.enableDrag || ot.enableDrop;
3109         if(dd && (!this.node.isRoot || ot.rootVisible)){
3110             Ext.dd.Registry.register(this.elNode, {
3111                 node: this.node,
3112                 handles: this.getDDHandles(),
3113                 isHandle: false
3114             });
3115         }
3116     },
3117
3118     // private
3119     getDDHandles : function(){
3120         return [this.iconNode, this.textNode, this.elNode];
3121     },
3122
3123 /**
3124  * Hides this node.
3125  */
3126     hide : function(){
3127         this.node.hidden = true;
3128         if(this.wrap){
3129             this.wrap.style.display = "none";
3130         }
3131     },
3132
3133 /**
3134  * Shows this node.
3135  */
3136     show : function(){
3137         this.node.hidden = false;
3138         if(this.wrap){
3139             this.wrap.style.display = "";
3140         }
3141     },
3142
3143     // private
3144     onContextMenu : function(e){
3145         if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) {
3146             e.preventDefault();
3147             this.focus();
3148             this.fireEvent("contextmenu", this.node, e);
3149         }
3150     },
3151
3152     // private
3153     onClick : function(e){
3154         if(this.dropping){
3155             e.stopEvent();
3156             return;
3157         }
3158         if(this.fireEvent("beforeclick", this.node, e) !== false){
3159             var a = e.getTarget('a');
3160             if(!this.disabled && this.node.attributes.href && a){
3161                 this.fireEvent("click", this.node, e);
3162                 return;
3163             }else if(a && e.ctrlKey){
3164                 e.stopEvent();
3165             }
3166             e.preventDefault();
3167             if(this.disabled){
3168                 return;
3169             }
3170
3171             if(this.node.attributes.singleClickExpand && !this.animating && this.node.isExpandable()){
3172                 this.node.toggle();
3173             }
3174
3175             this.fireEvent("click", this.node, e);
3176         }else{
3177             e.stopEvent();
3178         }
3179     },
3180
3181     // private
3182     onDblClick : function(e){
3183         e.preventDefault();
3184         if(this.disabled){
3185             return;
3186         }
3187         if(this.fireEvent("beforedblclick", this.node, e) !== false){
3188             if(this.checkbox){
3189                 this.toggleCheck();
3190             }
3191             if(!this.animating && this.node.isExpandable()){
3192                 this.node.toggle();
3193             }
3194             this.fireEvent("dblclick", this.node, e);
3195         }
3196     },
3197
3198     onOver : function(e){
3199         this.addClass('x-tree-node-over');
3200     },
3201
3202     onOut : function(e){
3203         this.removeClass('x-tree-node-over');
3204     },
3205
3206     // private
3207     onCheckChange : function(){
3208         var checked = this.checkbox.checked;
3209         // fix for IE6
3210         this.checkbox.defaultChecked = checked;
3211         this.node.attributes.checked = checked;
3212         this.fireEvent('checkchange', this.node, checked);
3213     },
3214
3215     // private
3216     ecClick : function(e){
3217         if(!this.animating && this.node.isExpandable()){
3218             this.node.toggle();
3219         }
3220     },
3221
3222     // private
3223     startDrop : function(){
3224         this.dropping = true;
3225     },
3226
3227     // delayed drop so the click event doesn't get fired on a drop
3228     endDrop : function(){
3229        setTimeout(function(){
3230            this.dropping = false;
3231        }.createDelegate(this), 50);
3232     },
3233
3234     // private
3235     expand : function(){
3236         this.updateExpandIcon();
3237         this.ctNode.style.display = "";
3238     },
3239
3240     // private
3241     focus : function(){
3242         if(!this.node.preventHScroll){
3243             try{this.anchor.focus();
3244             }catch(e){}
3245         }else{
3246             try{
3247                 var noscroll = this.node.getOwnerTree().getTreeEl().dom;
3248                 var l = noscroll.scrollLeft;
3249                 this.anchor.focus();
3250                 noscroll.scrollLeft = l;
3251             }catch(e){}
3252         }
3253     },
3254
3255 /**
3256  * Sets the checked status of the tree node to the passed value, or, if no value was passed,
3257  * toggles the checked status. If the node was rendered with no checkbox, this has no effect.
3258  * @param {Boolean} value (optional) The new checked status.
3259  */
3260     toggleCheck : function(value){
3261         var cb = this.checkbox;
3262         if(cb){
3263             cb.checked = (value === undefined ? !cb.checked : value);
3264             this.onCheckChange();
3265         }
3266     },
3267
3268     // private
3269     blur : function(){
3270         try{
3271             this.anchor.blur();
3272         }catch(e){}
3273     },
3274
3275     // private
3276     animExpand : function(callback){
3277         var ct = Ext.get(this.ctNode);
3278         ct.stopFx();
3279         if(!this.node.isExpandable()){
3280             this.updateExpandIcon();
3281             this.ctNode.style.display = "";
3282             Ext.callback(callback);
3283             return;
3284         }
3285         this.animating = true;
3286         this.updateExpandIcon();
3287
3288         ct.slideIn('t', {
3289            callback : function(){
3290                this.animating = false;
3291                Ext.callback(callback);
3292             },
3293             scope: this,
3294             duration: this.node.ownerTree.duration || .25
3295         });
3296     },
3297
3298     // private
3299     highlight : function(){
3300         var tree = this.node.getOwnerTree();
3301         Ext.fly(this.wrap).highlight(
3302             tree.hlColor || "C3DAF9",
3303             {endColor: tree.hlBaseColor}
3304         );
3305     },
3306
3307     // private
3308     collapse : function(){
3309         this.updateExpandIcon();
3310         this.ctNode.style.display = "none";
3311     },
3312
3313     // private
3314     animCollapse : function(callback){
3315         var ct = Ext.get(this.ctNode);
3316         ct.enableDisplayMode('block');
3317         ct.stopFx();
3318
3319         this.animating = true;
3320         this.updateExpandIcon();
3321
3322         ct.slideOut('t', {
3323             callback : function(){
3324                this.animating = false;
3325                Ext.callback(callback);
3326             },
3327             scope: this,
3328             duration: this.node.ownerTree.duration || .25
3329         });
3330     },
3331
3332     // private
3333     getContainer : function(){
3334         return this.ctNode;
3335     },
3336
3337 /**
3338  * Returns the element which encapsulates this node.
3339  * @return {HtmlElement} The DOM element. The default implementation uses a <code>&lt;li></code>.
3340  */
3341     getEl : function(){
3342         return this.wrap;
3343     },
3344
3345     // private
3346     appendDDGhost : function(ghostNode){
3347         ghostNode.appendChild(this.elNode.cloneNode(true));
3348     },
3349
3350     // private
3351     getDDRepairXY : function(){
3352         return Ext.lib.Dom.getXY(this.iconNode);
3353     },
3354
3355     // private
3356     onRender : function(){
3357         this.render();
3358     },
3359
3360     // private
3361     render : function(bulkRender){
3362         var n = this.node, a = n.attributes;
3363         var targetNode = n.parentNode ?
3364               n.parentNode.ui.getContainer() : n.ownerTree.innerCt.dom;
3365
3366         if(!this.rendered){
3367             this.rendered = true;
3368
3369             this.renderElements(n, a, targetNode, bulkRender);
3370
3371             if(a.qtip){
3372                if(this.textNode.setAttributeNS){
3373                    this.textNode.setAttributeNS("ext", "qtip", a.qtip);
3374                    if(a.qtipTitle){
3375                        this.textNode.setAttributeNS("ext", "qtitle", a.qtipTitle);
3376                    }
3377                }else{
3378                    this.textNode.setAttribute("ext:qtip", a.qtip);
3379                    if(a.qtipTitle){
3380                        this.textNode.setAttribute("ext:qtitle", a.qtipTitle);
3381                    }
3382                }
3383             }else if(a.qtipCfg){
3384                 a.qtipCfg.target = Ext.id(this.textNode);
3385                 Ext.QuickTips.register(a.qtipCfg);
3386             }
3387             this.initEvents();
3388             if(!this.node.expanded){
3389                 this.updateExpandIcon(true);
3390             }
3391         }else{
3392             if(bulkRender === true) {
3393                 targetNode.appendChild(this.wrap);
3394             }
3395         }
3396     },
3397
3398     // private
3399     renderElements : function(n, a, targetNode, bulkRender){
3400         // add some indent caching, this helps performance when rendering a large tree
3401         this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
3402
3403         var cb = Ext.isBoolean(a.checked),
3404             nel,
3405             href = a.href ? a.href : Ext.isGecko ? "" : "#",
3406             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">',
3407             '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
3408             '<img src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
3409             '<img src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
3410             cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
3411             '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
3412              a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',n.text,"</span></a></div>",
3413             '<ul class="x-tree-node-ct" style="display:none;"></ul>',
3414             "</li>"].join('');
3415
3416         if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
3417             this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
3418         }else{
3419             this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
3420         }
3421
3422         this.elNode = this.wrap.childNodes[0];
3423         this.ctNode = this.wrap.childNodes[1];
3424         var cs = this.elNode.childNodes;
3425         this.indentNode = cs[0];
3426         this.ecNode = cs[1];
3427         this.iconNode = cs[2];
3428         var index = 3;
3429         if(cb){
3430             this.checkbox = cs[3];
3431             // fix for IE6
3432             this.checkbox.defaultChecked = this.checkbox.checked;
3433             index++;
3434         }
3435         this.anchor = cs[index];
3436         this.textNode = cs[index].firstChild;
3437     },
3438
3439 /**
3440  * Returns the &lt;a> element that provides focus for the node's UI.
3441  * @return {HtmlElement} The DOM anchor element.
3442  */
3443     getAnchor : function(){
3444         return this.anchor;
3445     },
3446
3447 /**
3448  * Returns the text node.
3449  * @return {HtmlNode} The DOM text node.
3450  */
3451     getTextEl : function(){
3452         return this.textNode;
3453     },
3454
3455 /**
3456  * Returns the icon &lt;img> element.
3457  * @return {HtmlElement} The DOM image element.
3458  */
3459     getIconEl : function(){
3460         return this.iconNode;
3461     },
3462
3463 /**
3464  * Returns the checked status of the node. If the node was rendered with no
3465  * checkbox, it returns false.
3466  * @return {Boolean} The checked flag.
3467  */
3468     isChecked : function(){
3469         return this.checkbox ? this.checkbox.checked : false;
3470     },
3471
3472     // private
3473     updateExpandIcon : function(){
3474         if(this.rendered){
3475             var n = this.node,
3476                 c1,
3477                 c2,
3478                 cls = n.isLast() ? "x-tree-elbow-end" : "x-tree-elbow",
3479                 hasChild = n.hasChildNodes();
3480             if(hasChild || n.attributes.expandable){
3481                 if(n.expanded){
3482                     cls += "-minus";
3483                     c1 = "x-tree-node-collapsed";
3484                     c2 = "x-tree-node-expanded";
3485                 }else{
3486                     cls += "-plus";
3487                     c1 = "x-tree-node-expanded";
3488                     c2 = "x-tree-node-collapsed";
3489                 }
3490                 if(this.wasLeaf){
3491                     this.removeClass("x-tree-node-leaf");
3492                     this.wasLeaf = false;
3493                 }
3494                 if(this.c1 != c1 || this.c2 != c2){
3495                     Ext.fly(this.elNode).replaceClass(c1, c2);
3496                     this.c1 = c1; this.c2 = c2;
3497                 }
3498             }else{
3499                 if(!this.wasLeaf){
3500                     Ext.fly(this.elNode).replaceClass("x-tree-node-expanded", "x-tree-node-collapsed");
3501                     delete this.c1;
3502                     delete this.c2;
3503                     this.wasLeaf = true;
3504                 }
3505             }
3506             var ecc = "x-tree-ec-icon "+cls;
3507             if(this.ecc != ecc){
3508                 this.ecNode.className = ecc;
3509                 this.ecc = ecc;
3510             }
3511         }
3512     },
3513
3514     // private
3515     onIdChange: function(id){
3516         if(this.rendered){
3517             this.elNode.setAttribute('ext:tree-node-id', id);
3518         }
3519     },
3520
3521     // private
3522     getChildIndent : function(){
3523         if(!this.childIndent){
3524             var buf = [],
3525                 p = this.node;
3526             while(p){
3527                 if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){
3528                     if(!p.isLast()) {
3529                         buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-elbow-line" />');
3530                     } else {
3531                         buf.unshift('<img src="'+this.emptyIcon+'" class="x-tree-icon" />');
3532                     }
3533                 }
3534                 p = p.parentNode;
3535             }
3536             this.childIndent = buf.join("");
3537         }
3538         return this.childIndent;
3539     },
3540
3541     // private
3542     renderIndent : function(){
3543         if(this.rendered){
3544             var indent = "",
3545                 p = this.node.parentNode;
3546             if(p){
3547                 indent = p.ui.getChildIndent();
3548             }
3549             if(this.indentMarkup != indent){ // don't rerender if not required
3550                 this.indentNode.innerHTML = indent;
3551                 this.indentMarkup = indent;
3552             }
3553             this.updateExpandIcon();
3554         }
3555     },
3556
3557     destroy : function(){
3558         if(this.elNode){
3559             Ext.dd.Registry.unregister(this.elNode.id);
3560         }
3561
3562         Ext.each(['textnode', 'anchor', 'checkbox', 'indentNode', 'ecNode', 'iconNode', 'elNode', 'ctNode', 'wrap', 'holder'], function(el){
3563             if(this[el]){
3564                 Ext.fly(this[el]).remove();
3565                 delete this[el];
3566             }
3567         }, this);
3568         delete this.node;
3569     }
3570 };
3571
3572 /**
3573  * @class Ext.tree.RootTreeNodeUI
3574  * This class provides the default UI implementation for <b>root</b> Ext TreeNodes.
3575  * The RootTreeNode UI implementation allows customizing the appearance of the root tree node.<br>
3576  * <p>
3577  * If you are customizing the Tree's user interface, you
3578  * may need to extend this class, but you should never need to instantiate this class.<br>
3579  */
3580 Ext.tree.RootTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
3581     // private
3582     render : function(){
3583         if(!this.rendered){
3584             var targetNode = this.node.ownerTree.innerCt.dom;
3585             this.node.expanded = true;
3586             targetNode.innerHTML = '<div class="x-tree-root-node"></div>';
3587             this.wrap = this.ctNode = targetNode.firstChild;
3588         }
3589     },
3590     collapse : Ext.emptyFn,
3591     expand : Ext.emptyFn
3592 });/**
3593  * @class Ext.tree.TreeLoader
3594  * @extends Ext.util.Observable
3595  * A TreeLoader provides for lazy loading of an {@link Ext.tree.TreeNode}'s child
3596  * nodes from a specified URL. The response must be a JavaScript Array definition
3597  * whose elements are node definition objects. e.g.:
3598  * <pre><code>
3599     [{
3600         id: 1,
3601         text: 'A leaf Node',
3602         leaf: true
3603     },{
3604         id: 2,
3605         text: 'A folder Node',
3606         children: [{
3607             id: 3,
3608             text: 'A child Node',
3609             leaf: true
3610         }]
3611    }]
3612 </code></pre>
3613  * <br><br>
3614  * A server request is sent, and child nodes are loaded only when a node is expanded.
3615  * The loading node's id is passed to the server under the parameter name "node" to
3616  * enable the server to produce the correct child nodes.
3617  * <br><br>
3618  * To pass extra parameters, an event handler may be attached to the "beforeload"
3619  * event, and the parameters specified in the TreeLoader's baseParams property:
3620  * <pre><code>
3621     myTreeLoader.on("beforeload", function(treeLoader, node) {
3622         this.baseParams.category = node.attributes.category;
3623     }, this);
3624 </code></pre>
3625  * This would pass an HTTP parameter called "category" to the server containing
3626  * the value of the Node's "category" attribute.
3627  * @constructor
3628  * Creates a new Treeloader.
3629  * @param {Object} config A config object containing config properties.
3630  */
3631 Ext.tree.TreeLoader = function(config){
3632     this.baseParams = {};
3633     Ext.apply(this, config);
3634
3635     this.addEvents(
3636         /**
3637          * @event beforeload
3638          * Fires before a network request is made to retrieve the Json text which specifies a node's children.
3639          * @param {Object} This TreeLoader object.
3640          * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
3641          * @param {Object} callback The callback function specified in the {@link #load} call.
3642          */
3643         "beforeload",
3644         /**
3645          * @event load
3646          * Fires when the node has been successfuly loaded.
3647          * @param {Object} This TreeLoader object.
3648          * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
3649          * @param {Object} response The response object containing the data from the server.
3650          */
3651         "load",
3652         /**
3653          * @event loadexception
3654          * Fires if the network request failed.
3655          * @param {Object} This TreeLoader object.
3656          * @param {Object} node The {@link Ext.tree.TreeNode} object being loaded.
3657          * @param {Object} response The response object containing the data from the server.
3658          */
3659         "loadexception"
3660     );
3661     Ext.tree.TreeLoader.superclass.constructor.call(this);
3662     if(Ext.isString(this.paramOrder)){
3663         this.paramOrder = this.paramOrder.split(/[\s,|]/);
3664     }
3665 };
3666
3667 Ext.extend(Ext.tree.TreeLoader, Ext.util.Observable, {
3668     /**
3669     * @cfg {String} dataUrl The URL from which to request a Json string which
3670     * specifies an array of node definition objects representing the child nodes
3671     * to be loaded.
3672     */
3673     /**
3674      * @cfg {String} requestMethod The HTTP request method for loading data (defaults to the value of {@link Ext.Ajax#method}).
3675      */
3676     /**
3677      * @cfg {String} url Equivalent to {@link #dataUrl}.
3678      */
3679     /**
3680      * @cfg {Boolean} preloadChildren If set to true, the loader recursively loads "children" attributes when doing the first load on nodes.
3681      */
3682     /**
3683     * @cfg {Object} baseParams (optional) An object containing properties which
3684     * specify HTTP parameters to be passed to each request for child nodes.
3685     */
3686     /**
3687     * @cfg {Object} baseAttrs (optional) An object containing attributes to be added to all nodes
3688     * created by this loader. If the attributes sent by the server have an attribute in this object,
3689     * they take priority.
3690     */
3691     /**
3692     * @cfg {Object} uiProviders (optional) An object containing properties which
3693     * specify custom {@link Ext.tree.TreeNodeUI} implementations. If the optional
3694     * <i>uiProvider</i> attribute of a returned child node is a string rather
3695     * than a reference to a TreeNodeUI implementation, then that string value
3696     * is used as a property name in the uiProviders object.
3697     */
3698     uiProviders : {},
3699
3700     /**
3701     * @cfg {Boolean} clearOnLoad (optional) Default to true. Remove previously existing
3702     * child nodes before loading.
3703     */
3704     clearOnLoad : true,
3705
3706     /**
3707      * @cfg {Array/String} paramOrder Defaults to <tt>undefined</tt>. Only used when using directFn.
3708      * Specifies the params in the order in which they must be passed to the server-side Direct method
3709      * as either (1) an Array of String values, or (2) a String of params delimited by either whitespace,
3710      * comma, or pipe. For example,
3711      * any of the following would be acceptable:<pre><code>
3712 nodeParameter: 'node',
3713 paramOrder: ['param1','param2','param3']
3714 paramOrder: 'node param1 param2 param3'
3715 paramOrder: 'param1,node,param2,param3'
3716 paramOrder: 'param1|param2|param|node'
3717      </code></pre>
3718      */
3719     paramOrder: undefined,
3720
3721     /**
3722      * @cfg {Boolean} paramsAsHash Only used when using directFn.
3723      * Send parameters as a collection of named arguments (defaults to <tt>false</tt>). Providing a
3724      * <tt>{@link #paramOrder}</tt> nullifies this configuration.
3725      */
3726     paramsAsHash: false,
3727
3728     /**
3729      * @cfg {String} nodeParameter The name of the parameter sent to the server which contains
3730      * the identifier of the node. Defaults to <tt>'node'</tt>.
3731      */
3732     nodeParameter: 'node',
3733
3734     /**
3735      * @cfg {Function} directFn
3736      * Function to call when executing a request.
3737      */
3738     directFn : undefined,
3739
3740     /**
3741      * Load an {@link Ext.tree.TreeNode} from the URL specified in the constructor.
3742      * This is called automatically when a node is expanded, but may be used to reload
3743      * a node (or append new children if the {@link #clearOnLoad} option is false.)
3744      * @param {Ext.tree.TreeNode} node
3745      * @param {Function} callback Function to call after the node has been loaded. The
3746      * function is passed the TreeNode which was requested to be loaded.
3747      * @param {Object} scope The scope (<code>this</code> reference) in which the callback is executed.
3748      * defaults to the loaded TreeNode.
3749      */
3750     load : function(node, callback, scope){
3751         if(this.clearOnLoad){
3752             while(node.firstChild){
3753                 node.removeChild(node.firstChild);
3754             }
3755         }
3756         if(this.doPreload(node)){ // preloaded json children
3757             this.runCallback(callback, scope || node, [node]);
3758         }else if(this.directFn || this.dataUrl || this.url){
3759             this.requestData(node, callback, scope || node);
3760         }
3761     },
3762
3763     doPreload : function(node){
3764         if(node.attributes.children){
3765             if(node.childNodes.length < 1){ // preloaded?
3766                 var cs = node.attributes.children;
3767                 node.beginUpdate();
3768                 for(var i = 0, len = cs.length; i < len; i++){
3769                     var cn = node.appendChild(this.createNode(cs[i]));
3770                     if(this.preloadChildren){
3771                         this.doPreload(cn);
3772                     }
3773                 }
3774                 node.endUpdate();
3775             }
3776             return true;
3777         }
3778         return false;
3779     },
3780
3781     getParams: function(node){
3782         var bp = Ext.apply({}, this.baseParams),
3783             np = this.nodeParameter,
3784             po = this.paramOrder;
3785
3786         np && (bp[ np ] = node.id);
3787
3788         if(this.directFn){
3789             var buf = [node.id];
3790             if(po){
3791                 // reset 'buf' if the nodeParameter was included in paramOrder
3792                 if(np && po.indexOf(np) > -1){
3793                     buf = [];
3794                 }
3795
3796                 for(var i = 0, len = po.length; i < len; i++){
3797                     buf.push(bp[ po[i] ]);
3798                 }
3799             }else if(this.paramsAsHash){
3800                 buf = [bp];
3801             }
3802             return buf;
3803         }else{
3804             return bp;
3805         }
3806     },
3807
3808     requestData : function(node, callback, scope){
3809         if(this.fireEvent("beforeload", this, node, callback) !== false){
3810             if(this.directFn){
3811                 var args = this.getParams(node);
3812                 args.push(this.processDirectResponse.createDelegate(this, [{callback: callback, node: node, scope: scope}], true));
3813                 this.directFn.apply(window, args);
3814             }else{
3815                 this.transId = Ext.Ajax.request({
3816                     method:this.requestMethod,
3817                     url: this.dataUrl||this.url,
3818                     success: this.handleResponse,
3819                     failure: this.handleFailure,
3820                     scope: this,
3821                     argument: {callback: callback, node: node, scope: scope},
3822                     params: this.getParams(node)
3823                 });
3824             }
3825         }else{
3826             // if the load is cancelled, make sure we notify
3827             // the node that we are done
3828             this.runCallback(callback, scope || node, []);
3829         }
3830     },
3831
3832     processDirectResponse: function(result, response, args){
3833         if(response.status){
3834             this.handleResponse({
3835                 responseData: Ext.isArray(result) ? result : null,
3836                 responseText: result,
3837                 argument: args
3838             });
3839         }else{
3840             this.handleFailure({
3841                 argument: args
3842             });
3843         }
3844     },
3845
3846     // private
3847     runCallback: function(cb, scope, args){
3848         if(Ext.isFunction(cb)){
3849             cb.apply(scope, args);
3850         }
3851     },
3852
3853     isLoading : function(){
3854         return !!this.transId;
3855     },
3856
3857     abort : function(){
3858         if(this.isLoading()){
3859             Ext.Ajax.abort(this.transId);
3860         }
3861     },
3862
3863     /**
3864     * <p>Override this function for custom TreeNode node implementation, or to
3865     * modify the attributes at creation time.</p>
3866     * Example:<pre><code>
3867 new Ext.tree.TreePanel({
3868     ...
3869     loader: new Ext.tree.TreeLoader({
3870         url: 'dataUrl',
3871         createNode: function(attr) {
3872 //          Allow consolidation consignments to have
3873 //          consignments dropped into them.
3874             if (attr.isConsolidation) {
3875                 attr.iconCls = 'x-consol',
3876                 attr.allowDrop = true;
3877             }
3878             return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
3879         }
3880     }),
3881     ...
3882 });
3883 </code></pre>
3884     * @param attr {Object} The attributes from which to create the new node.
3885     */
3886     createNode : function(attr){
3887         // apply baseAttrs, nice idea Corey!
3888         if(this.baseAttrs){
3889             Ext.applyIf(attr, this.baseAttrs);
3890         }
3891         if(this.applyLoader !== false && !attr.loader){
3892             attr.loader = this;
3893         }
3894         if(Ext.isString(attr.uiProvider)){
3895            attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
3896         }
3897         if(attr.nodeType){
3898             return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);
3899         }else{
3900             return attr.leaf ?
3901                         new Ext.tree.TreeNode(attr) :
3902                         new Ext.tree.AsyncTreeNode(attr);
3903         }
3904     },
3905
3906     processResponse : function(response, node, callback, scope){
3907         var json = response.responseText;
3908         try {
3909             var o = response.responseData || Ext.decode(json);
3910             node.beginUpdate();
3911             for(var i = 0, len = o.length; i < len; i++){
3912                 var n = this.createNode(o[i]);
3913                 if(n){
3914                     node.appendChild(n);
3915                 }
3916             }
3917             node.endUpdate();
3918             this.runCallback(callback, scope || node, [node]);
3919         }catch(e){
3920             this.handleFailure(response);
3921         }
3922     },
3923
3924     handleResponse : function(response){
3925         this.transId = false;
3926         var a = response.argument;
3927         this.processResponse(response, a.node, a.callback, a.scope);
3928         this.fireEvent("load", this, a.node, response);
3929     },
3930
3931     handleFailure : function(response){
3932         this.transId = false;
3933         var a = response.argument;
3934         this.fireEvent("loadexception", this, a.node, response);
3935         this.runCallback(a.callback, a.scope || a.node, [a.node]);
3936     },
3937
3938     destroy : function(){
3939         this.abort();
3940         this.purgeListeners();
3941     }
3942 });/**
3943  * @class Ext.tree.TreeFilter
3944  * Note this class is experimental and doesn't update the indent (lines) or expand collapse icons of the nodes
3945  * @param {TreePanel} tree
3946  * @param {Object} config (optional)
3947  */
3948 Ext.tree.TreeFilter = function(tree, config){
3949     this.tree = tree;
3950     this.filtered = {};
3951     Ext.apply(this, config);
3952 };
3953
3954 Ext.tree.TreeFilter.prototype = {
3955     clearBlank:false,
3956     reverse:false,
3957     autoClear:false,
3958     remove:false,
3959
3960      /**
3961      * Filter the data by a specific attribute.
3962      * @param {String/RegExp} value Either string that the attribute value
3963      * should start with or a RegExp to test against the attribute
3964      * @param {String} attr (optional) The attribute passed in your node's attributes collection. Defaults to "text".
3965      * @param {TreeNode} startNode (optional) The node to start the filter at.
3966      */
3967     filter : function(value, attr, startNode){
3968         attr = attr || "text";
3969         var f;
3970         if(typeof value == "string"){
3971             var vlen = value.length;
3972             // auto clear empty filter
3973             if(vlen == 0 && this.clearBlank){
3974                 this.clear();
3975                 return;
3976             }
3977             value = value.toLowerCase();
3978             f = function(n){
3979                 return n.attributes[attr].substr(0, vlen).toLowerCase() == value;
3980             };
3981         }else if(value.exec){ // regex?
3982             f = function(n){
3983                 return value.test(n.attributes[attr]);
3984             };
3985         }else{
3986             throw 'Illegal filter type, must be string or regex';
3987         }
3988         this.filterBy(f, null, startNode);
3989         },
3990
3991     /**
3992      * Filter by a function. The passed function will be called with each
3993      * node in the tree (or from the startNode). If the function returns true, the node is kept
3994      * otherwise it is filtered. If a node is filtered, its children are also filtered.
3995      * @param {Function} fn The filter function
3996      * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the function is executed. Defaults to the current Node.
3997      */
3998     filterBy : function(fn, scope, startNode){
3999         startNode = startNode || this.tree.root;
4000         if(this.autoClear){
4001             this.clear();
4002         }
4003         var af = this.filtered, rv = this.reverse;
4004         var f = function(n){
4005             if(n == startNode){
4006                 return true;
4007             }
4008             if(af[n.id]){
4009                 return false;
4010             }
4011             var m = fn.call(scope || n, n);
4012             if(!m || rv){
4013                 af[n.id] = n;
4014                 n.ui.hide();
4015                 return false;
4016             }
4017             return true;
4018         };
4019         startNode.cascade(f);
4020         if(this.remove){
4021            for(var id in af){
4022                if(typeof id != "function"){
4023                    var n = af[id];
4024                    if(n && n.parentNode){
4025                        n.parentNode.removeChild(n);
4026                    }
4027                }
4028            }
4029         }
4030     },
4031
4032     /**
4033      * Clears the current filter. Note: with the "remove" option
4034      * set a filter cannot be cleared.
4035      */
4036     clear : function(){
4037         var t = this.tree;
4038         var af = this.filtered;
4039         for(var id in af){
4040             if(typeof id != "function"){
4041                 var n = af[id];
4042                 if(n){
4043                     n.ui.show();
4044                 }
4045             }
4046         }
4047         this.filtered = {};
4048     }
4049 };
4050 /**
4051  * @class Ext.tree.TreeSorter
4052  * Provides sorting of nodes in a {@link Ext.tree.TreePanel}.  The TreeSorter automatically monitors events on the
4053  * associated TreePanel that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange).
4054  * Example usage:<br />
4055  * <pre><code>
4056 new Ext.tree.TreeSorter(myTree, {
4057     folderSort: true,
4058     dir: "desc",
4059     sortType: function(node) {
4060         // sort by a custom, typed attribute:
4061         return parseInt(node.id, 10);
4062     }
4063 });
4064 </code></pre>
4065  * @constructor
4066  * @param {TreePanel} tree
4067  * @param {Object} config
4068  */
4069 Ext.tree.TreeSorter = function(tree, config){
4070     /**
4071      * @cfg {Boolean} folderSort True to sort leaf nodes under non-leaf nodes (defaults to false)
4072      */
4073     /**
4074      * @cfg {String} property The named attribute on the node to sort by (defaults to "text").  Note that this
4075      * property is only used if no {@link #sortType} function is specified, otherwise it is ignored.
4076      */
4077     /**
4078      * @cfg {String} dir The direction to sort ("asc" or "desc," case-insensitive, defaults to "asc")
4079      */
4080     /**
4081      * @cfg {String} leafAttr The attribute used to determine leaf nodes when {@link #folderSort} = true (defaults to "leaf")
4082      */
4083     /**
4084      * @cfg {Boolean} caseSensitive true for case-sensitive sort (defaults to false)
4085      */
4086     /**
4087      * @cfg {Function} sortType A custom "casting" function used to convert node values before sorting.  The function
4088      * will be called with a single parameter (the {@link Ext.tree.TreeNode} being evaluated) and is expected to return
4089      * the node's sort value cast to the specific data type required for sorting.  This could be used, for example, when
4090      * a node's text (or other attribute) should be sorted as a date or numeric value.  See the class description for
4091      * example usage.  Note that if a sortType is specified, any {@link #property} config will be ignored.
4092      */
4093
4094     Ext.apply(this, config);
4095     tree.on("beforechildrenrendered", this.doSort, this);
4096     tree.on("append", this.updateSort, this);
4097     tree.on("insert", this.updateSort, this);
4098     tree.on("textchange", this.updateSortParent, this);
4099
4100     var dsc = this.dir && this.dir.toLowerCase() == "desc";
4101     var p = this.property || "text";
4102     var sortType = this.sortType;
4103     var fs = this.folderSort;
4104     var cs = this.caseSensitive === true;
4105     var leafAttr = this.leafAttr || 'leaf';
4106
4107     this.sortFn = function(n1, n2){
4108         if(fs){
4109             if(n1.attributes[leafAttr] && !n2.attributes[leafAttr]){
4110                 return 1;
4111             }
4112             if(!n1.attributes[leafAttr] && n2.attributes[leafAttr]){
4113                 return -1;
4114             }
4115         }
4116         var v1 = sortType ? sortType(n1) : (cs ? n1.attributes[p] : n1.attributes[p].toUpperCase());
4117         var v2 = sortType ? sortType(n2) : (cs ? n2.attributes[p] : n2.attributes[p].toUpperCase());
4118         if(v1 < v2){
4119             return dsc ? +1 : -1;
4120         }else if(v1 > v2){
4121             return dsc ? -1 : +1;
4122         }else{
4123             return 0;
4124         }
4125     };
4126 };
4127
4128 Ext.tree.TreeSorter.prototype = {
4129     doSort : function(node){
4130         node.sort(this.sortFn);
4131     },
4132
4133     compareNodes : function(n1, n2){
4134         return (n1.text.toUpperCase() > n2.text.toUpperCase() ? 1 : -1);
4135     },
4136
4137     updateSort : function(tree, node){
4138         if(node.childrenRendered){
4139             this.doSort.defer(1, this, [node]);
4140         }
4141     },
4142
4143     updateSortParent : function(node){
4144         var p = node.parentNode;
4145         if(p && p.childrenRendered){
4146             this.doSort.defer(1, this, [p]);
4147         }
4148     }
4149 };/**
4150  * @class Ext.tree.TreeDropZone
4151  * @extends Ext.dd.DropZone
4152  * @constructor
4153  * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dropping
4154  * @param {Object} config
4155  */
4156 if(Ext.dd.DropZone){
4157     
4158 Ext.tree.TreeDropZone = function(tree, config){
4159     /**
4160      * @cfg {Boolean} allowParentInsert
4161      * Allow inserting a dragged node between an expanded parent node and its first child that will become a
4162      * sibling of the parent when dropped (defaults to false)
4163      */
4164     this.allowParentInsert = config.allowParentInsert || false;
4165     /**
4166      * @cfg {String} allowContainerDrop
4167      * True if drops on the tree container (outside of a specific tree node) are allowed (defaults to false)
4168      */
4169     this.allowContainerDrop = config.allowContainerDrop || false;
4170     /**
4171      * @cfg {String} appendOnly
4172      * True if the tree should only allow append drops (use for trees which are sorted, defaults to false)
4173      */
4174     this.appendOnly = config.appendOnly || false;
4175
4176     Ext.tree.TreeDropZone.superclass.constructor.call(this, tree.getTreeEl(), config);
4177     /**
4178     * The TreePanel for this drop zone
4179     * @type Ext.tree.TreePanel
4180     * @property
4181     */
4182     this.tree = tree;
4183     /**
4184     * Arbitrary data that can be associated with this tree and will be included in the event object that gets
4185     * passed to any nodedragover event handler (defaults to {})
4186     * @type Ext.tree.TreePanel
4187     * @property
4188     */
4189     this.dragOverData = {};
4190     // private
4191     this.lastInsertClass = "x-tree-no-status";
4192 };
4193
4194 Ext.extend(Ext.tree.TreeDropZone, Ext.dd.DropZone, {
4195     /**
4196      * @cfg {String} ddGroup
4197      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
4198      * interact with other drag drop objects in the same group (defaults to 'TreeDD').
4199      */
4200     ddGroup : "TreeDD",
4201
4202     /**
4203      * @cfg {String} expandDelay
4204      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
4205      * over the target (defaults to 1000)
4206      */
4207     expandDelay : 1000,
4208
4209     // private
4210     expandNode : function(node){
4211         if(node.hasChildNodes() && !node.isExpanded()){
4212             node.expand(false, null, this.triggerCacheRefresh.createDelegate(this));
4213         }
4214     },
4215
4216     // private
4217     queueExpand : function(node){
4218         this.expandProcId = this.expandNode.defer(this.expandDelay, this, [node]);
4219     },
4220
4221     // private
4222     cancelExpand : function(){
4223         if(this.expandProcId){
4224             clearTimeout(this.expandProcId);
4225             this.expandProcId = false;
4226         }
4227     },
4228
4229     // private
4230     isValidDropPoint : function(n, pt, dd, e, data){
4231         if(!n || !data){ return false; }
4232         var targetNode = n.node;
4233         var dropNode = data.node;
4234         // default drop rules
4235         if(!(targetNode && targetNode.isTarget && pt)){
4236             return false;
4237         }
4238         if(pt == "append" && targetNode.allowChildren === false){
4239             return false;
4240         }
4241         if((pt == "above" || pt == "below") && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){
4242             return false;
4243         }
4244         if(dropNode && (targetNode == dropNode || dropNode.contains(targetNode))){
4245             return false;
4246         }
4247         // reuse the object
4248         var overEvent = this.dragOverData;
4249         overEvent.tree = this.tree;
4250         overEvent.target = targetNode;
4251         overEvent.data = data;
4252         overEvent.point = pt;
4253         overEvent.source = dd;
4254         overEvent.rawEvent = e;
4255         overEvent.dropNode = dropNode;
4256         overEvent.cancel = false;  
4257         var result = this.tree.fireEvent("nodedragover", overEvent);
4258         return overEvent.cancel === false && result !== false;
4259     },
4260
4261     // private
4262     getDropPoint : function(e, n, dd){
4263         var tn = n.node;
4264         if(tn.isRoot){
4265             return tn.allowChildren !== false ? "append" : false; // always append for root
4266         }
4267         var dragEl = n.ddel;
4268         var t = Ext.lib.Dom.getY(dragEl), b = t + dragEl.offsetHeight;
4269         var y = Ext.lib.Event.getPageY(e);
4270         var noAppend = tn.allowChildren === false || tn.isLeaf();
4271         if(this.appendOnly || tn.parentNode.allowChildren === false){
4272             return noAppend ? false : "append";
4273         }
4274         var noBelow = false;
4275         if(!this.allowParentInsert){
4276             noBelow = tn.hasChildNodes() && tn.isExpanded();
4277         }
4278         var q = (b - t) / (noAppend ? 2 : 3);
4279         if(y >= t && y < (t + q)){
4280             return "above";
4281         }else if(!noBelow && (noAppend || y >= b-q && y <= b)){
4282             return "below";
4283         }else{
4284             return "append";
4285         }
4286     },
4287
4288     // private
4289     onNodeEnter : function(n, dd, e, data){
4290         this.cancelExpand();
4291     },
4292     
4293     onContainerOver : function(dd, e, data) {
4294         if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
4295             return this.dropAllowed;
4296         }
4297         return this.dropNotAllowed;
4298     },
4299
4300     // private
4301     onNodeOver : function(n, dd, e, data){
4302         var pt = this.getDropPoint(e, n, dd);
4303         var node = n.node;
4304         
4305         // auto node expand check
4306         if(!this.expandProcId && pt == "append" && node.hasChildNodes() && !n.node.isExpanded()){
4307             this.queueExpand(node);
4308         }else if(pt != "append"){
4309             this.cancelExpand();
4310         }
4311         
4312         // set the insert point style on the target node
4313         var returnCls = this.dropNotAllowed;
4314         if(this.isValidDropPoint(n, pt, dd, e, data)){
4315            if(pt){
4316                var el = n.ddel;
4317                var cls;
4318                if(pt == "above"){
4319                    returnCls = n.node.isFirst() ? "x-tree-drop-ok-above" : "x-tree-drop-ok-between";
4320                    cls = "x-tree-drag-insert-above";
4321                }else if(pt == "below"){
4322                    returnCls = n.node.isLast() ? "x-tree-drop-ok-below" : "x-tree-drop-ok-between";
4323                    cls = "x-tree-drag-insert-below";
4324                }else{
4325                    returnCls = "x-tree-drop-ok-append";
4326                    cls = "x-tree-drag-append";
4327                }
4328                if(this.lastInsertClass != cls){
4329                    Ext.fly(el).replaceClass(this.lastInsertClass, cls);
4330                    this.lastInsertClass = cls;
4331                }
4332            }
4333        }
4334        return returnCls;
4335     },
4336
4337     // private
4338     onNodeOut : function(n, dd, e, data){
4339         this.cancelExpand();
4340         this.removeDropIndicators(n);
4341     },
4342
4343     // private
4344     onNodeDrop : function(n, dd, e, data){
4345         var point = this.getDropPoint(e, n, dd);
4346         var targetNode = n.node;
4347         targetNode.ui.startDrop();
4348         if(!this.isValidDropPoint(n, point, dd, e, data)){
4349             targetNode.ui.endDrop();
4350             return false;
4351         }
4352         // first try to find the drop node
4353         var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null);
4354         return this.processDrop(targetNode, data, point, dd, e, dropNode);
4355     },
4356     
4357     onContainerDrop : function(dd, e, data){
4358         if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
4359             var targetNode = this.tree.getRootNode();       
4360             targetNode.ui.startDrop();
4361             var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, 'append', e) : null);
4362             return this.processDrop(targetNode, data, 'append', dd, e, dropNode);
4363         }
4364         return false;
4365     },
4366     
4367     // private
4368     processDrop: function(target, data, point, dd, e, dropNode){
4369         var dropEvent = {
4370             tree : this.tree,
4371             target: target,
4372             data: data,
4373             point: point,
4374             source: dd,
4375             rawEvent: e,
4376             dropNode: dropNode,
4377             cancel: !dropNode,
4378             dropStatus: false
4379         };
4380         var retval = this.tree.fireEvent("beforenodedrop", dropEvent);
4381         if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){
4382             target.ui.endDrop();
4383             return dropEvent.dropStatus;
4384         }
4385     
4386         target = dropEvent.target;
4387         if(point == 'append' && !target.isExpanded()){
4388             target.expand(false, null, function(){
4389                 this.completeDrop(dropEvent);
4390             }.createDelegate(this));
4391         }else{
4392             this.completeDrop(dropEvent);
4393         }
4394         return true;
4395     },
4396
4397     // private
4398     completeDrop : function(de){
4399         var ns = de.dropNode, p = de.point, t = de.target;
4400         if(!Ext.isArray(ns)){
4401             ns = [ns];
4402         }
4403         var n;
4404         for(var i = 0, len = ns.length; i < len; i++){
4405             n = ns[i];
4406             if(p == "above"){
4407                 t.parentNode.insertBefore(n, t);
4408             }else if(p == "below"){
4409                 t.parentNode.insertBefore(n, t.nextSibling);
4410             }else{
4411                 t.appendChild(n);
4412             }
4413         }
4414         n.ui.focus();
4415         if(Ext.enableFx && this.tree.hlDrop){
4416             n.ui.highlight();
4417         }
4418         t.ui.endDrop();
4419         this.tree.fireEvent("nodedrop", de);
4420     },
4421
4422     // private
4423     afterNodeMoved : function(dd, data, e, targetNode, dropNode){
4424         if(Ext.enableFx && this.tree.hlDrop){
4425             dropNode.ui.focus();
4426             dropNode.ui.highlight();
4427         }
4428         this.tree.fireEvent("nodedrop", this.tree, targetNode, data, dd, e);
4429     },
4430
4431     // private
4432     getTree : function(){
4433         return this.tree;
4434     },
4435
4436     // private
4437     removeDropIndicators : function(n){
4438         if(n && n.ddel){
4439             var el = n.ddel;
4440             Ext.fly(el).removeClass([
4441                     "x-tree-drag-insert-above",
4442                     "x-tree-drag-insert-below",
4443                     "x-tree-drag-append"]);
4444             this.lastInsertClass = "_noclass";
4445         }
4446     },
4447
4448     // private
4449     beforeDragDrop : function(target, e, id){
4450         this.cancelExpand();
4451         return true;
4452     },
4453
4454     // private
4455     afterRepair : function(data){
4456         if(data && Ext.enableFx){
4457             data.node.ui.highlight();
4458         }
4459         this.hideProxy();
4460     }    
4461 });
4462
4463 }/**
4464  * @class Ext.tree.TreeDragZone
4465  * @extends Ext.dd.DragZone
4466  * @constructor
4467  * @param {String/HTMLElement/Element} tree The {@link Ext.tree.TreePanel} for which to enable dragging
4468  * @param {Object} config
4469  */
4470 if(Ext.dd.DragZone){
4471 Ext.tree.TreeDragZone = function(tree, config){
4472     Ext.tree.TreeDragZone.superclass.constructor.call(this, tree.innerCt, config);
4473     /**
4474     * The TreePanel for this drag zone
4475     * @type Ext.tree.TreePanel
4476     * @property
4477     */
4478     this.tree = tree;
4479 };
4480
4481 Ext.extend(Ext.tree.TreeDragZone, Ext.dd.DragZone, {
4482     /**
4483      * @cfg {String} ddGroup
4484      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
4485      * interact with other drag drop objects in the same group (defaults to 'TreeDD').
4486      */
4487     ddGroup : "TreeDD",
4488
4489     // private
4490     onBeforeDrag : function(data, e){
4491         var n = data.node;
4492         return n && n.draggable && !n.disabled;
4493     },
4494
4495     // private
4496     onInitDrag : function(e){
4497         var data = this.dragData;
4498         this.tree.getSelectionModel().select(data.node);
4499         this.tree.eventModel.disable();
4500         this.proxy.update("");
4501         data.node.ui.appendDDGhost(this.proxy.ghost.dom);
4502         this.tree.fireEvent("startdrag", this.tree, data.node, e);
4503     },
4504
4505     // private
4506     getRepairXY : function(e, data){
4507         return data.node.ui.getDDRepairXY();
4508     },
4509
4510     // private
4511     onEndDrag : function(data, e){
4512         this.tree.eventModel.enable.defer(100, this.tree.eventModel);
4513         this.tree.fireEvent("enddrag", this.tree, data.node, e);
4514     },
4515
4516     // private
4517     onValidDrop : function(dd, e, id){
4518         this.tree.fireEvent("dragdrop", this.tree, this.dragData.node, dd, e);
4519         this.hideProxy();
4520     },
4521
4522     // private
4523     beforeInvalidDrop : function(e, id){
4524         // this scrolls the original position back into view
4525         var sm = this.tree.getSelectionModel();
4526         sm.clearSelections();
4527         sm.select(this.dragData.node);
4528     },
4529     
4530     // private
4531     afterRepair : function(){
4532         if (Ext.enableFx && this.tree.hlDrop) {
4533             Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || "c3daf9");
4534         }
4535         this.dragging = false;
4536     }
4537 });
4538 }/**
4539  * @class Ext.tree.TreeEditor
4540  * @extends Ext.Editor
4541  * Provides editor functionality for inline tree node editing.  Any valid {@link Ext.form.Field} subclass can be used
4542  * as the editor field.
4543  * @constructor
4544  * @param {TreePanel} tree
4545  * @param {Object} fieldConfig (optional) Either a prebuilt {@link Ext.form.Field} instance or a Field config object
4546  * that will be applied to the default field instance (defaults to a {@link Ext.form.TextField}).
4547  * @param {Object} config (optional) A TreeEditor config object
4548  */
4549 Ext.tree.TreeEditor = function(tree, fc, config){
4550     fc = fc || {};
4551     var field = fc.events ? fc : new Ext.form.TextField(fc);
4552     
4553     Ext.tree.TreeEditor.superclass.constructor.call(this, field, config);
4554
4555     this.tree = tree;
4556
4557     if(!tree.rendered){
4558         tree.on('render', this.initEditor, this);
4559     }else{
4560         this.initEditor(tree);
4561     }
4562 };
4563
4564 Ext.extend(Ext.tree.TreeEditor, Ext.Editor, {
4565     /**
4566      * @cfg {String} alignment
4567      * The position to align to (see {@link Ext.Element#alignTo} for more details, defaults to "l-l").
4568      */
4569     alignment: "l-l",
4570     // inherit
4571     autoSize: false,
4572     /**
4573      * @cfg {Boolean} hideEl
4574      * True to hide the bound element while the editor is displayed (defaults to false)
4575      */
4576     hideEl : false,
4577     /**
4578      * @cfg {String} cls
4579      * CSS class to apply to the editor (defaults to "x-small-editor x-tree-editor")
4580      */
4581     cls: "x-small-editor x-tree-editor",
4582     /**
4583      * @cfg {Boolean} shim
4584      * True to shim the editor if selects/iframes could be displayed beneath it (defaults to false)
4585      */
4586     shim:false,
4587     // inherit
4588     shadow:"frame",
4589     /**
4590      * @cfg {Number} maxWidth
4591      * The maximum width in pixels of the editor field (defaults to 250).  Note that if the maxWidth would exceed
4592      * the containing tree element's size, it will be automatically limited for you to the container width, taking
4593      * scroll and client offsets into account prior to each edit.
4594      */
4595     maxWidth: 250,
4596     /**
4597      * @cfg {Number} editDelay The number of milliseconds between clicks to register a double-click that will trigger
4598      * editing on the current node (defaults to 350).  If two clicks occur on the same node within this time span,
4599      * the editor for the node will display, otherwise it will be processed as a regular click.
4600      */
4601     editDelay : 350,
4602
4603     initEditor : function(tree){
4604         tree.on({
4605             scope      : this,
4606             beforeclick: this.beforeNodeClick,
4607             dblclick   : this.onNodeDblClick
4608         });
4609         
4610         this.on({
4611             scope          : this,
4612             complete       : this.updateNode,
4613             beforestartedit: this.fitToTree,
4614             specialkey     : this.onSpecialKey
4615         });
4616         
4617         this.on('startedit', this.bindScroll, this, {delay:10});
4618     },
4619
4620     // private
4621     fitToTree : function(ed, el){
4622         var td = this.tree.getTreeEl().dom, nd = el.dom;
4623         if(td.scrollLeft >  nd.offsetLeft){ // ensure the node left point is visible
4624             td.scrollLeft = nd.offsetLeft;
4625         }
4626         var w = Math.min(
4627                 this.maxWidth,
4628                 (td.clientWidth > 20 ? td.clientWidth : td.offsetWidth) - Math.max(0, nd.offsetLeft-td.scrollLeft) - /*cushion*/5);
4629         this.setSize(w, '');
4630     },
4631
4632     /**
4633      * Edit the text of the passed {@link Ext.tree.TreeNode TreeNode}.
4634      * @param node {Ext.tree.TreeNode} The TreeNode to edit. The TreeNode must be {@link Ext.tree.TreeNode#editable editable}.
4635      */
4636     triggerEdit : function(node, defer){
4637         this.completeEdit();
4638                 if(node.attributes.editable !== false){
4639            /**
4640             * The {@link Ext.tree.TreeNode TreeNode} this editor is bound to. Read-only.
4641             * @type Ext.tree.TreeNode
4642             * @property editNode
4643             */
4644                         this.editNode = node;
4645             if(this.tree.autoScroll){
4646                 Ext.fly(node.ui.getEl()).scrollIntoView(this.tree.body);
4647             }
4648             var value = node.text || '';
4649             if (!Ext.isGecko && Ext.isEmpty(node.text)){
4650                 node.setText('&#160;');
4651             }
4652             this.autoEditTimer = this.startEdit.defer(this.editDelay, this, [node.ui.textNode, value]);
4653             return false;
4654         }
4655     },
4656
4657     // private
4658     bindScroll : function(){
4659         this.tree.getTreeEl().on('scroll', this.cancelEdit, this);
4660     },
4661
4662     // private
4663     beforeNodeClick : function(node, e){
4664         clearTimeout(this.autoEditTimer);
4665         if(this.tree.getSelectionModel().isSelected(node)){
4666             e.stopEvent();
4667             return this.triggerEdit(node);
4668         }
4669     },
4670
4671     onNodeDblClick : function(node, e){
4672         clearTimeout(this.autoEditTimer);
4673     },
4674
4675     // private
4676     updateNode : function(ed, value){
4677         this.tree.getTreeEl().un('scroll', this.cancelEdit, this);
4678         this.editNode.setText(value);
4679     },
4680
4681     // private
4682     onHide : function(){
4683         Ext.tree.TreeEditor.superclass.onHide.call(this);
4684         if(this.editNode){
4685             this.editNode.ui.focus.defer(50, this.editNode.ui);
4686         }
4687     },
4688
4689     // private
4690     onSpecialKey : function(field, e){
4691         var k = e.getKey();
4692         if(k == e.ESC){
4693             e.stopEvent();
4694             this.cancelEdit();
4695         }else if(k == e.ENTER && !e.hasModifier()){
4696             e.stopEvent();
4697             this.completeEdit();
4698         }
4699     },
4700     
4701     onDestroy : function(){
4702         clearTimeout(this.autoEditTimer);
4703         Ext.tree.TreeEditor.superclass.onDestroy.call(this);
4704         var tree = this.tree;
4705         tree.un('beforeclick', this.beforeNodeClick, this);
4706         tree.un('dblclick', this.onNodeDblClick, this);
4707     }
4708 });