カテゴリー
コンピューター

Vue.js 3 で Dropzone をラップした SFC その3 。ファイルアップロード前に確認操作を挟む

Vue.js 3 で Dropzone をラップした SFC その2 。 HTML 要素 の id ではなく要素そのものを指定して Dropzone と紐付ける等 – oki2a24 の続きとなります。

Dropzone はファイルをドロップや選択すると予め指定した URL に対して、自動的に即座にアップロードを開始します。 とても便利ですけれども、例えば、ヘッダに情報を追加した Axios を他のところでも使っているので Dropzone でも使いたい、ファイルアップロード前に確認操作を挟みたい、という時はどうしたら良いでしょうか?

本投稿では、ファイルアップロード前に確認操作を挟みたい、の例をとりあげます。

最初に考えた方法と諦めた理由

ファイルのアップロードの処理は自分自身のコードで行いたい、ということは可能でしょうか? いろいろ調べて今のところ結論は、それは難しい、です。というのもファイルアップロードを Dropzone で行わないため、アップロード中の状況や結果を Dropzone に伝えるのが難しいからです。もしかしたら可能なのかもしれませんが、少なくとも簡単ではなさそうでした。

以上の理由で Dropzone による自動でのアップロードを中止して、ファイルを取得して好きなやり方でアップロードを行う方法は諦めました。

Dropzone の機能を使った方法

実は Dropzone 自身にファイルアップロード前に確認を挟むことを想定したオプション等がありました。

  • autoProcessQueue: false の場合、キューのファイルを自動的にアップロードしない。
  • addedfile: ファイルが (処理すべき) リストに加えられた時 (つまり、ドロップした時、または、ファイル選択ダイアログでファイルを選択した時) 。

これらを組み合わせることで実現できました。次は差分です。

  • autoProcessQueue を false にすることによって、ファイルの自動アップロードをオフにしている。
  • 自動アップロードはオフになっているので手動アップロードは Dropzone の addedfile イベント内で行う。手動アップロード開始は myDropzone.processQueue() 関数となる。
  • addedfile 完了後に myDropzone.processQueue() を実行したいので、 myDropzone.processQueue()setTimeout でラップした。
  • 今回の実装ではモーダルダイアログでキャンセルを選択した場合、 Dropzone のファイルを表示するエリアにファイルを表示しないようにしたい、また、キャンセルしたファイルは Dropzone で扱うリストから除去したい、と考えた。そこで、モーダルダイアログでキャンセルした場合は、 myDropzone.removeFile(file) を呼び出してそれらのやりたいことを実現した。

laravel/resources/js/components/BaseDropzone.vue

$ git diff
diff --git a/laravel/resources/js/components/BaseDropzone.vue b/laravel/resources/js/components/BaseDropzone.vue
index d8844bd..9d1b34b 100644
--- a/laravel/resources/js/components/BaseDropzone.vue
+++ b/laravel/resources/js/components/BaseDropzone.vue
@@ -29,6 +29,7 @@ export default {
       const myDropzone = new Dropzone(root.value, {
         url: props.url,
         addRemoveLinks: true,
+        autoProcessQueue: false,
         dictDefaultMessage:
           "画像ファイルをここにドロップするか、クリックしてアップロード",
         dictCancelUpload: "アップロードをキャンセル",
@@ -36,6 +37,16 @@ export default {
         ...props.options,
       });

+      myDropzone.on("addedfile", (file) => {
+        const confirmed = window.confirm("アップロードしますよ?");
+        if (confirmed) {
+          // myDropzone.processQueue(); では動作しない。
+          // `addedfile` 完了後に `myDropzone.processQueue()` を実行するために setTimeout を使用
+          setTimeout(() => myDropzone.processQueue(), 0);
+        } else {
+          myDropzone.removeFile(file);
+        }
+      });
       myDropzone.on("success", (file, response) => {
         emit("onSuccess", file, response);
         setTimeout(() => myDropzone.removeFile(file), 10000);
$

laravel/resources/js/components/BaseDropzone.vue の全体です。

<template>
  <div ref="root" class="dropzone" />
</template>

<script>
import { onMounted, ref } from "vue";
import Dropzone from "dropzone";
export default {
  name: "BaseDropzone",
  props: {
    options: {
      default: () => {},
      required: false,
      type: Object,
    },
    url: {
      type: String,
      require: true,
      default: "",
    },
  },
  emits: ["onSuccess"],
  setup(props, { emit }) {
    const root = ref(null);

    onMounted(() => {
      Dropzone.autoDiscover = false;
      //new Dropzone("div#myId", { url: "https://httpbin.org/post" });
      const myDropzone = new Dropzone(root.value, {
        url: props.url,
        addRemoveLinks: true,
        autoProcessQueue: false,
        dictDefaultMessage:
          "画像ファイルをここにドロップするか、クリックしてアップロード",
        dictCancelUpload: "アップロードをキャンセル",
        dictRemoveFile: "操作エリアから除去",
        ...props.options,
      });

      myDropzone.on("addedfile", (file) => {
        const confirmed = window.confirm("アップロードしますよ?");
        if (confirmed) {
          // myDropzone.processQueue(); では動作しない。
          // `addedfile` 完了後に `myDropzone.processQueue()` を実行するために setTimeout を使用
          setTimeout(() => myDropzone.processQueue(), 0);
        } else {
          myDropzone.removeFile(file);
        }
      });
      myDropzone.on("success", (file, response) => {
        emit("onSuccess", file, response);
        setTimeout(() => myDropzone.removeFile(file), 10000);
      });
      myDropzone.on("error", function (file, errorMessage) {
        console.log("error", file, errorMessage); // TODO デバッグ用途。後で消すこと。
        // レスポンスの内容を使用するときは、エラーメッセージの後ろに付与すればよさそう。
        file.previewTemplate.getElementsByClassName(
          "dz-error-message"
        )[0].textContent = "アップロードに失敗しました。";
      });
    });

    return { root };
  },
};
</script>

<style>
.dz-success {
  transition: opacity 10s, visibility 0s ease 10s;
  opacity: 0;
  visibility: hidden;
}
</style>
<!--
ここでスタイルを読み込むことも可能
<style src="../../../node_modules/dropzone/dist/dropzone.css"></style>
-->

おわりに

アップロードの処理を自前で実装できれば幅が広がるかと思ったのですけれども、冒頭で触れたように難しそうです。

ですので Dropzone.js で何ができるのか、それをどうやって知るのか、を把握することで問題を解決できるようにしておかなければなりませんね。

困ったことが一つ残っております。 Vue.js 3 で使える Dropzone をラップした SFC を作っているわけですけれども、今回書いたコードではこの SFC を使う場合は必ずファイルアップロード前の確認が挟まってしまいます。理想は addedfile イベントの中の処理を外部から与えられれば良いのですけれども、外部から与えるのにもかかわらずその与えるコードには SFC 内のコード myDropzone.processQueue() を実行するように書かなければなりません。まだ何も試していませんけれども何か罠がありそうです。

話はズレますけれども、今回最も勉強になったのは、 setTimeout の使い方でした。これを学んだウェブサイトは何者だろうと思ったのですけれども、いくつかページをみてみると、有志によって作成されているようで、質も高そうです。今後このページも頼りになる参照元としてリストに加えたいと思います。

以上です。

コメントを残す