jQuery（jQuery UI）selectableとsortableを使って複数の要素をまとめて並び替えできる機能を作る

# 背景
これまでsortableは使って単一項目の並び替え機能は実装していたが、複数選択できて尚且つそれらをまとめて並び替えできる機能を実装。
sortableを使っていたので同じjQuery UIのselectableを組み合わせて実現させたが、見た目の調整とか含めた参考記事が全然見つけられなかったので自分用にメモ。

### 変更前のコード（単一項目のみの並び替え）
div.targetsの子要素div.itemを並び替える機能。
ほんと単純に、並び替えたいエレメントに対してsortableを使っているだけ。

```
$("div.targets").sortable({items: "div.item")};
```

オプションとか色々あるけど、これだけで対象項目をドラック＆ドロップして並び替えができるようになる。
すごい便利。。。

# とりあえず機能だけを実装する場合

### seletableで複数項目を選択できるようにする
selectableを使うと、選択した項目のクラスに"ui-selected"が付与される。
選択した項目をsortableの並び替え対象にするために、cancelオプションで".ui-selected"を指定すること。

```
$("div.targets").selectable({
	cancel: ".ui-selected"
});
```

### sortableを使ってまとめて並び替え
selectableで複数項目を選択できるようになったので、それらをまとめて並び替えるためにsortableにhelperオプションとstopイベントを追加で設定。

```
.sortable({
	items: "div.item",
	helper: function(e, item) {
				let selected_all = item.parent().children('.ui-selected').clone();
				item.data('multidrag', selected_all).siblings('.ui-selected').remove();
				return $('<div>').append(selected_all);
			},
	stop: function(e, ui) {
				let selected = ui.item.data('multidrag');
				ui.item.after(selected);
				ui.item.remove();
			},
});
```

### 機能だけであればこれで終わり
上の2つのコードにメソッドチェーンを使ってあげれば、div.itemの複数並び替えはできるようになっているはず。

```
$("div.targets").selectable({
	cancel: ".ui-selected"
})
.sortable({
	items: "div.item",
	helper: function(e, item) {
				let selected_all = item.parent().children('.ui-selected').clone();
				item.data('multidrag', selected_all).siblings('.ui-selected').remove();
				return $('<div>').append(selected_all);
			},
	stop: function(e, ui) {
				let selected = ui.item.data('multidrag');
				ui.item.after(selected);
				ui.item.remove();
			},
});
```

# 見た目も調整する場合

今回の並び替えの機能面には影響ないけど、その他の機能向けや見た目の調整用に設定を追加したのでついでに記載。

### selectableの追加設定
■追加内容
・selectedイベントの追加
・unselectedイベントの追加
・cancelオプションの設定追加

並び替え以外で、選択された項目をまとめて操作するために、選択されたらチェックボックスにチェックor選択が外れたらチェックボックスのチェックも外すような処理も入れています。
selectedイベントで選択時の処理、unselectedイベントで選択が外れた時の処理を設定。（[ここの記事を参考にしました](https://js.studio-kingdom.com/jqueryui/interactions/selectable)）
操作トリガー用のdiv.buttonをcancelオプションに追加しています。

```
$("div.targets").selectable({
		cancel: "div.button, .ui-selected",
		selected: function(e, ui) {
					$(ui.selected).find('input[name="checkbox[]"]').prop("checked", true);
				},
		unselected: function(e, ui) {
					$(ui.unselected).find('input[name="checkbox[]"]').prop("checked", false);
				},
});
```

### sortableの追加設定
■追加内容
・itemsやtoleranceなどのオプションを追加
・helperオプションの返り値を変更

オプションの一番上のitemsからtoleranceは書いてある通りだったので割愛。（[参考記事](https://js.studio-kingdom.com/jqueryui/interactions/sortable)）

ドラック中に選択した項目が全てヘルパーに表示されちゃってダサかったので、代表の1項目だけを表示されるように変更。
helperオプションで代表の1項目を定義 `let selected_one = item.clone();` して、
定義した`selected_one`をhelperオプションの返り値に設定することで、ドラック中はこれだけが表示されるようになった。


```
.sortable({
		items: "div.item",
		cursor: "pointer",
		cursorAt: {top: 80, left: 90},
		containment: "div.imgs",
		tolerance: "pointer",
		helper: function(e, item) {
					let selected_all = item.parent().children('.ui-selected').clone();
					let selected_one = item.clone();
					item.data('multidrag', selected_all).siblings('.ui-selected').remove();
					return $('<div>').append(selected_one);
				},
		stop: function(e, ui) {
					let selected = ui.item.data('multidrag');
					ui.item.after(selected);
					ui.item.remove();
				},
});
```

# 最終的なコード

```
$("div.targets").selectable({
		cancel: "div.button, .ui-selected",
		selected: function(e, ui) {
					$(ui.selected).find('input[name="checkbox[]"]').prop("checked", true);
				},
		unselected: function(e, ui) {
					$(ui.unselected).find('input[name="checkbox[]"]').prop("checked", false);
				},
})
.sortable({
		items: "div.item",
		cursor: "pointer",
		cursorAt: {top: 80, left: 90},
		containment: "div.imgs",
		tolerance: "pointer",
		helper: function(e, item) {
					let selected_all = item.parent().children('.ui-selected').clone();
					let selected_one = item.clone();
					item.data('multidrag', selected_all).siblings('.ui-selected').remove();
					return $('<div>').append(selected_one);
				},
		stop: function(e, ui) {
					let selected = ui.item.data('multidrag');
					ui.item.after(selected);
					ui.item.remove();
				},
});
```

#