I am developing a simple game (similar to Blockly and Scratch) that involves drag and drop. Currently, I am able to drag the button to a target container. However, the button gets completely transferred to another container. What I want to achieve is something like copy-pasting - from one container to another (basically drag-and-copy). I understand I have to clone it and give it a new ID but to no avail.
Here are the relevant codes: HTML and JS
<!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, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="">
<title>Game</title>
<link href="{{ url_for('static',filename='css/playmaze.css') }}" rel="stylesheet">
</head>
<body id="page-top">
<div id="wrapper">
<ul class="navbar-nav bg-gradient-primary sidebar sidebar-dark accordion" id="accordion_sidebar">
<a class="sidebar-brand d-flex align-items-center justify-content-center">
<div class="sidebar-brand-icon rotate-n-15"><i class="fas fa-laugh-wink"></i></div>
<div class="sidebar-brand-text mx-3">Directions</div>
</a>
<form action="/" method="post">
<div class="directions">
<input type="button" class="draggable" id="forward" value="Forward" draggable="true"></button>
<input type="button" class="draggable" id="backward" value="Backward" draggable="true"></button>
<input type="button" class="draggable" id="right" value="Right" draggable="true"></button>
<input type="button" class="draggable" id="left" value="Left" draggable="true"></button>
<input type="button" class="draggable" id="repeat" value="Repeat" draggable="true"></button>
</div>
</form>
</ul>
<div id="content-wrapper" class="d-flex flex-column">
<div id="content">
<div class="container-fluid">
<div class="row">
<div class="col-xl-12 col-lg-12">
<div class="card shadow mb-4">
<div class="card-header py-3 d-flex flex-row align-items-center justify-content-between">
<h2 class="m-0 font-weight-bold text-primary" id="maze_name">Maze</h2>
</div>
<div class="card-body">
<div class="container">
</div>
<input type="reset" id="reset" class="btn btn-lg btn-primary" value="Reset"></button>
<input type="submit" id="run" class="btn btn-lg btn-primary" value="Run"></button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>
<script>
const draggables = document.querySelectorAll('.draggable')
const containers = document.querySelectorAll('.container')
// loop through each of the draggables (aka buttons)
draggables.forEach(draggable => {
draggable.addEventListener('dragstart', () => {
console.log('drag start')
draggable.classList.add('dragging')
})
draggable.addEventListener('dragend', () => {
draggable.classList.remove('dragging')
})
})
containers.forEach(container => {
container.addEventListener('dragover', e => {
// by default, dropping inside an element is disabled
e.preventDefault()
// return of the getDragAfterElement function
// e.clientY = y position of the mouse on the screen
const afterElement = getDragAfterElement(container, e.clientY)
// get the element you are currently dragging
const draggable = document.querySelector('.dragging')
// clone the dragged element
const draggableClone = document.getElementById(draggable).cloneNode(true)
// give new ID
draggableClone.id = "cloneId"
if (afterElement == null) {
// append the element that is currently being dragged
container.appendChild(draggableClone)
} else {
container.insertBefore(draggableClone, afterElement)
}
})
})
// determine the mouse position when dragging elements and
// return whichever element our mouse position is directly after
function getDragAfterElement(container, y) {
// array
const draggableElements = [...container.querySelectorAll('.draggable:not(.dragging)')]
// reduce -> loop through the list of draggableElements and determine
// which single element is directly after the mouse based on the y position (retrieved from the event)
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect()
const offset = y - box.top - box.height / 2
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child }
} else {
return closest
}
}, { offset: Number.NEGATIVE_INFINITY }).element
}
</script>
</html>
CSS:
.container
{
background-color: #ffeaf0;
padding: 1rem;
flex-direction: column;
display: flex;
align-content: center;
}
.draggable
{
padding: 1rem;
margin-bottom: 1rem;
background-color: white;
border: 1px solid black;
cursor: move;
border-radius: 1rem;
}
.draggable.dragging
{
opacity: .5;
}
.grid-wrapper
{
display: grid;
padding: 0px;
margin: 0px;
grid-template-columns: repeat(5, 100px);
grid-template-rows: repeat(5, 100px);
}
.grid-item
{
background-color: rgba(255, 255, 255, 0.8);
border: 2px solid rgb(139,69,19);
padding: 20px;
font-size: 30px;
text-align: center;
}
.directions {
flex-direction: column;
display: flex;
align-content: center;
padding: 0px;
margin: 0px 20px 10px 20px;
}
.btn-lg {
margin-bottom: 10px;
}
#forward {
background-color: #98e690;
border:#98e690;
color: #404040;
}
#backward {
background-color: #ff85a2;
border: #ff85a2;
color: #404040;
}
#right {
background-color: #92dff3;
border: #45b3e0;
color: #404040;
}
#left {
background-color: #fdfd96;
border: #fdfd96;
color: #404040;
}
#repeat {
background-color: #CC99FF;
border: #CC99FF;
color: #202020;
}
#reset {
background-color: black;
border: #CC99FF;
color: white;
margin-top: 30px;
margin-right: 10px;
width: 6rem;
}
#run {
background-color: #32CD32;
border: #32CD32;
color: white;
margin-top: 30px;
width: 6rem;
}
CodePudding user response:
You need to clone the dragged html element:
So your code should be:
const draggables = document.querySelectorAll(".draggable");
const containers = document.querySelectorAll(".container");
let currentDraggableClone = null;
let currentAfterElement = null;
let currentContainer = null;
// loop through each of the draggables (aka buttons)
draggables.forEach((draggable) => {
draggable.addEventListener("dragstart", () => {
console.log("drag start");
draggable.classList.add("dragging");
});
draggable.addEventListener("dragend", () => {
if (currentContainer === null || currentDraggableClone === null) return;
//Random ID
currentDraggableClone.id =
currentDraggableClone.id Math.random() * 100000000;
if (currentAfterElement == null) {
// append the element that is currently being dragged
currentContainer.appendChild(currentDraggableClone);
} else {
currentContainer.insertBefore(currentDraggableClone, currentAfterElement);
}
currentDraggableClone.classList.remove("dragging");
draggable.classList.remove("dragging");
});
});
containers.forEach((container) => {
container.addEventListener("dragover", (e) => {
// by default, dropping inside an element is disabled
e.preventDefault();
currentContainer = container;
currentAfterElement = getDragAfterElement(container, e.clientY);
const draggable = document.querySelector(".dragging");
if (
draggable !== null &&
(currentDraggableClone === null ||
currentDraggableClone.id !== draggable.id)
) {
currentDraggableClone = draggable.cloneNode(true);
}
});
});
// determine the mouse position when dragging elements and
// return whichever element our mouse position is directly after
function getDragAfterElement(container, y) {
// array
const draggableElements = [
...container.querySelectorAll(".draggable:not(.dragging)")
];
// reduce -> loop through the list of draggableElements and determine
// which single element is directly after the mouse based on the y position (retrieved from the event)
return draggableElements.reduce(
(closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
},
{ offset: Number.NEGATIVE_INFINITY }
).element;
}