tobbe
tobbe

Reputation: 1807

Tutorial for HTML5 drag&drop - sortable list

Do anyone know a really good tutorial for HTML5 drag&drop? Im making a toDo-list and I want to be able to reorder/sort it with this API. I've been googling for it like a mad man and now im start giving up... ANY tips is welcomed! Thanks!

p.s I really want to use html5 drag&drop API, not jQuery-sortable()

Upvotes: 44

Views: 62363

Answers (5)

igneosaur
igneosaur

Reputation: 3346

Don't worry guys. I'm here, a decade later. This is how you do it.

And to answer your question, here is a good tutorial https://web.dev/articles/drag-and-drop.

document.querySelectorAll("li").forEach(listItem => {
  listItem.addEventListener("dragstart", handleDragstart);
  listItem.addEventListener("dragover", handleDragover);
  listItem.addEventListener("dragleave", handleDragleave);
  listItem.addEventListener("drop", handleDrop);
})

let draggedElement;

function handleDragstart(event) {
  draggedElement = this;
  event.dataTransfer.effectAllowed = "move";
  event.dataTransfer.setData("text/html", this.innerHTML);
}

function handleDragover(event) {
  event.preventDefault(); // @note This is needed for drop to fire.
  event.dataTransfer.dropEffect = "move";
  this.classList.add("over");
}

function handleDragleave() {
  this.classList.remove("over");
}

function handleDrop(event) {
  draggedElement.innerHTML = this.innerHTML;
  this.innerHTML = event.dataTransfer.getData('text/html');
  this.classList.remove("over");
}

function handleDragend() {
  draggedElement = null;
}
body {
  margin: 1rem;
}

ul {
  display: flex;
  /* Try this too! */
  /* flex-direction: column; */
  gap: 1rem;
  list-style: none;
  padding: 0;
}

li {
  border: 2px solid black;
  cursor: grab;
  padding: 1rem;
}

li:active {
  cursor: grabbing;
}

.over {
  border-style: dashed;
  opacity: 0.5;
}
<ul>
  <li draggable="true">Apples</li>
  <li draggable="true">Oranges</li>
  <li draggable="true">Bananas</li>
  <li draggable="true">Strawberries</li>
</ul>

The issue with a lot of these suggestions is that they are doing live swapping on hover which can cause a lot of jank if the items are in columns and of different sizes. This way, there is no weird DOM comparison and it's all more declarative as the actual swap happens on drop.

The powerful thing here is event.dataTransfer. It can be used to communicate all sorts of things. If you are in a more "reactive" environment you can use it to pass around IDs and use them to update state, for example.

Upvotes: -1

davidf
davidf

Reputation: 977

I've tried to keep this sample as simple as possible.

If you create a HTML list:

<ul>
  <li draggable="true" ondragover="dragOver(event)" ondragstart="dragStart(event)">Apples</li>
  <li draggable="true" ondragover="dragOver(event)" ondragstart="dragStart(event)">Oranges</li>
  <li draggable="true" ondragover="dragOver(event)" ondragstart="dragStart(event)">Bananas</li>
  <li draggable="true" ondragover="dragOver(event)" ondragstart="dragStart(event)">Strawberries</li>
</ul>

...and the following javascript:

var _el;

function dragOver(e) {
  if (isBefore(_el, e.target))
    e.target.parentNode.insertBefore(_el, e.target);
  else
    e.target.parentNode.insertBefore(_el, e.target.nextSibling);
}

function dragStart(e) {
  e.dataTransfer.effectAllowed = "move";
  e.dataTransfer.setData("text/plain", null); // Thanks to bqlou for their comment.
  _el = e.target;
}

function isBefore(el1, el2) {
  if (el2.parentNode === el1.parentNode)
    for (var cur = el1.previousSibling; cur && cur.nodeType !== 9; cur = cur.previousSibling)
      if (cur === el2)
        return true;
  return false;
}

... you should get a sortable list.

You can try the code on https://codepen.io/crouchingtigerhiddenadam/pen/qKXgap

Please be aware of the following bug in FireFox: https://developer.mozilla.org/en-US/docs/Web/Events/dragenter

Hope this helps.

Upvotes: 86

Bene Laci
Bene Laci

Reputation: 112

If you're going with the solution by adamf (Mar 10 '15 at 11:16), and want to use it on table rows, replace the dragenter function to the following:

function dragenter(e) {
    var target = e.target;
    while (target.parentNode.tagName != 'TBODY') {
        target = target.parentNode;
    }

    if (isbefore(source, target)) {
        target.parentNode.insertBefore(source, target);
    }
    else {
        target.parentNode.insertBefore(source, target.nextSibling);
    }
}

This way the target will only apply for TR elements, and not any of their child elements.

The same thing would apply for ul > li strunctures, if the li elements have children.

If there are img child elements, add a draggable="false" attribute to each.

Upvotes: 3

cmpenney
cmpenney

Reputation: 71

If you are looking to do this with table rows you need to make a slight change:

https://jsfiddle.net/cmpenney/6rx6u2kf/

<table>
    <tr draggable="true" ondragenter="dragenter(event)" ondragstart="dragstart(event)">
        <td style="border: 1px solid black">Apples</td>
        <td style="border: 1px solid black">A-Column2</td>
    </tr>
    <tr draggable="true" ondragenter="dragenter(event)" ondragstart="dragstart(event)">
        <td style="border: 1px solid black">Oranges</td>
        <td style="border: 1px solid black">O-Column2</td>
    </tr>
    <tr draggable="true" ondragenter="dragenter(event)" ondragstart="dragstart(event)">
        <td style="border: 1px solid black">Bananas</td>
        <td style="border: 1px solid black">B-Column2</td>
    </tr>
    <tr draggable="true" ondragenter="dragenter(event)" ondragstart="dragstart(event)">
        <td style="border: 1px solid black">Strawberries</td>
        <td style="border: 1px solid black">S-Column2</td>
    </tr>
</table>



var source;

function isbefore(a, b) {
    if (a.parentNode == b.parentNode) {
        for (var cur = a; cur; cur = cur.previousSibling) {
            if (cur === b) {
                return true;
            }
        }
    }
    return false;
}

function dragenter(e) {
    var targetelem = e.target;
    if (targetelem.nodeName == "TD") {
       targetelem = targetelem.parentNode;   
    }  

    if (isbefore(source, targetelem)) {
        targetelem.parentNode.insertBefore(source, targetelem);
    } else {
        targetelem.parentNode.insertBefore(source, targetelem.nextSibling);
    }
}

function dragstart(e) {
    source = e.target;
    e.dataTransfer.effectAllowed = 'move';
}

Upvotes: 7

KendallB
KendallB

Reputation: 4807

For a beginning to end tutorial, check this out: http://taximeeting.tumblr.com/post/26539340142/lightweight-jquery-plugin-for-html5-sortable-lists.

It's based on html5sortable: http://farhadi.ir/projects/html5sortable/. Another great tutorial on HTML5's drag and drop can be found here: http://www.html5rocks.com/en/tutorials/dnd/basics/.

Upvotes: 7

Related Questions