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

Vue.js 3 で Dropzone をラップした SFC その 4 。ファイルアップロード前の確認などの操作を SFC 呼び出し元で自由に制御する

まとめ

  • Dropzone の addedfile イベント時に emit するが、この時に送出する変数としてイベント発生時に受け取れる file 以外に、 Dropzone オブジェクトも加える。

話としては、 Vue.js 3 で Dropzone をラップした SFC その3 。ファイルアップロード前に確認操作を挟む – oki2a24 の続きとなります。

試した方法とその結果

  • 関数を props として渡す方法。これはダメだった。渡す関数の中で Dropzone の関数を呼び出すことができなかったため。
  • Dropzone SFC から emit 時に Dropzone オブジェクトを受け取り、 SFC 呼び出し元で Dropzone オブジェクトに属する関数を使う。これは、上手く行った。 SFC の外に SFC 内のオブジェクトを出す、というのは SFC の責務を超すことを意味することになるのであまりやりたくなかった。とはいえ、関数を props として渡す方法でも同じことは言えるので SFC の責務自体を認識し直す方が良いのかもしれない。
  • 確認ダイアログフラグを渡すことで制御する方法。試していないが、おそらくこれで実現可能。ただし、どのような確認の仕方をさせるのか、または他のことがしたい、といった時に対応できないし、対応するために SFC が膨らんでしまうという欠点がある。

SFC と利用側のコード差分

laravel/resources/js/components/BaseDropzone.vue 。 SFC の変更部分です。

$ git diff laravel/resources/js/components/BaseDropzone.vue
diff --git a/laravel/resources/js/components/BaseDropzone.vue b/laravel/resources/js/components/BaseDropzone.vue
index 9d1b34b..f6d3831 100644
--- a/laravel/resources/js/components/BaseDropzone.vue
+++ b/laravel/resources/js/components/BaseDropzone.vue
@@ -19,7 +19,7 @@ export default {
       default: "",
     },
   },
-  emits: ["onSuccess"],
+  emits: ["onAddedfile", "onSuccess"],
   setup(props, { emit }) {
     const root = ref(null);

@@ -38,14 +38,7 @@ export default {
       });

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

“ 。 SFC を利用する側の変更部分です。

$ git diff laravel/resources/js/views/SampleDropzone.vue
diff --git a/laravel/resources/js/views/SampleDropzone.vue b/laravel/resources/js/views/SampleDropzone.vue
index 3e3d091..9137714 100644
--- a/laravel/resources/js/views/SampleDropzone.vue
+++ b/laravel/resources/js/views/SampleDropzone.vue
@@ -24,7 +24,11 @@
       </li>
     </ul>
     <h2>アップロード操作エリア</h2>
-    <base-dropzone :url="'api/files'" @on-success="onSuccess" />
+    <base-dropzone
+      :url="'api/files'"
+      @on-addedfile="onAddedfile"
+      @on-success="onSuccess"
+    />
   </div>
 </template>

@@ -65,9 +69,21 @@ export default {
       files.value = reloaded;
     };

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

Dropzone SFC コード全体

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: ["onAddedfile", "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) => {
        emit("onAddedfile", file, myDropzone);
      });
      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>
-->

おわりに

関数を props として渡す方法をあれこれ試していたため、全体として時間がかかってしまいました。 時間をかけてみたのは、関数を props として渡す (callback) 方法がすでに試みられていたからです。

試してみて解ったのは、SFC に渡すコールバック内に SFC の中でのみ使われているオブジェクトを使うのは難しい、ということでした。 ですので、ここから emit vs callback props の話ですが、もしかしたらさらに良い解決方法があるかもしれませんけれども、今のところは emit の方が適用できる範囲が広く、引き続きこちらを使っていこうかと思っています。

以上です。

コメントを残す