DEV Community

Cover image for Drag & Drop Sort List
Usman Suleiman
Usman Suleiman

Posted on

Drag & Drop Sort List

The drag-and-drop API allows us to drag items and drop them anywhere in the browser and other applications. In this article, we'll learn how to use it to sort a list of items.

Let's start simple. We'll structure the UI according to the header image. We only want the bare minimum required, so we won't be implementing the "add items" and "checkbox" features.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Sorting with Drag & Drop</title>
  </head>
  <body>
    <ul></ul>
  </body>
  <script>
    const items = [
      {
        index: 0,
        title: "0 - Zero",
      },
      {
        index: 1,
        title: "1 - One",
      },
      {
        index: 2,
        title: "2 - Two",
      },
    ];

    // We add more code here
  </script>
</html>
Enter fullscreen mode Exit fullscreen mode

First, we populate the unordered list with items. Each item has a unique index and a button that will serve as the drag handle.

function displayItems() {
  const ul = document.querySelector("ul");
  ul.innerHTML = "";
  items.forEach((item) => {
    const li = document.createElement("li");
    li.innerText = item.title;

    const dragHandle = document.createElement("button");
    dragHandle.innerText = "@";
    li.appendChild(dragHandle);

    ul.appendChild(li);
  });
}

window.addEventListener("load", () => {
  displayItems();
});
Enter fullscreen mode Exit fullscreen mode

To start dragging an item, we click and hold on a drag handle. The DOM event equivalent for that is the mousedown event.

function displayItems() {
  const ul = document.querySelector("ul");
  items.forEach((item) => {
    const li = document.createElement("li");
    li.innerText = item.title;

    const dragHandle = document.createElement("button");
    dragHandle.innerText = "@";
    li.appendChild(dragHandle);

    // Equal to Click and Hold
    dragHandle.addEventListener("mousedown", () => {
      console.log("holding");
    });

    ul.appendChild(li);
  });
}
Enter fullscreen mode Exit fullscreen mode

If you try dragging an item, you'll notice there is no indication of it being dragged around. By default, all links, text nodes, and image elements are draggable. For others, we tell the browser that an element can be dragged around by setting a draggable attribute.

dragHandle.addEventListener("mousedown", () => {
  li.setAttribute("draggable", true);
});
Enter fullscreen mode Exit fullscreen mode

Now, try dragging an item around, and you'll see it highlighted. Our browser knows we're dragging a li element, but it doesn't know what item of the array. To tell the browser what item we're dragging, we can use the dataTransfer object. The dataTransfer object allows us to communicate with the browser when dragging and dropping.

// After the mousedown listener
li.addEventListener("dragstart", (event) => {
  event.dataTransfer.setData("index", item.index);
});
Enter fullscreen mode Exit fullscreen mode

Why do we even need to tell the browser? The answer is simply because the item we're dropping on needs to know what is being dropped on it, and the only way for it to know is through the browser's dataTransfer object.

So, how does an item know something is being dropped on it? Every element can listen for a drop event. The browser fires a drop event whenever we drop an item. For instance, when we drag item-0 and drop it on item-2, item-0 will listen for dragstart event, while item-2 will listen for a drop event.

// After the dragstart listener
li.addEventListener("drop", (event) => {
  const draggedIndex = event.dataTransfer.getData("index"); // item being dragged
  const dropIndex = item.index; // item we're dropping on
  console.log("dragging", draggedIndex);
  console.log("dropping on", dropIndex);
});
Enter fullscreen mode Exit fullscreen mode

Now, try dragging and dropping an item. Check your console. If you're lucky, you'll see an output. But if you're unlucky like me, then allow me to explain.

When you grab an item and drag it into a different location without dropping it, it means you are dragging over. The browser locks every element we drag over to. Preventing us from dropping anything.

Any element that wants to allow the dropping of dragged-over items needs to prevent the browser's default behavior.

// After the drop listener
li.addEventListener("dragover", (event) => {
  event.preventDefault();
});
Enter fullscreen mode Exit fullscreen mode

Try testing the code again, and it should work on any browser.

The final step is to swap positions instead of logging to console. For that, we create a swap function.

function swap(draggedIndex, dropIndex) {
  // We get the current items
  const dragged = items[draggedIndex];
  const drop = items[dropIndex];

  // We swap their positions
  items[draggedIndex] = drop;
  items[dropIndex] = dragged;

  // Update their indexes to reflect their new positions
  dragged.index = dropIndex;
  drop.index = draggedIndex;

  // Then finally update the display
  displayItems();
}
Enter fullscreen mode Exit fullscreen mode

We call the swap function to change the positions of the items. Then we set the draggable attribute to false because we want to drag items using only the drag handle.

li.addEventListener("drop", (event) => {
  const draggedIndex = event.dataTransfer.getData("index"); // item being dragged
  const dropIndex = item.index; // item we're dropping on

  swap(draggedIndex, dropIndex);
  li.setAttribute("draggable", false);
});
Enter fullscreen mode Exit fullscreen mode

That's it! We now have a working drag-sort list.

Here are a few things you can try:

  • Hide the original dragging item on drag start to improve user experience.
  • Reduce the opacity of the item you're dragging over to.

Happy Coding!

Latest comments (0)