skip to Main Content

I have HTML like this

<details draggable="true" open="">
<summary>author</summary>
<div draggable="true">name</div>
<div draggable="true">company</div>
</details>

I want seperate "dragstart" event listeners for the divs and a third for the containing block when i start my cursor on summary.

Currently div.addEventListener("dragstart", (event) => {}) also fires the event for the parent when the child divs are dragged. I did try event.stopPropagation but that makes the drop stop working when i drop onto a rich text field (the dataTransfer data doesn’t transfer).

2

Answers


  1. You can try this approach:

    • Create a variable (global for listener functions) where you will track which event is called

    • When started child dragstart then set this variable to true

    • When started child dragend then set this variable to false

    Here is complete code for basic situation:

    const detailsContainer = document.querySelector('details');
    const detailsList = detailsContainer.querySelectorAll('div');
    
    let isChildEvent = false;
    
    const parentDragStartListener = e => {
      if (isChildEvent) return;
      
      console.log('parent');
    }
    
    const childDragStartListener = () => {
      isChildEvent = true;
      
      console.log('child');
    }
    
    const childDragEndListener = () => {
      isChildEvent = false;
    }
    
    detailsContainer.addEventListener('dragstart', parentDragStartListener);
    
    detailsList.forEach(detail => {
      detail.addEventListener('dragstart', childDragStartListener);
      detail.addEventListener('dragend', childDragEndListener);
    });
    <details draggable="true" open="">
      <summary>author</summary>
      <div draggable="true">name</div>
      <div draggable="true">company</div>
    </details>
    Login or Signup to reply.
  2. It sounds like you have two event listeners. You can just use one event listener, listening on a parent element (Event delegation). The e.target will always be either the <details> or the <div>.

    In this example I add a class name to the element being dragged. That way I know what the element in in the drop event callback function. And then depending on the nodeName you can decide what to do next. In the example I clone either the <details> or the <div> and append it on an appropriate element.

    const list = document.querySelector('section.details');
    const drop = document.querySelector('section.drop');
    
    list.addEventListener('dragstart', e => {
      document.querySelectorAll('.currentlydraged')
        .forEach(elm => elm.classList.remove('currentlydraged'));
      e.target.classList.add('currentlydraged');
    });
    
    drop.addEventListener('dragover', e => {
      e.preventDefault();
    });
    
    drop.addEventListener('drop', e => {
      e.preventDefault();
      let item = document.querySelector('.currentlydraged');
      let detailsElm = e.target.closest('details');
      let clone = item.cloneNode(true);
      if(detailsElm && item.nodeName == 'DIV'){
        detailsElm.append(clone);
      }else if(item.nodeName == 'DETAILS'){
        let drop = e.target.closest('section.drop');
        drop.append(clone);
      }
      document.querySelectorAll('.currentlydraged')
        .forEach(elm => elm.classList.remove('currentlydraged'));
    });
    body {
      display: flex;
      gap: 2em;
    }
    
    section.drop {
      min-height: 200px;
      width: 100%;
      border: solid thin black;
    }
    <section class="details">
      <details draggable="true" open="">
        <summary>author</summary>
        <div draggable="true">name</div>
        <div draggable="true">company</div>
      </details>
      <details draggable="true" open="">
        <summary>author</summary>
        <div draggable="true">name</div>
        <div draggable="true">company</div>
      </details>
    </section>
    <section class="drop">
    </section>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search