Hi folks,

reading this forums for a couple of weeks it seems to me that Drag and Drop is still a mystery for many users and a few days ago it was also for me. Now, that I implemented my own drag & drop I've got a little bit more knowledge and experiences on the subject so I think it would be good to share it.

First of all: see it in action.

And now comes explanation:

I'm using proxy dragging in that example what means that I do not drag elements themselves but I create a proxy (a ghost) and I drag that. For this I've extended Ext.dd.DDProxy class as it is nearest to my needs.

Each panel has its own instance of this extended class (the name of the class is Ext.Accordion.DDDock). There is also one Ext.dd.DropZone for the Accordion dock. Instance of the DropZone is created in the Accordion constructor:

PHP Code:
Ext.Accordion = function(elconfig) {
    
    
// call parent constructor
    
Ext.Accordion.superclass.constructor.call(thiselconfig);

    
// code here deleted for clarity

    
this.dd = new Ext.dd.DropZone(this.body.dom, {ddGroup:'dock-' this.id });

}; 
// end of constructor 
When a panel is added to the Accordion it's own instance of Ext.Accordion.DDDock is created for it:

PHP Code:
Ext.extend(Ext.AccordionExt.ContentPanel, {

    
/**
        * Adds the panel to Accordion
        * @param {Ext.InfoPanel} panel Panel to add
        * @return {Ext.InfoPanel} added panel
        */
    
add: function(panel) {

        
// code here deleted for clarity

        // panel dragging
        
panel.dd = new Ext.Accordion.DDDock(panel'dock-' this.idthis);

        
// code here deleted for clarity
        
return panel;
    } 
And the jewel on the crown is the DDProxy extension itself. Code is well commented so it should be understandable:

PHP Code:
/**
    * @class Ext.Accordion.DDDock
    * @constructor
    * @extends Ext.dd.DDProxy
    * @param {Ext.InfoPanel} panel Panel the dragging object is created for
    * @param {String} group Only elements of same group interact
    * @param {Ext.Accordion} dock Place where panels are docked/undocked
    */
Ext.Accordion.DDDock = function(panelgroupdock) {

    
// call parent constructor
    
Ext.Accordion.DDDock.superclass.constructor.call(thispanel.el.domgroup);

    
// save panel and dock references for use in methods
    
this.panel panel;
    
this.dock dock;

    
// drag by grabbing the title only
    
this.setHandleElId(panel.titleEl.id);

    
// move only in the dock if undockable
    
if(false === dock.undockable) {
        
this.setXConstraint(00);
    }

    
// init internal variables
    
this.lastY 0;
    
this.dY 0;
    
this.lastDY 0;

    
// debugging flag. When true some console logs are produced while dragging
    // Warning! Debugging works only with FF/FB
    
this.debug false;

}; 
// end of constructor
// }}}

// extend
Ext.extend(Ext.Accordion.DDDockExt.dd.DDProxy, {

    
// {{{
    /**
        * Default DDProxy startDrag override
        * Saves some variable for use by other methods 
        * and creates nice dragging proxy (ghost)
        *
        * Received x, y arguments are not used
        */
    
startDrag: function(xy) {

        
// get srcEl (the original) and dragEl (the ghost)
        
var srcEl Ext.get(this.getEl());
        var 
dragEl Ext.get(this.getDragEl());

        
// make the ghost look same as original
        
dragEl.dom.className srcEl.dom.className;

        
// semitransparent ghost
        
dragEl.setOpacity(0.7);

        
// raise panel's "window" above others
        
if(!this.panel.docked) {
            
this.dock.raise(this.panel);
        }

        
// hide panel's shadow if any
        
if(this.panel.shadow) {
            
this.panel.shadow.hide();
        }

        
// cancel ugly default ghost border and raise it above all panels (windows)
        
dragEl.applyStyles({'border':'0''z-index'this.dock.zindex this.dock.zindexInc});

        
// copy the panel's content to ghost
        
dragEl.update(srcEl.dom.innerHTML);

        
// position the ghost to the source position
        
dragEl.setBox(srcEl.getBox());

        
// clear visibility of panel's body (was setup by animations)
        
this.panel.body.dom.style.visibility ''

        
// hide source panel if undocked
        
if(!this.panel.docked) {
            
srcEl.hide();
        }

        
// debugging output
        
if(this.debug) {
            
console.info(
                
'Start dragging: ' 
                
this.panel.id
                
', groups='
                
this.groups.toSource()
            )
        } 
// end of debug output
    
// end of function startDrag
    // }}}
    // {{{
    /**
        * Default DDProxy onDragOver override
        * It is called when dragging over a panel
        * or over the dock.body DropZone
        *
        * @param {Event} e not used
        * @param {String} targetId id of the target we're over
        *
        * Beware: While dragging over docked panels it's called
        * twice. Once for panel and once for DropZone
        */
    
onDragOver: function(etargetId) {

        
// save targetId for use by endDrag
        
this.lastTarget targetId;

        
// {{{
        // debugging output
        
if(this.debug) {
            
console.log(
                
'Dragging panel: '
                
this.panel.id
                
' over '
                
targetId
            
);
        } 
// end of debug output
        // }}}

        // get panel element
        
var srcEl Ext.get(this.getEl());

        
// get target panel
        
var targetPanel this.dock.items.get(targetId);

        
// do nothing if we're not over another docked panel
        
if(!targetPanel || !targetPanel.docked) {
            return;
        }

        
// reorder panels in dock if we're docked too
        
if(this.panel.docked && targetPanel) {
            var 
targetEl targetPanel.el;

            
// we have dY (delta Y) from onDrag method
            
if(this.dY < -2) {
                
srcEl.insertBefore(targetEl);
            }
            else if(
this.dY 2) {
                
srcEl.insertAfter(targetEl);
            }
        }
        return;
    } 
// end of fucntion onDragOver
    // }}}
    // {{{
    /**
        * Default DDProxy onDrag override
        *
        * It's called whilde dragging
        * @param {Event} e used to get coordinates
        */
    
onDrag: function(e) {

        
// get source (original) and proxy (ghost) elements
        
var srcEl Ext.get(this.getEl());
        var 
dragEl Ext.get(this.getDragEl());

        
// calculate dY for onDragOver method
        // todo: the whole hysteresis logic might be simplified
        
var e.getPageY();
        
this.dY this.lastY;
        if((
this.dY && this.lastDY 0) || (this.dY && this.lastDY 0)) {
            
this.dY 0;
        }
        if(
this.dY < -|| this.dY 2) {
            
this.lastY y;
        }
    } 
// end of function onDrag
    // }}}
    // {{{
    /**
        * Default DDProxy endDrag override
        *
        * Called when dragging is finished
        */
    
endDrag: function() {

        
// debugging output
        
if(this.debug) {
            
console.info(
                
'End dragging:   '
                
this.panel.id
                
', groups='
                
this.groups.toSource()
            );
        }

        
// get the source (original) and proxy (ghost) elements
        
var srcEl Ext.get(this.getEl())
        var 
dragEl Ext.get(this.getDragEl())

        
// setup withinDock flag
        
var box dragEl.getBox();
        var 
dockBox this.dock.body.getBox();
        var 
withinDock 
            
box.<= dockBox.right && box.right >= dockBox.
            
&& box.<= dockBox.bottom && box.bottom >= dockBox.y
        
;

        
// undock the panel if it was dragged outside of dock
        
if(!withinDock && this.panel.docked) {
            
this.dock.undock(this.panelbox);
        }

        
// dock the panel if it was dragged from outside to dock
        
else if(withinDock && !this.panel.docked) {
            
this.dock.dock(this.panelthis.lastTarget)
        }

        
// hide the ghost
        
dragEl.hide();

        
// repair/adjust visuals of dropped panel
        
if(this.panel.docked) {
            
srcEl.applyStyles({
                
'top':''
                
'left':''
                
'width':''
                
'height':''
                
'z-index':''
                
'position':''
                
'visibility':''
            
});
        }
        else {
            
srcEl.setBox(dragEl.getBox());
            
srcEl.applyStyles({
                
position:'absolute'
                
'z-index'this.panel.zindex
                
visibility:''
                
height:''
            
});
        }

        
/// clear the ghost content and move it off screen
        
dragEl.update('');
        
dragEl.applyStyles({
            
'top':'-9999px'
            
'left':'-9999px'
            
'height':'0px'
            
'width':'0px'
        
});

        
// update order of docked panels
        
this.dock.updateOrder();

        
// repair the expanded/collapsed states of panels in the dock
        
if(!this.panel.collapsed && !this.dock.independent && this.panel.docked) {
            
this.dock.collapseAll(falsethis.panel);
            
this.dock.expanded this.panel;
        }

        
// show panel shadow
        
if(this.panel.shadow && !this.panel.docked) {
            
this.panel.shadow.show(srcEl);
        }

        
// let the state manager know the new panel position
        
this.dock.fireEvent('panelbox'this.panelbox);

        
// reset internal flags
        
this.lastY 0;
        
this.dY 0;
        
this.lastDY 0;

    } 
// end of function endDrag
    // }}}

});