RitoLabo

jQueryでドラッグ&ドロップによる要素の並び替えとそれをCookieで記憶する仕組みを作る

  • 公開:
  • 更新:
  • カテゴリ: JavaScript jQuery
  • タグ: JavaScript,Frontend,jQuery,Cookie,Sortable.js,jquery.cookie.js

JavaScriptのフレームワークjQueryとライブラリを使い、ドラッグアンドドロップで要素の移動を行います。また、応用として、それらをCookieに保存し、ページ遷移・リロードを行っても変更を保持できる仕組みを構築します。

アジェンダ
  1. 開発環境
  2. Sortable.js
  3. 最小構成
  4. 要素の移動時にアニメーションを付ける
  5. ドラッグする要素を限定する
  6. 入れ替えた要素を記憶する
  7. 別の領域へ要素を移動する

開発環境

今回はHTML/CSS/JavaScriptのみを使用します。また、jQueryを使用するので、予めコアソースをダウンロードしておいてください。
jQuery
https://jquery.com

Sortable.js

ドラッグアンドドロップを実現する為に、Sortable.jsというライブラリを使います。

Sortable.js
https://github.com/RubaXa/Sortable

ちなみに、jQuery-uiを使ってもドラッグアンドドロップは実現できますが、このライブラリとは挙動が違うので、どちらを使うかは完全に好みです。

最小構成

まずはミニマムで動作を確認します。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>drag and drop | jQuery JavaScript</title>
</head>
<body>
<style>
body {
margin: 10px;
}
#listWithHandle div {
float: left;
margin-left: 5px;
width: 100px;
height: 100px;
border: 1px solid #00A5E3;
font-size: 24px;
text-align: center;
}
#listWithHandle div:hover {
cursor: move;
}
</style>

<div id="listWithHandle">
<div>01</div>
<div>02</div>
<div>03</div>
<div>04</div>
<div>05</div>
<div>06</div>
<div>07</div>
<div>08</div>
<div>09</div>
</div>

<script src="js/jquery-3.3.1.min.js"></script>
<script src="js/Sortable.min.js"></script>
<script>
$(function(){
Sortable.create(listWithHandle);
});
</script>
</body>
</html>

記述したstyleは見た目を整えただけなので、ライブラリ用に必要な記述ではありません。無くてもいいし、自分の好きに変更できます。

<div id="listWithHandle">
<div>01</div>
<div>02</div>
<div>03</div>
<div>04</div>
<div>05</div>
<div>06</div>
<div>07</div>
<div>08</div>
<div>09</div>
</div>

ここが並べ替えるメインの部分です。div要素で作っていますが、リストでも良いし、何でもOKです。今回の場合で言えば、親ラッパーにlistWithHandleというID名を付けている事が、ライブラリを動作させる為に必要な記述です。

<script src="js/jquery-3.3.1.min.js"></script>
<script src="js/Sortable.min.js"></script>

jQueryとSortable.jsを読み込んでいます。CDNでももちろんOK。

<script>
$(function(){
Sortable.create(listWithHandle);
});
</script>

ページ読み込み時にスクリプトを走らせて要素をdraggableにしています。create()メソッドの引数にドラッグアンドドロップを行いたい要素たちの親要素であるlistWithHandleというID名を渡しています。

これをブラウザで表示すると以下のようになります。

最小構成・ブラウザ確認

ドラッグアンドドロップを行ってみてください。

最小構成・ドラッグアンドドロップを行う

01
02
03
04
05
06
07
08
09

ミニマムでのドラッグアンドドロップを実現できました。

要素の移動時にアニメーションを付ける

実際に動かしてみるとわかりますが、要素が入れ替わる際に雑然と入れ替わるので、なんだかインタラクティブではありません。そこで、オブションを指定して要素の入れ替わり時にアニメーションをつけます。

オプションを指定するには、create()メソッドの第二引数にオプションオブジェクトを渡します。

<script>
$(function(){
Sortable.create(listWithHandle, {
animation: 150,
});
});
</script>

animationプロパティを設定する事で、要素の入れ替わりの動きにインタラクションを加える事が出来ます。値はアニメーションのスピードで、ms(ミリ秒)で指定します。

01
02
03
04
05
06
07
08
09

ドラッグする要素を限定する

もし単純なリストではなく、色々な要素を詰め込んだラッパー自体を入れ替えていく場合、通常のようにラッパー自体をドラッガブルにしてしまうと要素内のテキストなどが選択できず、ユーザビリティが低下します。

その場合はドラッグを行う要素を作成し(アイコンなど)、それによって対象の領域が入れ替えられるようにします。

まずはドラッグ対象のHTMLを以下に変更します。

<div id="listWithHandle">
<div><span class="icon-move" aria-hidden="true">#</span>01</div>
<div><span class="icon-move" aria-hidden="true">#</span>02</div>
<div><span class="icon-move" aria-hidden="true">#</span>03</div>
<div><span class="icon-move" aria-hidden="true">#</span>04</div>
<div><span class="icon-move" aria-hidden="true">#</span>05</div>
<div><span class="icon-move" aria-hidden="true">#</span>06</div>
<div><span class="icon-move" aria-hidden="true">#</span>07</div>
<div><span class="icon-move" aria-hidden="true">#</span>08</div>
<div><span class="icon-move" aria-hidden="true">#</span>09</div>
</div>

各リストに、以下のようにしてドラッグを行う為の要素を追加しています。

<span class="icon-move" aria-hidden="true">#</span>

この#をアイコンと見立てて、これをドラッグする事で、要素を移動する。という流れになります。

次にjQuery側を変更します。

<script>
$(function(){
Sortable.create(listWithHandle, {
handle: '.icon-move',
animation: 150,
});
});
</script>

handleプロパティに対して、対象のクラス名を指定する事で、ドラッグ要素を限定しドラッグアンドドロップを実現する事ができます。

ブラウザで確認すると以下のようになります。

指定要素によるドラッグ&ドロップ確認画面

#を押下する事でこれらの要素を移動できます。#05の要素を見てもらうとわかる通り、こうする事で#以外のテキストを選択できるようになります。(通常では、どこを押下してもドラッグが発火するので選択できない)

01
02
03
04
05
06
07
08
09

尚、この時点で以下のようなエラーでドラッグが発火しない場合があります。

Uncaught DOMException: Failed to execute 'matches' on 'Element': '>*' is not a valid selector.

その場合はコアソースに問題があるかもしれません。今回のサンプルソースをGithubに上げていますので、そこに収録しているSortable.min.jsを使用してみてください。

入れ替えた要素を記憶する

最後に、入れ替えた要素を記憶させ、ページをリロードしても入れ替えた要素を再現できるようにします。

Cookieを簡単に扱う為に、jquery.cookie.jsというライブラリを使います。
jquery.cookie.js
https://github.com/carhartl/jquery-cookie

まずはjquery.cookie.jsを読み込みます。

<script src="js/jquery-3.3.1.min.js"></script>
<script src="js/Sortable/Sortable.min.js"></script>
<script src="js/jquery.cookie.1.4.1.min.js"></script>

ドラッグ領域のHTMLを以下に変更します。

<div id="listWithHandle" class="list-group">
</div>

中身はJSで挿入するので、ラッパーのみになります。

最後にjQuery側の変更です。

<script>
var page;
var table = ["01", "02", "03", "04", "05", "06", "07", "08", "09"];

function init() {
sort_table = getTable();

for (i = 0; i < sort_table.length; i++) {
$('#listWithHandle').append(createHtml(sort_table[i]));
}
}

function getTable() {
$.cookie.json = true;
page = $.cookie('page.page01');

var sort_table;
if(page !== undefined) {
sort_table = page;
} else {
sort_table = table;
}

return sort_table;
}

function createHtml(name) {
return '<div class="wrap list-group-item" data-name="' + name + '">' +
'<span class="icon-move" aria-hidden="true">#</span>' + name +
'</div>';
}

$(function(){
init();

Sortable.create(listWithHandle, {
handle: '.icon-move',
animation: 150,
onUpdate: function (e) {
var box = [];
$('.wrap').each(function(index, element){
box.push($(element).data('name'));
});
$.cookie('page.page01', box);
},
});
});
</script>

上から解説します。

var page;
var table = ["01", "02", "03", "04", "05", "06", "07", "08", "09"];

Cookie値を収納する為の変数pageと、デフォルトである要素名を収録した変数tableを宣言しています。

function init() {
sort_table = getTable();

for (i = 0; i < sort_table.length; i++) {
$('#listWithHandle').append(createHtml(sort_table[i]));
}
}

init()メソッドは初期化用メソッドです。描画対象の要素を取得し、ドラッグ領域に挿入しています。

function getTable() {
$.cookie.json = true;
page = $.cookie('page.page01');

var sort_table;
if(page !== undefined) {
sort_table = page;
} else {
sort_table = ;
}

return sort_table;
}

getTable()メソッドでは、Cookieに値がある場合はその要素を、ない場合にはデフォルト要素を返します。

function createHtml(name) {
return '<div class="wrap list-group-item" data-name="' + name + '">' +
'<span class="icon-move" aria-hidden="true">#</span>' + name +
'</div>';
}

createHtml()メソッドでは、挿入するHTMLを生成します。

$(function(){
init();

Sortable.create(listWithHandle, {
handle: '.icon-move',
animation: 150,
onUpdate: function (e) {
var box = [];
$('.wrap').each(function(index, element){
box.push($(element).data('name'));
});
$.cookie('page.page01', box);
},
});
});

ページ読み込みが完了したら初期化メソッドを実行し、その後でSortable.jsを動作させます。その際にオプションでonUpdateプロパティをクロージャで設定します。ここでは変更されたDOMのdata属性を収集し、Cookieに保存しています。

これで要素の変更を行った後でページをリロードしても、変更された並び順で表示されます。

別の領域へ要素を移動する

これまでは、1つのリスト(グループ)内での並び替えを行いましたが、次は異なるリストへも要素を移動できるようにします。(Cookieでの記憶は使いません)

今回は3つのリスト間を移動できるようにしていきます。HTMLソースは以下のようになります。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>drag and drop | jQuery JavaScript</title>
</head>
<body>
<style>
body {
margin: 10px;
}
.list {
float: left;
}
.list li {
padding: 5px 10px;
border: 1px solid #0c5460;
list-style: none;
}
.list li:hover {
cursor: move;
}
.a { background-color: #a2c2e6; }
.b { background-color: #f3981d; }
.c { background-color: #a4d5bd; }
</style>

<div class="container">
<div class="lists_wrap">
<ul class="list" id="list1">
<li class="a">AAA 1</li>
<li class="a">AAA 2</li>
<li class="a">AAA 3</li>
<li class="a">AAA 4</li>
<li class="a">AAA 5</li>
</ul>
<ul class="list" id="list2">
<li class="b">BBB 1</li>
<li class="b">BBB 2</li>
<li class="b">BBB 3</li>
<li class="b">BBB 4</li>
<li class="b">BBB 5</li>
</ul>
<ul class="list" id="list3">
<li class="c">CCC 1</li>
<li class="c">CCC 2</li>
<li class="c">CCC 3</li>
<li class="c">CCC 4</li>
<li class="c">CCC 5</li>
</ul>
</div>
</div>

<script src="../js/jquery-3.3.1.min.js"></script>
<script src="../js/Sortable/Sortable.min.js"></script>
<script>
$(function(){
Sortable.create(list1, {
sort: 1,
group: {
name: 'common_lists',
pull: 'clone',
put: true
},
animation: 150
});
Sortable.create(list2, {
sort: 1,
group: {
name: 'common_lists',
pull: true,
put: true
},
animation: 150
});
Sortable.create(list3, {
sort: 1,
group: {
name: 'common_lists',
pull: true,
put: true
},
animation: 150
});
});
</script>
</body>
</html>

3つのリスト(と言っていますがuiの意味ではありません)に対してそれぞれcreate()メソッドを呼んでいますが、ポイントは以下の部分です。

group: {
name: 'common_lists',
pull: true,
put: true
}

groupプロパティを設定し、それぞれnameプロパティには共通の名前を付けます。こうする事で、異なる領域間での移動が可能になります。

また、pullプロパティを以下のように変更する事で、異なる領域にドラッグアンドドロップさせた後でその要素を「コピー」するのか「移動」させるのかを切り替える事ができます。「コピー」とはその名の通り、要素をコピーするので移動元からは無くなりません。対して「移動」は、移動元からその要素は無くなります。

pull: 'clone', // コピー
pull: true, // 移動

ブラウザからアクセスして動作確認を行います。

別領域ドラッグ&ドロップ前

別の領域へドラッグアンドドロップを行います。

別領域ドラッグ&ドロップ後

別領域へも移動が出来ている事を確認できました。ちなみによく見るとわかりますが、「AAA」のリストはコピーの設定にしているので、「AAA1」が複数できています。

もちろん、リスト内での並び替えもいつも通り行えます。

  • AAA 1
  • AAA 2
  • AAA 3
  • AAA 4
  • AAA 5
  • BBB 1
  • BBB 2
  • BBB 3
  • BBB 4
  • BBB 5
  • CCC 1
  • CCC 2
  • CCC 3
  • CCC 4
  • CCC 5

まとめ

以上で作業は終了です。jQueryでドラッグアンドドロップを実現する手法は他にもありますが、導入しやすいものの1つだと思うので、是非試してみてください。

尚、今回のサンプルソースはGithubに上げてあります。
rito-nishino/jquery-sample-drag-and-drop