skip to Main Content

I am rewriting a jquery dragable script to plain javascript. At the insertBefore (https://developer.mozilla.org/en-US/docs/Web/API/Node/insertBefore) point I get this error message:
NotFoundError: Node.insertBefore: Child to insert before is not a child of this node.

This error occurs when I want to move a lower list element to the top.

Here is my code.

let items = document.querySelectorAll("#items-list > li");

items.forEach((item) => {
  item.setAttribute("draggable", true);
  item.addEventListener("dragstart", dragStart);
  item.addEventListener("drop", dropped);
  item.addEventListener("dragenter", cancelDefault);
  item.addEventListener("dragover", cancelDefault);
});

const getNodeIndex = elm => [...elm.parentNode.children].indexOf(elm)

function dragStart(e) {
  const index = getNodeIndex(e.target);  
  e.dataTransfer.setData("text/plain", index);
}

function dropped(e) {
  cancelDefault(e);

  // get new and old index
  let oldIndex = e.dataTransfer.getData("text/plain");
  let target = e.target;
  let newIndex = getNodeIndex(e.target);

  let droppedElement = e.currentTarget.parentNode.children[oldIndex];
  droppedElement.remove();
  
  // insert the dropped items at new place
  if (newIndex < oldIndex) {
    //---> this is the problematic line <---
    target.insertBefore(droppedElement, e.currentTarget.parentNode);
  } else {
    target.after(droppedElement);
  }
}

function cancelDefault(e) {
  e.preventDefault();
  e.stopPropagation();
  return false;
}
<div class="container">
  <ul id="items-list" class="moveable">
      <li>One</li>
      <li>Two</li>
      <li>Three</li>
      <li>Four</li>
  </ul>
  
</div>

2

Answers


  1. You need to insert the dragged node into the target.parentNode, and before the target:

    The insertBefore() method of the Node interface inserts a node before a reference node as a child of a specified parent node.

    target.parentNode.insertBefore(droppedElement, target);
    

    Example:

    let items = document.querySelectorAll("#items-list > li");
    
    items.forEach((item) => {
      item.setAttribute("draggable", true);
      item.addEventListener("dragstart", dragStart);
      item.addEventListener("drop", dropped);
      item.addEventListener("dragenter", cancelDefault);
      item.addEventListener("dragover", cancelDefault);
    });
    
    const getNodeIndex = elm => [...elm.parentNode.children].indexOf(elm)
    
    function dragStart(e) {
      const index = getNodeIndex(e.target);
      e.dataTransfer.setData("text/plain", index);
    }
    
    function dropped(e) {
      cancelDefault(e);
    
      // get new and old index
      let oldIndex = e.dataTransfer.getData("text/plain");
      let target = e.target;
      let newIndex = getNodeIndex(e.target);
    
      let droppedElement = e.currentTarget.parentNode.children[oldIndex];
      
      // insert the dropped items at new place
      if (newIndex < oldIndex) {
        //---> this is the problematic line <---
        target.parentNode.insertBefore(droppedElement, target);
      } else {
        target.after(droppedElement);
      }
    }
    
    function cancelDefault(e) {
      e.preventDefault();
      e.stopPropagation();
      return false;
    }
    <div class="container">
      <ul id="items-list" class="moveable">
          <li>One</li>
          <li>Two</li>
          <li>Three</li>
          <li>Four</li>
      </ul>
      
    </div>

    Another option is to use Element.before() in the same way you use Element.after():

    let items = document.querySelectorAll("#items-list > li");
    
    items.forEach((item) => {
      item.setAttribute("draggable", true);
      item.addEventListener("dragstart", dragStart);
      item.addEventListener("drop", dropped);
      item.addEventListener("dragenter", cancelDefault);
      item.addEventListener("dragover", cancelDefault);
    });
    
    const getNodeIndex = elm => [...elm.parentNode.children].indexOf(elm)
    
    function dragStart(e) {
      const index = getNodeIndex(e.target);
      e.dataTransfer.setData("text/plain", index);
    }
    
    function dropped(e) {
      cancelDefault(e);
    
      // get new and old index
      let oldIndex = e.dataTransfer.getData("text/plain");
      let target = e.target;
      let newIndex = getNodeIndex(e.target);
    
      let droppedElement = e.currentTarget.parentNode.children[oldIndex];
      
      // insert the dropped items at new place
      if (newIndex < oldIndex) {
        target.before(droppedElement); // <- before
      } else {
        target.after(droppedElement);
      }
    }
    
    function cancelDefault(e) {
      e.preventDefault();
      e.stopPropagation();
      return false;
    }
    <div class="container">
      <ul id="items-list" class="moveable">
          <li>One</li>
          <li>Two</li>
          <li>Three</li>
          <li>Four</li>
      </ul>
      
    </div>
    Login or Signup to reply.
  2. You are saying to append the li after the UL. The correct way would be

    e.currentTarget.parentNode.insertBefore(droppedElement, target);

    But you are using the old way, just use before.

    target.before(droppedElement);
    
    let items = document.querySelectorAll("#items-list > li");
    
    items.forEach((item) => {
      item.setAttribute("draggable", true);
      item.addEventListener("dragstart", dragStart);
      item.addEventListener("drop", dropped);
      item.addEventListener("dragenter", cancelDefault);
      item.addEventListener("dragover", cancelDefault);
    });
    
    const getNodeIndex = elm => [...elm.parentNode.children].indexOf(elm)
    
    function dragStart(e) {
      const index = getNodeIndex(e.target);  
      e.dataTransfer.setData("text/plain", index);
    }
    
    function dropped(e) {
      cancelDefault(e);
    
      // get new and old index
      let oldIndex = e.dataTransfer.getData("text/plain");
      let target = e.target;
      let newIndex = getNodeIndex(e.target);
    
      let droppedElement = e.currentTarget.parentNode.children[oldIndex];
      droppedElement.remove();
      
      console.log(droppedElement, e.currentTarget.parentNode);
      
      // insert the dropped items at new place
      if (newIndex < oldIndex) {
        //---> this is the problematic line <---
        target.before(droppedElement);
      } else {
        target.after(droppedElement);
      }
    }
    
    function cancelDefault(e) {
      e.preventDefault();
      e.stopPropagation();
      return false;
    }
    <div class="container">
      <ul id="items-list" class="moveable">
          <li>One</li>
          <li>Two</li>
          <li>Three</li>
          <li>Four</li>
      </ul>
      
    </div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search