1 /**
  2 * @fileOverview  
  3 * Copyright (c) 2013 Regione Autonoma della Sardegna
  4 * Published under the GPL license.<br>
  5 * See <a href="https://sardegnageoportale.it/webgis/license.txt">https://sardegnageoportale.it/webgis/license.txt</a>
  6 * for the full text of the license.
  7 * @version 0.1
  8 */
  9 
 10 /**
 11  * @include GeoExt/widgets/MapPanel.js
 12  * @require OpenLayers/Layer.js
 13  */
 14 
 15 /**
 16  * @namespace framework.plugins
 17  */
 18 Ext.namespace("GeoExt.tree");
 19 
 20 /** 
 21  *  The TreeNode UI implementation is separate from the tree implementation, and allows customizing of the appearance of tree nodes.
 22  *  @name_ LayerNodeUI
 23  *  @class Layer node object. 
 24  *  @constructor
 25  *  @extends <a target="_blank" href="http://all-docs.info/extjs4/docs/api/Ext.tree.TreeNodeUI.html">Ext.tree.TreeNodeUI</a>
 26  */
 27 GeoExt.tree.LayerNodeUI = Ext.extend(Ext.tree.TreeNodeUI, 
 28 /** 
 29 * @lends GeoExt.tree.LayerNodeUI.prototype 
 30 */
 31 {
 32     
 33     /**
 34     * @private
 35     * @param {Object} config  configurations object
 36     */  
 37     constructor: function(config) {
 38         GeoExt.tree.LayerNodeUI.superclass.constructor.apply(this, arguments);
 39     },
 40    
 41     /** 
 42     * @private
 43     * @param {boolan} bulkRender  configurations object
 44     */ 
 45     render: function(bulkRender) {
 46         var a = this.node.attributes;
 47         if (a.checked === undefined) {
 48             a.checked = this.node.layer.getVisibility();
 49         }
 50         /* Ext.tree.treeNodeUI render looks for and handles checked
 51          * attribute, but not the disabled attribute, so we set it
 52          * directly on the node object and not the attributes hash*/
 53         if (a.disabled === undefined && this.node.autoDisable) {
 54             this.node.disabled = this.node.layer.inRange === false || !this.node.layer.calculateInRange();
 55         }
 56         GeoExt.tree.LayerNodeUI.superclass.render.apply(this, arguments);
 57         var cb = this.checkbox;
 58         if(a.checkedGroup) {
 59             // replace the checkbox with a radio button
 60             var radio = Ext.DomHelper.insertAfter(cb,
 61                 ['<input type="radio" name="', a.checkedGroup,
 62                 '_checkbox" class="', cb.className,
 63                 cb.checked ? '" checked="checked"' : '',
 64                 '"></input>'].join(""));
 65             radio.defaultChecked = cb.defaultChecked;
 66             Ext.get(cb).remove();
 67             this.checkbox = radio;
 68         }
 69         this.enforceOneVisible();
 70     },
 71      
 72     /** Method .
 73     * onClick method
 74     * @private
 75     * @param {Object} e
 76     */ 
 77     onClick: function(e) {
 78         
 79     	// Setting background color baselayer  	
 80    	    var container = Ext.get(this.node.layer.map.getViewport());
 81       
 82         if(e.getTarget('.x-tree-node-cb', 1)) {
 83             this.toggleCheck(this.isChecked());
 84         } else {
 85             GeoExt.tree.LayerNodeUI.superclass.onClick.apply(this, arguments);
 86         }
 87         
 88          if (this.node.attributes.checkedGroup === "background") {
 89              if (this.node.attributes.layer.background){
 90                 if(this.node.attributes.layer.visibility === true) 
 91                     container.setStyle('background-color', this.node.attributes.layer.background);
 92              }
 93 	     else
 94 	        container.setStyle('background-color', "FFFFFF");
 95         } 
 96     },
 97     
 98     /**
 99     * toggleCheck method
100     * @private
101     * @param {boolean} value
102     */ 
103     toggleCheck: function(value) {
104         value = (value === undefined ? !this.isChecked() : value);
105         GeoExt.tree.LayerNodeUI.superclass.toggleCheck.call(this, value);
106         
107         this.enforceOneVisible();
108     },
109     
110     /**
111     * Makes sure that only one layer is visible if checkedGroup is set.
112     * @private
113     */
114     enforceOneVisible: function() {
115         var attributes = this.node.attributes;
116         var group = attributes.checkedGroup;
117         // If we are in the baselayer group, the map will take care of
118         // enforcing visibility.
119         if(group && group !== "gx_baselayer") {
120             var layer = this.node.layer;
121             var checkedNodes = this.node.getOwnerTree().getChecked();
122             var checkedCount = 0;
123             // enforce "not more than one visible"
124             Ext.each(checkedNodes, function(n){
125                 var l = n.layer;
126                 if(!n.hidden && n.attributes.checkedGroup === group) {
127                     checkedCount++;
128                     if(l != layer && attributes.checked) {
129                         l.setVisibility(false);
130                         if (l.layerNT) {
131                             l.map.removeLayer(l.layerNT);
132                         }
133                         
134                     }
135                 }
136             });
137             
138             //CONTROLLO LAYER TMS "NON DISPONIBILE
139            if (layer.layerNT && layer.map && layer.visibility) {
140                 layer.map.addLayer(layer.layerNT);
141                 layer.layerNT.setZIndex(-9);
142                 layer.setZIndex(-10);
143             }
144                 
145             // enforce "at least one visible"
146             if(checkedCount === 0 && attributes.checked == false) {
147                 layer.setVisibility(true);
148             }
149         }
150     },
151      
152     /**
153     * For radio buttons, makes sure that we do not use the option group of
154     * the original, otherwise only the original or the clone can be checked
155     * @private
156     * @param {String} ghostNode DOMElement
157     */
158     appendDDGhost : function(ghostNode){
159         var n = this.elNode.cloneNode(true);
160         var radio = Ext.DomQuery.select("input[type='radio']", n);
161         Ext.each(radio, function(r) {
162             r.name = r.name + "_clone";
163         });
164         ghostNode.appendChild(n);
165     }
166 });
167 
168 
169 
170 /** 
171  * A subclass of 'Ext.tree.TreeNode' that is connected to an
172  * 'OpenLayers.Layer' by setting the node's layer property. Checking or
173  * unchecking the checkbox of this node will directly affect the layer and
174  * vice versa. The default iconCls for this node's icon is
175  * "gx-tree-layer-icon", unless it has children.
176  *
177  * Setting the node's layer property to a layer name instead of an object
178  * will also work. As soon as a layer is found, it will be stored as layer
179  * property in the attributes hash.
180  * 
181  * The node's text property defaults to the layer name.
182  *      
183  * If the node has a checkedGroup attribute configured, it will be
184  * rendered with a radio button instead of the checkbox. The value of
185  * the checkedGroup attribute is a string, identifying the options group
186  * for the node.
187  * 
188  * To use this node type in a ``TreePanel`` config, set ``nodeType`` to
189  * "gx_layer". 
190  * @name_ LayerNode
191  * @class  A subclass of 'Ext.tree.TreeNode' that is connected to an 'OpenLayers.Layer' by setting the node's layer property.
192  * @constructor
193  * @extends <a target="_blank" href="http://dev.sencha.com/deploy/ext-1.1.1/docs/output/Ext.tree.AsyncTreeNode.html">Ext.tree.AsyncTreeNode</a>
194  */
195 GeoExt.tree.LayerNode = Ext.extend(Ext.tree.AsyncTreeNode, 
196 /** 
197 * @lends GeoExt.tree.LayerNode.prototype 
198 */     
199 {
200     
201     /**
202     * The layer that this layer node will
203     * be bound to, or the name of the layer (has to match the layer's
204     * name property). If a layer name is provided, ``layerStore`` also has
205     * to be provided.
206     * @public
207     * @type OpenLayers.Layer/String
208     */
209     layer: null,
210     
211     /**
212      * Should this node automattically disable itself when the layer
213      * is out of range and enable itself when the layer is in range.
214      * Defaults to true, unless ``layer`` has ``isBaseLayer``==true
215      * or ``alwaysInRange``==true.
216      * @public
217      * @type boolean
218      */
219     autoDisable: null,
220     
221     /** 
222      * The layer store containing the layer that this node represents.  If set
223      * to "auto", the node will query the ComponentManager for a
224      * :class:`GeoExt.MapPanel`, take the first one it finds and take its layer
225      * store. This property is only required if ``layer`` is provided as a
226      * string.  
227      * @public
228      * @type GeoExt.data.LayerStore/String
229      */     
230     layerStore: null,
231     
232     /** private: method[constructor]
233      *  Private constructor override.
234      */
235      
236     /** 
237     * Constructor.
238     * @public
239     * @param {Object} config object configuration
240     */  
241     constructor: function(config) {
242         config.leaf = config.leaf || !(config.children || config.loader);
243         
244         if(!config.iconCls && !config.children) {
245             config.iconCls = "gx-tree-layer-icon";
246         }
247         if(config.loader && !(config.loader instanceof Ext.tree.TreeLoader)) {
248             config.loader = new GeoExt.tree.LayerParamLoader(config.loader);
249         }
250         
251         this.defaultUI = this.defaultUI || GeoExt.tree.LayerNodeUI;
252         
253         Ext.apply(this, {
254             layer: config.layer,
255             layerStore: config.layerStore,
256             autoDisable: config.autoDisable
257         });
258         if (config.text) {
259             this.fixedText = true;
260         }
261         GeoExt.tree.LayerNode.superclass.constructor.apply(this, arguments);
262     },
263      
264     /** 
265      * render method
266     * @private
267     * @param {boolean} bulkRender
268     */  
269     render: function(bulkRender) {
270         var layer = this.layer instanceof OpenLayers.Layer && this.layer;
271         if(!layer) {
272             // guess the store if not provided
273             if(!this.layerStore || this.layerStore == "auto") {
274                 this.layerStore = GeoExt.MapPanel.guess().layers;
275             }
276             // now we try to find the layer by its name in the layer store
277             var i = this.layerStore.findBy(function(o) {
278                 return o.get("title") == this.layer;
279             }, this);
280             if(i != -1) {
281                 // if we found the layer, we can assign it and everything
282                 // will be fine
283                 layer = this.layerStore.getAt(i).getLayer();
284             }
285         }
286         if (!this.rendered || !layer) {
287             var ui = this.getUI();
288             
289             if(layer) {
290                 this.layer = layer;
291                 // no DD and radio buttons for base layers
292                 if(layer.isBaseLayer) {
293                     this.draggable = false;
294                     Ext.applyIf(this.attributes, {
295                         checkedGroup: "gx_baselayer"
296                     });                  
297                 }
298                 
299                 //base layers & alwaysInRange layers should never be auto-disabled
300                 this.autoDisable = !(this.autoDisable===false || this.layer.isBaseLayer || this.layer.alwaysInRange);
301                 
302                 if(!this.text) {
303                     this.text = layer.name;
304                 }
305                 
306                 ui.show();
307                 this.addVisibilityEventHandlers();
308             } else {
309                 ui.hide();
310             }
311             
312             if(this.layerStore instanceof GeoExt.data.LayerStore) {
313                 this.addStoreEventHandlers(layer);
314             }            
315         }
316         GeoExt.tree.LayerNode.superclass.render.apply(this, arguments);
317     },
318     
319     /** private: method[addVisibilityHandlers]
320      *  Adds handlers that sync the checkbox state with the layer's visibility
321      *  state
322      */
323      
324     /** 
325     * Adds handlers that sync the checkbox state with the layer's visibility state
326     * @private
327     */  
328     addVisibilityEventHandlers: function() {
329         this.layer.events.on({
330             "visibilitychanged": this.onLayerVisibilityChanged,
331             scope: this
332         }); 
333         this.on({
334             "checkchange": this.onCheckChange,
335             scope: this
336         });
337         if(this.autoDisable){
338             if (this.layer.map) {
339                 this.layer.map.events.register("moveend", this, this.onMapMoveend);
340             } else {
341                 this.layer.events.register("added", this, function added() {
342                     this.layer.events.unregister("added", this, added);
343                     this.layer.map.events.register("moveend", this, this.onMapMoveend);
344                 });
345             }
346         }
347     },
348     
349     /** 
350     * handler for visibilitychanged events on the layer.
351     * @private
352     * @param {Object} target Target link to the father object
353     */  
354     onLayerVisibilityChanged: function() {
355         if(!this._visibilityChanging) {
356             this.getUI().toggleCheck(this.layer.getVisibility());
357         }
358     },
359     
360     /** 
361     * handler for checkchange events.
362     * @private
363     * @param {GeoExt.tree.LayerNode} node
364     * @param {boolan} checked
365     */  
366     onCheckChange: function(node, checked) {
367         if(checked != this.layer.getVisibility()) {
368             this._visibilityChanging = true;
369             var layer = this.layer;
370             if(checked && layer.isBaseLayer && layer.map) {
371                 layer.map.setBaseLayer(layer);
372             } else {
373                 layer.setVisibility(checked);
374             }
375             delete this._visibilityChanging;
376             
377             if(node.parentNode && node.parentNode.fireEvent)
378                 node.parentNode.fireEvent('childcheckchange',node,checked);
379         }
380     },
381      
382     /** 
383     * handler for map moveend events to determine if node should be
384     * disabled or enabled 
385     * @private
386     * @param {OpenLayers.Event} evt
387     */
388     onMapMoveend: function(evt){
389         /* scoped to node */
390         if (this.autoDisable) {
391             if (this.layer.inRange === false) {
392                 this.disable();
393             }
394             else {
395                 this.enable();
396             }
397         }
398     },
399     
400     /** 
401     * Adds handlers that make sure the node disappeares when the layer is
402     * removed from the store, and appears when it is re-added. 
403     * @private
404     */
405     addStoreEventHandlers: function() {
406         this.layerStore.on({
407             "add": this.onStoreAdd,
408             "remove": this.onStoreRemove,
409             "update": this.onStoreUpdate,
410             scope: this
411         });
412     },
413     
414     /** private: method[onStoreAdd]
415      *  :param store: ``Ext.data.Store``
416      *  :param records: ``Array(Ext.data.Record)``
417      *  :param index: ``Number``
418      *
419      *  handler for add events on the store 
420      */
421      
422     /**
423     * handler for add events on the store. 
424     * @private
425     * @param {Ext.data.Store} store
426     * @param {Array(Ext.data.Record)} records
427     * @param {Number} index
428     */
429     onStoreAdd: function(store, records, index) {
430         var l;
431         for(var i=0; i<records.length; ++i) {
432             l = records[i].getLayer();
433             if(this.layer == l) {
434                 this.getUI().show();
435                 break;
436             } else if (this.layer == l.name) {
437                 // layer is a string, which means the node has not yet
438                 // been rendered because the layer was not found. But
439                 // now we have the layer and can render.
440                 this.render();
441                 break;
442             }
443         }
444     },
445      
446     /**
447     * handler for remove events on the store.
448     * @private
449     * @param {Ext.data.Store} store
450     * @param {Array(Ext.data.Record)} records
451     * @param {Number} index
452     */
453     onStoreRemove: function(store, record, index) {
454         if(this.layer == record.getLayer()) {
455             this.getUI().hide();
456         }
457     },
458 
459     /**
460     * Listener for the store's update event.
461     * @private
462     * @param {Ext.data.Store} store
463     * @param {Array(Ext.data.Record)} records
464     * @param {Number} index
465     */
466     onStoreUpdate: function(store, record, operation) {
467         var layer = record.getLayer();
468         if(!this.fixedText && (this.layer == layer && this.text !== layer.name)) {
469             this.setText(layer.name);
470         }
471     },
472 
473     /** private: method[destroy]
474      */
475      
476     /**
477     * Delete layer
478     * @private
479     */
480     destroy: function() {
481         var layer = this.layer;
482         if (layer instanceof OpenLayers.Layer) {
483             if (layer.map) {
484                 layer.map.events.unregister("moveend", this, this.onMapMoveend);
485             }
486             layer.events.un({
487                 "visibilitychanged": this.onLayerVisibilityChanged,
488                 scope: this
489             });
490         }
491         delete this.layer;
492         var layerStore = this.layerStore;
493         if(layerStore) {
494             layerStore.un("add", this.onStoreAdd, this);
495             layerStore.un("remove", this.onStoreRemove, this);
496             layerStore.un("update", this.onStoreUpdate, this);
497         }
498         delete this.layerStore;
499         this.un("checkchange", this.onCheckChange, this);
500 
501         GeoExt.tree.LayerNode.superclass.destroy.apply(this, arguments);
502     }
503 });
504 
505 /**
506  * NodeType: gx_layer
507  */
508 Ext.tree.TreePanel.nodeTypes.gx_layer = GeoExt.tree.LayerNode;
509 
510 /**
511  * Extension for drag node effects
512  */
513 Ext.extend(Ext.tree.TreeDropZone, Ext.dd.DropZone, {
514     /**
515      * @cfg {String} ddGroup
516      * A named drag drop group to which this object belongs.  If a group is specified, then this object will only
517      * interact with other drag drop objects in the same group (defaults to 'TreeDD').
518      */
519     ddGroup : "TreeDD",
520 
521     /**
522      * @cfg {String} expandDelay
523      * The delay in milliseconds to wait before expanding a target tree node while dragging a droppable node
524      * over the target (defaults to 1000)
525      */
526     expandDelay : 1000,
527 
528     // private
529     expandNode : function(node){
530         if(node.hasChildNodes() && !node.isExpanded()){
531             node.expand(false, null, this.triggerCacheRefresh.createDelegate(this));
532         }
533     },
534 
535     // private
536     queueExpand : function(node){
537         this.expandProcId = this.expandNode.defer(this.expandDelay, this, [node]);
538     },
539 
540     // private
541     cancelExpand : function(){
542         if(this.expandProcId){
543             clearTimeout(this.expandProcId);
544             this.expandProcId = false;
545         }
546     },
547 
548     // private
549     isValidDropPoint : function(n, pt, dd, e, data){
550         if(!n || !data){ return false; }
551         var targetNode = n.node;
552         var dropNode = data.node;
553         // default drop rules
554         if(!(targetNode && targetNode.isTarget && pt)){
555             return false;
556         }
557         if(pt == "append" && targetNode.allowChildren === false){
558             return false;
559         }
560         if((pt == "above" || pt == "below") && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){
561             return false;
562         }
563         if(dropNode && (targetNode == dropNode || dropNode.contains(targetNode))){
564             return false;
565         }
566         // reuse the object
567         var overEvent = this.dragOverData;
568         overEvent.tree = this.tree;
569         overEvent.target = targetNode;
570         overEvent.data = data;
571         overEvent.point = pt;
572         overEvent.source = dd;
573         overEvent.rawEvent = e;
574         overEvent.dropNode = dropNode;
575         overEvent.cancel = false;  
576         var result = this.tree.fireEvent("nodedragover", overEvent);
577         return overEvent.cancel === false && result !== false;
578     },
579 
580     // private
581     getDropPoint : function(e, n, dd){
582         var tn = n.node;
583         if(tn.isRoot){
584             return tn.allowChildren !== false ? "append" : false; // always append for root
585         }
586         var dragEl = n.ddel;
587         var t = Ext.lib.Dom.getY(dragEl), b = t + dragEl.offsetHeight;
588         var y = Ext.lib.Event.getPageY(e);
589         var noAppend = tn.allowChildren === false || tn.isLeaf();
590         if(this.appendOnly || tn.parentNode.allowChildren === false){
591             return noAppend ? false : "append";
592         }
593         var noBelow = false;
594         if(!this.allowParentInsert){
595             noBelow = tn.hasChildNodes() && tn.isExpanded();
596         }
597         var q = (b - t) / (noAppend ? 2 : 3);
598         if(y >= t && y < (t + q)){
599             return "above";
600         }else if(!noBelow && (noAppend || y >= b-q && y <= b)){
601             return "below";
602         }else{
603             return "append";
604         }
605     },
606 
607     // private
608     onNodeEnter : function(n, dd, e, data){
609         if(!data.node.layer && (n.node.layer || (n.node.firstChild && n.node.expanded))){
610             var node = n.node;
611             if(n.node.layer)
612                 node = node.parentNode;
613             if(!this.listCloseNodes)
614                 this.listCloseNodes = [];
615             node.collapse();
616             this.listCloseNodes.push(node);
617         }
618         this.cancelExpand();
619     },
620     
621     onContainerOver : function(dd, e, data) {
622         if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
623             return this.dropAllowed;
624         }
625         return this.dropNotAllowed;
626     },
627 
628     // private
629     onNodeOver : function(n, dd, e, data){
630         var pt = this.getDropPoint(e, n, dd);
631         var node = n.node;
632         
633         // auto node expand check
634         if(!this.expandProcId && pt == "append" && node.hasChildNodes() && !n.node.isExpanded()){
635             this.queueExpand(node);
636         }else if(pt != "append"){
637             this.cancelExpand();
638         }
639         
640         // set the insert point style on the target node
641         var returnCls = this.dropNotAllowed;
642         if(this.isValidDropPoint(n, pt, dd, e, data)){
643            if(pt){
644                var el = n.ddel;
645                var cls;
646                if(pt == "above"){
647                    returnCls = n.node.isFirst() ? "x-tree-drop-ok-above" : "x-tree-drop-ok-between";
648                    cls = "x-tree-drag-insert-above";
649                }else if(pt == "below"){
650                    returnCls = n.node.isLast() ? "x-tree-drop-ok-below" : "x-tree-drop-ok-between";
651                    cls = "x-tree-drag-insert-below";
652                }else{
653                    if(data.node.layer && !node.layer){
654                         returnCls = "x-tree-drop-ok-append";
655                         cls = "x-tree-drag-append";
656                    }else{
657                         returnCls = "x-dd-drop-nodrop";
658                         cls = "_noclass";
659                    }
660                }
661                if(this.lastInsertClass != cls){
662                    Ext.fly(el).replaceClass(this.lastInsertClass, cls);
663                    this.lastInsertClass = cls;
664                }
665            }
666        }
667        return returnCls;
668     },
669 
670     // private
671     onNodeOut : function(n, dd, e, data){
672         this.cancelExpand();
673         this.removeDropIndicators(n);
674     },
675 
676     // private
677     onNodeDrop : function(n, dd, e, data){
678         var point = this.getDropPoint(e, n, dd);
679         var targetNode = n.node;
680         targetNode.ui.startDrop();
681         if(!this.isValidDropPoint(n, point, dd, e, data)){
682             targetNode.ui.endDrop();
683             return false;
684         }
685         // first try to find the drop node
686         var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null);
687         //reopen the closed nodes
688         if(this.listCloseNodes){
689             for(var i=0;i<this.listCloseNodes.length;i++){
690                 this.listCloseNodes[i].expand();
691             }
692         this.listCloseNodes = null;
693         }
694         return this.processDrop(targetNode, data, point, dd, e, dropNode);
695     },
696     
697     onContainerDrop : function(dd, e, data){
698         if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
699             var targetNode = this.tree.getRootNode();       
700             targetNode.ui.startDrop();
701             var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, 'append', e) : null);
702             return this.processDrop(targetNode, data, 'append', dd, e, dropNode);
703         }
704         return false;
705     },
706     
707     // private
708     processDrop: function(target, data, point, dd, e, dropNode){
709         var dropEvent = {
710             tree : this.tree,
711             target: target,
712             data: data,
713             point: point,
714             source: dd,
715             rawEvent: e,
716             dropNode: dropNode,
717             cancel: !dropNode,
718             dropStatus: false
719         };
720         var retval = this.tree.fireEvent("beforenodedrop", dropEvent);
721         if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){
722             target.ui.endDrop();
723             return dropEvent.dropStatus;
724         }
725     
726         target = dropEvent.target;
727         if(point == 'append' && !target.isExpanded()){
728             target.expand(false, null, function(){
729                 this.completeDrop(dropEvent);
730             }.createDelegate(this));
731         }else{
732             this.completeDrop(dropEvent);
733         }
734         return true;
735     },
736 
737     // private
738     completeDrop : function(de){
739         var ns = de.dropNode, p = de.point, t = de.target;
740         if(!Ext.isArray(ns)){
741             ns = [ns];
742         }
743         var n;
744         for(var i = 0, len = ns.length; i < len; i++){
745             n = ns[i];
746             if(p == "above"){
747                 t.parentNode.insertBefore(n, t);
748             }else if(p == "below"){
749                 t.parentNode.insertBefore(n, t.nextSibling);
750             }else{
751                 t.appendChild(n);
752             }
753         }
754         n.ui.focus();
755         if(Ext.enableFx && this.tree.hlDrop){
756             n.ui.highlight();
757         }
758         t.ui.endDrop();
759         this.tree.fireEvent("nodedrop", de);
760     },
761 
762     // private
763     afterNodeMoved : function(dd, data, e, targetNode, dropNode){
764         if(Ext.enableFx && this.tree.hlDrop){
765             dropNode.ui.focus();
766             dropNode.ui.highlight();
767         }
768         this.tree.fireEvent("nodedrop", this.tree, targetNode, data, dd, e);
769     },
770 
771     // private
772     getTree : function(){
773         return this.tree;
774     },
775 
776     // private
777     removeDropIndicators : function(n){
778         if(n && n.ddel){
779             var el = n.ddel;
780             Ext.fly(el).removeClass([
781                     "x-tree-drag-insert-above",
782                     "x-tree-drag-insert-below",
783                     "x-tree-drag-append"]);
784             this.lastInsertClass = "_noclass";
785         }
786     },
787 
788     // private
789     beforeDragDrop : function(target, e, id){
790         this.cancelExpand();
791         return true;
792     },
793 
794     // private
795     afterRepair : function(data){
796         if(data && Ext.enableFx){
797             data.node.ui.highlight();
798         }
799         this.hideProxy();
800     }    
801 });
802