Skip to content →

Stimulus.js Tutorial: How Do I Drag and Drop Items in a List?

You’ve heard about Stimulus in the DHH press tour. You’ve read the Stimulus Handbook. How about a more complicated example than what you’ve seen? Here’s a tutorial on using Stimulus to drag and drop items around in a list.

Let’s start with our simple ordered list in html. Notice that each item has a id associated with it, so that we’ll be able to keep track of which item needs to be moved. We also need to make each item draggable, so that the correct javascript events are sent to our controller.

<ol>
    <li draggable="true" data-todo-id="1">Take out trash</li>
    <li draggable="true" data-todo-id="2">Check Email</li>
    <li draggable="true" data-todo-id="3">Subscribe to mailing list</li>
    <li draggable="true" data-todo-id="4">Research Stimulus</li>
</ol>

Now, we’ll need to create the stimulus controller that is just going to handle all the events. We’ll name it drag-item, and if we are using Rails and webpacker, it would go in app/javascript/controllers/drag_item_controller.js:

import { Controller } from "stimulus"
export default class extends Controller {
}

Go ahead and hook up the controller to your html:

<ol data-controller="drag-item">  

We must now listen for a couple drag events, so add those to the html. Notice how we can add multiple actions just by separating each one with a space.

<ol data-controller="drag-item"
    data-action="dragstart->drag-item#dragstart dragover->drag-item#dragover dragenter->drag-item#dragenter drop->drag-item#drop dragend->drag-item#dragend">

Let’s go back to our controller and add those actions. First, we’ll keep track of the item-todo-id when we start dragging so that we know which todo to move when we end dragging:

dragstart(event) {
    event.dataTransfer.setData("application/drag-key", event.target.getAttribute("data-todo-id"))
    event.dataTransfer.effectAllowed = "move"
}

We’ll prevent the default action when dragging an item. This makes sure the drag operation isn’t handled by the browser itself:

dragover(event) {
    event.preventDefault()
    return true
}

dragenter(event) {
    event.preventDefault()
}

On the drop event, we get the element that we were dragging based on it’s data-todo-id, and then in order for the drop to visually make sense, we see where the dragged element compares to where it was dropped, and then insert it before or after the drop target depending on the result.

drop(event) {
    var data = event.dataTransfer.getData("application/drag-key")
    const dropTarget = event.target
    const draggedItem = this.element.querySelector(`[data-todo-id='${data}']`);
    const positionComparison = dropTarget.compareDocumentPosition(draggedItem)
    if ( positionComparison & 4) {
        event.target.insertAdjacentElement('beforebegin', draggedItem);
    } else if ( positionComparison & 2) {
        event.target.insertAdjacentElement('afterend', draggedItem);
    }
    event.preventDefault()
}

This is where we might want to post our action to the server so it can properly handle the movement, but there is no need to do anything at this moment, so we’ll leave it blank.

dragend(event) {
}

Now you have can drag and drop items in a list, using Stimulus as the javascript library.

Comments or Questions? Find me on twitter @jpbeatty

Want To Learn More?

Try out some more of my Stimulus.js Tutorials.

Excited for Stimulus?

Drop your email below, and you won’t miss the next Stimulus Tutorial


Published in ruby on rails Stimulus JS

Comments are closed.