ちょっと話題の記事

とても簡単にドラッグアンドドロップが実現できる Vue-draggable を使ってみた

Vue.js でドラックドロップでリストを入れ替えることができる Vue-draggable を使ってみました
2020.01.27

西田@大阪です

以前 v-kansai Vue.js/Nuxt.js meetup #13 に参加させていただいた時に気になっていた Vue.js でドラックドロップでリストを入れ替えることができる Vue-draggable を使ってみました

SortableJS/Vue.Draggable: Vue drag-and-drop component based on Sortable.js

プロジェクトの作成

Vue.jsのプロジェクトを作成します。今回は今流行りの TypeScript を選びました

$ vue create vue-draggable-sample  

? Please pick a preset: Manually select features
? Check the features needed for your project: TS
? Use class-style component syntax? No                                                                                                                       
? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes                                                   
? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? Yes
? Save preset as: 

Vue-draggableをインストール

$ yarn add vuedraggable 

TypeScript の定義ファイルを追加する

Vue-Draggable は TypeScript 対応されていないので、d.tsファイルを追加します

types/vuedraggable.d.ts

declare module 'vuedraggable'

tsconfig.jsoncompilerOptionspathsに定義を追加します

{
  "compilerOptions": {
    "paths": {
      "@/*": [
        "src/*"
      ],
      "vuedraggable": [
        "src/types/vuedraggable"
      ]
    },

※ 上記例は省略されています

ドラッグアンドドロップで要素を並び替えできるコンポーネントを作成します

ドラッグアンドドロップで要素を入れ替えることができるコンポーネントを Vue SFC(Single File Component) で実装していきます

スクリプト部分です

import Vue from 'vue'
import draggable from 'vuedraggable'

export default Vue.extend({
  name: "Draggable",
  components: {
    draggable
  },
  data() {
    return {
      items: [
        {
          id: 1,
          name: "ITEM - 1"
        },
        {
          id: 2,
          name: "ITEM - 2"
        },
        {
          id: 3,
          name: "ITEM - 3"
        },
        {
          id: 4,
          name: "ITEM - 4"
        },
      ]
    }
  },
  methods: {
    showItems() {
      alert(this.items.map(val => { return val.id }))
    }
  }
})

Vue-Draggable をコンポーネントとして使えるよう登録し、dataitemsという名前でドラッグアンドドロップさせるデータを配列として登録しています。ドラッグアンドドロップした際にこの配列順番が入れ替わります。showItemsという名前で、現在の items の状態をアラート表示するメソッドを定義しています。

テンプレート部分です

<template>
  <div>
    <draggable v-model="items" draggable=".item">
      <div v-for="item in items" :key="item.id" class="item">
        {{item.name}}
      </div>
    </draggable>
    <button @click="showItems">表示</button>
  </div>
</template>

draggableコンポーネントに v-modelitemsを渡しています。また、draggable属性にドラッグアンドドロップする要素を特定するためのセレクタを指定しています。上記例だとitemクラスを指定しています

これだけで、実際にドラッグアンドドロップでき、入れ替わった値がv-modelで反映されていることがわかります

ドラッグアンドドロップで2つのリストの要素を入れかえできるコンポーネントを作成します

ドラッグアンドドロップして2つのリストの要素を並び替え及び入れ替えれるようにします

スクリプト部分です

import Vue from 'vue';
import draggable from 'vuedraggable';

function dumpObj(obj: any): string {
  return JSON.stringify(obj, null, 2)
}

export default Vue.extend({
  name: "Swap",
  components: {
    draggable
  },
  data() {
    return {
      items1: [
        { id: 1, name: "ITEM - 1" },
        { id: 2, name: "ITEM - 2" },
        { id: 3, name: "ITEM - 3" },
        { id: 4, name: "ITEM - 4" },
      ],
      items2: [
        { id: 5, name: "ITEM - 5" },
        { id: 6, name: "ITEM - 6" },
        { id: 7, name: "ITEM - 7" },
        { id: 8, name: "ITEM - 8" },
      ]
    }
  },
  computed: {
    formattedItems1(): string {
      return dumpObj(this.items1);
    },
    formattedItems2(): string {
      return dumpObj(this.items2);
    },
  }
})

items1items2という2つの配列を用意してます。この配列間でドラッグアンドドロップ可能になり、要素が入れ替わります。dumpObjというJSON表示用の関数を追加し、computedのメンバーとしてitemsの中身をフォーマットする関数を追加しています。

テンプレート部分です

<div class="container">
  <draggable v-model="items1" draggable=".item" group="items">
    <div v-for="item in items1" :key="item.id" class="item">{{item.name}}</div>
  </draggable>

  <draggable v-model="items2" draggable=".item" group="items">
    <div v-for="item in items2" :key="item.id" class="item">{{item.name}}</div>
  </draggable>
  <div><pre>{{formattedItems1}}</ pre></div>
  <div><pre>{{formattedItems2}}</ pre></div>
</div>

2つのVue-Draggable要素とそれぞれに設定されたdataの値を表示する要素を並べています。Vue-Draggable要素ののgroup属性にドラッグアンドドロップで入れ替え可能にしたいVue-Draggable要素で同じ値を設定します。上の例だと2つのVue-Draggable要素のgroup属性にitemsと同じ文字列を指定しています

CSS部分です。見た目を少しだけ整えてます

pre {
  text-align: start;
  background: #2c3e50;
  color: white;
  padding: 10px;
  font-weight: bold;
}

.item {
  padding: 5px;
}

.container {
  margin: auto;
  width: 600px;
  display: flex;
  justify-content: space-around;
}

ドラッグアンドドロップしている間をわかるようにする

要素を入れ替えるサンプルのコンポーネントをすこしだけ改修して、ドラッグアンドドロップしている間に色が変わる要素を追加してみたいと思います 

スクリプト部分

// ... 省略
export default Vue.extend({
  // ... 省略
  data() {
    return {
      inDrag: false,
      //... 省略
})

dataにドラッグアンドドロップ中の間に true となる inDrag メンバーを追加しています

テンプレート部分

<div>
  <div class="indicator" :class="{ inMove: inDrag }"></div>
  <div class="container">
    <draggable v-model="items1" draggable=".item" group="items" @start="inDrag=true" @end="inDrag=false">
      <div v-for="item in items1" :key="item.id" class="item">{{item.name}}</div>
    </draggable>

    <draggable v-model="items2" draggable=".item" group="items" @start="inDrag=true" @end="inDrag=false">
      <div v-for="item in items2" :key="item.id" class="item">{{item.name}}</div>
    </draggable>
        <!-- ... 省略 -->

ドラッグアンドドロップ中に色が変わる class属性にindicatorクラスが設定された要素が追加されています。また Vue-Draggable@start属性にinDragtrueに設定するコード、@end属性にinDragfalseに設定するコードが設定されています。それぞれ、ドラッグアンドドロップの開始と終了に対応しているイベントハンドラーになります

CSS部分

/* ...省略 */

.inMove {
  background: brown !important;
}
.indicator {
  margin: auto;
  width: 300px;
  height: 30px;
  background: #42b983;
}

色が変わるよう見た目を調整しています

最後に

とても簡単にドラッグアンドドロップを実現できて驚きです。今後 Vue でドラッグアンドドロップを実装する機会があればぜひ使ってみようとおもいます。この記事が誰かの参考になれば幸いです

参考

SortableJS/Vue.Draggable: Vue drag-and-drop component based on Sortable.js

Vue.Draggable で手軽にリストをドラッグ&ドロップで並び替える - Speaker Deck