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(el, config) {
// call parent constructor
Ext.Accordion.superclass.constructor.call(this, el, config);
// 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.Accordion, Ext.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.id, this);
// 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(panel, group, dock) {
// call parent constructor
Ext.Accordion.DDDock.superclass.constructor.call(this, panel.el.dom, group);
// 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(0, 0);
}
// 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.DDDock, Ext.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(x, y) {
// 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(e, targetId) {
// 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 y = e.getPageY();
this.dY = y - this.lastY;
if((this.dY > 0 && this.lastDY < 0) || (this.dY < 0 && this.lastDY > 0)) {
this.dY = 0;
}
if(this.dY < -2 || 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.x <= dockBox.right && box.right >= dockBox.x
&& box.y <= 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.panel, box);
}
// dock the panel if it was dragged from outside to dock
else if(withinDock && !this.panel.docked) {
this.dock.dock(this.panel, this.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(false, this.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.panel, box);
// reset internal flags
this.lastY = 0;
this.dY = 0;
this.lastDY = 0;
} // end of function endDrag
// }}}
});