豆腐とコンソメ

豆腐とコンソメ

もろもろのプログラム勉強記録

ファイルアップロード機能で学ぶVue.js(2)

日記

なんかこう人生に対してやる気がでないときってあるよね!
今、そんな感じ。

よくあるのであまり気にしてないのだけれども、もしこれが続くようなことがあった場合、フリーランスだと怖いなぁって思う。
小説の死のロングウォークを思い出したりするよ。

よくよく考えれば、会社員でも同じかもしれないけれども。

前回の続き

前回はdrop.vueコンポーネントを作成して、main.jsに登録したところまでを行いました。

f:id:konoemario:20180118222045p:plain
前回の様子

その続きを書いていくんだよ!


drop.vueにドラッグ & ドロップでファイル選択を追加する

前回は、inputタグだけを使ってファイル選択のみがてきていた状態でしたが、当初の予定通りドラッグ&ドロップ機能もつけていきます。

まずはテンプレートですが、以下のように、ドラッグ&ドロップができるエリアを用意してあげます。
スタイルは、ここには記載しませんが、dropクラスに対してpaddingなりwidth、heightを指定して、エリアを確保しています。

drop.vue

<template>
    <div class="drop" @dragleave.prevent @dragover.prevent @drop.prevent="onDrop">
    </div>
</template>

@dragleave.preventあたりはドラッグ&ドロップでファイル選択するにあたって定番の処理でしょうか。
ブラウザにドラッグ&ドロップをしてしまうと、ブラウザがファイルを表示しようとしてくれます。
Vue.jsではイベント名の後に.preventと書くこと、デフォルトのイベントの処理をキャンセルすることができます。

ドラッグ&ドロップを行ったときのファイルは@dropイベントが発生したときのonDropに書くことにします。

さて、onDropの処理を書く前に、ドラッグ&ドロップのエリアの中にinputタグもいれることにします。

drop.vue

<template>
    <div class="drop" @dragleave.prevent @dragover.prevent @drop.prevent="onDrop">
        <div class="drop__default-container">
            <label> ファイルを選択
                <input class="drop__input" type="file" multiple="multiple" @change="onDrop">
            </label>
        </div>
    </div>
</template>

こんな感じになりました。

f:id:konoemario:20180122222838p:plain

onDropの処理を書く

つづいてonDropの処理を書いていきます。

といっても大した内容ではないのでさくっといきます。

drop.vue

<script>
    export default {
        methods:{
        //inputタグとドラッグ&ドロップから呼ばれる
            onDrop:function(event){
                let fileList = event.target.files ? 
                               event.target.files:
                               event.dataTransfer.files;

                let files = [];

                for(let i = 0; i < fileList.length; i++){
                    files.push(fileList[i]);
                }
            }
        },
    }
</script>

onDropはファイル選択とドラッグ&ドロップから呼ばれます。
ファイル選択から呼ばれた場合、event.target.filesからファイルを取得することができます。

一方、ドラッグ&ドロップの場合は、event.dataTransfer.filesで取得することができます。

その後に、取得したファイルをfiles変数に格納しています。

これだけでとりあえず、ファイルを取得するコンポーネントdropが完成しました!


ちょっと前回を振りかえる

前回は、dropコンポーネントをもつmain.jsがいました。
main.jscreate.htmlのフォームの内容をaxiosでサーバーに送信する処理を担っています。

main.js

//ファイル選択コンポーネント
const Drop = require('./components/drop');

new Vue({
    el:'.form',
    data:{
        //タイトル
        title:'',
        //ファイル
        files:[]
    },
    methods:{
        //ファイル送信処理
        onSubmit:function(){
            //送信データはFormDataを使うよ!
            let data = new FormData;

            //titleを追加
            data.append('title', this.title);

            //filesは複数ファイルを選択できる想定なのでループで追加するよ!
            for(let i = 0; i < this.files.length; i++){
                data.append('files[]', this.files[i]);
            }

            //axiosでサーバーに送るよ!
            axios.post('/file',data)
            .then((response) => {
                console.log(response.data);
            })
            .catch((error) => {
                console.log(error);
            })
        },    
    },
    components:{
        //コンポーネントを登録する!
        'drop':Drop
    }

create.htmlはこんな感じで、省略しちゃってますがさきほどのmain.jsを読み込んでおります。

create.html

    <form class="simple-form">
        <h2>ファイルアップロード</h2>
        <label for="title">タイトル</label>
        <input type="text" name="title" v-model="title">
        <button type="button" @click="onSubmit">投稿する</button>
        <drop></drop>
    </form>

図にするとこんな感じでしょうか。

f:id:konoemario:20180122215656p:plain
わかりにくい関係図

それでですね、今drop.vueにファイル選択とドラッグ&ドロップでファイルを取得することができるようになりました。

f:id:konoemario:20180122220034p:plain
今の状態

今回、この取得したファイルをmain.jsに渡して、サーバーに送信したいわけです。


子のdrop.vueから親にデータを渡す

ということで、Vue.jsのコンポーネントで避けて通れないemitを使うことにします。

このへんについては、以前も書いたのでここでは割愛します。
www.tohuandkonsome.site

イメージはものっそい汚い絵ですが、こんな感じになります。

f:id:konoemario:20180122221122p:plain
汚い絵

コードはこんな感じになります。

drop.vue

<script>
    export default {
        methods:{
        //inputタグとドラッグ&ドロップから呼ばれる
            onDrop:function(event){
                let fileList = event.target.files ? 
                               event.target.files:
                               event.dataTransfer.files;

                let files = [];

                for(let i = 0; i < fileList.length; i++){
                    files.push(fileList[i]);
                }

                //イベントsend-fileを発火させて、files変数を渡す
                this.$emit('send-file', files);
            }
        },
    }
</script>

create.htmlでは、ややこしいですがdrop.vueが発生するイベントsend-fileをキャッチして、sendFile関数を呼びます。

create.html

    <form class="simple-form">
        <h2>ファイルアップロード</h2>
        <label for="title">タイトル</label>
        <input type="text" name="title" v-model="title">
        <button type="button" @click="onSubmit">投稿する</button>
        <drop @send-file="sendFile"></drop>
    </form>

最後にsendFile関数をmain.jsに書いてあげます。

main.js

//ファイル選択コンポーネント
const Drop = require('./components/drop');

new Vue({
    el:'.form',
    data:{
        //タイトル
        title:'',
        //ファイル
        files:[]
    },
    methods:{
        //ファイル送信処理
        onSubmit:function(){
          //省略
        },    
        sendFile(files){
            this.files = files;
        }
    },
    components:{
        //コンポーネントを登録する!
        'drop':Drop
    }

senfFileは、drop.vueが発火させるイベントからfiles変数を受け取っています。この中身をmain.jsのデータであるthis.filesに格納することで無事、子から親にデータを渡すことができました。

あとは、前回書きましたonSubmitの処理を書くことで選択したファイルをサーバーに送信することができるようになりました。

やりたかったことは以上になります。

他にもdrop.vueコンポーネントは、ファイル選択したあとも見た目がかわらなかったりするので、もう少し使い勝手のよいコンポーネントに直したりとかも宿題はあるのですが、こちらは気がむいたら書いていこうと思います。