During these days I was trying to learn how to implement Drag and Drop API but I ran into a bug I can't fix. Practically, if I swap first and second box by drag, when I select the header and drag it into the second box, there happens the bug! This is just a case but there are even more. I'm trying to see what happen with the debug but I miss something. Below a screenshot of the bug and code:
let dragSrcEl = null;
function handleDragStart(e) {
this.classList.add('dnd__element--dragStart');
console.log(this);
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = 'move';
return false;
}
function handleDragEnter(e) {
this.classList.add('dnd__element--dragOver');
}
function handleDragLeave(e) {
this.classList.remove('dnd__element--dragOver');
}
function handleDragEnd(e) {
this.classList.remove('dnd__element--dragStart');
items.forEach(function(item) {
item.classList.remove('dnd__element--dragOver');
});
}
function handleDrop(e) {
if (e.stopPropagation) {
e.stopPropagation(); // stops the browser from redirecting.
}
if (dragSrcEl != this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
}
return false;
}
const items = document.querySelectorAll('.dnd__element');
items.forEach(function(item) {
item.classList.remove(
'dnd__element--dragStart',
'dnd__element--dragEnd',
'dnd__element--dragOver'
);
item.addEventListener('dragstart', handleDragStart, false);
item.addEventListener('dragover', handleDragOver, false);
item.addEventListener('dragenter', handleDragEnter, false);
item.addEventListener('dragleave', handleDragLeave, false);
item.addEventListener('drop', handleDrop, false);
item.addEventListener('dragend', handleDragEnd, false);
});
.dnd__element {
cursor: move;
border: 3px solid #777;
background-color: #ddd;
padding: 1rem;
border-radius: 0.5rem;
float: left;
}
.dnd__element--dragStart {
opacity: 0.4;
}
.dnd__element--dragEnd {
opacity: 1;
}
.dnd__element--dragOver {
border: 3px dotted #666;
}
.dnd__drag-zone {
height: 20rem;
background-color: cornsilk;
}
.heading-tertiary {
display: block;
background-color: darkkhaki;
padding: 2rem 1rem;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<section class="section-dnd">
<div class="dnd__drag-zone">
<h3 class="heading-tertiary">Drag zone</h3>
<div class="dnd__element dnd__element--dragStart" id="d1" draggable="true">
Box 1
</div>
<div class="dnd__element dnd__element--dragEnd" id="d2" draggable="true">
Box 2
</div>
<div class="dnd__element dnd__element--dragOver" draggable="true">
Box 3
</div>
</div>
</section>
</body>
</html>
The easiest way is to rely on 'handleDragStart()' because it can only be called with 1 of the 3 valid items. So set a test variable in that handler (for example started = true;
) and check for it (if (started)
) in 'handleDragEnter()' and 'handleDrop()'.
(reset 'started' to false in 'handleDrop()')...
Working example:
var started = false;
let dragSrcEl = null;
function handleDragStart(e) {
this.classList.add('dnd__element--dragStart');
started = true;
dragSrcEl = this;
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', this.innerHTML);
}
function handleDragOver(e) {
if (e.preventDefault) {
e.preventDefault();
}
e.dataTransfer.dropEffect = 'move';
return false;
}
function handleDragEnter(e) {
if (started) this.classList.add('dnd__element--dragOver');
}
function handleDragLeave(e) {
this.classList.remove('dnd__element--dragOver');
}
function handleDragEnd(e) {
this.classList.remove('dnd__element--dragStart');
items.forEach(function(item) {
item.classList.remove('dnd__element--dragOver');
});
started = false;
}
function handleDrop(e) {
if (e.stopPropagation) {
e.stopPropagation(); // stops the browser from redirecting.
}
if (started && dragSrcEl != this) {
dragSrcEl.innerHTML = this.innerHTML;
this.innerHTML = e.dataTransfer.getData('text/html');
}
started = false;
return false;
}
const items = document.querySelectorAll('.dnd__element');
items.forEach(function(item) {
item.classList.remove(
'dnd__element--dragStart',
'dnd__element--dragEnd',
'dnd__element--dragOver'
);
item.addEventListener('dragstart', handleDragStart, false);
item.addEventListener('dragover', handleDragOver, false);
item.addEventListener('dragenter', handleDragEnter, false);
item.addEventListener('dragleave', handleDragLeave, false);
item.addEventListener('drop', handleDrop, false);
item.addEventListener('dragend', handleDragEnd, false);
});
.dnd__element {
cursor: move;
border: 3px solid #777;
background-color: #ddd;
padding: 1rem;
border-radius: 0.5rem;
float: left;
}
.dnd__element--dragStart {
opacity: 0.4;
}
.dnd__element--dragEnd {
opacity: 1;
}
.dnd__element--dragOver {
border: 3px dotted #666;
}
.dnd__drag-zone {
height: 20rem;
background-color: cornsilk;
}
.heading-tertiary {
display: block;
background-color: darkkhaki;
padding: 2rem 1rem;
}
<section class="section-dnd">
<div class="dnd__drag-zone">
<h3 class="heading-tertiary">Drag zone</h3>
<div class="dnd__element dnd__element--dragStart" id="d1" draggable="true">
Box 1
</div>
<div class="dnd__element dnd__element--dragEnd" id="d2" draggable="true">
Box 2
</div>
<div class="dnd__element dnd__element--dragOver" draggable="true">
Box 3
</div>
</div>
</section>