6

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?

Zerkeras
  • 327
  • 1
  • 2
  • 13

5 Answers5

14

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>
lloiser
  • 1,161
  • 9
  • 11
  • 1
    This solution did not prevent draggable elements from receiving drops. See http://stackoverflow.com/a/28203782/670433 for a solution that worked for me. – s2t2 Mar 17 '17 at 22:27
5

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.

Zerkeras
  • 327
  • 1
  • 2
  • 13
  • I liked this due to a lack of complexity, but can't confirm if it's solid. Working for me for now. – Chris Barry Feb 15 '16 at 20:11
  • This works but the browser is not showing the "do not drop" icon you would expect for non droppable elements. – Marc Nov 20 '16 at 07:57
  • 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 Oct 09 '17 at 00:59
  • @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. – Glen Pierce Oct 09 '17 at 01:23
1

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.

Glen Pierce
  • 4,401
  • 5
  • 31
  • 50
0

I am using a custom drag and drop, mixing the mouse[down|move|up] of a element to be dragged with the drag[enter|over|leave] & drop on another element. And I am using Custom Events to trigger/fire/dispatch the drop event. That does not really make a difference to the issue at hand, just wanted to give context.

Anyhoo, I was experiencing the same type of issue, and after searching for a while, and doing some debugging, I realised that the event.[target|srcElement] is actually the element which 'sent' the event. So what I ended up doing was to; reject any drop where the [target|srcElement] was the not the 'this' element of the event and where 'this' did not contain the [target|srcElement] (i.e. not one of its children), only then do I allow further processing;

// Drop Event Handler
function dropEventHandler(event) {
    event = event || window.event;
    var target = event.target || event.srcElement;

    if (this !== target && this.contains(target)) {
        // Use if not a proper/normal event, this should stop any further 
        //   processing of the event 
        // if (event.stopPropagation) event.stopPropagation();  // Standard model
        // else event.cancelBubble = true;                       //IE

        // Stops the default action of an element from happening
        // Use the event.isDefaultPrevented() method to check whether 
        //   the preventDefault() method was called for the event
        if (event.preventDefault) event.preventDefault();   // Standard model
        else event.returnValue = false;                      //IE

        return false;
    }
    // Process drop event
}

Tested in Chrome, FF, Opera, IE Edge && IE 11 [emulation 7-11] on Windows 10, also IE v8.0.7601.17514 on Windows 7 [IE7 and IE8 Compatibility] modes tested.

Community
  • 1
  • 1
Dev Ops
  • 96
  • 8
0

If I understand correctly, the issue is that the children of the intended drop target can also allow drag/drop interactions. Therefore, if you add "this" to the ondrop call, you can tell which element is actually the intended one. Then you can append to or otherwise interaction with the element you had intended. See my other question/solution.

Image tries to add to self on drag/drop interaction

DrJosh
  • 1