Using Shopify Draggable to build a drag and drop timeline

The most popular feature in Parcelizer is an interactive gantt chart that lets users rearrange stops on a route by dragging and dropping them on a timeline. In this tutorial, I'm going to show you how to build something similar using Shopify Draggable, a lightweight open source javascript library that makes it easy to build interactive objects for users to drag from one place to another. After that, we'll refactor our code to make it modular and easy to use in a modern React web application. Finally, we'll add some nice CSS styling to our timeline so that it's almost identical to the one used in Parcelizer.

This tutorial is divided into three parts:

Part 1: Using Shopify Draggable to build an interactive drag and drop timeline in javascript (this article)
Part 2: How to use the Shopify Draggable with React
Part 3: Styling a Shopify Draggable React component with CSS

Shopify Draggable is a javascript library that lets users replicate the drag and drop functionality of mobile apps (just like how you can move app icons around on the iPhone simply by pressing and moving them around) on the web. It was launched by Shopify in 2018 (I'm writing this in June 2023) which makes it practically ancient as far as javascript libraries go. Even though all of the engineers who originally worked on it have either been laid off or left the company, it still works pretty well and compares favorably to actively maintained libraries such as React DnD and react-beautiful-dnd.

Afi Labs delivers custom software to meet your logistics needs. From vehicle routing, live tracking to proof of delivery, we've got you covered!
👋 Say Hello!

One notable feature of Shopify Draggable is its support for both mouse and touch events, ensuring a consistent user experience across different devices. It also includes additional functionalities like nested drag and drop, snapping to grid, and auto-scrolling.

Shopify Draggable example

To start, we are going to use Shopify Draggable to build a swappable list of numbered words that we can move around, similar to how stops are moved around in Parcelizer.

First, let's create a basic HTML file (full source code).

<html lang="en">
   <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
   </head>
   <body>
   </body>
</html>

Next, let's import the library via CDN (Content Delivery Network) by adding a script tag that points to @shopify/draggable in the HTML body.

<script src="https://cdn.jsdelivr.net/npm/@shopify/draggable@1.0.0-beta.2/lib/draggable.min.js"></script>

Finally, let's add the list of words. To keep things simple and fun, we'll use the first five words from lorem ipsum and give each word a distinct color. Add this code inside the body tag just above the script tag.

<ul class="horizontal-menu sortable">
   <li style="background-color: #00a8ff;">lorem</li>
   <li style="background-color: #9c88ff;">ipsum</li>
   <li style="background-color: #fbc531;">dolor</li>
   <li style="background-color: #4cd137;">sit</li>
   <li style="background-color: #487eb0;">amet</li>
</ul>

Save the file as shopify-draggable-example.html and open it in your browser. Here's what you'll see:

Not very impressive. Let's style the list with css to make it more block like. Add the CSS code below to your head tag.

<style>
.horizontal-menu {
	list-style-type: none;
	margin: 0;
	padding: 0;
}

.horizontal-menu li {
	display: inline-block;
}

.horizontal-menu li a {
	display: block;
	padding: 10px 20px;
	text-decoration: none;
}

.horizontal-menu li,
li.draggable-mirror {
	border: 1px solid black;
	padding: 10px !important;
	margin: 0 5px !important;
	width: 100px !important;
	text-align: center;
	cursor: grab !important;
	list-style: none;
	line-height: 20px;
	height: 20px !important;
	font-weight: bold;
	color: #fff;
}

.horizontal-menu li:active {
	cursor: grabbing !important;
}
</style>

Save the file again and refresh the page on your browser. Much better!

Now, we just need to add the drag and drop functionality from Shopify Draggable to it. Based on the documentation, it appears that we have four primary modules available for use:

  • Draggable (drag an object from A to B.)
  • Dropable (drag an object from A to B but only if B allows it.)
  • Sortable (there are 4 objects at A, B, C and D in a straight line. Dragging from A to C keeps the existing order i.e. B, C, A and D.)
  • Swappable (there are 4 objects at A, B, C and D in a straight line. Dragging from A to C swaps positions i.e. C, B, A and D.)

Since our end goal is to build a timeline with an ordered list of stops that can be moved around, the Sortable module looks like our best bet. Add this code just before closing the body tag.

const horizontalMenu = document.querySelector('.horizontal-menu');
        const sortable = new Draggable.Sortable(document.querySelectorAll('.sortable'), {
            draggable: 'li'
        });

        sortable.on('sortable:start', () => {
            console.log('sortable:start');
        });

        sortable.on('sortable:sort', () => {
            console.log('sortable:sort');
        });

        sortable.on('sortable:sorted', () => {
            console.log('sortable:sorted');
        });

        sortable.on('sortable:stop', () => {
            setTimeout(updateListItems, 10);
            console.log('sortable:stop');
        });

Here's what's going on. First we declared a constant variable to select the HTML element with the class of horizontal-menu and everything in it. This includes the ul and li elements we are using in our list,

const horizontalMenu = document.querySelector('.horizontal-menu');

Immediately after that we declare a new sortable object and target all the available li elements ("lorem", "ipsum", "dolor", "sit" and "amet") that can be used as draggable objects.

const sortable = new Draggable.Sortable(document.querySelectorAll('.sortable'), {
   draggable: 'li'
});

We now need to initialize the events sortable:start, 'sortable:sort', sortable:sorted and sortable:stop that are emitted during the drag and drop interaction. In the callback for sortable:stop we add the line setTimeout(updateListItems, 10); to call the updateListItems() method 10 milliseconds after the drag and drop interaction finishes. This allows us to update the order number of each list item after the sort.

function updateListItems() {
   const listItems = horizontalMenu.querySelectorAll('li');
   listItems.forEach((listItem, index) => {
      const spanElement = listItem.querySelector('span');
      if (!spanElement) {
         const newSpan = document.createElement('span');
         newSpan.textContent = ` ${index + 1} `;
         listItem.appendChild(newSpan);
      } else {
         spanElement.textContent = ` ${index + 1} `;
      }
   });
}

Then we use setTimeout() (which introduces a small 10 millisecond delay) to allow for all Sortable processes to finish. Once this is done, we call updateListItems() and iterate through all the list elements and number them from 1 to 5.

Now save shopify-draggable-example.html and refresh your browser. You should now see the drag and drop feature working perfectly!

There you go! We've built a simple javascript widget that lets you drag and drop objects in a list. In the next post, we'll refactor our code and rewrite it in React so that you can use it in a production React web application.

👋 As always, if you have any questions or suggestions for me, please reach out or say hello on LinkedIn.

Next: How to use Shopify Draggable with React