OSDN Git Service

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