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