DEV Community

Isaac Hagoel
Isaac Hagoel

Posted on • Updated on

Drag and drop with Svelte using svelte-dnd-action

TLDR: this post will show you how you can add amazing drag and drop capabilities to your Svelte app using svelte-dnd-action. If you've always wanted to build a Trello clone using Svelte (just with nicer animations than Trello's) you've come to the right place.

Let's talk about drag and drop for a minute

If you've ever tried implementing an app that has rich (or even basic) drag and drop interactions, you would know that it is surprisingly difficult. Sure, the browser has a built in drag and drop API. There is only a minor issue with it - it falls flat on its face when it comes to look and feel.
Don't believe me? svelte-sortable-list is a library that uses the browser's drag and drop API and goes above and beyond (with the help of Svelte) to add as much animation as possible. Despite the admirable effort (I mean it), it is not something I could ship to production. The dragged element, as well as all the other elements, stay in their original place until a drop event takes place. It feels very static and stale (you can try it for yourself). As Rich Harris says: "we can do better".

React developers have been enjoying the mighty, although heavyweight and complicated, react-beautiful-dnd. Svelte developers (at least yours truly) were left wanting.

svelte-dnd-action is a new library that aims to correct that.

How does svelte-dnd-action work?

As its name suggests, the library uses Svelte's actions mechanism in order to turn any list container to a drag and drop (dnd) zone. It relies on its host (=== your code) to update the list's data when requested to do so (via events). It also relies on its host to help out with some of the animations by using Svelte's built-in flip animation.
Let's look at a simple example;

Say we have the following component that displays a list with 3 items:

<style>
    div {
        height: 1.5em;
        width: 10em;
        text-align: center;
        border: 1px solid black;
        margin: 0.2em;
        padding: 0.3em;
    }
</style>
<script>
    let items = [
        {id:1, title: 'I'},
        {id:2, title: 'Am'},
        {id:3, title: 'Yoda'}
    ];
</script>
<section>
    {#each items as item(item.id)}
        <div>
            {item.title}    
        </div>
    {/each}
</section>
Enter fullscreen mode Exit fullscreen mode

Now let's say we want to make it re-sortable using drag and drop.
Let's add svelte-dnd-action into the mix:

<style>
    div {
        height: 1.5em;
        width: 10em;
        text-align: center;
        border: 1px solid black;
        margin: 0.2em;
        padding: 0.3em;
    }
</style>
<script>
    import {dndzone} from 'svelte-dnd-action';
    function handleSort(e) {
        items = e.detail.items;
    }
    let items = [
        {id:1, title: 'I'},
        {id:2, title: 'Am'},
        {id:3, title: 'Yoda'}
    ];
</script>
<section use:dndzone={{items}} on:consider={handleSort} on:finalize={handleSort}>
    {#each items as item(item.id)}
        <div>
            {item.title}    
        </div>
    {/each}
</section>
Enter fullscreen mode Exit fullscreen mode

Play with this example in the REPL
Easy, right?
We pass out items into the dndzone action and update our list when we get a consider or finalize event. The difference between the two is that consider is emitted for intermediary states (as items need to 'make room') and finalize is emitted when the element is dropped. The distinction between the two can be useful when deciding whether to save the new list in the server for example.
One important thing to notice is that each item in the list has an id property which we also pass as the key for the #each block. svelte-dnd-action relies on the existence of the id property so make sure you have it.

This is neat and all, but there are no animations yet. In order to make everything animate nicely, we need to add flip into the mix and pass the flip duration into dndzone as a parameter:

<style>
    div {
        height: 1.5em;
        width: 10em;
        text-align: center;
        border: 1px solid black;
        margin: 0.2em;
        padding: 0.3em;
    }
</style>
<script>
    import {dndzone} from 'svelte-dnd-action';
    import {flip} from 'svelte/animate';
    const flipDurationMs = 200;
    function handleSort(e) {
        items = e.detail.items;
    }
    let items = [
        {id:1, title: 'I'},
        {id:2, title: 'Am'},
        {id:3, title: 'Yoda'}
    ];
</script>
<section use:dndzone={{items, flipDurationMs}} on:consider={handleSort} on:finalize={handleSort}>
    {#each items as item(item.id)}
        <div animate:flip={{duration:flipDurationMs}}>
            {item.title}    
        </div>
    {/each}
</section>
Enter fullscreen mode Exit fullscreen mode

Play with this example in the REPL
Viola, it animates!

Dnd zones types

By default, if you use dnd-zone on multiple list containers, you will be able to grab an element from one list and drop it into another. That's pretty cool but sometimes you want control over what can go where.
In order to address this need svelte-dnd-action accepts an optional type parameter.
See it in action in the REPL.
In this example, you can move items between the two lists at the top, that have the type "light". You can't move items between the top lists and the bottom list, which has the type "dark" (luckily, Yoda and Luke are safe). You can still shuffle the item within each list like before.

One useful way to use types is for nested dnd-zones. For example, if you are building a Trello like board, each column can be a dndzone (so items can be moved from one column to another) and the container that holds the columns can also be a dndzone of a different type. That way, the columns can be re-ordered independently of the items that they hold.

What else can it do?

There is actually quite a bit more that this library can do.
To see a more complex example that includes horizontal and vertical lists, a board (nested zones as explained above) and demos the auto-scroll feature, please have a look at this REPL.

That's all for today folks. Happy dragging and dropping.

Edit Oct 3 2020: The library is now fully accessible as well. You can read more here.

Top comments (16)

Collapse
 
rickymed profile image
RickyMed

Didn't even think of using a library for dnd functionality. Thank you for this article!

Collapse
 
nixxdev profile image
Niki

I'm looking for a solution for multiple virtual lists (infinite load on scroll) which can be filtered by search input and also be able to drag&drop items between lists. Is there a way for achieve this?

Collapse
 
isaachagoel profile image
Isaac Hagoel

the virtual list aspect is not covered by the lib but i don't see why it won't be compatible with that. same goes for the flitering by search

Collapse
 
nixxdev profile image
Niki

Thank you! Well I have a repl for this (svelte.dev/repl/c4fc230da62f4f8295...)
however, I'm getting getBoundingClientRect of undefined error when I try to move the for the first time an item, but not sure if the problem is with my code, or should I open an issue for this? :)

Thread Thread
 
isaachagoel profile image
Isaac Hagoel

sure. feel free to create an issue. please explain what you are trying to acheive (your consider and finalize handlers seems more complex that i would expect)

Collapse
 
ericchapman profile image
Eric The Coder

Great. I will have to build a small drag and drop app soon and I think I will consider svelte. I did not hear much about svelte before your post but first impression is good. Do you have any beginner reading/tutorial that you suggest me to start learning about svelte?

Collapse
 
isaachagoel profile image
Isaac Hagoel

Hi,
Svelte is quite amazing. The official tutorial is really good and I recommend starting there.

Collapse
 
c0dehamster profile image
Sergej Smelov • Edited

May I ask how to synchronize the items list with a store?

I tried naive approach such as

import { ListStore } from "./ListStore"

let items = $ListStore

const handleSortEnd = e => {
items = e.detail.items
ListStore.set(items)
}

but got a bizarre case of double headings in draggable elements

Update: here is full code
github.com/c0dehamster/drag-and-dr...

Collapse
 
isaachagoel profile image
Isaac Hagoel

You should be able to use the store directly and with no problem. Feel free to create a github issue with a Repl link

Collapse
 
c0dehamster profile image
Sergej Smelov

Thank you, I figured out how to connect to the store, is was actually very easy. Works as intended now

Collapse
 
lwkchan profile image
Laura Chan

Awesome library and write up for it!

Collapse
 
ivan_jrmc profile image
Ivan Jeremic

Every example is with hardcoded data, as soon as data comes from an api drag and drop does not work anymore, can you make an example for this?

Collapse
 
isaachagoel profile image
Isaac Hagoel

what do you mean? of course it works. In my company we are using it with data from all kinds of sources all of the time. Feel free to make an issue with an example in github and I could have a look

Collapse
 
ivan_jrmc profile image
Ivan Jeremic • Edited

Sure here a codesanbox with the example. I need to say that I'm new at Svelte coming from React so I'm sure I'm missing something would be awesome if you could take a look, thanks! codesandbox.io/s/svelte-api-dnd-9wzpe PS. the codesanbox is a bit buggy you need to refresh it in order to show the app.

Thread Thread
 
isaachagoel profile image
Isaac Hagoel

line 30 should be {#each items as todo(todo.id)}
you are iterating over a variable you are not updating in handleSort

Thread Thread
 
ivan_jrmc profile image
Ivan Jeremic

thanks