I'm trying to create a simple drag & drop example to use that will allow swapping of items. For example:
Item 0 Item 1 Item 2 Item 3
If I drag and drop "Item 0" over "Item 3" they should swap places. What I have below does not swap the correct elements, will also make some slots "un-droppable" and error out due to e.dataTransfer
not providing any data.
const log = console.log.bind(console);
const $ = document.getElementById.bind(document);
function drop(e) {
e.preventDefault();
let dragindex = 0;
let clone = e.target.cloneNode(true);
let data = e.dataTransfer.getData("text/plain");
if (clone.dataset.id !== data) {
[...$("container").children].forEach((el, i) => {
if (el.dataset.id == data) {
dragindex = +i;
}
})
log(data, clone.dataset.id, dragindex, e.target.dataset.id);
$("container").replaceChild(document.querySelector(`[data-id=${data}]`), e.target);
$("container").insertBefore(clone, $("container").childNodes[dragindex]);
}
}
[...document.querySelectorAll(".draggable")].map((el) => {
el.setAttribute("draggable", true);
});
[...document.querySelectorAll(".draggable")].map((el) => {
el.addEventListener("dragover", (e) => {
e.preventDefault();
})
el.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", e.target.dataset.id);
});
el.addEventListener("drop", (e) => {
drop(e);
});
})
#container {
width: 200px;
height: auto;
position: absolute;
left: 50%;
top: 50%;
background: dodgerblue;
color: #fff;
transform: translate(-50%, -50%);
border: 1px solid #000;
}
.draggable {
display: flex;
justify-content: start;
align-items: center;
border: 1px solid #fff;
margin: 2px;
padding: .5em;
text-align: center;
cursor: grab;
}
.draggable i {
margin-right: 25px;
}
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<div id="container">
<div class="draggable" data-id="drag0"><i class="material-icons">drag_indicator</i>Draggable 0</div>
<div class="draggable" data-id="drag1"><i class="material-icons">drag_indicator</i>Draggable 1</div>
<div class="draggable" data-id="drag2"><i class="material-icons">drag_indicator</i>Draggable 2</div>
<div class="draggable" data-id="drag3"><i class="material-icons">drag_indicator</i>Draggable 3</div>
</div>
So.... I finally cracked it. It was several things.
You have to re-add event listeners to a cloned node if they were added via "document.addEventListener()".
Had to use ".childNodes" not ".children" as the indexing does not work out the same.
Had to take care where/when I created the variable holding the reference node.
Also figured out that it acts weird in Safari on IOS if the drag/drop parent container is absolutely positioned so need to use flex positioning, as well as a few other minor details; any how, in case any one finds it helpful, here is the working code. Works in Android-Chrome/IOS-Safari.
const log = console.log.bind(console);
const $ = document.getElementById.bind(document);
function drop(e) {
e.preventDefault();
let dragindex = 0;
let referenceNode = "";
let clone = e.target.cloneNode(true);
addListeners(clone);
let data = e.dataTransfer.getData("text/plain");
if (clone.dataset.id !== data) {
[...$("container").childNodes].forEach((el, i) => {
if (el.dataset?.id == data) {
dragindex = i;
}
});
$("container").replaceChild(
document.querySelector(`[data-id=${data}]`),
e.target
);
referenceNode = $("container").childNodes[dragindex];
$("container").insertBefore(clone, referenceNode);
clone.classList.remove("dragActive");
}
}
function addListeners(el) {
el.addEventListener("dragover", (e) => {
e.preventDefault();
e.target.classList.add("dragActive");
});
el.addEventListener("dragstart", (e) => {
e.dataTransfer.setData("text/plain", e.target.dataset.id);
});
el.addEventListener("dragleave", (e) => {
e.target.classList.remove("dragActive");
});
el.addEventListener("dragend", (e) => {
e.target.classList.remove("dragActive");
});
el.addEventListener("drop", (e) => {
e.target.classList.remove("dragActive");
drop(e);
});
}
[...document.querySelectorAll(".draggable")].map((el) => {
el.setAttribute("draggable", true);
});
[...document.querySelectorAll(".draggable")].map((el) => {
addListeners(el);
});
html,
body {
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
#container {
width: 200px;
height: auto;
background: dodgerblue;
color: #fff;
border: 1px solid #000;
position: absolute;
/* top: 50%;
left: 50%;
transform: translate(-50%, -50%); */
}
.draggable {
border: 1px solid #fff;
margin: 2px;
padding: .5em;
text-align: center;
cursor: grab;
color: #000;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAzCAYAAAAdD7HCAAAACXBIWXMAAA7DAAAOwwHHb6hkAAADGElEQVRYhe1Zy2oUURA9MSoiWYQRxEdAMBoDxpVGZhFQF5qV+AgSPyK6CPgFbswvCD4WrkQQHwsFDUY3JuomISjic8CZhYk6PtCoUev07Rlm2p7uPj2TXR84zUxSValO31t16nYb6rHM2G/sMf40PjG+QGvQaTxo7DX+Nk4Z7xp/hBkfMr4y/g1w3LityURGjV9CYheMg0HjkRDDWpaNO1MmMhYTexHuH+Ghz/grxoF8aVwpJjKQIC45b8zR4VJCB/K4mMxVIfYJLtg9QvBBMZndgu1eJtMlOKyFhpxg28FkCoJDCRreCLZvmcwdweEmNFwTbK/wssW4gPgFNgNXFBXwMb1PEHui1mkY0du75CedBnnjx4jY08Z1QSfuqsmAIRO8DG2Rh2GT8SLqq3DReNq4umLUFuK42dgN1z+Y9TxaBxbN9X7sd8iQIcMSIWxrrzH2+p+foXVbm9V7l3GH/30WTnr+CTOmtGTvofKqLXrXjd1NJrLP/+PB6jvr/64OLNllNC7ZLOdpZecQolsNb/5YxbgDrvfENbOSb6uAPaecIPY32rbb5aTxaILATGTO+FBI5pTxQAK7FXCjER4guU69DQ1K7EmucEUabhCT6RRsPdn5VXD4AA1FwXZOlZ33oOGWYHuDF654rua4Z1pGiCKLQaqdStm5GGHMOjGEdOBBQpTs5E3mg05SlRSxFa6S1xY/3jwrfvVQIexIhFPgdv87J4LHaNA/UmCp+l6GDBnq0N7g5xvhGuh3uHrQKnBrs3T0+HHLjQw583L2ZXOrFCbOxufhZuVmwFmdM3tQ8XG2/+/kjD1nGo1LNotTPmUiPL2I6k9McLjWYQLxzYznLDkxEVb0mQSxF/ykPVmYVI2dEZM5LMQ+R4ezgsNTMZkLQmzvTE9ZnOqhkSJTu1TZ+RkaFNlZZDL3BYcpaFAkrXfIyB3CrZvkuQ6IyfDY7HWCuNzefRWnI4iWneQY0oFjcdxUORJ04nuBQoghq/AomgOl5XhIbL7fqr7eCcrOVcb9cEcXy+GkIUeIT2gNWNj6/djPjY9QlbTAPz6/t2nPvICTAAAAAElFTkSuQmCC');
background-repeat: no-repeat;
background-size: 15px;
background-position: 5px;
position: relative;
}
.dragActive {
background: rgba(255, 255, 255, .25);
color: #000;
border: 1px solid #000;
}
<div id="container">
<div class="draggable" data-id="drag0">Draggable 0</div>
<div class="draggable" data-id="drag1">Draggable 1</div>
<div class="draggable" data-id="drag2">Draggable 2</div>
<div class="draggable" data-id="drag3">Draggable 3</div>
</div>