👌
Vue3.5で Vue.Draggable を使用してD&Dを簡単に実装する
はじめに
業務でプラグイン「Vue.Draggable」を使用してドラッグアンドドロップ機能を実装したのと、
Vueのv3.5で開発してみたかったので、備忘録として残します。
動作環境・使用するツールや言語
- Vue: v3.5.13
- Vuetify: v3.7.16
- vuedraggable: v4.1.0
作成したリポジトリ
導入
公式ドキュメントに沿って進めます。
npm i -S vuedraggable@next
使用例
sample.vue
<script setup lang="ts">
import { ref } from 'vue'
import draggableComponent from 'vuedraggable'
const samples = ref([])
</script>
<template>
<draggableComponent
v-model="samples"
>
<template #item="{ element }">
<div>{{ element.name }}</div>
</template>
</draggableComponent>
</template>
オプション設定
今回使用したオプション一覧です。
オプションはVue.Draggableが用意しているものと、
Vue.DraggableがサポートしているSortableのものがあります。
- Vue.Draggable
- item-key
- 一意にするために設定する内部キー。
- tag
- ドラッグ可能なコンポーネントが含まれたスロットの外部要素として作成するHTMLノードタイプ。
- item-key
- Sortable
- group
- ドラッグ可能な対象をグループ化。
- ghost-class
- ドラッグ時の背景要素のクラス。
- handle
- ドラッグ要素をクラスで指定。(通常はスロットに含まれるすべての要素がドラッグ対象になる)
- animation
- ドラッグ時のアニメーション設定。
- group
コンポーネント作成
以下のコンポーネントを作成しました。
ghost-classに指定しているクラスは、main.cssに定義しております。
FakeUserに関しては、余談として後ほど紹介します。
単一リスト
DraggableSinglePanel.vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import draggableComponent from 'vuedraggable'
import { makeFakeUsers } from '@/helpers/fakeHelpers'
import type { FakeUser } from '@/types/fakes'
const users = ref<FakeUser[]>([])
onMounted(() => {
users.value = makeFakeUsers(5)
})
</script>
<template>
<v-container fluid>
<v-row>
<v-col cols="4">
<div class="h-4">Group1</div>
</v-col>
</v-row>
<v-row>
<v-col cols="4">
<draggableComponent
v-model="users"
item-key="id"
class="draggable-handle"
ghost-class="draggable-ghost"
animation="200"
>
<template #item="{ element }">
<v-card :title="element.name" prepend-icon="mdi-account" class="mt-4 mb-4">
<v-card-item>
<p>都道府県: {{ element.state }}</p>
<p>郵便番号: {{ element.zipCode ?? '' }}</p>
</v-card-item>
</v-card>
</template>
</draggableComponent>
</v-col>
</v-row>
</v-container>
</template>
複数リスト
グループを同一にすることで、列の横断が可能です。
DraggableMultiplePanel.vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import draggableComponent from 'vuedraggable'
import { makeFakeUsers } from '@/helpers/fakeHelpers'
import type { FakeUser } from '@/types/fakes'
const users1 = ref<FakeUser[]>([])
const users2 = ref<FakeUser[]>([])
const users3 = ref<FakeUser[]>([])
onMounted(() => {
users1.value = makeFakeUsers(5)
users2.value = makeFakeUsers(5)
users3.value = makeFakeUsers(5)
})
</script>
<template>
<v-container fluid>
<v-row>
<v-col v-for="i in 3" :key="i" cols="3">
<div class="h-4">Group{{ i }}</div>
</v-col>
</v-row>
<v-row>
<v-col cols="3">
<draggableComponent
v-model="users1"
item-key="id"
class="draggable-handle"
ghost-class="draggable-ghost"
animation="200"
group="users"
>
<template #item="{ element }">
<v-card :title="element.name" prepend-icon="mdi-account" class="mt-4 mb-4">
<v-card-item>
<p>都道府県: {{ element.state }}</p>
<p>郵便番号: {{ element.zipCode ?? '' }}</p>
</v-card-item>
</v-card>
</template>
</draggableComponent>
</v-col>
<v-col cols="3">
<draggableComponent
v-model="users2"
item-key="id"
ghost-class="draggable-ghost"
animation="200"
group="users"
>
<template #item="{ element }">
<v-card :title="element.name" prepend-icon="mdi-account" class="mt-4 mb-4">
<v-card-item>
<p>都道府県: {{ element.state }}</p>
<p>郵便番号: {{ element.zipCode ?? '' }}</p>
</v-card-item>
</v-card>
</template>
</draggableComponent>
</v-col>
<v-col cols="3">
<draggableComponent
v-model="users3"
item-key="id"
ghost-class="draggable-ghost"
animation="200"
group="users"
>
<template #item="{ element }">
<v-card :title="element.name" prepend-icon="mdi-account" class="mt-4 mb-4">
<v-card-item>
<p>都道府県: {{ element.state }}</p>
<p>郵便番号: {{ element.zipCode ?? '' }}</p>
</v-card-item>
</v-card>
</template>
</draggableComponent>
</v-col>
</v-row>
</v-container>
</template>
ドラッグ箇所のカスタム
handleプロパティにクラスを指定することで、ドラッグ箇所のカスタムが可能です。
DraggableCustomDragPanel.vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import draggableComponent from 'vuedraggable'
import { makeFakeUsers } from '@/helpers/fakeHelpers'
import type { FakeUser } from '@/types/fakes'
const users = ref<FakeUser[]>([])
onMounted(() => {
users.value = makeFakeUsers(5)
})
</script>
<template>
<v-container fluid>
<v-row>
<v-col cols="4">
<div class="h-4">Group1</div>
</v-col>
</v-row>
<v-row>
<v-col cols="4">
<draggableComponent
v-model="users"
item-key="id"
ghost-class="draggable-ghost"
animation="200"
handle=".draggable-handle"
>
<template #item="{ element }">
<v-card class="mt-4 mb-4">
<v-card-item :title="element.name">
<template v-slot:prepend>
<v-icon icon="mdi-drag" class="draggable-handle" />
</template>
<p>都道府県: {{ element.state }}</p>
<p>郵便番号: {{ element.zipCode ?? '' }}</p>
</v-card-item>
</v-card>
</template>
</draggableComponent>
</v-col>
</v-row>
</v-container>
</template>
テーブル
tagにtbodyを指定することで、スロットの外部要素がtbodyとして出力されます。
DraggableTablePanel.vue
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import draggableComponent from 'vuedraggable'
import { makeFakeUsers } from '@/helpers/fakeHelpers'
import type { FakeUser } from '@/types/fakes'
const users = ref<FakeUser[]>([])
onMounted(() => {
users.value = makeFakeUsers(5)
})
</script>
<template>
<v-container fluid>
<v-table class="bg-grey-darken-3">
<thead class="bg-grey-darken-4">
<tr>
<th>氏名</th>
<th>都道府県</th>
<th>郵便番号</th>
</tr>
</thead>
<draggableComponent
v-model="users"
tag="tbody"
item-key="id"
class="draggable-handle"
ghost-class="draggable-ghost"
animation="200"
>
<template #item="{ element }">
<tr>
<td>{{ element.name }}</td>
<td>{{ element.state }}</td>
<td>{{ element.zipCode }}</td>
</tr>
</template>
</draggableComponent>
</v-table>
</v-container>
</template>
余談
Vue.Draggableのバージョンについて
npmで「vuedraggable」と検索しても、バージョンが2.24.3と古いままで
どこに最新のバージョンがあるのかと疑問に思いました。
タグで管理されているようです。
FakeUserについて
Fakerというプラグインを使用してヘルパーを作成しました。
uuidというプラグインも使用して一意のidを生成しましたが、
Fakerにもuuidを生成する機能があったので、不要な導入でした💦
fakeHelpers.ts
import { fakerJA as faker } from '@faker-js/faker'
import { v4 as uuid } from 'uuid'
import type { FakeUser } from '@/types/fakes'
export const makeFakeUser = (): FakeUser => {
return {
id: uuid(),
name: faker.person.fullName(),
state: faker.location.state(),
zipCode: faker.location.zipCode(),
}
}
export const makeFakeUsers = (length: number): FakeUser[] => {
return Array.from({ length }, makeFakeUser)
}
おわりに・まとめ
Vue.Draggableを使用することでD&D機能を簡単に実装できます。
もちろん自前で作成することも可能ではあります。
ただ、ドラッグ要素に複雑な機能の追加が発生した際に、自前で作成したD&Dが悪さをすることもあります。(ありました。)
便利なものはありがたく使用させていただきましょう。
ここまで読んでいただき、ありがとうございました。
Discussion