JavaScript
jQuery

jQuery ドラッグアンドドロップで「並び替え」と「入れ子関係変更」の両方ができるメニュー画面を作りたい

More than 3 years have passed since last update.


概要

ドラッグアンドドロップで「並び替え」と「入れ子関係変更」の両方ができるメニュー画面を作りたいと思い、jQuery UIのsortableとdroppableを組み合わせてみました。ところが、この2つの組み合わせではうまくいかなかったのでdroppableの部分だけ自前で作りこみました。完成イメージは以下のように動作するUIです。

demo.gif


失敗したアプローチ

単純にjQuery UIのsortableとdroppableを組み合わせてうまくいくと思いましたが、sortableで要素が移動した後の位置にdropすると「移動前にその位置にあった要素にdropされた」と認識されてしまい、「droppableが動作してしまう」といった問題が発生しました。droppableの仕様をきちんと調べていませんが、drag開始前にdrop対象の座標を保持するのかもしれません。


成功したアプローチ

droppableの使用を止めて、(1)drag対象、(2)drop対象、(3)マウスポインタの3つが重なっている状態でsortableの動作が終了(stop)した際に、dropされたと認識するようなロジックを作成しました。


成功コード例


example.html

<nav>

<ul class="nav-sites sortable">
<li class="nav-site" data-id="35" data-type="Sites">
<a href="/items/35/index">folder 1</a>
</li>
<li class="nav-site" data-id="37" data-type="Sites">
<a href="/items/37/index">folder 1-1</a>
</li>
<li class="nav-site" data-id="36" data-type="Sites">
<a href="/items/36/index">folder 2</a>
</li>
</ul>
</nav>


SiteMenu.js

$(function () {

$('.nav-sites.sortable').sortable({

// sortableの動作終了
stop: function (event, ui) {

// ドラッグ中の要素のデータを取得
var siteId = ui.item.attr('data-id');

// ドラッグ中の要素以外でマウスポインタと重なっている要素を取得
var $element = getHoveredItem($('.nav-site:not([data-id="' + siteId + '"])'));

// マウスポインタと重なっている要素があるか?
if ($element) {

// ドラッグ中の要素を非表示にする
ui.item.hide();

// 「入れ子関係変更」の処理を記述(サーバサイドにデータ送信)
//
//
}
},

// sortableにより順番が入れ替わった
update: function () {

// 「並び替え」の処理を記述(サーバサイドにデータ送信)
//
//
}
});
});



Position.js

var mouseX;

var mouseY;

// マウス移動時にマウスポインタのXY座標をメモリに保持
$(window).mousemove(function (e) {
mouseX = e.pageX;
mouseY = e.pageY;
});

// マウスポインタが重なっている要素を返却
function getHoveredItem($elements) {
var $element;
$elements.each(function () {
if (isHover($(this))) {
$element = $(this);
return false;
}
});
return $element;
}

// マウスポインタが重なっているか判定
function isHover($element) {
var left = $element.offset().left;
var top = $element.offset().top;
var right = left + $element.outerWidth();
var bottom = top + $element.outerHeight();
if (mouseX >= left &&
mouseX <= right &&
mouseY >= top &&
mouseY <= bottom) {
return true;
} else {
return false
}
}



補足

マウスが重なっているdrop対象を :hover で取得しようとしましたが、こちらはdrag中の要素の下に隠れてしまうため :hover が反応しませんでした。そのためマウスポインタの位置で判定するロジックにしております。このあたり、もう一工夫すればシンプルな作りにできそうな気がしています。


ご参考

下記プロジェクトで使用しています。ほかにも実装例などがありましたら情報交換お願いします。

https://github.com/Implem/Implem.CodeDefiner

コード例のJavaScriptファイルはこちら。

https://github.com/Implem/Implem.CodeDefiner/tree/master/Implem.Outputs/Scripts