DEV Community

Cover image for Multi-column drag and drop with SortableJS and Stimulus
Vlad Andreev
Vlad Andreev

Posted on

Multi-column drag and drop with SortableJS and Stimulus

Preface

Recently, in a Rails project, I was tasked with implementing the ability to drag and drop cards both within a single list and between two lists (or columns, for that matter).

Without further ado, our choice was to use Stimulus.

Manual Approach

At first, I tried to implement drag and drop by reinventing the wheel, using Drag and Drop API directly for event handling. That is, I needed to write an action for all the events like "dragstart", "dragenter" and so on and so forth.

Quite quickly, it became clear that I was doing absolutely useless job and only complicated code support: describing every step of the drag-and-drop process was quite a pain. There was code that was not very easy to understand at first (especially the calculation of where to throw the cards, and not only that).

Stimulus Sortable Component

Well, the obvious thought was to look for something Stimulus-related and ready to use, and there was a solution: Stimulus Sortable.

Well, it worked. But only for one case: dragging stuff within only one column. You see, stimulus-sortable uses SortableJS under the hood, which is powerful.

But it turned out, that the Stimulus component only handled the onUpdate event and wrapped only a few options out of many available in the original SortableJS. That was not enough for multi-column drag and drop support.

Well, it's open source, then why not enhance it? And I did locally, it was not that hard: pulled the stimulus-sortable repository, wrote a couple lines of code (see diff below), ran yarn build and provided the path to my local package in my original project. It was good to go and it worked.

// src/index.ts 

export default class extends Controller {
  // ...
+ groupValue: string

// ...

static values = {
    // ...
+   group: String
// ... 
get options (): Sortable.Options {
    return {
      // ...
+     onChange: this.defaultOptions.onChange,
+     group: this.groupValue || this.defaultOptions.group || undefined,
    }
  }
Enter fullscreen mode Exit fullscreen mode

All you need to handle multi-column drag in SortableJS is to set the same group option value for every list/column

That was a solution, but... I have my own copy of the package now. That comes with a cost of maintaining my own fork, and we didn't want that.

Wait a minute, can't I just ...

...use SortableJS directly in my regular Stimulus controller without having an odd dependency? Well, yes you can, and it's actually easier, than using the Stimulus component wrapper around it.

Just do the:

yarn add sortablejs
Enter fullscreen mode Exit fullscreen mode

in your project and write this in your Stimulus controller:

import { Controller } from "@hotwired/stimulus"
import Sortable from "sortablejs";

export default class extends Controller {
  static values = {
    group: String
  }

  connect() {
    new Sortable(this.element, {
      group: this.groupValue,
      onChange: this.onChange
    })
  }

  onChange(event) {
    // ... here goes your logic to send AJAX requests with updated data
  }
}
Enter fullscreen mode Exit fullscreen mode

Don't forget to register your controller in index.js:

import DragController from "./drag_controller"
application.register("drag", DragController)
Enter fullscreen mode Exit fullscreen mode

Don't forget to add data-controller for your parent container and provide the shared group name:

<div class="container" data-controller="drag">
  <div class="left-column" data-drag-group-value="your_group_name">
    <!-- cards go here -->
  </div>

  <div class="right-column" data-drag-group-value="your_group_name">
    <!-- and cards go here -->
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Yeah, that's it. Actually, there was no need for a wrapper at all. You can now drag and drop the cards within the same list as well as between multiple lists, just handle the onChange event for stuff like updating position values.

And if you need more features that are available in SortableJS, you can just implement them the same way

Top comments (1)

Collapse
 
janmpeterka profile image
Jan Peterka

Some time ago, I needed this too, and added issue on the stimulus-sortable repo.
Sadly, activity of the project creator is limited (which is fully understandable), so I found some workaroud myself (shown in issue).

Thanks for this article, I will soon to use this functionality again in slightly different context, so it will come handy.

Maybe you can create a PR in the project (or first ask in the issue if that would be acceptable for the maintainer)? It would be really nice to have that support in the component, making it acessible for many devs.