序章

この記事では、VueJでファイルのアップロードを処理する方法について説明します。 ユーザーがドラッグアンドドロップまたはファイル選択ダイアログで単一または複数の画像ファイルをアップロードできるようにする画像アップローダーを作成します。

次に、選択した画像をアップロードし、それに応じて表示します。 また、アップロードファイルタイプのフィルタリングについても学習します。たとえば、画像のみを許可し、PDFなどのファイルタイプは許可しません。

Image uploader

ファイルアップロードUIとAPI

ファイルのアップロードは、 UI(フロントエンド) API(バックエンド)の2つの部分で構成されています。 UI部分の処理にはVueJsを使用します。 アップロードされたファイルを受け入れるには、バックエンドアプリケーションが必要です。 バックエンドのチュートリアルに従うか、これらのサーバー側アプリケーションのいずれかをダウンロードして実行し、バックエンドのファイルアップロードを処理できます。-

この記事では、バックエンドとしてファイルのアップロードとHapi.jsを使用します。 また、フロントエンドで偽のアップロードを有効にするための秘訣についても学びます。

Vue-Cliを使用したプロジェクトのセットアップ

vue-cliを使用してVue.jsプロジェクトをスキャフォールディングします。 webpack-simpleプロジェクトテンプレートを使用します。

# install cli
npm install vue-cli -g

# then create project, with sass
# follow the instructions to install all necessary dependencies
vue init webpack-simple file-upload-vue

了解しました。すべて設定しました。 コンポーネントの作成に進みましょう。

ファイルアップロードコンポーネント

コードはApp.vueで記述します。 ファイル内の自動生成されたコードをすべて削除します。

<!-- App.vue -->

<!-- HTML Template -->
<template>
  <div id="app">
    <div class="container">
      <!--UPLOAD-->
      <form enctype="multipart/form-data" novalidate v-if="isInitial || isSaving">
        <h1>Upload images</h1>
        <div class="dropbox">
          <input type="file" multiple :name="uploadFieldName" :disabled="isSaving" @change="filesChange($event.target.name, $event.target.files); fileCount = $event.target.files.length"
            accept="image/*" class="input-file">
            <p v-if="isInitial">
              Drag your file(s) here to begin<br> or click to browse
            </p>
            <p v-if="isSaving">
              Uploading {{ fileCount }} files...
            </p>
        </div>
      </form>
  </div>
</template>

<!-- Javascript -->
<script>
</script>

<!-- SASS styling -->
<style lang="scss">
</style>

ノート:-

  1. App.vueコンポーネントは、テンプレート(HTML)、スクリプト(Javascript)、スタイル(SASS)の3つの部分で構成されています。
  2. テンプレートにはアップロードフォームがあります。
  3. フォーム属性enctype="multipart/form-data"は重要です。 ファイルのアップロードを有効にするには、この属性を設定する必要があります。 enctype の詳細については、こちらをご覧ください。
  4. ファイルのアップロードを受け入れるためのファイル入力<input type="file" />があります。 プロパティmultipleは、複数のファイルのアップロードを許可することを示します。 単一ファイルのアップロードのためにそれを削除します。
  5. ファイル入力changeイベントを処理します。 ファイル入力が変更されると(誰かがファイルをドロップまたは選択する)、filesChange関数をトリガーし、コントロール名と選択したファイル$event.target.filesを渡して、サーバーにアップロードします。
  6. ファイル入力を制限して、属性accept="image/*"を持つ画像のみを受け入れます。
  7. アップロード中はファイル入力が無効になるため、ユーザーはアップロードの完了後にのみファイルを再度ドロップ/選択できます。
  8. ファイルが変更されたときのfileCountをキャプチャします。 Uploading {{ fileCount }} files...をアップロードするファイルの数を表示する際に、fileCount変数を使用します。

ファイルアップロードコンポーネントのスタイルを設定する

さて、それは興味深い部分です。 現在、コンポーネントは次のようになっています。

File upload component without styling

次のように変換する必要があります。

File upload component with styling

スタイリングしましょう!

<!-- App.vue -->
...

<!-- SASS styling -->
<style lang="scss">
  .dropbox {
    outline: 2px dashed grey; /* the dash box */
    outline-offset: -10px;
    background: lightcyan;
    color: dimgray;
    padding: 10px 10px;
    min-height: 200px; /* minimum height */
    position: relative;
    cursor: pointer;
  }
  
  .input-file {
    opacity: 0; /* invisible but it's there! */
    width: 100%;
    height: 200px;
    position: absolute;
    cursor: pointer;
  }
  
  .dropbox:hover {
    background: lightblue; /* when mouse over to the drop zone, change color */
  }
  
  .dropbox p {
    font-size: 1.2em;
    text-align: center;
    padding: 50px 0;
  }
</style>

scssの数行だけで、コンポーネントはよりきれいに見えます。

ノート:-

  1. opacity: 0スタイルを適用して、ファイル入力を非表示にします。 これはファイル入力を非表示にするのではなく、ファイルを非表示にするだけです。
  2. 次に、ファイル入力の親要素であるdropboxcssクラスのスタイルを設定します。 ダッシュで囲まれたドロップファイルゾーンのように見せます。
  3. 次に、ドロップボックス内のテキストを中央に揃えます。

ファイルアップロードコンポーネントコード

コンポーネントのコーディングに進みましょう。

<!-- App.vue -->
...

<!-- Javascript -->
<script>
  import { upload } from './file-upload.service';

  const STATUS_INITIAL = 0, STATUS_SAVING = 1, STATUS_SUCCESS = 2, STATUS_FAILED = 3;

  export default {
    name: 'app',
    data() {
      return {
        uploadedFiles: [],
        uploadError: null,
        currentStatus: null,
        uploadFieldName: 'photos'
      }
    },
    computed: {
      isInitial() {
        return this.currentStatus === STATUS_INITIAL;
      },
      isSaving() {
        return this.currentStatus === STATUS_SAVING;
      },
      isSuccess() {
        return this.currentStatus === STATUS_SUCCESS;
      },
      isFailed() {
        return this.currentStatus === STATUS_FAILED;
      }
    },
    methods: {
      reset() {
        // reset form to initial state
        this.currentStatus = STATUS_INITIAL;
        this.uploadedFiles = [];
        this.uploadError = null;
      },
      save(formData) {
        // upload data to the server
        this.currentStatus = STATUS_SAVING;

        upload(formData)
          .then(x => {
            this.uploadedFiles = [].concat(x);
            this.currentStatus = STATUS_SUCCESS;
          })
          .catch(err => {
            this.uploadError = err.response;
            this.currentStatus = STATUS_FAILED;
          });
      },
      filesChange(fieldName, fileList) {
        // handle file changes
        const formData = new FormData();

        if (!fileList.length) return;

        // append the files to FormData
        Array
          .from(Array(fileList.length).keys())
          .map(x => {
            formData.append(fieldName, fileList[x], fileList[x].name);
          });

        // save it
        this.save(formData);
      }
    },
    mounted() {
      this.reset();
    },
  }

</script>

ノート:-

  1. コンポーネントにはいくつかのステータスがあります:STATUS_INITIAL、STATUS_SAVING、STATUS_SUCCESS、STATUS_FAILED、変数名はそれ自体かなり表現力があります。
  2. 後で、Hapi.jsファイルアップロードAPIを呼び出して画像をアップロードします。APIはフィールド呼び出しphotosを受け入れます。 これがファイル入力フィールド名です。
  3. ファイルの変更はfilesChange関数で処理します。 FileList HTMLのfilesプロパティによって返されるオブジェクトですエレメント。 これにより、で選択されたファイルのリストにアクセスできます。 エレメント。 詳細[こちら](( https://developer.mozilla.org/en/docs/Web/API/FileList )。
  4. 次に、新しいFormDataを作成し、それにすべてのphotosファイルを追加します。 FormDataインターフェースは、フォームフィールドとその値を表すキーと値のペアのセットを簡単に構築する方法を提供します。 詳細はこちらをご覧ください。
  5. save関数は、ファイルアップロードサービスを呼び出します(しばらくお待ちください。次にサービスを作成します)。 また、結果に応じてステータスを設定します。
  6. mount()は、vueコンポーネントのライフサイクルフックです。 その間に、コンポーネントのステータスを初期状態に設定します。

ファイルアップロードサービス

サービスの作成に進みましょう。 axiosを使用してHTTP呼び出しを行います。

axiosをインストールします

# install axios
npm install axios --save

サービス

// file-upload.service.js

import * as axios from 'axios';

const BASE_URL = 'http://localhost:3001';

function upload(formData) {
    const url = `${BASE_URL}/photos/upload`;
    return axios.post(url, formData)
        // get data
        .then(x => x.data)
        // add url field
        .then(x => x.map(img => Object.assign({},
            img, { url: `${BASE_URL}/images/${img.id}` })));
}

export { upload }

何もありませんが、コード自体はかなり表現力豊かです。 ファイルをアップロードし、結果を待ち、それに応じてマッピングします。

npm run devコマンドでアプリケーションを実行できます。 いくつかの画像をアップロードしてみてください、そしてそれはうまくいきます! (バックエンドサーバーを起動することを忘れないでください)

成功と失敗の結果を表示する

これでファイルを正常にアップロードできます。 ただし、UIには表示がありません。 HTMLテンプレートを更新しましょう。

<!-- App.vue -->

<!-- HTML Template -->
<template>
  <div id="app">
    <div class="container">
      ...form...

      <!--SUCCESS-->
      <div v-if="isSuccess">
        <h2>Uploaded {{ uploadedFiles.length }} file(s) successfully.</h2>
        <p>
          <a href="javascript:void(0)" @click="reset()">Upload again</a>
        </p>
        <ul class="list-unstyled">
          <li v-for="item in uploadedFiles">
            <img :src="item.url" class="img-responsive img-thumbnail" :alt="item.originalName">
          </li>
        </ul>
      </div>
      <!--FAILED-->
      <div v-if="isFailed">
        <h2>Uploaded failed.</h2>
        <p>
          <a href="javascript:void(0)" @click="reset()">Try again</a>
        </p>
        <pre>{{ uploadError }}</pre>
      </div>
    </div>
  </div>
</template>

ノート:-

  1. 正常にアップロードされたら、アップロードされた画像を表示します。
  2. アップロードに失敗したときにエラーメッセージを表示します。

フロントエンドでの偽のアップロード

ファイルのアップロードを処理するためにバックエンドアプリケーション(Hapi、Expressなど)を起動するのが面倒な場合。 これは、ファイルアップロードサービスを置き換える偽のサービスです。

// file-upload.fake.service.js

function upload(formData) {
    const photos = formData.getAll('photos');
    const promises = photos.map((x) => getImage(x)
        .then(img => ({
            id: img,
            originalName: x.name,
            fileName: x.name,
            url: img
        })));
    return Promise.all(promises);
}

function getImage(file) {
    return new Promise((resolve, reject) => {
        const fReader = new FileReader();
        const img = document.createElement('img');

        fReader.onload = () => {
            img.src = fReader.result;
            resolve(getBase64Image(img));
        }

        fReader.readAsDataURL(file);
    })
}

function getBase64Image(img) {
    const canvas = document.createElement('canvas');
    canvas.width = img.width;
    canvas.height = img.height;

    const ctx = canvas.getContext('2d');
    ctx.drawImage(img, 0, 0);

    const dataURL = canvas.toDataURL('image/png');

    return dataURL;
}

export { upload }

このStackoverflowpostでこのソリューションに出くわしました。 かなり便利です。 私のオンラインデモはこのサービスを使用しています。

基本的に、コードはソースを読み取り、それをキャンバスに描画し、canvastoDataURL関数を使用してデータURLとして保存します。 キャンバスの詳細については、こちらをご覧ください。

これで、実際のサービスを偽のサービスと交換できます。

<!-- App.vue -->
...

<!-- Javascript -->
<script>
  // swap as you need
  import { upload } from './file-upload.fake.service'; // fake service
  // import { upload } from './file-upload.service';   // real service
</script>

...

終わり! バックエンドAPIを停止し、ブラウザを更新します。アプリがまだ機能していることを確認し、代わりに偽のサービスを呼び出します。

ボーナス:約束を遅らせる

場合によっては、状態の変化を確認するために約束を遅らせたいことがあります。 この場合、ファイルのアップロードが速すぎる可能性があります。 そのためのヘルパー関数を書いてみましょう。

// utils.js

// utils to delay promise
function wait(ms) {
    return (x) => {
        return new Promise(resolve => setTimeout(() => resolve(x), ms));
    };
}

export { wait }

その後、コンポーネントで使用できます

<!-- App.vue -->
...

<!-- Javascript -->
<script>
  import { wait } from './utils';
  ...
  
  save(formData) {
	 ....
	 
        upload(formData)
          .then(wait(1500)) // DEV ONLY: wait for 1.5s 
          .then(x => {
            this.uploadedFiles = [].concat(x);
            this.currentStatus = STATUS_SUCCESS;
          })
         ...
		 
      },
</script>

結論

それでおしまい。 これは、Vueでサードパーティのライブラリやプラグインを使用せずにファイルのアップロードを処理する方法です。 そんなに大変じゃないですか?

ハッピーコーディング!

UI(フロントエンド)

API(バックエンド)チュートリアルとSourcode