【Vue.draggable.next】要素をドロップしたタイミングでエラー判定を行い、ドラッグする前の状態に戻す方法を解説します。

error TypeScript Vue.js 便利ツール

【Vue.draggable.next】要素をドロップしたタイミングでエラー判定を行い、ドラッグする前の状態に戻す方法を解説します。

なやむくん
特定の条件でエラーを発生させてドロップできないようにしたい…。
なやむさん
実装方法が分からない…。

などの疑問や悩みを解決してまいります。

Vue.jsのプラグインのVue.draggable.nextを使用してドラッグ&ドロップを実装することができます。

実装方法については以下の記事で解説していますのでぜひ参考にしてみてください。

参考記事
【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>
みつた
要素suzukiが既に存在するリスト内にドラッグ&ドロップするとエラーメッセージが表示されてドラッグ&ドロップする前に戻っているのが分かると思います!

コードの解説

それでは上から順にコードの解説を行っていきます。

みつた
scriptの解説をしていきます!template内は特別な書き方をしているわけではないので、ここでの解説は割愛させていただきます。

リストの型指定

実装コードではTypeScriptを使用しているため型を指定しています。
まず最初にドラッグ&ドロップをするリストの型をtypeで指定しています。

// リストの型設定
type List = {
  name: string
  id: number
}

ドラッグ&ドロップができるリストの準備

ドラッグ&ドロップができるリストをpeopleDatapeopleData2の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が同じである場合にエラーを起こして、ドラッグ&ドロップ前の状態に戻すという処理は、ドラッグ&ドロップ前の状態のリストの状態を別に用意した変数に一度保存する形を取っています。

そのため以下のoriginalData1originalData2の変数を保存用に用意しています。

// ドラッグ前の状態を保存する変数
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を返します。

この結果に応じてエラーメッセージを出すという処理を最後に行っています。

  • この記事を書いた人
  • 最新記事

みつた

完全未経験&異業種から30歳の年でIT企業に転職。

Web系開発言語が好き。
どちらかというとバックエンドよりもフロントエンドが好き(現時点では…)

最近はサウナと観葉植物にハマっている。
野球が好きで一応投手。

-error, TypeScript, Vue.js, 便利ツール