Vuetifyの TreeviewをDrag and Drop可能にしたコンポーネントを作った

この記事は suusan2go Advent Calendar 2019 - Adventar の6日目の記事です。

仕事でVuetifyを使っていて、 TreeviewコンポーネントのUIでドラッグアンドドロップ出来るようにする必要があった。Treeviewというのは以下のように、階層構造をもつデータ構造をいい感じにするコンポーネントf:id:suzan2go:20191207225659p:plain

が、以下のIssueにもあるように現状はドラッグアンドドロップをサポートする予定はないとのこと。

github.com

There is currently no plan to implement drag & drop functionality

というわけで、作った

作ったもの

こんな感じでコンポーネントを定義してやれば、Treeviewかつドラッグアンドドロップ可能なコンポーネントが使えるようになる。

<template>
<v-draggable-treeview
  group="test"
  v-model="items"
></v-draggable-treeview>
</template>

<script>
export default {
  data() {
    return {
      items: [{ id: 1, name: "hoge", children: [{ id:11, name: "hoge-child1" }] }]
    }
  }
}
</script>

github.com

見ての通り、もともとのTreeviewのUIでドラッグアンドドロップが可能になっている

https://user-images.githubusercontent.com/8841470/70327688-b6ca2800-187a-11ea-907e-79d7dc3afca9.gif

中身

実装はめちゃくちゃ力技で、オリジナルのTreeviewがコンポーネントが描画するDOM構造をもとに、Vue.Draggable を噛まして子要素をドラッグアンドドロップ可能な作りで組み直している。そのためprops / event / slotも自分が必要だった最低限しか実装していない… もともとはVuetifyのコンポーネントを拡張して作ろうと思ったのだけど、普段使ってないclassコンポーネントを読み解くのが辛かったので、とりあえず仕事上必要になった最低限の仕様をDOM構造だけトレースして実装するにとどめている。

難しかったのは、複数のTreeA、TreeB... のように複数のTreeがあったときに、TreeBのchildrenからTreeAのchildrenに要素を移した場合の挙動で、結論からいうとこれは単純に親にprops / emitしていく形式だとレースコンディションが起きて意図した結果にならない。TreeBのchildrenからTreeAのchildrenに要素を移した場合にprops / emitを上のコンポーネントに伝えていくと以下のようなことが起こる。

  • [TreeA] から [TreeB] に TreeAの子要素aを移す
  • ① [TreeB]に子要素aを追加したvalueをemitする
  • ② [TreeA]の子要素aを抜いたvalueをemitする

rootコンポーネント[TreeA, TreeB] のようなvalueをpropsとして渡されているとすると、全てをprops / emitで処理する場合にはrootコンポーネントで以下のような形で emit を実施したい。

  • this.$emit('input', [TreeA, 更新後のTreeB])
  • this.$emit('input', [更新後のTreeA, 更新後のTreeB])

しかし実際には①でemitした結果はすぐにpropsとして反映されるわけではないので、 以下のようになりドラッグアンドドロップした要素がどこかに消えてしまう。

  • this.$emit('input', [TreeA, 更新後のTreeB])
  • this.$emit('input', [更新後のTreeA, TreeB])

というわけで、全て一度localのstateにして受け取って、更新があったらemitするような形式にしている。というか仕事で必要だったのは一つのTree内での並べ替えだけで、ライブラリ化するにあたって全てのTreeで要素を異動できるように変えたんだけども、思いの外これが大変で時間を食ってしまった。

まとめ

コンポーネント化してnpmパッケージ化するにあたっては、クックブックがとても参考になりました。Rollup触るのも初めてだったけど割とシュッとかけてよかった。

jp.vuejs.org