Child elements are allowing drag/drop

15,000

Solution 1

Basically you have to set the correct ev.dataTransfer.dropEffect in allowDrop. For your draggable elements it should be "none" (dropping is not allowed), and for everything else (or any other element where dropping is allowed) to "all"

Further reading of dropEffect

Live example:

window.allowDrop = function(ev) {
    ev.preventDefault();
    if (ev.target.getAttribute("draggable") == "true")
        ev.dataTransfer.dropEffect = "none"; // dropping is not allowed
    else
        ev.dataTransfer.dropEffect = "all"; // drop it like it's hot
};

window.drag = function(ev) {
    ev.dataTransfer.setData("id", ev.target.id);
};

window.drop = function(ev) {
    ev.preventDefault();
    var id = ev.dataTransfer.getData("id");

    var dragged = document.getElementById(id);
    ev.target.appendChild(dragged);
    dragged.className += " dropped";
};
.drag {
    display: inline-block;
    padding: 5px;
    background: rgba(0, 0, 0, 0.2);
    margin: 2px;
}

.dropzone {
    background: #ccc;
    padding: 5px;
    margin-top: 10px;
}
<div id="first" class="drag" draggable="true" ondragstart="drag(event)">1</div>
<div id="second" class="drag" draggable="true" ondragstart="drag(event)">2</div>

<div class="dropzone" ondrop="drop(event)" ondragover="allowDrop(event)"><div id="third" class="drag dropped" draggable="true" ondragstart="drag(event)">3</div></div>

Solution 2

I'm not sure if this was the BEST solution, however I did come up with something.

I changed drop(ev) to this:

function drop(ev) {
    ev.preventDefault();

    if (!ev.target.getAttribute("ondrop"))
        return false;

    var data = ev.dataTransfer.getData("Text");
    ev.target.appendChild(document.getElementById(data));
}

This or a variant of this prevents anything from being dropped into an element that has not had "ondrop" explicitly defined.

Solution 3

I've improved a bit on @Zerkeras answer by allowing the user to drop anywhere inside the drop target, but, rather than failing if the user happens to drop inside of an element inside the drop target, it just adjusts where the element gets dropped.

function drop(ev) {
        ev.preventDefault();
        var data = ev.dataTransfer.getData("text");
        var i = 0;
        var element = ev.target
        while (!element.getAttribute("ondrop") && i < 3) {
            console.log("recalibrating");
            element = element.parentElement;
            i++;
        }
        element.appendChild(document.getElementById(data));
    }

Note, I've added an arbitrary 3 to escape from the while loop since I'm suspicious of while loops that aren't guaranteed to escape. This is pure paranoia on my part in this particular case, but it was very helpful when I was testing it.

The reason I used the new variable element was because directly modifying the ev.target seemed impossible (I didn't think immutability worked in JavaScript? if anyone knows why this was, I'm curious). So my while loop was never exiting which is why you see my paranoid escape clause in there.

Share:
15,000
Zerkeras
Author by

Zerkeras

Updated on June 29, 2022

Comments

  • Zerkeras
    Zerkeras almost 2 years

    I'm having a weird issue with dragging and dropping in html5.

    Panel A has a list of the type of elements you can drag. Panel B is where the elements are dragged. Panel C and D are other places you can drag elements, and you can drag and rearrange elements between Panels B, C and D.

    My issue is that I'm able to drag an element and drop it INSIDE of another element that's inside one of the panels, which I don't want the user to be able to do. The child elements of those panels don't have any kind of javascript or drag-related properties attached to them, and yet they are currently allowing elements to be dragged inside them.

    I've tried attaching "ondrop='return false;'" and "ondragover='return false;'", but neither has worked.

    Surely there is a way to turn off the 'allow dragging into' property on an element?

    Here's my code:

    <div id="elem-002" draggable="true" ondragstart="drag(event)">content</div>
    

    The main panel:

    <div id="panel-b" ondrop="drop(event)" ondragover="allowDrop(event)">
      <div id="elem-001" draggable="true" ondragstart="drag(event)">content</div>
    </div>
    

    The JS:

    function allowDrop(ev) {
        ev.preventDefault();
    }
    
    function drag(ev) {
        ev.dataTransfer.setData("Text", ev.target.id);
    }
    
    function drop(ev) {
        ev.preventDefault();
        var data = ev.dataTransfer.getData("Text");
        ev.target.appendChild(document.getElementById(data));
    }
    

    Said another way: I'm able to drag my "elem-002" directly into "elem-001", an element that was already dragged in. This causing nesting of elements that I don't want to occur. How can I prevent this?

  • Yasir
    Yasir about 8 years
    I liked this due to a lack of complexity, but can't confirm if it's solid. Working for me for now.
  • Marc
    Marc over 7 years
    This works but the browser is not showing the "do not drop" icon you would expect for non droppable elements.
  • s2t2
    s2t2 about 7 years
    This solution did not prevent draggable elements from receiving drops. See stackoverflow.com/a/28203782/670433 for a solution that worked for me.
  • Glen Pierce
    Glen Pierce over 6 years
    Is there a way to get the ev.target's parent element so that this could result in always dropping on the element that has "ondrop" specifically added to it?
  • Glen Pierce
    Glen Pierce over 6 years
    @Marc, the reason it's not showing "do not drop" icon is because you're still inside the element that is a drop target. This code just cancels a drop if it is dropped inside of a child element of a drop target when that child element is not specifically made into a drop target as well.