Perfectly functional drag-and-drop-to-sort-list code, but it's producing TONS of console log errors

Viewed 32

To add drag-and-drop "reorder the list" functionality to my Chrome Extension, I modified the following code, found on Codepen -- https://codepen.io/krmfla/pen/NwyKYV --

HTML:

<h1>A Sortable List</h1>
<div id="box" class="box">
  <div class="item" draggable="true">item 1</div>
  <div class="item" draggable="true">item 2</div>
  <div class="item" draggable="true">item 3</div>
  <div class="item" draggable="true">item 4</div>
  <div class="item" draggable="true">item 5</div>
  <div class="item" draggable="true">item 6</div>
  <div class="item"></div>
</div>
<div id="itemClip" class="item itemClip hide">some item</div>

Javascript:

// === Define Variables and Elements ===
var elements = document.querySelectorAll('.box .item');
var targetEl;
var wrapper = document.getElementById("box");
var itemClip = document.getElementById("itemClip");

var scopeObj;

// === Event Binding ===
for (var i = 0, max = elements.length; i < max; i++) {
  elements[i].addEventListener("dragstart", handleDrag);
  elements[i].addEventListener("dragend", handleDragEnd);
  elements[i].addEventListener("dragenter", handleDragEnter);
  
  elements[i].addEventListener("touchstart", handleTouch);
  elements[i].addEventListener("touchend", handleTouchEnd);
  elements[i].addEventListener("touchmove", handleTouchMove);
}

// === Function Kits ===
function handleDrag(event) {
  targetEl = event.target;
  targetEl.classList.add("onDrag");
}

function handleDragEnd(event) {
  targetEl.classList.remove("onDrag");
}

function handleDragEnter(event) {
  wrapper.insertBefore(targetEl, event.target);
}

function handleTouch(event) {
  defineScope(elements);
  targetEl = event.target;
  itemClip.style.top = event.changedTouches[0].clientY + "px";
  itemClip.style.left = event.changedTouches[0].clientX + "px";
  itemClip.innerText = event.target.innerText;
  itemClip.classList.remove("hide");
  targetEl.classList.add("onDrag");
}

function handleTouchEnd(event) {
  itemClip.classList.add("hide");
  targetEl.classList.remove("onDrag");
}

function handleTouchMove(event) {
  itemClip.style.top = event.changedTouches[0].clientY + "px";
  itemClip.style.left = event.changedTouches[0].clientX + "px";
  hitTest(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
}

function hitTest(thisX, thisY) {
  for (var i = 0, max = scopeObj.length; i < max; i++) {
    if (thisX > scopeObj[i].startX && thisX < scopeObj[i].endX) {
      if (thisY > scopeObj[i].startY && thisY < scopeObj[i].endY) {
        wrapper.insertBefore(targetEl, scopeObj[i].target);
        return;
      }
    }
  }
}

function defineScope(elementArray) {
  scopeObj = [];
  for (var i = 0, max = elementArray.length; i < max; i++) {
    var newObj = {};
    newObj.target = elementArray[i];
    newObj.startX = elementArray[i].offsetLeft;
    newObj.endX = elementArray[i].offsetLeft + elementArray[i].offsetWidth;
    newObj.startY = elementArray[i].offsetTop;
    newObj.endY = elementArray[i].offsetTop + elementArray[i].offsetHeight;
    scopeObj.push(newObj);
  }
}

That's the original code you just saw. I kept the Javascript exactly the same. The HTML in my case, however, is different -- and this produces lots of console log errors.

The thing is, it's perfectly functional, and does exactly what I want it to -- but in the process it produces an outrageous amount of errors in the console log each time the user drags and drops. We're talking hundreds of errors per instance.

"Uncaught NotFoundError: Failed to execute 'insertBefore' on 'Node': The node before which the new node is to be inserted is not a child of this node."

The core issue is that instead of just the simple HTML setup seen in the Codepen example? My HTML is more complicated. Basically, inside of each div with the class "item"? There are several sub-divs inside of there. And that is seemingly what's causing the errors. (I confirmed this by modifying that original codepen code in a similar way -- by adding sub-divs inside of those core "item" divs. The same console log error gets produced. Yet it also remains similarly functional.)

What's the best way to modify this Javascript so as to not produce those errors?

Here's the HTML of each one of my class="item" div elements, for comparison (housed inside of the core parent div with id="DivTesting" and class="box":

    <div id='DivTesting' class='box'> // parent box container
    
    <div id='SavedIdeaDiv" + [i] + "' class=' SavedIdeaDivContainer item' draggable='true'> // this div corresponds to the individual "item" divs in the original codepen code
    
    <div id='IdeaTextDivs'> // additional divs hold other stuff inside of the 'item' divs
    
    <div id='ArrowDiv'><span id='MoveIdeaUp" + [i] + "' class='material-symbols-outlined arrow-up-1'>expand_less</span><span id='MoveIdeaDown" + [i] + "' class='material-symbols-outlined arrow-down-1'>expand_more</span>
</div>
    
    <div id='IdeaTextDiv2' class='IdeaTextDivParagraph'><p id='SavedIdeaPText'>"+FullSavedIdeasArray[i]+"</p>
</div>
    
    <div id='DeleteIdeaDivs'><span id='DeleteIdeaButton" + [i] + "' class='material-symbols-outlined x-button-1'>close</span>
</div>
    </div>";
    
    </div>

    </div>

    <div id="itemClip" class="item itemClip hide">some item</div> // this is thrown at the bottom to match the codepen code. not sure it's even necessary.

Thanks!

1 Answers

Fixed var wrapper = document.getElementById("box"); to have the correct selector.

Fixed wrapper.insertBefore(targetEl, event.target); to insert before the actual element and not child of it (added .closest(".item"))

You might want to do the same for the touch events.

// === Define Variables and Elements ===
var elements = document.querySelectorAll('.box .item');
var targetEl;
var wrapper = document.querySelector(".box");
var itemClip = document.getElementById("itemClip");

var scopeObj;

// === Event Binding ===
for (var i = 0, max = elements.length; i < max; i++) {
  elements[i].addEventListener("dragstart", handleDrag);
  elements[i].addEventListener("dragend", handleDragEnd);
  elements[i].addEventListener("dragenter", handleDragEnter);

  elements[i].addEventListener("touchstart", handleTouch);
  elements[i].addEventListener("touchend", handleTouchEnd);
  elements[i].addEventListener("touchmove", handleTouchMove);
}

// === Function Kits ===
function handleDrag(event) {
  targetEl = event.target;
  targetEl.classList.add("onDrag");
}

function handleDragEnd(event) {
  targetEl.classList.remove("onDrag");
}

function handleDragEnter(event) {
  wrapper.insertBefore(targetEl, event.target.closest(".item"));
}

function handleTouch(event) {
  defineScope(elements);
  targetEl = event.target;
  itemClip.style.top = event.changedTouches[0].clientY + "px";
  itemClip.style.left = event.changedTouches[0].clientX + "px";
  itemClip.innerText = event.target.innerText;
  itemClip.classList.remove("hide");
  targetEl.classList.add("onDrag");
}

function handleTouchEnd(event) {
  itemClip.classList.add("hide");
  targetEl.classList.remove("onDrag");
}

function handleTouchMove(event) {
  itemClip.style.top = event.changedTouches[0].clientY + "px";
  itemClip.style.left = event.changedTouches[0].clientX + "px";
  hitTest(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
}

function hitTest(thisX, thisY) {
  for (var i = 0, max = scopeObj.length; i < max; i++) {
    if (thisX > scopeObj[i].startX && thisX < scopeObj[i].endX) {
      if (thisY > scopeObj[i].startY && thisY < scopeObj[i].endY) {
        wrapper.insertBefore(targetEl, scopeObj[i].target);
        return;
      }
    }
  }
}

function defineScope(elementArray) {
  scopeObj = [];
  for (var i = 0, max = elementArray.length; i < max; i++) {
    var newObj = {};
    newObj.target = elementArray[i];
    newObj.startX = elementArray[i].offsetLeft;
    newObj.endX = elementArray[i].offsetLeft + elementArray[i].offsetWidth;
    newObj.startY = elementArray[i].offsetTop;
    newObj.endY = elementArray[i].offsetTop + elementArray[i].offsetHeight;
    scopeObj.push(newObj);
  }
}
h1 {
  text-align: center;
}

.box {
  width: 250px;
  margin: 0 auto;
  border: 1px solid #AAA;
}

.item {
  width: 250px;
  height: 40px;
  border: 1px solid #DDD;
  font-size: 20px;
  line-height: 40px;
  text-align: center;
  color: #333;
  cursor: pointer;
  position: relative;
  transition: all .3s;
  user-select: none;
}

.item.onDrag {
  /*transform: scale(1.05, 1.1);*/
  opacity: 1;
  background-color: #F5F5F5;
  box-shadow: 0 0 5px rgba(0, 0, 0, .1);
}

.item:last-child {
  height: 20px;
}

.item:last-child::before {
  border: none;
  height: 0;
}

.itemClip {
  position: absolute;
  background-color: white;
  opacity: 1;
  top: 0;
  left: 0;
  transform: translate(-50%, -50%);
  transition: none;
  background-color: white;
}

.hide {
  display: none;
}
<!-- 
<h1>A Sortable List</h1>
<div id="box" class="box">
  <div class="item" draggable="true">item 1</div>
  <div class="item" draggable="true">item 2</div>
  <div class="item" draggable="true">item 3</div>
  <div class="item" draggable="true">item 4</div>
  <div class="item" draggable="true">item 5</div>
  <div class="item" draggable="true">item 6</div>
  <div class="item"></div>
</div>
<div id="itemClip" class="item itemClip hide">some item</div>

-->

<div id='DivTesting' class='box'>


  <div id='SavedIdeaDiv" + [i] + "' class=' SavedIdeaDivContainer item' draggable='true'>

    <div id='IdeaTextDivs'>
      <div id='ArrowDiv'><span id='MoveIdeaUp" + [i] + "' class='material-symbols-outlined arrow-up-1'>expand_less 1</span><span id='MoveIdeaDown" + [i] + "' class='material-symbols-outlined arrow-down-1'>expand_more</span>
      </div>

      <div id='IdeaTextDiv2' class='IdeaTextDivParagraph'>
        <p id='SavedIdeaPText'>"+FullSavedIdeasArray[i]+"</p>
      </div>

      <div id='DeleteIdeaDivs'><span id='DeleteIdeaButton" + [i] + "' class='material-symbols-outlined x-button-1'>close</span>
      </div>
    </div>

  </div>

  <div id='SavedIdeaDiv" + [i] + "' class=' SavedIdeaDivContainer item' draggable='true'>

    <div id='IdeaTextDivs'>
      <div id='ArrowDiv'><span id='MoveIdeaUp" + [i] + "' class='material-symbols-outlined arrow-up-1'>expand_less 2</span><span id='MoveIdeaDown" + [i] + "' class='material-symbols-outlined arrow-down-1'>expand_more</span>
      </div>

      <div id='IdeaTextDiv2' class='IdeaTextDivParagraph'>
        <p id='SavedIdeaPText'>"+FullSavedIdeasArray[i]+"</p>
      </div>

      <div id='DeleteIdeaDivs'><span id='DeleteIdeaButton" + [i] + "' class='material-symbols-outlined x-button-1'>close</span>
      </div>
    </div>

  </div>

  <div id='SavedIdeaDiv" + [i] + "' class=' SavedIdeaDivContainer item' draggable='true'>

    <div id='IdeaTextDivs'>
      <div id='ArrowDiv'><span id='MoveIdeaUp" + [i] + "' class='material-symbols-outlined arrow-up-1'>expand_less 3</span><span id='MoveIdeaDown" + [i] + "' class='material-symbols-outlined arrow-down-1'>expand_more</span>
      </div>

      <div id='IdeaTextDiv2' class='IdeaTextDivParagraph'>
        <p id='SavedIdeaPText'>"+FullSavedIdeasArray[i]+"</p>
      </div>

      <div id='DeleteIdeaDivs'><span id='DeleteIdeaButton" + [i] + "' class='material-symbols-outlined x-button-1'>close</span>
      </div>
    </div>

  </div>
  <div class="item"></div>

</div>
<div id="itemClip" class="item itemClip hide">some item</div>

Related