本文書では複数のコラムを作成し、コラム内にある要素をドラッグ&ドラップを使うことでコラム間移動を行う方法について説明を行なっています。vue.jsにはVue.Draggableといったライブラリが存在しますが、本文書ではライブラリを使用せずにHTML5 Drag and Dropの機能を利用して実装しています。

drag&dropの流れ
drag&dropの流れ

テーブルの行要素をDrag&Dropで移動させる方法については下記の文書で公開しています。

準備

vue.jsはcdnを利用します。またページに簡易的なCSSを適用するためにbootstrapをcdnを経由して利用します。vue.jsとbootstrapの動作確認を行うために下記のコードを記述しブラウザで確認します。

bootstapは必須ではありませんが各所でbootstrapのクラスを利用しています。
fukidashi

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
  <title>Vue.js + HTML Drag&Drop</title>
</head>
<body>
  <div id="app" class="container mt-5">
    <h1>{{ message }}</h1>
  </div>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: 'HTML Drag&Drop in vue.js page'
    }
  })
</script>
</body>
</html>

ブラウザを開いて下記のようにメッセージが表示されたらvue.jsもbootstapも正常に動作しています。

vue.jsの動作確認
vue.jsの動作確認

データの準備

商品情報の入ったlistsの配列をvue.jsのデータプロパティに追加します。lists配列はオブジェクトで構成され、id, name, categoryの3つのプロパティを持っています。categoryは’A’, ‘B’, ‘C’の3つの値を設定することができこの値によってコラム分けを行なっています。


data: {
  message: 'HTML Drag&Drop in vue.js page',
  lists: [
    {
      id: 1, 
      name: 'ProductA',
      category: 'A'
    },
    {
      id: 2, 
      name: 'ProductB',
      category: 'B'
    },
    {
      id: 3, 
      name: 'ProductC',
      category: 'B'
    },
    {
      id: 4, 
      name: 'ProductD',
      category: 'C'
    },
    {
      id: 5, 
      name: 'ProductE',
      category: 'A'
    },
  ]
},

flexboxを使って3つのコラムを作成しています。各コラムはCategoryによってわけられます。v-forの展開ではlistsを使うのではなくCategoryA, CategoryB, CategoryCの3つのcomputedプロパティを使用して展開しています。


  <div id="app" class="container mt-5">
    <h1>{{ message }}</h1>
    <div class="d-flex justify-content-between bg-dark p-3">
      <div class="bg-secondary p-2 m-2 w-100">
        <h2 class="text-light">Category A</h2>
        <div class="bg-white m-2 p-2" v-for="(list,index) in CategoryA">
          {{ list.name }}
        </div>
      </div>
      <div class="bg-secondary p-2 m-2 w-100">
        <h2 class="text-light">Category B</h2>
        <div class="bg-white m-2 p-2" v-for="(list,index) in CategoryB">
            {{ list.name }}
        </div>           
      </div>
      <div class="bg-secondary p-2 m-2 w-100">
        <h2 class="text-light">Category C</h2>
        <div class="bg-white m-2 p-2" v-for="(list,index) in CategoryC">
          {{ list.name }}
        </div>
      </div>
    </div>

Computedプロパティはfilter関数を利用して各listオブジェクトのcategoryの値で別々のリストを作成しています。


computed: {
    CategoryA () {
        return this.lists.filter(list => list.category == 'A')
    },
    CategoryB () {
        return this.lists.filter(list => list.category == 'B')
    },
    CategoryC () {
        return this.lists.filter(list => list.category == 'C')
    }
},

ここまでの設定後にブラウザで確認すると下記のように表示されます。

カテゴリー分けされたリスト
カテゴリー分けされたリスト

ドラッグの設定

現時点の設定ではマウスを利用して要素をドラッグしようとしても要素を掴むこともできません。要素を掴むためにはdraggableを要素に追加設定する必要があります。ここではCategoryAのdivタグのみ表示していますが、CategoryBやCategoryCのdivタグにもdraggableの設定を行います。


<div class="bg-secondary p-2 m-2 w-100">
    <h2 class="text-light">Category A</h2>
    <div 
        class="bg-white m-2 p-2" 
        v-for="(list,index) in CategoryA"
        draggable
    >
    {{ list.name }}
    </div>
</div>

draggableの設定が完了すると要素を掴みドラッグすることができるようになります。

ドラッグした要素の情報を取得

ドラッグする要素に@dragstartイベントを設定することでドラッグを開始した直後に指定した関数を実行することができます。dragList関数の引数にはイベントとリストを一意に識別することができるidを指定しています。


<div 
class="bg-secondary p-2 m-2 w-100"
>
    <h2 class="text-light">Category A</h2>
    <div
    class="bg-white m-2 p-2"
    v-for="(list,index) in CategoryA"
    draggable
    @dragstart="dragList($event,list.id)"
    >
    {{ list.name }}
    </div>
</div>
CategoryBやCategoryCの要素にもdragstartイベントの設定を行なってください。
fukidashi

dragList関数をvue.jsのmethodsに追加します。動作確認のためにドラッグした要素のlistのidをコンソールログに表示させます。


methods:{
    dragList(event, dragId){
      console.log(dragId)
    },
}

要素をドラッグしてコンソールログにlistのidが表示されることを確認してください。

dataTransferインターフェイスの設定

ドロップ後の処理に必要となるドラッグ要素のidを保存するためdataTransferインターフェイスの設定を行います。

dataTransferインターフェイスを使うことでドラッグした要素に関する情報の保存、ドラッグ&ドロップ操作の制御に関する設定を行うことができます。


methods:{
  dragList(event, dragId){
    event.dataTransfer.effectAllowed = 'move'
    event.dataTransfer.dropEffect = 'move'
    event.dataTransfer.setData('list-id',dragId)
  },
dataTransferはdragイベントで利用できる機能です。mouseイベントでは利用できません。
fukidashi

dataTransferのsetDataメソッドでドラッグした要素のidを保存します。setDataで保存したデータはgetDataで取り出すことができます。setDataでは文字列のみ保存することが可能です。

dropEffectではドラッグで行う操作を設定します。デフォルトではnoneに設定され、今回は移動なのでmoveを設定しています。

effectAllowedではドロップ先で行える操作を設定します。デフォルトではuninitializedが設定され、今回は移動なのでmoveを設定しています。

Chromeを利用して動作確認を行いましたが、dropEffectとeffectAllowedを設定しなくてもdrop&drag動作は行われます。またdropEffectとeffectAllowedを異なる値に設定しても動作しました。しかし、effectAllowedをnoneに設定するとDropさせることができませんでした。
fukidashi

Dropの設定

ドラッグした要素に関する設定とdragstartイベントの設定が完了したので、ここからはドロップ側で行う処理の設定を行なっていきます。

ドラッグの時はdragstartイベントを設定しましたが、ドロップの場合はdropイベントを設定します。また、dropイベントを設定する場所はdragstartイベントを設定したdivタグではなくその親のdivタグに設定を行います。

dropイベントで実行する関数はdropListで第2引数にCategoryの名前を設定します。


<div 
class="bg-secondary p-2 m-2 w-100"
@drop="dropList($event, 'A')"
@dragover.prevent
@dragenter.prevent
>
    <h2 class="text-light">Category A</h2>
    <div
    class="bg-white m-2 p-2"
    v-for="(list,index) in CategoryA"
    draggable
    @dragstart="dragList($event,list.id)"
    >
    {{ list.name }}
    </div>
</div>

dropListメソッドをvue.jsのmethodsに追加します。

dropListメソッドではdragListメソッドの中のdataTransferで設定したlist-idをgetDataを使って取り出します。次にJavaScriptのfind関数を利用して取り出したlist idを持つlistを取り出します(dragList)。dragListはドラッグを行なったlistオブジェクトなのでcategoryプロパティをドロップした要素のCategoryに更新します。更新する値はdropListの第2引数で渡されるカテゴリーの名前です。更新するとリストが更新され、元いたカテゴリーからdragListオブジェクトの情報が消え、ドラップ先のカテゴリーのリストに表示されます。


methods:{
    dragList(event, dragId){
      event.dataTransfer.effectAllowed = 'move'
      event.dataTransfer.dropEffect = 'move'
      event.dataTransfer.setData('list-id',dragId)
    },
    dropList(event, dropCategory){
      const dragId = event.dataTransfer.getData('list-id')
      const dragList = this.lists.find(list => list.id == dragId)
      dragList.category = dropCategory
    }
}

要素をドラッグして別のカテゴリーに移動できるか確認を行なってください。下記のようにドラッグ&ドロップが行えればプログラムは正常に動作しています。

drag&dropの流れ
drag&dropの流れ

作成したコードは下記の通りです。


<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
  <title>Vue.js + HTML Drag&Drop</title>
</head>
<body>
  <div id="app" class="container mt-5">
    <h1>{{ message }}</h1>
    <div class="d-flex justify-content-between bg-dark p-3">
      <div 
        class="bg-secondary p-2 m-2 w-100"
        @drop="dropList($event, 'A')"
        @dragover.prevent
        @dragenter.prevent
      >
        <h2 class="text-light">Category A</h2>
        <div
          class="bg-white m-2 p-2"
          v-for="(list,index) in CategoryA"
          draggable
          @dragstart="dragList($event,list.id)"
        >
          {{ list.name }}
        </div>
      </div>
      <div 
        class="bg-secondary p-2 m-2 w-100"
        @drop="dropList($event, 'B')"
        @dragover.prevent
        @dragenter.prevent
      >
        <h2 class="text-light">Category B</h2>
        <div
          class="bg-white m-2 p-2"
          v-for="(list,index) in CategoryB"
          draggable
          @dragstart="dragList($event,list.id)"
        >
          {{ list.name }}
        </div>
      </div>
      <div 
        class="bg-secondary p-2 m-2 w-100"
        @drop="dropList($event, 'C')"
        @dragover.prevent
        @dragenter.prevent
      >
        <h2 class="text-light">Category C</h2>
        <div
          class="bg-white m-2 p-2"
          v-for="(list,index) in CategoryC"
          draggable
          @dragstart="dragList($event,list.id)"
        >
          {{ list.name }}
        </div>
      </div>
    </div>
  </div>
<script>
  const app = new Vue({
    el: "#app",
    data: {
      message: 'HTML Drag&Drop in vue.js page',
      lists: [
        {
          id: 1, 
          name: 'ProductA',
          category: 'A'
        },
        {
          id: 2, 
          name: 'ProductB',
          category: 'B'
        },
        {
          id: 3, 
          name: 'ProductC',
          category: 'B'
        },
        {
          id: 4, 
          name: 'ProductD',
          category: 'C'
        },
        {
          id: 5, 
          name: 'ProductE',
          category: 'A'
        },
      ]
    },
    computed: {
      CategoryA () {
        return this.lists.filter(list => list.category == 'A')
      },
      CategoryB () {
        return this.lists.filter(list => list.category == 'B')
      },
      CategoryC () {
        return this.lists.filter(list => list.category == 'C')
      }
    },
    methods:{
      dragList(event, dragId){
        event.dataTransfer.effectAllowed = 'move'
        event.dataTransfer.dropEffect = 'move'
        event.dataTransfer.setData('list-id',dragId)
      },
      dropList(event, dropCategory){
        const dragId = event.dataTransfer.getData('list-id')
        const dragList = this.lists.find(list => list.id == dragId)
        dragList.category = dropCategory
      }
    }
  })
</script>
</body>
</html>