I’m attempting to setup drag and drop in my project
I’ve got several fields setup like this
If a user clicks the image on the left and drags, they can re-order the rows
When a valid dropzone is detected (in this case, any other row is a valid dropzone) a class is applied to the div wrapping all elements of that row which renders a border underneath to indicate where the dropped row will be inserted
My problem is, for some reason, if I drag a row over one of the input
or select
elements that are a child of the dropzone, they are concidered to be the dropzone rather than the underlying element.
Its a little hard to see but theres a dashed line under the right most input indicating my dropzone class has been added.
How its setup
The following JS is called for each valid dropzone
function dropzone(node, options) {
console.log(`created dropzone on ${node}`);
let state = {
dropEffect: "move",
dragover_class: ["dropzone", "pb-3"],
...options,
};
function handle_dragenter(e) {
console.log(`DragEnter ${e.target}`);
e.stopPropagation();
e.target.classList.add(...state.dragover_class);
}
function handle_dragleave(e) {
console.log(`DragLeave ${e.target}`);
e.stopPropagation();
e.target.classList.remove(...state.dragover_class);
}
function handle_dragover(e) {
console.log(`DragOver ${e.target}`);
e.stopPropagation();
e.dataTransfer.dropEffect = state.dropEffect;
}
node.addEventListener("dragenter", handle_dragenter, true);
node.addEventListener("dragleave", handle_dragleave, true);
node.addEventListener("dragover", handle_dragover, true);
}
What I’ve tried
CSS
I’ve tried adding pointer-events: none;
to all children of my dropzone
class thats added in my dragenter
handler which works but only when the dropzone is successfully entered before any of its children. I’ve also tried blanked setting pointer-events: none;
on all child elements of dropzones which does fix the issue but it also prevents the inputs from working
JS
I’ve tried setting useCapture
to true when adding my event listeners via addEventListener
and calling e.stopPropagation()
inside all of my handlers which to my understanding should cause the node the event listener is added to (in this case, the dropzone row) to be called first before propogating down to all children. However, the inputs are still receiving dragenter
events
useCapture
feels like the most promising approach but I must me missing something here (or it doesnt work on drag events for some reason).
Can anyone see anything obviously wrong with my setup or provide me with any other suggestions to fix my issue?
EDIT: I’ve gotten things working reasonably well by
- using
this
rather thane.target
in my handlers. Turns oute.target
always references the element the event fired on (i.e. what your mouse if over) where asthis
ore.currentTarget
references the element with the listener attached - Even with the above, I was leave and enter events firing when I moved the mouse between any of the children of a dropzone which caused lots of graphical issues. This was fixed by implementing a basic reference counter so whenever you dragenter fires, the counter is incremented and when dragleave is fired its decremented. The class adding the underline is only removed when this counter reaches 0.
I’m still open to suggestions that can make this better but I’m happy with the current result
2
Answers
Restructuring your HTML would be the ideal way to go but you can also…
add these events to all children that you don’t want to be drop zones:
For a quick & dirty fix.
You can restructure the HTML in this way…
Instead of the drag/drop elements having CHILDREN that bubble, make them their SIBLINGS so they won’t be inheriting the drag/drop.
The parent will now be a wrapper that holds them all together as siblings and with no more bubbling issues.