Vue.jsで複数の画像アップロード機能の作成

こんにちはエンジニアの山崎です。

自分はいままでjavascriptのライブラリはjQueryしか使ったことがなかったのですが、 jQueryだとOOが変わったらOOも変わるといった処理を明示的に書かなければいけないためコードが肥大化しがちなので、 あるプロジェクトで最近流行りのVue.jsを導入することにしました。 Vue.jsを使えば、HTMLとデータを紐づけることができるため、冒頭でも触れたOOが変わったらOOも変わるといった処理が少ないコードですっきりと書くことができ、保守性が高まります。(あくまでjQueryとの比較ですが...)

今回作るサンプルですが、 DBの設計で、枚数制限なく複数の画像を登録したい場合は画像を別テーブルで管理して親テーブルと紐づけるというのはよくあるパターンかと思います。(例えばECサイトで製品にスライドショーで使う画像を複数枚登録できるようにしたいなど)

製品の登録フォームで画像に枚数制限がないのでアップロードしたい画像の数だけフォームのfileフィールドを増やせる処理を実装しようと思います。(サーバーサイドのDB保存や画像アップロード処理などは今回のサンプルには含まれないのでご了承ください。)

以下が今回のサンプルコードになります。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div id="app">
        <input name="images[]" type="file" v-for="field in fileFields">
        <button type="button" v-on:click="addFileField">画像を追加</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>
    <script type="text/javascript">
        app = new Vue({
            el: '#app',
            data: {
                fileFields: ['ファイルフィールド1'],
            },
            computed: {
                count: function() {
                    return this.fileFields.length;
                }
            },
            methods: {
                addFileField: function() {
                    var nextCount = this.count += 1;
                    this.fileFields.push('ファイルフィールド'+nextCount);
                }
            }
        });
    </script>
</body>
</html>

コードを解説していきます。

まず最初にCDN経由でVueを読み込みます

<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>

次にVueインスタンスのelプロパティでVueインスタンスの有効範囲を指定します。ここでは#appを指定しているので、#appの外ではこのインスタンスのデータは使用できないので注意してください。

            el: '#app',
        

次に、data属性を指定していきます。今回は複数のファイルフィールドが入るので、fileFieldというプロパティに配列でデータを持たせます。初期状態でfileフィールドを1つ表示させたいので、配列には1つのデータを入れています。

data: {
    fileFields: ['ファイルフィールド1'],
}

fileFields配列の要素の数だけinputを表示させたいので、v-forを使います。 v-forはv-for="ループ変数名 in Vueの配列データ" という風に書くことでその配列の要素数だけHTMLの要素を追加することができます。 今回のサンプルではループ変数は使用していませんが、ループ変数もループの中でマスタッシュ構文で {{field}} のようにかけば表示することができます。(そもそも今回のサンプルだとinputなので閉じタグがない。苦笑)

<input name="images[]" type="file" v-for="field in fileFields">

次に、computedプロパティを説明します。computedプロパティに登録されるプロパティは算出プロパティといって、あるデータが変化したら連動してOOが変わるというデータになります。 下記の場合、countプロパティはfileFieldsの配列の要素数が増えたら(つまり作成されたファイルフィールドの数)自動的にcountが変わる仕組みになっています。 今回はサンプルとして見せたかっただけなのですが、実際今回のケースの場合数値を求める処理が簡単なので、算出プロパティを使うメリットはあまりないかもしれません。

computed: {
    count: function() {
        return this.fileFields.length;
    }
},

次にfileFieldsの配列に要素を追加するメソッドを実装します。 要素を追加されるまでは、countは増えないので次の要素が何番目なのかというのを変数nextCountに代入しています。 そしてfileFieldsの配列の末尾に要素を追加します。 ここで追加される文字列データは今回のサンプルでは表示していないので極端な話なんでもいいです。 inputのname属性で使う文字列を入れてもいいですが、name="images[]"ってやればPHP側で自動的にimagesを配列で受け取ってくれるので動的にname属性を変更する必要がないのでここではやっていません。

methods: {
    addFileField: function() {
         var nextCount = this.count += 1;
         this.fileFields.push('ファイルフィールド'+nextCount);
    }
}

そしてファイルフィールドの追加ボタンを設置します。v-on:イベント名="メソッド名"でイベントが発火した時に実行されるメソッドを登録することができます。

<button type="button" v-on:click="addFileField">画像を追加</button>

これでボタンをクリックするとaddFileFieldメソッドが呼ばれて、fileFieldsの配列に要素が追加され、inputにv-forをつけているので連動して自動的にinputが増えるようになります。

次回は編集画面にて、画像がDBに複数登録されていると過程して、登録されている画像を複数枚表示し、削除したりする処理を書いていきたいと思います!

以下で実際に処理を確かめることができます。

https://jsfiddle.net/yamazakiasahi/znx5kqwf/30/