ドラッグ&ドロップが可能なツリー状のインターフェイスをつくるためにVue.Draggableを使いましたが、結構手間取ってしまったので備忘録的に使い方をまとめました。
目一杯シンプルにした例です。
ADs
単にドラッグできるツリー表示だけなら<draggable>
で挟むだけなのですが、組み換えや入力などの編集がされたデータを保持するためにコンポーネント化する必要があります。
<draggable>
コンポーネントを含めたコンポーネントを作成します(ここでは<tree>
がそれにあたります)。
そのtreeコンポーネントにv-model
で使いたいデータを指定します。
1 2 3 4 5 6 7 8 9 |
<template> <div id="app" class="container-fluid"> <div class="row"> <div class="col"> <tree v-model="datas"></tree> </div> </div> </div> </template> |
なお、datasデータは以下のようになっています。
入力が伴う場合、入力値はこのdatasデータに含めることがポイントです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
data() { return { datas: [ { name: "あいうえお", checked: false, child: [ { name: "子供A", checked: false, child: [ { name: "孫A", checked: false, child: [] }, { name: "孫B", checked: false, child: [] } ] } ] }, { name: "かきくけこ", checked: false, child: [] }, { name: "さしすせそ", checked: false, child: [] }, { name: "ABCD", checked: false, child: [ { name: "子B", checked: false, child: [] }, { name: "子C", checked: false, child: [] } ] }, { name: "EFGH", checked: false, child: [] }, { name: "RGB", child: [ { name: "子D", checked: false, child: [{ name: "孫C", checked: false, child: [] }] } ] } ] }; } |
ネストされたツリー構造を実現するために、treeコンポーネント内部で自分を呼び出す再帰構造となります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<template> <draggable :list="list" :value="value" tag="ul" @input="emitter" :group="{ name: 'dates' }"> <li class="form-check" v-for="(item,index) in realValue" :key="index"> <input type="checkbox" name="items" class="form-check-input" v-model="item.checked" /> <input type="text" class="form-control my-3" v-model="item.name" /> <tree-child :list="item.child"></tree-child> </li> </draggable> </template> <script> import draggable from "vuedraggable"; export default { name: "tree-child", components: { draggable }, props: { list: {}, value: {} }, computed: { realValue() { return this.value ? this.value : this.list; } }, methods: { emitter(value) { this.$emit("input", value); } } }; </script> |
必ず指定しなければならない属性がいくつかあるので漏れなく指定します。
・:listと:value
属性名は「list/value」で固定です。
ネストされたときに子階層のツリーデータが入ります。
ツリーデータはlist/valueのどちらかに入るので次に続くv-forではcomputedにより存在するものどちらかを使うように指定します(realValueが該当する)。
・@input=”emitter”
親コンポーネントに編集されたデータを渡すために必要です。
methodsで関数の定義も忘れずに行います。
・:group
この属性でnameを定義することで親子階層のドラッグができます。
逆にいうと:group
属性の指定がない場合、兄弟関係はドラッグで変更できますが親子を組み替えることができません。
・入力値(フォーム類)
v-for内でv-modelで指定することで、親コンポーネントのdatasに編集内容が反映されます。
・子コンポーネント(tree-child)
tree-childコンポーネントは自分自身を指します。
:list
で子に当たるツリーデータを指定します。
サンプルの右側がdatasをJSON.stringifyで出力していますが、ドラッグによる順番変更や入力内容が即時に反映されているのが分かります。
あとはこのdatasを保存したり外部に送信したり違うツリーを作ったり、と使い回してWebアプリケーションを構築していきます。
入力データはtree.vue
のdata
で持ちたくなりますが、子コンポーネントはネストされるためここでデータを持ってしまうと取得ができなくなります(できるかもしれないがとてもめんどくさそう)。
実際に業務で使ったときは手こずってしまいましたが、整理するとシンプルなコードで実装できました。
なぜあんなに時間がかかってしまったのか…。
ドラッグ&ドロップのインターフェイスは一見すごそうでやりたがる方も多いのですが、実際使ってみるとどうなんだろう…というのが正直なところです。
ADs
コメントはまだありません。