📖

Vue.Draggableで固定したいコントロールがある場合の固定方法

2021/05/24に公開

やりたいこと

複数の画像を登録・並び替えを行うUIを作成したい。
画像の並び替え操作はドラッグ・アンド・ドロップ(以降はD&D)で実現したい。
画像登録は追加画像をクリックして画像選択を呼び出すイメージとしたい。

ここで使ったサンプルコードは以下。
https://github.com/tomosuke13b/vue-draggable-example

Vue.Draggableについて

https://github.com/SortableJS/Vue.Draggable

D&Dの自前実装は、主にスマートフォン向けのタッチイベント制御が大変なのでVue.Draggableを使って簡単に実装したいと考えた。

Vueテンプレート上のD&Dを実施したい任意の箇所を指定することができるカスタムコンポーネント。

v-forで配列を展開する箇所をVue.Draggableコンポーネントで囲ってしまう事で、コンポーネント配下ではD&Dが可能となる。
そして、配下のタグやコンポーネントは全てその影響を受ける。

<Draggable class="row" v-model="images" element="div" :move="onMove" @end="onDragEnd">
  <v-col
      cols="3"
      v-for="(image, index) in images"
      :key="index">
      <AddControl v-if="image.isControl" @onImageAdd="inputFiles" />
      <AddImage v-else :image="image" @onImageDelete="onImageDelete" class="page" />
  </v-col>
</Draggable>

このコードの場合、Draggableタグの内部のv-colをD&D対象として指定することができる。

そして、このように追加画像もD&D対象となってしまうのが悩みどころ。

v-col配下のコンポーネントについて補足説明

本件とは若干ずれるが、サンプルコードについて補足説明。
このコードではv-forでimagesをループしており、v-ifの判断でパラメータに応じてAddImageAddControlのどちらかを表示するようにしている。
今回、D&D対象としたいのはAddImageであり、AddControlはD&D対象から外したいという明確な目的を持っている。
なお、追加画像AddControlで実現している。
D&D対象から外すコンポーネントが明確に分かれているほうが除外の対策は打ちやすい。

対応

Vue.Draggableコンポーネントは直下のタグをすべてdraggableにしてしまう。
また、D&Dを実施すると影響下のdivタグのdraggable属性が動的に書き換わるので、
直接変更を実施するのは悪手と考えた。
その結果、AddControlで発火するイベントをすべてキャンセルする手段を考えた。

この場合、大きく2種類の実装が必要となる。

  • D&Dイベント抑止

  • Vue.Draggableのmoveイベント実装

D&Dイベント抑止

D&DしたくないAddControlでイベントキャンセルの実装を追加する。
Vue.Draggableではコンポーネント内部のrender処理で子コンポーネントにD&Dイベントをフックするという仕様になっている。
そのため、子コンポーネント側でイベントの上書きを行い、イベントが発火したらキャンセルを行う事でD&Dの開始を抑止できる。

mounted() {
    this.$el.addEventListener('pointerdown', this.pointerdown);
},
destroyed() {
    this.$el.removeEventListener('pointerdown', this.pointerdown);
},
methods: {
    pointerdown(event) {
        event.stopImmediatePropagation();
    },
},

ここではAddControlのmoutedでaddEventListenerを使ってイベントをセット。
イベントが発火した先の関数でイベントをストップ。
これで、D&Dの開始を抑制できる。
また、コンポーネントの破棄のタイミングでセットしたイベントを破棄するため、destroyedでremoveEventListenerを呼び出してイベントを削除する。

Vue.Draggableのmoveイベント実装

他の画像をD&D中に動かしたくない項目の場合の判定が必要となる。
Vue.DraggableはD&Dのmove途中に処理の差し込みが行えるようになっている。
コンポーネントのmoveプロパティに関数を挿入することでmove元と先のelementを参照することができ、elementのプロパティ参照でD&D対象外かの判定が可能となる。
このサンプルではisControlプロパティのbool値でD&D対象か否かを判定している。

onMove({ relatedContext, draggedContext }) {
    const relatedElement = relatedContext.element;
    const draggedElement = draggedContext.element;
    return (
        (!relatedElement || !relatedElement.isControl) && !draggedElement.isControl
    );
},

最後に

Vue.Draggableは子の項目を全てD&D対象にするという作りが可能となっている。
なので、私が実現したいと思ったD&D非対称の項目が混在するケースはこのライブラリとしては若干イレギュラーなケースと思います。
しかし、あくまでイベントによる対応を徹底してくれたおかげでライブラリの外からD&D制御が可能となった。
Vue.Draggableの開発者には感謝いたします。

Discussion