Sortable.jsで実現するドラッグ&ドロップUI


はじめに

こんにちは、渡邊です。

社内アプリのUI改修を行う際に、Sortable.jsというライブラリを使用し、一部ページにドラッグ&ドロップ(以下、D&D)機能を使ったUIを実装しました。

今回は、Sortable.jsを使ってD&D機能を実装する基本的な方法から、D&D操作後に移動した項目や移動先を取得し、サーバー側のデータ更新につなげる方法まで紹介します。


Sortable.jsとは

Sortable.jsは、DOM要素をD&D操作で移動可能にし、D&D操作に応じて設定したコールバックを実行できるJavaScriptライブラリです。

通常、D&D操作をJavaScriptで一から実装する場合、マウスイベントや要素の位置計算など多くの処理を記述する必要がありますが、Sortable.jsを使うことでこれらの複雑な処理をライブラリ側に任せることができ、少ないコードでD&D機能を実装することができます。

また、Vanilla JavaScriptで利用できるため、特定のフレームワークに依存しておらず導入しやすいライブラリです。


基本的な使い方

導入

今回の開発ではSortable.jsを既存のJavaScript管理方式に合わせて組み込みました。

※一般的にはnpmやYarnを利用した導入方法もありますが、今回は社内アプリケーションがRailsのAsset PipelineでJavaScriptを管理していたため。

Asset Pipeline:RailsがJS/CSSなどの静的ファイルをまとめて管理・最適化して配信する仕組み


実装例

Sortable.jsを利用する場合、D&D対象となるDOM要素を指定して、Sortableインスタンスを作成します。

例えば、以下のようなリストがあるとします。

<ul id="sortable-list"> 
 <li>項目A</li>  
 <li>項目B</li>  
 <li>項目C</li>
</ul>

JavaScript側で対象の要素を指定します。

const element = document.querySelector('#sortable-list');

new Sortable(element, {
  animation: 150
});

new Sortable() に対象となるDOM要素を渡すことで、そのリスト内の要素をD&Dで並び替えられるようになります。

今回の場合、Sortable.jsを適用しているのはul要素なので、ul直下のli要素がドラッグ対象になります。


リスト間の移動

Sortable.jsでは、groupオプションを設定することで、複数のリスト間で要素を移動させることもできます。

例えば、以下のような複数リストを用意した場合、

<div>
  <h3>リストA</h3>
  <ul id="list-a" data-id="1">
    <li data-item-id="1">項目A</li>
  </ul>
</div>

<div>
  <h3>リストB</h3>
  <ul id="list-b" data-id="2">
    <li data-item-id="2">項目B</li>
  </ul>
</div>

それぞれのリストにSortable.jsを適用し、itemsという同じgroup名を指定します。

new Sortable(document.querySelector('#list-a'), {
  group: 'items',
  animation: 150
});

new Sortable(document.querySelector('#list-b'), {
  group: 'items',
  animation: 150
});

このように、groupに同じ値を設定することで、異なるリスト間で要素をD&Dできるようになります。


ドラッグ終了後の処理

Sortable.jsでは、ドラッグ操作完了時に実行するコールバックを設定するためのonEndプロパティが用意されています。また、onEndに設定したコールバック関数には、ドラッグ操作に関する情報を持ったeventオブジェクトが引数として渡されます。

このeventオブジェクトには、ドラッグ操作に関する情報がSortable.jsで定義されたプロパティとして含まれており、それらを利用してバックエンドへ移動情報を送信し、レコード更新などの後続処理を行うことができます。

下記の表ではonEndでよく利用されるeventプロパティをまとめています。

プロパティ内容
event.item移動したDOM要素
event.from移動元のリスト
event.to移動先のリスト
event.oldIndex移動前の位置
event.newIndex移動後の位置

※Sortable.jsではonEndの他にも以下のコールバックが用意されています。

1. onAdd(別リストから要素が追加されたとき)
2. onRemove(別リストへ要素が移動したことで、元のリストから要素が取り除かれたとき)
3. onUpdate(同リスト内で要素が並び替えられたとき)

また、それぞれのコールバックに渡されるeventオブジェクトには、コールバックに応じた情報が含まれています。


onEndでドラッグした項目と移動先を取得する

D&Dによる画面上の移動だけでなく、実際のアプリケーションでは、移動した内容をデータベースに反映するために、どの項目をどのリストへ移動したのかを特定する必要があります。

そこで、リスト間の移動を行う場合、移動した項目や移動先のリストを識別できるよう、DOM要素にdata属性を設定しておくと便利です。

例えば、以下のように項目とリストそれぞれにIDを設定します。

<ul id="list-a" data-id="1">
  <li data-item-id="123">項目A</li>
</ul>

<ul id="list-b" data-id="2">
  <li data-item-id="456">項目B</li>
</ul>

ドラッグ操作が完了すると、onEndに設定したコールバック関数が実行されます。

new Sortable(document.querySelector('#list-a'), {
  group: 'items',
  animation: 150,

  onEnd: function(event) {
    const itemId = event.item.dataset.itemId;
    const targetId = event.to.dataset.id;

    console.log(itemId);
    console.log(targetId);
  }
});

例えば、項目AをリストAからリストBへ移動した場合、

event.item
  ↓
<li data-item-id="123">
  ↓
event.item.dataset.itemId
  ↓
123

となり、移動した項目のIDを取得できます。

同様に、

event.to
  ↓
<ul data-id="2">
  ↓
event.to.dataset.id
  ↓
2

となり、移動先のリストのIDを取得できます。

つまり、この処理によって、

itemId   → 123(移動した項目)
targetId → 2(移動先のリスト)

という移動情報を取得できます。

また、取得した情報をAjax通信などでサーバーへ送信することで、画面上の変更をデータベースへ反映できます。

$.ajax({
  url: '/items/move',
  type: 'POST',
  data: {
    item_id: itemId,
    target_id: targetId
  }
});

ただし、Sortable.js自身がAjax通信やDB更新を行うわけではないため、移動後のデータ保存処理はアプリケーション側で実装する必要があります。


処理の流れ

D&Dを行って、実際にDB更新が起こるまでの処理の流れは以下のようになっています。

Sortable.js
    ↓
D&D操作を検知
    ↓
onEndに設定したコールバック関数を実行
    ↓
eventオブジェクトを受け取る
    ↓
event.item / event.to から移動情報を取得
    ↓
Ajaxでサーバーへ送信
    ↓
サーバー側で処理
    ↓
DB更新

まとめ

  • new Sortable()にD&D対象となるDOM要素を渡すことで、リスト内の要素をD&Dで並び替えられるようにできる。
  • groupオプションに同じ値を設定することで、複数のリスト間で要素を移動できる。
  • onEndなどのコールバックを設定することで、D&D操作の完了時に処理を実行できる。
  • コールバックに渡されるeventオブジェクトから、移動した要素や移動先、移動前後の位置などの情報を取得できる。
  • eventから取得した情報をAjaxなどでサーバーへ送信することで、D&Dによる画面上の変更をデータベースへ反映できる。

参考:

https://qiita.com/AVELWP/items/536c797a63c081cf7bc0

https://zenn.dev/sdkfz181tiger/articles/e5979cde0238c1

https://sortablejs.github.io/Sortable


さいごに

現在デザイン・システム室では、新しいメンバーを募集しています。少しでも興味を持たれた方は、ぜひご応募ください
皆様からのご応募、心よりお待ちしております。