Bienvenue sur PostGIS.fr

Bienvenue sur PostGIS.fr , le site de la communauté des utilisateurs francophones de PostGIS.

PostGIS ajoute le support d'objets géographique à la base de données PostgreSQL. En effet, PostGIS "spatialise" le serverur PostgreSQL, ce qui permet de l'utiliser comme une base de données SIG.

Maintenu à jour, en fonction de nos disponibilités et des diverses sorties des outils que nous testons, nous vous proposons l'ensemble de nos travaux publiés en langue française.

source: trunk/workshop-routing-foss4g/web/ext/pkgs/pkg-tree-debug.js @ 76

Revision 76, 152.6 KB checked in by djay, 12 years ago (diff)

Ajout du répertoire web

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