OnDrop Event Target is children when dropped over children, even when Capture phase is used
Asked Answered
S

2

7

I'm trying to make my Drag and Drop work properly in JavaScript without having to explicitly scan for the parent element nor other ID, class loops and other hacky magic.

Right now when I drag one of the elements in the element pool and drag it to the grey zone, the element is copied to the drop zone (So far everything is correct). I basically copy the HTML of the dragged element, clear the drop zone (target) and add the new element to it.

The problem starts when you have already some child element in the drop zone and you drag it over the child element. Then the target in the drop event is the child element and not the div where the event has been attached to. This results in the dragged element clearing and copying itself into the children instead of the drop container.

The expected result should be that no matter if you drop it over the children or on the main drop container directly, the target should always be the parent node as I am using Event Capture mode.

Here is my example code in JS Fiddle. I'm using a event listener with capture, but it seems to be doing the bubbling phase anyway and ignoring the capture one. Why?

What is the proper way to do this?

function onDrag(ev) {
  var el = ev.target;
  //console.log("Drag: " + ev.target.outerHTML)
  ev.dataTransfer.setData('text/html', ev.target.outerHTML);
}

function onContainerOver(ev) {
  ev.preventDefault();
  ev.dataTransfer.dropEffect = "move"
}

function onContainerDrop(ev) {
  ev.preventDefault();
  //console.log("Drop: " + ev.dataTransfer.getData("text/html"))
  ev.target.innerHTML = ev.dataTransfer.getData("text/html");
}

document.getElementById('target').addEventListener('drop', onContainerDrop, true); // This should not bubble, but it does. Target is always the child first rather than the element where the event listener resides. Why???
#list {}

.el {
  padding: 5px;
  margin: 10px;
  border: solid 1px black;
  background-color: #86d4ff;
  cursor: move;
    line-height: 1em;
    text-align: left;
}

#target {
  min-height: 200px;
  width: 100%;
  background-color: #ffdea6;
  border: 1px dashed gray;
  text-align: center;
  line-height: 100px;
}
<div id="list">
  <div class="el" draggable="true" ondragstart="onDrag(event)">ELEMENT A</div>
  <div class="el" draggable="true" ondragstart="onDrag(event)">ELEMENT B</div>
  <div class="el" draggable="true" ondragstart="onDrag(event)">ELEMENT C</div>
</div>
<div id="target" ondragover="onContainerOver(event)">
  Drop Here
</div>

The Original JSFiddle Link.

Stomy answered 14/12, 2017 at 10:59 Comment(0)
S
12

Never mind, I didn't notice the availability of the "currentTarget" element in the Event which does exactly what I was looking for.

So instead of using "event.target" it should be "event.currentTarget" to get the real parent element where the event is triggered.

Here is a working JSFiddle: https://jsfiddle.net/xs20xy27/

Updated JS:

function onDrag(ev){
  var el = ev.srcElement;
  console.log("Drag: " + ev.srcElement.outerHTML)
  ev.dataTransfer.setData('text/html', ev.srcElement.outerHTML);
}

function onContainerOver(ev){
  ev.preventDefault();
  ev.dataTransfer.dropEffect = "move"
}

function onContainerDrop(ev){
  ev.preventDefault();
  console.log("Drop: " + ev.dataTransfer.getData("text/html"))
  ev.currentTarget.innerHTML =   ev.dataTransfer.getData("text/html");
}

document.getElementById('target').addEventListener('drop', onContainerDrop, true); // This should not bubble, but it does. Target is always the child first rather than the element where the event listener resides. Why???
Stomy answered 14/12, 2017 at 11:44 Comment(1)
Perfect, I did same by event capturing on 'drop' event to get the parent target id.. Finally event capturing isn't required for this. :)Calgary
M
15

When you drop anything on the child element or any event takes place on the child element, the Event Target is always the child element because that is the place where the event took place. That is the target.

┌─────────────────────────────────────┐
│   ┌────────────────────────┐        │
│   │     Child [Target]     │        │
│   └────────────────────────┘        │
│             Parent                  │
└─────────────────────────────────────┘

Then this event is passed to the child element's event handler as the child element as both Target and CurrentTarget

┌─────────────────────────────────────┐
│   ┌───────────────────────────────┐ │
│   │ Child [Target][CurrentTarget] │ │
│   └───────────────────────────────┘ │
│         Parent Element              │
└─────────────────────────────────────┘

Then this event is passed to the parent element's event handler as the child element as Target and parent element as CurrentTarget

┌─────────────────────────────────────┐
│   ┌───────────────────────────────┐ │
│   │        Child [Target]         │ │
│   └───────────────────────────────┘ │
│         Parent [CurrentTarget]      │
└─────────────────────────────────────┘

Then then event is passed to elements higher in dom tree successively in the same fashion until the top level element is reached or any event handler cancels this event bubbling by calling event.stopPropagation()

So what happens when you create event handler passing the third parameter useCapture as true.

parentElement.addEventListener('click', myClickHandler, true);

In this case, the event is first passed to the parent element [currentTarget:Parent] and then to the child element [currentTarget:Child] but as always, the event target is always the child element because this is where the event was triggered.

About The CurrentTarget this MDN WebDocs page says,

Identifies the current target for the event, as the event traverses the DOM. It always refers to the element to which the event handler has been attached, as opposed to event.target which identifies the element on which the event occurred.

Mortenson answered 5/2, 2018 at 21:47 Comment(0)
S
12

Never mind, I didn't notice the availability of the "currentTarget" element in the Event which does exactly what I was looking for.

So instead of using "event.target" it should be "event.currentTarget" to get the real parent element where the event is triggered.

Here is a working JSFiddle: https://jsfiddle.net/xs20xy27/

Updated JS:

function onDrag(ev){
  var el = ev.srcElement;
  console.log("Drag: " + ev.srcElement.outerHTML)
  ev.dataTransfer.setData('text/html', ev.srcElement.outerHTML);
}

function onContainerOver(ev){
  ev.preventDefault();
  ev.dataTransfer.dropEffect = "move"
}

function onContainerDrop(ev){
  ev.preventDefault();
  console.log("Drop: " + ev.dataTransfer.getData("text/html"))
  ev.currentTarget.innerHTML =   ev.dataTransfer.getData("text/html");
}

document.getElementById('target').addEventListener('drop', onContainerDrop, true); // This should not bubble, but it does. Target is always the child first rather than the element where the event listener resides. Why???
Stomy answered 14/12, 2017 at 11:44 Comment(1)
Perfect, I did same by event capturing on 'drop' event to get the parent target id.. Finally event capturing isn't required for this. :)Calgary

© 2022 - 2024 — McMap. All rights reserved.