Home > Mobile >  Why is my eventlistener being removed after an element has been dropped using drag / drop API
Why is my eventlistener being removed after an element has been dropped using drag / drop API

Time:02-01

Below I have a simplified version of my code where I can click a button to add new elements. These element should all be draggable so they can swap places and they should also have functionality to be deleted when clicking somewhere on them. I have implemented this successfully as you can see by running my snippet ... except for one thing ...

If you try to click on the button area in the middle before swapping it with anything it works just like I want it to.

But if you try to click on it after you have swapped it, it no longer works. Please help me fix this!

const btn_add_element = document.querySelector('.btn_add_element');
const my_draggable_elements = document.querySelector('.my_draggable_elements');

function handleDragStart(e) {
  this.style.opacity = '0.4';
  dragSrcEl = this;
  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', this.innerHTML);
}
function handleDragEnd(e) {
  this.style.opacity = '1';  my_draggable_elements.querySelectorAll('.container').forEach(elm => {
    elm.classList.remove('dragged_over');
  });
}
function handleDragOver(e) {
  e.preventDefault();
  return false;
}
function handleDragEnter(e) {
  this.classList.add('dragged_over');
}
function handleDragLeave(e) {
  this.classList.remove('dragged_over');
}
function handleDrop(e) {
  e.stopPropagation();
  if (dragSrcEl !== this) {
    dragSrcEl.innerHTML = this.innerHTML;
    this.innerHTML = e.dataTransfer.getData('text/html');
  }
  return false;
}

function getRandomColor() { return '#' Math.floor(Math.random()*16777215).toString(16); }

function add_element() {

  // Create Container & Make it draggable
  const new_container = document.createElement('div');
  new_container.classList.add('container');
  new_container.setAttribute('draggable', true);
  new_container.addEventListener('dragstart', handleDragStart);
  new_container.addEventListener('dragover', handleDragOver);
  new_container.addEventListener('dragenter', handleDragEnter);
  new_container.addEventListener('dragleave', handleDragLeave);
  new_container.addEventListener('dragend', handleDragEnd);
  new_container.addEventListener('drop', handleDrop);
  
  // Create Content
  const new_content = document.createElement('div');
  new_content.classList.add('content');
  new_content.style.color = getRandomColor();
  new_content.innerText = 'Click to Delete';
  new_content.addEventListener('click', () => {
    new_container.remove();
  });
  new_container.appendChild(new_content);
  my_draggable_elements.appendChild(new_container);
}

btn_add_element.addEventListener('click', () => add_element());

btn_add_element.click();
btn_add_element.click();
btn_add_element.click();
.my_draggable_elements {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.container {
  padding: 2rem;
  border: 0.1rem solid black;
  cursor: grab;
}
.container.dragged_over {
  border: 0.1rem dashed black;
}
.container > .content {
  background-color: #ddd;
  padding: 0.25rem;
  cursor: pointer;
}
.btn_add_element {
  margin-top: 2rem;
  width: 100%;
  text-align: center;
  padding: 0.5rem;
}
<div class='my_draggable_elements'></div>

<button class='btn_add_element'>Add Element</button>

CodePudding user response:

In handleDrop() the element get's transfered from event.dataTransfer.getData('text/html') to the innerHTML of the element it is being dropped on. In that, the eventListener is lost. You have to add it again, as demonstrated in the modified snippet below.

const btn_add_element = document.querySelector('.btn_add_element');
const my_draggable_elements = document.querySelector('.my_draggable_elements');

function handleDragStart(e) {
  this.style.opacity = '0.4';
  dragSrcEl = this;
  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', this.innerHTML);
}
function handleDragEnd(e) {
  this.style.opacity = '1';  my_draggable_elements.querySelectorAll('.container').forEach(elm => {
    elm.classList.remove('dragged_over');
  });
}
function handleDragOver(e) {
  e.preventDefault();
  return false;
}
function handleDragEnter(e) {
  this.classList.add('dragged_over');
}
function handleDragLeave(e) {
  this.classList.remove('dragged_over');
}
function handleDrop(e) {
  e.stopPropagation();
  if (dragSrcEl !== this) {
    dragSrcEl.innerHTML = this.innerHTML;
    console.log(this.innerHTML);
    this.innerHTML = e.dataTransfer.getData('text/html');
    this.addEventListener('click', () => {
      this.remove();
    });
  }
  return false;
}

function getRandomColor() { return '#' Math.floor(Math.random()*16777215).toString(16); }

function add_element() {

  // Create Container & Make it draggable
  const new_container = document.createElement('div');
  new_container.classList.add('container');
  new_container.setAttribute('draggable', true);
  new_container.addEventListener('dragstart', handleDragStart);
  new_container.addEventListener('dragover', handleDragOver);
  new_container.addEventListener('dragenter', handleDragEnter);
  new_container.addEventListener('dragleave', handleDragLeave);
  new_container.addEventListener('dragend', handleDragEnd);
  new_container.addEventListener('drop', handleDrop);
  
  // Create Content
  const new_content = document.createElement('div');
  new_content.classList.add('content');
  new_content.style.color = getRandomColor();
  new_content.innerText = 'Click to Delete';
  new_content.addEventListener('click', () => {
    new_container.remove();
  });
  new_container.appendChild(new_content);
  my_draggable_elements.appendChild(new_container);
}

btn_add_element.addEventListener('click', () => add_element());

btn_add_element.click();
btn_add_element.click();
btn_add_element.click();
.my_draggable_elements {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.container {
  padding: 2rem;
  border: 0.1rem solid black;
  cursor: grab;
}
.container.dragged_over {
  border: 0.1rem dashed black;
}
.container > .content {
  background-color: #ddd;
  padding: 0.25rem;
  cursor: pointer;
}
.btn_add_element {
  margin-top: 2rem;
  width: 100%;
  text-align: center;
  padding: 0.5rem;
}
<div class='my_draggable_elements'></div>

<button class='btn_add_element'>Add Element</button>

CodePudding user response:

With the help of @anarchist912, I finally got the desired result.

As he stated in his answer, the eventlistener for removing the element gets lost inside the handleDrop function. To fix this we had to manually add the eventlistener back again.

this.addEventListener('click', () => {
  this.remove();
});

However this was apparently not enough.

I accidentally solved this by writing a remove_element() function for my personal use-case and getting it to work, then realising it was not working here when I tested it in the snippet using the arrow function like above. So here are the changes that made it work:

function remove_element(e) {
  e.target.closest('.container').remove();
}

// and inside handleDrop()
dragSrcEl.querySelector('.content').addEventListener('click', remove_element);
this.querySelector('.content').addEventListener('click', remove_element);

const btn_add_element = document.querySelector('.btn_add_element');
const my_draggable_elements = document.querySelector('.my_draggable_elements');

function handleDragStart(e) {
  this.style.opacity = '0.4';
  dragSrcEl = this;
  e.dataTransfer.effectAllowed = 'move';
  e.dataTransfer.setData('text/html', this.innerHTML);
}
function handleDragEnd(e) {
  this.style.opacity = '1';  my_draggable_elements.querySelectorAll('.container').forEach(elm => {
    elm.classList.remove('dragged_over');
  });
}
function handleDragOver(e) {
  e.preventDefault();
  return false;
}
function handleDragEnter(e) {
  this.classList.add('dragged_over');
}
function handleDragLeave(e) {
  this.classList.remove('dragged_over');
}
function handleDrop(e) {
  e.stopPropagation();
  if (dragSrcEl !== this) {
    dragSrcEl.innerHTML = this.innerHTML;
    dragSrcEl.querySelector('.content').addEventListener('click', remove_element);
    this.innerHTML = e.dataTransfer.getData('text/html');
    this.querySelector('.content').addEventListener('click', remove_element);
  }
  return false;
}

function getRandomColor() { return '#' Math.floor(Math.random()*16777215).toString(16); }

function remove_element(e) {
  e.target.closest('.container').remove();
}

function add_element() {

  // Create Container & Make it draggable
  const new_container = document.createElement('div');
  new_container.classList.add('container');
  new_container.setAttribute('draggable', true);
  new_container.addEventListener('dragstart', handleDragStart);
  new_container.addEventListener('dragover', handleDragOver);
  new_container.addEventListener('dragenter', handleDragEnter);
  new_container.addEventListener('dragleave', handleDragLeave);
  new_container.addEventListener('dragend', handleDragEnd);
  new_container.addEventListener('drop', handleDrop);
  
  // Create Content
  const new_content = document.createElement('div');
  new_content.classList.add('content');
  new_content.style.color = getRandomColor();
  new_content.innerText = 'Click to Delete';
  new_content.addEventListener('click', remove_element);
  new_container.appendChild(new_content);
  my_draggable_elements.appendChild(new_container);
}

btn_add_element.addEventListener('click', () => add_element());

btn_add_element.click();
btn_add_element.click();
btn_add_element.click();
.my_draggable_elements {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
.container {
  padding: 2rem;
  border: 0.1rem solid black;
  cursor: grab;
}
.container.dragged_over {
  border: 0.1rem dashed black;
}
.container > .content {
  background-color: #ddd;
  padding: 0.25rem;
  cursor: pointer;
}
.btn_add_element {
  margin-top: 2rem;
  width: 100%;
  text-align: center;
  padding: 0.5rem;
}
<div class='my_draggable_elements'></div>

<button class='btn_add_element'>Add Element</button>

  • Related