source: trunk/web/addons/job_monarch/lib/extjs-30/pkgs/pkg-tree-debug.js @ 647

Last change on this file since 647 was 625, checked in by ramonb, 15 years ago

lib/extjs-30:

  • new ExtJS 3.0
File size: 144.8 KB
Line 
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>
16var 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
35tree.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>
54new 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 */
119Ext.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>
322new 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
916Ext.tree.TreePanel.nodeTypes = {};
917
918Ext.reg('treepanel', Ext.tree.TreePanel);Ext.tree.TreeEventModel = function(tree){
919    this.tree = tree;
920    this.tree.on('render', this.initEvents, this);
921}
922
923Ext.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 */
1085Ext.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
1111Ext.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 */
1278Ext.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
1294Ext.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 */
1396Ext.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
1483Ext.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 */
1548Ext.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
1675Ext.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 */
2195Ext.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};
2319Ext.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
2705Ext.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};
2733Ext.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
2812Ext.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 */
2825Ext.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
2834Ext.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 */
3426Ext.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 */
3477Ext.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
3513Ext.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>
3559paramOrder: ['param1','param2','param3']
3560paramOrder: 'param1 param2 param3'
3561paramOrder: 'param1,param2,param3'
3562paramOrder: '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>
3703new 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 */
3779Ext.tree.TreeFilter = function(tree, config){
3780    this.tree = tree;
3781    this.filtered = {};
3782    Ext.apply(this, config);
3783};
3784
3785Ext.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>
3887new 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 */
3900Ext.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
3959Ext.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 */
3987if(Ext.dd.DropZone){
3988   
3989Ext.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
4025Ext.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 */
4301if(Ext.dd.DragZone){
4302Ext.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
4312Ext.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 */
4380Ext.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
4394Ext.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});
Note: See TracBrowser for help on using the repository browser.