などの疑問や悩みを解決してまいります。
Vue.jsのプラグインのVue.draggable.nextを使用してドラッグ&ドロップを実装することができます。
実装方法については以下の記事で解説していますのでぜひ参考にしてみてください。
-
【Vue.js】要素をドラッグ&ドロップできるようにする方法について解説します。【Vue.draggable.next】
続きを見る
ドラッグ&ドロップは実装できたが特定の条件でドラッグ&ドロップができないようにしたいと思われている方もいるのではないでしょうか。
実装例
まず先に実装例をご紹介します。
以下はドラッグ&ドロップ先のリスト内に同じ名前が存在する場合は、エラーメッセージを表示してドラッグ&ドロップする前の状態に戻すといった処理を行っています。
実装コード
<script setup lang="ts">
import { ref } from 'vue'
import draggable from 'vuedraggable'
// ドラッグ&ドロップができるリスト①
const peopleData = ref([
{ name: 'tanaka', id: 0 },
{ name: 'suzuki', id: 1 },
{ name: 'sato', id: 2 },
{ name: 'mori', id: 3 },
{ name: 'takago', id: 4 }
])
// ドラッグ&ドロップができるリスト②
const peopleData2 = ref([{ name: 'suzuki', id: 1 }])
// エラーメッセージ用の定数
const validMessage = ref('')
// ドラッグ前の状態を保存する変数
let originalData1 = []
let originalData2 = []
/**
* ドラッグ時にリストの状態を保存
*/
const onDragStart = () => {
validMessage.value = ''
originalData1 = JSON.parse(JSON.stringify(peopleData.value))
originalData2 = JSON.parse(JSON.stringify(peopleData2.value))
}
/**
*
* @param evt ドロップした要素
* ドロップ後にドロップした要素のエラーチェックを行う
*/
const onDragEnd = (evt) => {
const targetList = evt.to.children
const newItem = evt.item.innerText.split('. ')[1]
const isDuplicate =
Array.from(targetList).filter((item) => item.innerText.split('. ')[1] === newItem).length > 1
// エラーがあった場合、元の状態に戻す
if (isDuplicate) {
validMessage.value = '同じ名前のアイテムはドラッグできません。'
peopleData.value = originalData1
peopleData2.value = originalData2
}
}
</script>
<template>
<v-container>
<h1>ドラッグ&ドロップテスト</h1>
<v-row>
<v-col>
<v-list :border="true" class="pa-8">
<draggable
v-model="peopleData"
group="people"
item-key="id"
ghostClass="bg-red"
:animation="300"
@start="onDragStart"
@end="onDragEnd"
>
<template #item="{ element: element2 }">
<v-list-item :border="true" class="pa-8 mt-4">
{{ element2.id }}. {{ element2.name }}
</v-list-item>
</template>
</draggable>
</v-list>
<div>peopleDataデータの中身:{{ peopleData }}</div>
<p class="error">{{ validMessage }}</p>
</v-col>
<v-col>
<v-list :border="true" class="pa-8">
<draggable
v-model="peopleData2"
group="people"
item-key="id"
ghostClass="bg-blue"
:animation="300"
@start="onDragStart"
@end="onDragEnd"
>
<template #item="{ element: element2 }">
<v-list-item :border="true" class="pa-8 mt-4">
{{ element2.id }}. {{ element2.name }}
</v-list-item>
</template>
</draggable>
</v-list>
<div>peopleDataの中身:{{ peopleData2 }}</div>
<p class="error">{{ validMessage }}</p>
</v-col>
</v-row>
</v-container>
</template>
<style scoped>
.error {
color: #e53935;
}
.bg-red {
background-color: #e53935;
}
.bg-blue {
background-color: #35a4e5;
}
</style>
コードの解説
それでは上から順にコードの解説を行っていきます。
script
の解説をしていきます!template
内は特別な書き方をしているわけではないので、ここでの解説は割愛させていただきます。リストの型指定
実装コードではTypeScriptを使用しているため型を指定しています。
まず最初にドラッグ&ドロップをするリストの型をtype
で指定しています。
// リストの型設定
type List = {
name: string
id: number
}
ドラッグ&ドロップができるリストの準備
ドラッグ&ドロップができるリストをpeopleData
とpeopleData2
の2つを用意しています。
今回はname
の重複でエラーが発生するようにしていますので、両方にsuzukiの要素を入れています。
// ドラッグ&ドロップができるリスト①
const peopleData = ref<List[]>([
{ name: 'tanaka', id: 0 },
{ name: 'suzuki', id: 1 },
{ name: 'sato', id: 2 },
{ name: 'mori', id: 3 },
{ name: 'takago', id: 4 }
])
// ドラッグ&ドロップができるリスト②
const peopleData2 = ref<List[]>([{ name: 'suzuki', id: 1 }])
エラーメッセージ用の定数の準備
エラーメッセージ用の定数を用意しています。
ドロップ時にエラーの場合はエラーメッセージを、ドラッグ時に値をリセットする必要があるので、リアクティブな値としています。
// エラーメッセージ用の定数
const validMessage = ref<string>('')
ドラッグ前の状態を保存する変数の準備
要素が持つname
が同じである場合にエラーを起こして、ドラッグ&ドロップ前の状態に戻すという処理は、ドラッグ&ドロップ前の状態のリストの状態を別に用意した変数に一度保存する形を取っています。
そのため以下のoriginalData1
とoriginalData2
の変数を保存用に用意しています。
// ドラッグ前の状態を保存する変数
let originalData1: List[] = []
let originalData2: List[] = []
ドラッグ時にドラッグ前のリストの状態を保存する関数
ドラッグ時に先程用意した保存用の変数にドラッグ&ドロップ前のリストの状態を保存する関数を用意します。
また、エラーメッセージが表示された状態で再度要素をドラッグ&ドロップするときには表示しないようにしたいので、validMessage.value = ''
も追記しています。
/**
* ドラッグ時にリストの状態を保存
*/
const onDragStart = () => {
validMessage.value = ''
originalData1 = JSON.parse(JSON.stringify(peopleData.value))
originalData2 = JSON.parse(JSON.stringify(peopleData2.value))
}
ドロップしたときにリスト内に重複した要素がないか確認する関数
ここが本処理の肝となる部分です。
ドロップしたら、ドロップしたリスト内に重複したname要素がないかチェックする関数を実行します。
※evt.toとevt.itemの適切な型が不明なためエラー回避のために一旦anyとしていますが、他に適切な型指定ができればそちらの記述でいいと思います。
/**
*
* @param evt ドロップした要素
* ドロップ後にドロップした要素のエラーチェックを行う
*/
const onDragEnd = (evt: DragEvent) => {
const targetList = (evt as any).to.children as HTMLCollection
const newItem = (evt as any).item.innerText.split('. ')[1] as string
const isDuplicate =
Array.from(targetList).filter(
(item: Element) => (item as HTMLElement).innerText.split('. ')[1] === newItem
).length > 1
// エラーがあった場合、元の状態に戻す
if (isDuplicate) {
validMessage.value = '同じ名前のアイテムはドラッグできません。'
peopleData.value = originalData1
peopleData2.value = originalData2
}
}
関数の引数であるevt
はドラッグした要素そのものになります。
定数targetList
には、ドロップ先のリスト内の子要素を全て取得して代入されています。
定数newItem
には、ドラッグされたアイテムそのものを示しています。
そのinnerText
で要素のテキスト情報を取得し、split
メソッドで.
で区切り配列に置き換えた後、インデックス番号1番目を代入しています。
つまり、innerText
でテキスト情報を取得した内容は「1. suzuki」で、split
メソッドで.
で区切り配列に置き換えています。この時点で[1,"suzuki"]という配列になるのでインデックス番号の1番目、つまりsuzukiを取得し、代入しているという流れになります。
ここまできたら確認材料がそろっているので、filter
メソッドを使用して同じname
を持つ要素がリスト内に存在しないかを確認します。
もし、存在する場合はtrue
を返し、存在しない場合はfalse
を返します。
この結果に応じてエラーメッセージを出すという処理を最後に行っています。