'How to implement drag and drop so dragging child element will drag whole parent element
I'm working on a media import plugin for wordpress. I'm trying to implement a drag and drop feature to reorder media elements(audio and/or images) that the user has imported. I have a <div>
tag(we'll call these media divs) that holds up to three other <span>
, <img>
, or <audio>
tags. So I have all the media assets that have been imported displayed in a line and would like to be able to drag and drop to reorder the media divs. I have no problem implementing the basic drag and drop using html5. My problem is that right now when I click on the media child elements(<audio>
or <img>
) inside the media divs, the child element is the target of the drag event. Is there any way I can reset the target of the drag event to be the parent element so I drag the whole media div and not just the media element? I've looked into e.stopPropogation()
and read up on bubbling, and capturing but every way I've tried to utilize those, it doesn't solve my problem. Is there something I'm missing? I would prefer to avoid jQuery if possible and definitely can't use any libraries or frameworks.
TL;DR: How can I make the parent element the target of a drag event when a child element is clicked?
<div class="npr-import-media npr-import-audio npr-import-images" id="media-container">
<div class="npr-import-media-container" draggable="true">
<audio src="<?php echo $audio->format->mp4->{'$text'}; ?>" controls></audio>
<span class="npr-media-delete">X</span>
</div>
<div class="npr-import-image-container npr-import-media-container" draggable="true">
<img class="npr-import-image" src="<?php echo $image->src; ?>" >
<span class="npr-import-image-caption"><?php echo $image->caption->{'$text'} ?></span>
<span class="npr-media-delete">X</span>
</div>
<div class="npr-import-add-media npr-import-media-container">
Add Media+
</div>
</div>
This is the HTML portion of my code. There is originally some more php functionality to loop through the original source material to display all of the media elements being imported in from the original article, but I don't think it's necessary to include that.
Solution 1:[1]
You can set the handler for ondragstart
event to return false, like below:
<div class="npr-import-media npr-import-audio npr-import-images" id="media-container">
<div class="npr-import-image-container npr-import-media-container" draggable="true" ondragstart="drag(event)" style="border:1px solid blue;padding:10px;">
<img class="npr-import-image" src="img.jpg" >
<span class="npr-import-image-caption">Caption</span>
<span class="npr-media-delete">X</span>
</div>
<div class="npr-import-add-media npr-import-media-container">
Add Media+
</div>
</div>
<script>
function drag(e) {
console.log(e);
}
var images = document.getElementsByTagName('img');
for(i = 0 ; i < images.length; i++)
images[i].ondragstart = function() { return false; }
</script>
On console output you can see:
>> DragEvent {isTrusted: true, dataTransfer: DataTransfer, screenX: 66, screenY: 161, clientX: 66…}
UPDATE By setting drag event handlers on the parent elements either by html attributes as above or attaching event listeners by code as below:
document.getElementById('draggable').addEventListener('dragstart', function(e) {
console.log(e);
});
Solution 2:[2]
I was just in the same problem and couldn't work around img tag, so switched to div.
You can use
<div style="background-image: url('img.jpg')"></div>
instead of
<img class="npr-import-image" src="img.jpg" >
Solution 3:[3]
I just came across this issue as well. I resolved it without javascript by simply adding draggable="false" to each child element.
<div class="npr-import-image-container npr-import-media-container" draggable="true" ondragstart="drag(event)" style="border:1px solid blue;padding:10px;">
<img draggable="false" class="npr-import-image" src="img.jpg" >
<span draggable="false" class="npr-import-image-caption">Caption</span>
<span draggable="false" class="npr-media-delete">X</span>
</div>
Solution 4:[4]
I faced a similar situation. I had a parent with two children (gripper and content), and needed the whole parent to be dragged when gripper was dragged, but no dragging when content was dragged
Make the parent and content draggable, BUT in the content's ondragstart
, cancel the drag, that way the drag will start only when the mouse is not in the content (but in the gripper)
const catchAndPreventDragStartEventGettingToParent = (event) => {
//prevent drag behaviour in content item,
console.log("no dragging");
event.preventDefault();
};
const dragStart = (event) => {
console.log("dragging");
//event.dataTransfer.effectAllowed = "move";
//event.dataTransfer.setData("text/plain", JSON.stringify(data));
};
#parent{
padding: 1rem;
background-color:teal;
}
.gripper{
margin: 1rem;
padding:1rem;
background-color: linen;
}
.content{
margin: 1rem;
padding:1rem;
background-color: pink;
}
<div
id="parent"
draggable = true
ondragstart= "dragStart(event)">
<span class ="gripper">Gripper:</span>
<span
class = "content"
draggable= true
ondragstart="catchAndPreventDragStartEventGettingToParent(event)">
CONTENT
</span>
</div>
Solution 5:[5]
Maybe it's too late but I find out an easy way to get the closest parent with a specific css class.
Based on your example try to use
const handleDragEnter = (e) => {
let desiredTarget = e.target.closest('.npr-import-image-container');
console.log(desiredTarget);
// do stuff...
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Li Chen |
Solution 3 | Orion |
Solution 4 | Ken |
Solution 5 | Davide Boitano |