【Vue.js】リッチなUIのWEBアプリに必須なドラッグ・アンド・ドロップの実装

f:id:zuckey_17:20180304215932g:plain

WEBアプリケーションでリッチなUIを実現したい場合、内部的には複雑な操作を簡単そうに見せるためのひとつの工夫として、Drag and Dropは必須と言ってもいいと思います。
今回、Vue.jsとそのライブラリのVue.Draggableを利用して、簡単にリストのDrag and Dropを実現することができたので、紹介したいと思います。

本エントリに記載しているソースコードは以下にあります。

github.com

デモ => https://zuckeym-17.github.io/vue-dnd/

Vue.Draggable

Vue.DraggableはSortable.jsというJavaScriptでリストの Drag and Dropを実装するためのライブラリをベースに開発されています。
そのため、なにかやりたいなと思ったときは、Sortable.jsのドキュメントをあたったほうが、知りたかった情報が書かれていたりします。

Vueの基本的な説明は省略します。
画面の都合上、.vueファイルのstyleタグも省略しています。

また、コード中で利用しているListItemというコンポーネントは、単なるリストの1要素です。
liタグの中にspanタグを配置し、ドラッグハンドルとして使っています。

<template>
  <!-- spanタグをドラッグハンドルとして利用、dragHandle Propsがtrueのときのみ現れる -->
  <li><span v-if="dragHandle" class="drag-handler">:::</span>{{ item }}</li>
</template>

<script>
import Vue from "vue";

export default Vue.extend({
  props: {
    item: String,
    dragHandle: false
  }
});
</script>

1列のリスト

基本的な1列のリストを作成します。
Vue.Draggableをdraggableでインポートして、element属性を変更しているだけです。

<template>
  <div style="margin: 50px;">
    <p>1列リスト</p>
    <!-- Vue.Draggableのコンポーネントを利用。
    ListItemは li なので、レンダリング時の要素を ul にするため element 属性を設定 -->
    <draggable element="ul">
      <!-- list を v-for で 回す。
      key属性については、 https://jp.vuejs.org/v2/guide/list.html#key を参照。 -->
      <list-item
        v-for="item in list"
        :key="list.indexOf(item)"
        :item="item"></list-item>
    </draggable>
  </div>
</template>

<script>
import Vue from "vue";
import ListItem from "./ListItem";
import draggable from "vuedraggable"; // Vue.Draggableのコンポーネントをインポート

export default Vue.extend({
  components: { draggable, ListItem },
  props: ["list"]
});
</script>

与えられている listは以下のようなものです。

const list = [
  "トレイン=ハートネット",
  "スヴェン=ボルフィード",
  "イヴ",
  "リンスレット=ウォーカー"
];

複数列のリスト

複数列間でDrag and Dropを実現します。
Vue.Draggable コンポーネントは、options属性にオブジェクトを渡すことで色々な設定ができます。
ここでは、group オプションを指定し、Drag and Dropで要素を 複数列に渡って行き来させることができます。

<template>
  <div style="margin: 50px;">
    <p>複数列リスト</p>
    <div style="display: flex">
      <!-- options 属性に オブジェクトをbind
      board を v-for で 回す。 -->
      <draggable
        :options="options"
        element="ul"
        v-for="list in board"
        :key="board.indexOf(list)"
      >
        <!-- 1列リストのときと同様 -->
        <list-item
          v-for="item in list"
          :key="list.indexOf(item)"
          :item="item"></list-item>
      </draggable>
    </div>
  </div>
</template>

<script>
import Vue from "vue";
import ListItem from "./ListItem";
import draggable from "vuedraggable";

export default Vue.extend({
  components: { draggable, ListItem },
  props: ["board"],
  data() {
    return {
      options: {
        group: "board", // group オプションを指定した draggable 間はDrag and Dropで要素を移すことができる
        animation: 100 // animationオプションを指定すると ソート時に 値(ms)で アニメーションされる
      }
    };
  }
});
</script>
const board = [
  [
    "セフィリア=アークス",
    "ベルゼー=ロシュフォール",
    "エミリオ=ロウ",
    "クランツ=マドゥーク"
  ],
  [
    "ナイザー=ブラッカイマー",
    "アヌビス",
    "ジェノス=ハザード",
    "バルドリアス=S=ファンギーニ"
  ],
  [
    "デイビッド=ペッパー",
    "リン=シャオリー",
    "ベルーガ=J=ハード",
    "メイソン=オルドロッソ"
  ]
];

追加可能なリスト

リストであれば、要素を追加したかったりします。

<template>
  <div style="margin: 50px;">
    <p>追加可能なリスト</p>
    <draggable element="ul">
      <list-item
        v-for="item in list"
        :key="list.indexOf(item)"
        :item="item"
      ></list-item>
      <!-- slot="footer" をつけると、リストに並ぶ異なった要素を入れることができる。
      入力された値を、addItem メソッドによってリストに追加していく。 -->
      <div slot="footer">
        <input type="text" v-model="input"/>
        <button @click="addItem">Add</button>
      </div>
    </draggable>
  </div>
</template>

<script>
import Vue from "vue";
import ListItem from "./ListItem";
import draggable from "vuedraggable";

export default Vue.extend({
  components: { draggable, ListItem },
  props: ["list"],
  data() {
    return { input: "" };
  },
  methods: {
    // addItem メソッドを定義しておく
    addItem: function(e) {
      e.preventDefault();
      this.list = this.list.concat(this.input);
      this.input = "";
    }
  }
});
</script>

ドラッグハンドルを指定したリスト

ドラッグするときのツマミを指定して、他の要素には別のイベントを仕込みたい場合などがあります。
その時は handle オプションをつけます。

<template>
  <div style="margin: 50px;">
    <p>ドラッグハンドルありのリスト</p>
    <draggable
      element="ul"
      :options="options"
    >
      <!-- list-item に dragHandle Propsを渡して、ドラッグハンドルを表示させる -->
      <list-item
        v-for="item in list"
        :key="list.indexOf(item)"
        :item="item"
        :dragHandle="true"
        ></list-item>
    </draggable>
  </div>
</template>

<script>
import Vue from "vue";
import ListItem from "./ListItem";
import draggable from "vuedraggable";

export default Vue.extend({
  components: { draggable, ListItem },
  props: ["list"],
  data() {
    return {
      options: {
        // handle オプションによって、リストの中でつかめる要素を指定することができる
        handle: ".drag-handler"
      }
    };
  }
});
</script>

まとめ

Vue.Draggableを利用して、簡単にドラッグ・アンド・ドロップを実現することができました。 オプションや、ドラッグ開始時、ドロップ時の各種制御方法は以下のページに記載されているので参考にしてみてください。

github.com

関連

blog.zuckey17.org