amplesdk
amplesdk copied to clipboard
Drag and drop manager fails to find proper drop zone
The bug show case - http://codepen.io/tedbeer/pen/MwXEZg It happens when a drop zone inside another drop zone is bigger than the parent. DnD manager looks for a drop target and selectы the smallest. It does not take into account that a child node can be bigger than a parent node.
I propose changing the algorithm a bit:
- find all targets intersecting with the drag source
- from related targets (parent-child-descendant) leave the deepest (descendants) only
- among remaining targets choose the target with the biggest intersection
The pen using patched amplesdk - http://codepen.io/tedbeer/pen/KpeBBz
Here the patch implementing the algorithm and solving the problem:
--- /amplesdk3/ample/runtime/managers/DragAndDropManager.js
+++ /ample-patched/ample/runtime/managers/DragAndDropManager.js
@@ -242,10 +242,19 @@
// fill in array with drag targets
var aElements = fElement_getElementsByTagName(oBrowser_modalNode || this.documentElement, '*');
- for (var nIndex = 0, nLength = aElements.length; nIndex < nLength; nIndex++)
- if (aElements[nIndex].$droppable)
- aDragAndDropManager_dropTargets.push(aElements[nIndex]);
-
+ for (var oRect2, nArea, nIndex = 0, nLength = aElements.length; nIndex < nLength; nIndex++)
+ if (aElements[nIndex].$droppable) {
+ oRect2 = fElement_getBoundingClientRect(aElements[nIndex]);
+ nArea = (oRect2.right - oRect2.left) * (oRect2.bottom - oRect2.top);
+ //skip invisible
+ if (nArea > 0)
+ aDragAndDropManager_dropTargets.push({
+ elm: aElements[nIndex],
+ rect: oRect2,
+ area: nArea,
+ intersection: 0
+ });
+ }
//
nDragAndDropManager_dragState = nDragAndDropManager_STATE_DRAGGED;
@@ -274,41 +283,32 @@
nDragAndDropManager_scrollTop = oScroll.top;
}
- var oDropTarget = null,
- oRect2,
- nAreaSource =(oRect.right - oRect.left) * (oRect.bottom - oRect.top),
- nAreaTarget,
- nAreaTargetMin = nInfinity,
- nIntersection,
- nPartialMax = 0;
-
- for (var nIndex = 0, nLength = aDragAndDropManager_dropTargets.length; nIndex < nLength; nIndex++) {
- // if source is the target, continue
- if (aDragAndDropManager_dropTargets[nIndex] == oDragAndDropManager_dragSource)
- continue;
-
- // if target contains source, continue
- if (fNode_compareDocumentPosition(aDragAndDropManager_dropTargets[nIndex], oDragAndDropManager_dragSource) & 8 /* cNode.DOCUMENT_POSITION_CONTAINS */)
- continue;
-
- oRect2 = fElement_getBoundingClientRect(aDragAndDropManager_dropTargets[nIndex]);
- nAreaTarget =(oRect2.right - oRect2.left) * (oRect2.bottom - oRect2.top);
- nIntersection = fDragAndDropManager_intersectRectangle(oRect, oRect2);
- if (nIntersection < nAreaSource) {
- // partial intersection
- if (nIntersection > nPartialMax) {
- nPartialMax = nIntersection;
- oDropTarget = aDragAndDropManager_dropTargets[nIndex];
- }
- }
- else {
- // complete intersection
- if (nAreaTarget < nAreaTargetMin) {
- nAreaTargetMin = nAreaTarget;
- oDropTarget = aDragAndDropManager_dropTargets[nIndex];
+ var aCandidates = aDragAndDropManager_dropTargets.filter(function(one) {
+ if (one.area === 0 || one.elm == oDragAndDropManager_dragSource ||
+ fNode_compareDocumentPosition(one.elm, oDragAndDropManager_dragSource) & 8)
+ return false;
+ one.intersection = fDragAndDropManager_intersectRectangle(oRect, one.rect);
+ return one.intersection > 0;
+ });
+ var oElm, rel, oldLen = 0;
+ //filter out related candidates (leave the deepest)
+ while(aCandidates.length > 1 && oldLen !== aCandidates.length) {
+ oElm = aCandidates[0].elm;
+ oldLen = aCandidates.length; //to break the loop if no changes in the candidates array
+ for (var ind = aCandidates.length - 1; ind > 0; ind--) {
+ rel = fNode_compareDocumentPosition(oElm, aCandidates[ind].elm);
+ if (rel & 8) {
+ aCandidates.splice(ind, 1);
+ } else if (rel & 16) {
+ aCandidates.splice(0, 1);
+ break;
}
}
}
+ //find maximum intersection among candidates
+ var oDropTarget = aCandidates.reduce(function(prev, cand) {
+ return cand.intersection > prev.intersection ? cand : prev;
+ }, {intersection: 0}).elm;
// if there was a drop target and it is different from a new one
if (oDragAndDropManager_dropTarget) {