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