Vue.Draggableを使ってKanban形式のときのどこからどこに移動したかを取得する



[この記事の続き](https://qiita.com/relishable/items/88126e9ab16e1accd805)で、`composition-api`のイベント処理周りを確認したく
前回、Cardというコンポーネントを使ったのでそれをKanban風に表示して
その時のイベント周りで処理を確認をしようかと思い、[Vue.Draggable](https://github.com/SortableJS/Vue.Draggable)を入れてやってみました。

結果から言うと、`compostion-api`でもイベント周りの扱いは今までのvue2とほぼ同じでした...
確認する必要もなかった...

ただ、[この記事](https://techblog.roxx.co.jp/entry/2018/12/18/120000)にも書かれているのですが、
Vue.Draggableを使うと***どこからどこに移動したみたいな情報が簡単には取れなかった***ので、
最終的にどう実装したかをメモ代わりに記事にしてみました。


## できたもの

![vue.draggable.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/10344/a7f3311b-5dc2-7736-3d8a-772552d73510.gif)

gifのアニメーションが早すぎた....
よく見ると、下の黒いところにどのリストに追加された、どのリストから削除されたというのが取れているのがわかるかと思います。

## 実装内容

```pages/Card.vue

<template>
  <section class="section">
    <div class="columns">
      <template v-if="state.isLoading">読込中....</template>
      <template v-else>
        <div class="listA">
          <h2>List:A</h2>
          <draggable
            :list="state.listA"
            group="list"
            @change="onChange('A', $event)"
            @start="dragging = true"
            @end="dragging = false"
            ghost-class="ghost"
          >
            <card
              :title="card.title"
              :icon="card.icon"
              :content="card.content"
              :key="card.id"
              v-for="card in state.listA"
            >
            </card>
          </draggable>
        </div>
        <div class="listB">
          <h2>List:B</h2>
          <draggable
            :list="state.listB"
            group="list"
            @change="onChange('B', $event)"
            @start="dragging = true"
            @end="dragging = false"
            ghost-class="ghost"
          >
            <card
              :title="card.title"
              :icon="card.icon"
              :content="card.content"
              :key="card.id"
              v-for="card in state.listB"
            >
            </card>
          </draggable>
        </div>
        <div class="listC">
          <h2>List:C</h2>
          <draggable
            :list="state.listC"
            group="list"
            @change="onChange('C', $event)"
            @start="dragging = true"
            @end="dragging = false"
            ghost-class="ghost"
          >
            <card
              :title="card.title"
              :icon="card.icon"
              :content="card.content"
              :key="card.id"
              v-for="card in state.listC"
            >
            </card>
          </draggable>
        </div>
      </template>
    </div>
    <div class="column">
      <div class="log" v-html="state.log"></div>
    </div>
  </section>
</template>

<script lang="ts">
import {
  defineComponent,
  reactive,
  useAsync,
  useContext,
  useMeta,
} from '@nuxtjs/composition-api'
import Draggable from 'vuedraggable'
import Card from '../components/Card.vue'

interface CardData {
  id: Number
  group: String
  title: String
  icon: String
  content: String
}

export default defineComponent({
  name: 'Cards',
  head: {},
  components: {
    Card,
    Draggable,
  },
  props: {},
  setup() {
    useMeta({ title: 'カード一覧' })
    const { app } = useContext()
    const state = reactive<{
      listA: CardData[]
      listB: CardData[]
      listC: CardData[]
      isLoading: Boolean
      log: String
    }>({
      listA: [],
      listB: [],
      listC: [],
      isLoading: true,
      log: 'ログ:',
    })

    useAsync(async () => {
      const cards: CardData[] = await app.$axios
        .get('/json/data.json')
        .then((res: any) => {
          return res.data.cards
        })

      cards.forEach((card: CardData) => {
        switch (card.group) {
          case 'A':
            state.listA.push(card)
            break
          case 'B':
            state.listB.push(card)
            break
          case 'C':
            state.listC.push(card)
            break
        }
      })
      state.isLoading = false
    })

    const onChange = (
      group: String,
      { added, removed, moved }: { added: any; removed: any; moved: any }
    ) => {
      if (added) {
        const { element }: { element: CardData } = added
        state.log =
          state.log + '<br>' + group + 'に' + element.title + 'が追加されました'
      }

      if (removed) {
        const { element }: { element: CardData } = removed
        state.log =
          state.log + '<br>' + group + 'の' + element.title + 'が削除されました'
      }

      if (moved) {
        const {
          element,
          oldIndex,
          newIndex,
        }: { element: CardData; oldIndex: Number; newIndex: Number } = moved
        state.log =
          state.log +
          '<br>' +
          group +
          'の' +
          element.title +
          'の順番が' +
          oldIndex +
          '番目から' +
          newIndex +
          '番目に変わりました'
      }
    }

    return { state, onChange }
  },
})
</script>

<style lang="css">
.listA {
  background: antiquewhite;
  padding: 20px;
  width: 400px;
}
.listB {
  background: #b8d9b8;
  padding: 20px;
  width: 400px;
}
.listC {
  background: #b8b351;
  padding: 20px;
  width: 400px;
}
.ghost {
  opacity: 40%;
}
.log {
  background: #47494e;
  color: #f7f8fb;
  padding: 10px;
}
</style>


```

## 実装のポイント

今回、`change`イベントで情報を取得するようにしました。
まずは、`@change`でターゲットのリストのキーを第一引数で渡します。（第2引数で、元のeventを）

```vuejs
<draggable
  :list="state.listA"
  group="list"
  @change="onChange('A', $event)"
  @start="dragging = true"
  @end="dragging = false"
  ghost-class="ghost"
>
```

そして、イベントリスナーの方で第一引数のgroupと
第二引数の元のイベントを取得します。

イベントは、オブジェクト形式でキーが
`added` `removed` `moved`
の３タイプありそれぞれでリストに追加されたのか、削除されたのか、順番が変わったのかを判別することができます。

なので、キーの存在を確認して処理するようにしました。

```

const onChange = (
      group: String,
      { added, removed, moved }: { added: any; removed: any; moved: any }
    ) => {
      if (added) {
      }

      if (removed) {
      }

      if (moved) {
      }
    }
)
```

これで、Kanban形式のときの***どこからどこに移動したみたいな情報***が取れるようになりました。