Contents
はじめに
今回業務でWebページでドラッグ&ドロップで追加、移動ができるような表を作成する案件がありました。ドラッグ&ドロップの実装をしたことがなかったのでその際にいろいろ調べて完成まで至った流れをまとめていこうと思います。
実際に作ったもの
このようなかんじのものを作成しました。
ポイントとしては、
- 表の外にあるリストから表のマスにドラッグ&ドロップして追加することが出来る
- 表の中からドラッグで移動することが出来る
- 同じマスに複数要素が入ることも可能
- ホバーした時に背景色が変わる(おまけ)
というようにしています。
仕様したライブラリ
ドラッグ&ドロップの実装をしたと書きましたが、正確にはドラッグ&ドロップが簡単に使えるようにしてくれるライブラリを使用して実装しました。
ということで使用したのが、jQueryUI
です。
jQueryUI
はjQuery
の公式のUI用のライブラリになっていて、今回はドラッグ&ドロップを実装するために使いましたが他にもUI周りで便利な機能が多数あります。
↓こちらが公式ページになります
https://jqueryui.com/
jQueryUIを使って出来るドラッグ&ドロップの機能
先程の公式のリファレンスに使用例がたくさん乗っていたので、いくつかピックアップしていこうと思います。
Draggable
ドラッグ出来る機能です。
こんなかんじでドラッグできます。
右の「Examples」に応用例が色々乗っています。いくつか見ていきましょう。
移動可能な方向を縦方向横方向だけに制限したり、指定の範囲の中だけしか移動できない設定もできます。
スクロールのイベントも取ることが出来ます。
ドラッグし始め、最中、話した瞬間にそれぞれイベントを取ることが出来ます。
Droppable
ドロップできる領域を定義することが出来ます。
Sortable
順番を入れ替えるようにすることが出来ます。
リストではなくグリッド形式も使うことが出来ます。
いま紹介したのが基本的な機能で、これを応用したものが他にも例として上がっているので興味のある人は見てみてください。
どの使用例もサンプルコードが乗っているので参考にできると思います。
jQueryUIの導入方法
jQueryUI
の導入の方法はいたって簡単で、
<script type="text/javascript" src="https://code.jquery.com/ui/1.11.4/jquery-ui.min.js">
この1行を追加するだけです。
とりあえずまず簡単に作ってみました。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ページタイトル</title>
<meta name="keywords" content="キーワード1,キーワード2">
<meta name="description" content="ページの説明">
</head>
<body>
<div class="box" style="background-color:red;width:100px;height:100px"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script type="text/javascript">
$(function() {
$('.box').draggable();
});
</script>
</body>
</html>
htmlファイルを作成してこのコードを貼り付けてブラウザで開けば実行できるのでよかったら試してみてください。
これを実行してみるとこのようになります。
赤いマスがドラッグして移動できるようになったと思います。
ドラッグ可能にするようにするためのコードが以下になります。
<script type="text/javascript">
$(function() {
$('.box').draggable();
});
</script>
というようにすると、box
というクラスの要素がドラッグ可能になります。
ここで、どのようにしてドラッグ可能にしているかを探っていきます。
先程のGif画像の右側にChromeの「要素の検証」を載せておいたのですがそこに答えが隠されています。
ドラッグするごとに以下のようにcssが自動で変更されていることがわかると思います。
position: relative; left: xpx; right: ypx;
jQueryUI
を使えばdraggable()
を1行書くだけでドラッグ可能になりますが、内部的にはcssを先程のように書き換えることで実装されていることが分かるのではないかなと思います。
表を実装していく
前置きが長くなってしまいましたがjQueryUI
を使用してここから表を実装していきます。
実際に作成したものは、冒頭でも載せましたがこのようなものです。
満たしたい要件としては、
- 表の外にあるリストから表のマスにドラッグ&ドロップして追加することが出来る
- 表の中からドラッグで移動することが出来る
- 同じマスに複数要素が入ることも可能
ということでした。
実際のコードを以下に載せます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>ページタイトル</title>
<meta name="keywords" content="キーワード1,キーワード2">
<meta name="description" content="ページの説明">
</head>
<body>
<div class="table"></div>
<div class="addable-contents"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<script type="text/javascript">
$(function() {
const add_list = ["あ", "い", "う"];
const column = 4;
const row = 4;
var now_drag_object = null;
var elements = [];
function create_keywords_table() {
var table_html = '</p><tr class="head"><th></th><p>';
for (var j = 1; j <= column; j++) {
table_html += '</p><th>' + j + '</th><p>';
};
for (var i = 1; i <= row; i++) {
table_html += '</p><tr class="content"><th>' + i + '</th><p>';
for (var j = 1; j <= column; j++) {
table_html += '</p><td class="droppable" data-row="'+ i +'" data-column="' + j + '"><p>';
$.each(elements, function(index, element) {
if (element["row"] != i || element["column"] != j) return true;
table_html += '</p><div class="table-content" data-id="' + element["id"] + '">' + element["name"] + '</div>';
});
table_html += '</td><p>';
};
table_html += '</tr><p>';
};
$('#table').html(table_html);
}
function create_keyword_list() {
const keyword_list_div = add_list.map(function(element, index, array) { return '</p><div class="addable-content">' + element + '</div><p>'}).join('');
$('.addable-contents').html(keyword_list_div);
}
function bind_droppable() {
$('.table-content').draggable({
start:function() {
now_drag_object = $(this);
},
stop: function(event, ui) {
create_contents();
now_drag_object = null;
}
});
$('.addable-content').draggable({
start:function() {
now_drag_object = $(this);
},
stop: function(event, ui) {
create_contents();
now_drag_object = null;
}
});
$('.droppable').droppable({
classes: {
"ui-droppable-hover": "ui-state-hover"
},
drop: function(event, ui) {
console.log("drop");
const row = $(this).data("row");
const column = $(this).data("column");
const id = now_drag_object.data("id");
const index = elements.map(function(element, index, array) { return element.id }).indexOf(id);
if (now_drag_object.hasClass("addable-content")) {
elements.push({"id": elements.length + 1, "name": now_drag_object.text(), "row": row, "column": column});
} else {
elements[index]["row"] = row;
elements[index]["column"] = column;
}
}
});
};
function create_contents() {
create_keywords_table();
create_keyword_list();
bind_droppable();
}
create_contents();
});
</script>
</body>
</html>
コードをいきなり見てもわかりにくいと思うので1つずつ説明していこうと思います。
まず最初にドラッグ可能にするために、先程も簡単に説明しましたが、draggable
メソッドを用います。
$('.table-content').draggable({
start: function() {
now_drag_object = $(this);
},
stop: function(event, ui) {
create_contents();
now_drag_object = null;
}
});
$('.addable-content').draggable({
// ドラッグ開始時の処理
start: function() {
now_drag_object = $(this);
},
// ドラッグ終了時の処理
stop: function(event, ui) {
create_contents();
now_drag_object = null;
}
});
このようにしました。
表の中の要素と表の下にある要素にそれぞれdraggable
を適用しました。
先程の例だと$('.box').draggable();
というように何も引数に渡していませんでしたが、このように関数を引数に渡すこともできます。
start
の部分でドラッグし始めのタイミングで行う処理、stop
で離したタイミングで行う処理を渡すことが出来ます。
今回の場合now_drag_object
を定義して、ドラッグし始めにドラッグしてるDOM
のオブジェクトを格納し、離したときにnull
にすることで、ドラッグ中のオブジェクトを一時的に格納するようにしています。
次にdroppable
を定義していきます。
droppable
でドロップできる場所を定義することが出来ます。
$('.droppable').droppable({
classes: {
"ui-droppable-hover": "ui-state-hover"
},
drop: function(event, ui) {
const row = $(this).data("row");
const column = $(this).data("column");
if (now_drag_object.hasClass("addable-content")) {
elements.push({"id": elements.length + 1, "name": now_drag_object.text(), "row": row, "column": column});
} else {
const id = now_drag_object.data("id");
const index = elements.map(function(element, index, array) { return element.id }).indexOf(id);
elements[index]["row"] = row;
elements[index]["column"] = column;
}
}
});
このようにしました。
drop
にdroppable
上でdraggable
がドロップされた場合に実行される処理を渡すことが出来ます。
classes
の部分ではこのように定義することで、hover
されたときにui-state-hover
というクラスがつくように出来て、今回の場合は背景色を変えるために使用しています。
今回の実装で使用したjQueryUI
の機能は以上です。
これら以外は普通のjQuery
で実装していきました。
どのように実装したかというと、elements
という配列を定義して、表の中に入った要素を管理するようにしています。
どのように実装したかというと、まずdraggable
とdroppable
のイベントが発行されるたびにelements
,add_list
を参照してtable
とaddable-contents
を1から全て作り直しています。
このようにしたのは、ドラッグして離したときに、マスの中に入っていればマスの中にいい感じにフィットさせ、マスの中に入っていなかったらもとの位置に戻すといった挙動を実現しようと思うと、draggable
とdroppable
の機能を組み合わせただけでは実現できなかった(もしかしたら用意されているかもしれませんが)ので無理やり実装しました。
table-content
とaddable-content
にそれぞれdraggable
を適用させていて、stop
でcreate_contents
メソッドを呼ぶようにしています。
create_contents
メソッドでは、
function create_contents() {
create_keywords_table(); //tableを再生成する
create_keyword_list(); //add_listを再生成する
bind_droppable(); //draggableとdroppableを貼り直す
}
このように3つのメソッドを呼んでいます。table
とadd_list
を再生成するようにしたのは先程説明したとおりです。
bind_droppable()
を呼んで、draggable
とdroppable
を貼り直しているのは何故かと言うと、毎回table
とadd_list
を作り直すので作り直したDOMにはdraggable
とdroppable
が適用されていないからです。
onメソッド
でやれるようにしたかったのですがうまくいく方法が見つからなかったのでこのように強引にやってしまいました。(いいやり方があれば教えてほしいです)
ここまでで、要素をドラッグできドロップした瞬間に最初の状態に戻るというところまで実装できます。次はマスの中でドロップした際に表のマスにいい感じにすっぽりはまるようにする部分を説明していきます。
表のマスそれぞれにdroppable
を適用していて、draggable
の要素をドラッグ&ドロップするとdrop
に渡した関数を実行します。
表のマスにdata-row
とdata-column
属性を持っていてどのマスかが分かるようになっていて、droppable
でdrop
に渡した関数内で$(this)
でドロップされたマスのDOMが取得できます。表内にある要素を{"id","name","row","column"}
を要素に持つelements
という配列で管理するようにしています。
表の下にある要素を表内にドロップした場合はelements
に追加、表のマスの中の要素からマスの中にドロップした場合はelements
内のその要素のrow
とcolumn
を書き換えるようにしています。
それを分岐させるために、先程も説明したようにドラッグしている要素はnow_drag_object
に格納されているのでそこからクラスが何かによって分岐するように実装しています。
その後、先ほど説明した、draggable
のstop
に渡した関数が呼ばれ、表を1から作成し直すcreate_contents
メソッドを呼び出し、その後にnow_drag_object
をnull
にします。
以上が大体の流れになります。
draggable
とdroppable
の基本的な機能だけ利用して、残りはjQuery
の基本的な操作を組み合わせて実装しています。
1つ実装する際に悩んだ点があるので書いておくと、
先程説明した流れは、ドロップした際に
droppable
のdrop
に渡した関数でelements
を書き換えるdraggable
のstop
に渡した関数で表を再生成して、now_drag_object
をnull
にする
という流れで実装しています。この順番が逆になってしまうとうまくいかない実装方法になっています。表のマスにドロップした際に、droppable
のdrop
とdraggable
のstop
は発火条件は同じなのでこの2つが呼ばれる順番が逆だったり、同時にスタートする可能性もあるかなって最初思いました。その場合の実装がちょっとめんどくさくなるなーって思ってました。
しかし実験したりして探ってみると、そこはうまく作られていて毎回、droppable
のdrop
イベントが先に呼ばれるようになっていて、それが終わり次第draggable
のstop
イベントが呼ばれるという流れになっていました。なのでこの実装方法で大丈夫となりました。
まとめ
今回jQueryUI
を使って簡単なサンプルを作成してみましたが、拡張性も高いので幅広く対応できるのではないかなと思います。この記事が参考になれば嬉しいです。