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

Vue.js 3 でラップしてコンポーネント化した Dropzone.js の SFC (シングルファイルコンポーネント) を作り、利用する例を作った

やりたいことや前提

作ったものの構成

  • laravel/resources/js/components/BaseDropzone.vue: Dropzone.js をラップした SFC 。
    • SFC へのプロパティは、 API エンドポイントの URL 。入力というよりも開発者による初期設定値。
    • SFC から emit されるときに呼び出し側が受け取るのは API エンドポイントのレスポンス。
  • laravel/resources/js/views/SampleDropzone.vue: BaseDropzone.vue を利用するコンポーネント。

実際にやったこと

npm install --save-dev dropzone
$ git diff laravel/package.json
diff --git a/laravel/package.json b/laravel/package.json
index 7f77014..5b2de6c 100644
--- a/laravel/package.json
+++ b/laravel/package.json
@@ -16,6 +16,7 @@
         "axios": "^0.21.1",
         "bootstrap": "^4.0.0",
         "cross-env": "^7.0",
+        "dropzone": "^5.9.2",
         "eslint": "^7.24.0",
         "eslint-config-prettier": "^8.2.0",
         "eslint-plugin-vue": "^7.9.0",

$

続いて、今回のサンプルページを Vue Rourter に定義しました。

$ git diff laravel/resources/js/router/index.js
diff --git a/laravel/resources/js/router/index.js b/laravel/resources/js/router/index.js
index a835d2f..4a263ed 100644
--- a/laravel/resources/js/router/index.js
+++ b/laravel/resources/js/router/index.js
@@ -1,5 +1,6 @@
 import { createRouter, createWebHistory } from "vue-router";
 import ExampleComponent from "../components/ExampleComponent.vue";
+import SampleDropzone from "../views/SampleDropzone.vue";
 import Vue3Dropzone from "../views/Vue3Dropzone.vue";
 const Home = { template: "<div>Home</div>" };
 const About = { template: "<div>About</div>" };
@@ -15,6 +16,11 @@ const routes = [
     name: "About",
     component: About,
   },
+  {
+    path: "/sample_dropzone",
+    name: "SampleDropzone",
+    component: SampleDropzone,
+  },
   {
     path: "/example_component",
     name: "ExampleComponent",
 
 $

laravel/resources/js/components/BaseDropzone.vue です。すでにサーバにアップロード済のファイルを一覧表示し、その下にアップロード操作をする部分 (Dropzone.js を扱う SFC を呼び出し) を設けています。 onSuccessdeleteFile で、それぞれの内部で処理完了後にアップロード済ファイルをどう変更するかの方法を統一していないです。個人的には、アップロード済ファイルは再度サーバから取得する方が好みです。

<template>
  <div class="container">
    <h1>Dropzone.js sample</h1>
    <h2>アップロード済ファイル</h2>
    <ul class="list-group">
      <li
        v-for="(file, i) in files"
        :key="i"
        class="list-group-item d-flex justify-content-between align-items-center"
      >
        <img class="thumbnail" :src="file.url" />
        <div>
          <p>{{ file.name }}</p>
          <small>{{ file.size }} byte</small>
        </div>
        <button type="button" class="btn btn-danger" @click="deleteFile(file)">
          削除
        </button>
      </li>
    </ul>
    <h2>アップロード操作エリア</h2>
    <base-dropzone :url="'api/files'" @on-success="onSuccess" />
  </div>
</template>

<script>
import { onMounted, ref } from "vue";
import BaseDropzone from "../components/BaseDropzone.vue";

const url = "api/files";

export default {
  name: "SampleDropzone",
  components: { BaseDropzone },
  setup() {
    const files = ref([]);

    const getFiles = () => {
      return fetch(url).then((response) => response.json());
    };

    onMounted(async () => {
      const data = await getFiles();
      files.value = data;
    });

    const onSuccess = (file, response) => {
      //console.log("SampleDropzone.vue onSuccesss.", file, response);
      files.value.push(response);
    };

    const deleteFile = async (file) => {
      console.log(file, file.id);
      // 削除
      await fetch(`${url}/${file.id}`, { method: "DELETE" });

      // 再読み込み
      const reloaded = await getFiles();
      //console.log(reloaded);
      files.value = reloaded;
    };

    return {
      deleteFile,
      files,
      onSuccess,
    };
  },
};
</script>

<style scoped>
.thumbnail {
  height: 120px;
  width: 120px;
}
</style>

laravel/resources/js/components/BaseDropzone.vue です。今回のメインです。

入力として API エンドポイントの URL を受け取ります。入力というよりも開発者側が行う初期設定値としての意味合いとなります。 ファイルを入力するのはウェブブラウザ操作者となります。出力として API エンドポイントのレスポンスを返します。

<template>
  <div id="myDropzoneId" class="dropzone"></div>
</template>

<script>
import { onMounted } from "vue";
import Dropzone from "dropzone";
export default {
  name: "BaseDropzone",
  props: {
    url: {
      type: String,
      require: true,
      default: "",
    },
  },
  emits: ["onSuccess"],
  setup(props, { emit }) {
    const initDropzone = () => {
      Dropzone.autoDiscover = false;
      //new Dropzone("div#myId", { url: "https://httpbin.org/post" });
      const myDropzone = new Dropzone("div#myDropzoneId", { url: props.url });
      myDropzone.on("success", (file, response) => {
        emit("onSuccess", file, response);
      });
    };

    onMounted(() => {
      initDropzone();
    });
  },
};
</script>

<!--
ここでスタイルを読み込むことも可能
<style src="../../../node_modules/dropzone/dist/dropzone.css"></style>
-->

ウェブブラウザに表示した時、 Dropzon.js のスタイルが全く適用されていませんでした。公式ドキュメント に次のようにありました。

This is all you need to get dropzone up and running, but if you want it to look like the dropzone on this page, you’ll need to use the dropzone.css in the dist folder.

次のように CSS ファイルを読み込むことで、無事、スタイルを適用することができました。

$ git diff laravel/resources/sass/app.scss
diff --git a/laravel/resources/sass/app.scss b/laravel/resources/sass/app.scss
index 3193ffa..612c1c9 100644
--- a/laravel/resources/sass/app.scss
+++ b/laravel/resources/sass/app.scss
@@ -6,3 +6,6 @@

 // Bootstrap
 @import '~bootstrap/scss/bootstrap';
+
+// Dropzone.js
+@import '~dropzone/dist/dropzone.css';

$

以上で、サーバのファイルを表示し、個別のファイルを削除し、Dropzon.js をラップした SFC を使用したアップロードを実現することができました!

おわりに

Dropzone.js を Vue.js で使うためのライブラリがいくつかありましたけれども、 Vue.js 3 には対応していなかったり、カスタマイズ方法がよくわからなかったりしました (Vue.js 用の Dropzone ライブラリを軽く試したメモ – oki2a24 参照) 。

それならば、自分でやってみよう、というのが本投稿の主旨でした。結果、なんとかできたのでよかったです。 次は SFC にした Dropzone.js 部分をもう少しカスタマイズする方法を探るつもりです。

以上です。

コメントを残す