豆腐とコンソメ

豆腐とコンソメ

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

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

日記

会社で有料セミナーにいってもいいと言われると、お高い普段なかなかいけないような勉強会を探したりしています。

これいいな!と思ったらちょっと高かったりして敬遠したことが何度かあったのに、いざ探すとなるとなかなか見つからないものですね。

本題

Vue.jsでファイルアップロード処理をつくった際にいろいろと勉強になったので、作成の過程を残しておくよ!

正直なところ、バッドプラクティスみたいなものもいっぱいあるかもしれないし、未だにいろいろ悩んでるんだ。

なので指摘をいだけるととすごくうれしいです!


今回つくりたいもの

ファイルとテキスト項目をボタンを押下することで、サーバに送る処理をVueコンポーネントを使ってつくるよ!

環境

Vue.jsはwebpack(laravel-mix)を使ってコンパイルしているよ!


まずはHTML

Vue.jsではコンポートとしてHTMLをがりがり書けるだけに、どこまでをVue.jsで書くのか、というのは悩みどころですね。

今回、大まかなレイアウトはHTMLファイルとして、直接書くことにします。 「タイトル」のテキスト入力欄があるだけのシンプルなフォームになります。

f:id:konoemario:20180116232953p:plain
HTMLの初期イメージ

シンプルフォーム「create.html」

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

上記のHTMLにはコードを見やすくするため、スタイルのためのDOM要素だったり、classは一切書いていません。

ここに、ファイルをドラッグ&ドロップでファイル選択を行うことのできる部品を追加する予定です。

未来のcreate.html

    <form class="simple-form">
        <h2>ファイルアップロード</h2>
        <label for="title">タイトル</label>
        <input type="text" name="title">
        <!-- こんなかんじのVueコンポーネントをつくりたい -->
        <drop></drop>
        <button>投稿する</button>
    </form>

ドラッグ&ドロップをしたタイミングでアップロードする機能はサンプルが結構あったりするのですが、今回は「タイトル」と「ファイル」を投稿するボタンを押したタイミングで合わせてサーバーに送信するところが厄介な部分になります。

f:id:konoemario:20180116234306p:plain
タイトルとファイルをサーバーにおくる


最初のmain.js

さて、早速最初のプログラムmain.jsを書いていきます。

main.jsには、ドラッグ&ドロップのコードは書かずに、ファイルをサーバーにAjaxで送る処理を書くことにします。

まずは、いつも通りにVueインスタンスを生成します。
生成する際には、simple-formクラスを要素に指定します。

main.js

new Vue({
    el:'.form',
})

これだけですが、大きな一歩になります。
さきほどのcreate.htmlからmain.jsを読み込めるようにして置くのを忘れないようにします。


サーバーにデータを送信する処理を書く

では、サーバーにデータを送信する処理を先に書いていきます。

まずは、buttonタグに@clickを追記してボタンをクリックしたときの処理を書くことにします。

buttonタグのtype属性を「button」にすることを忘れて、たまにあれーとなるので気をつけてください。
Ajaxではなく普通のpostを行ったりします。

create.html

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

続いてmain.jsです。
とりあえずclickイベントが発生できているか確認するため、console.logを行うだけの処理onSubmitを書くことにします。

main.js

new Vue({
    el:'.form',
    methods:{
        //ファイル送信処理
        onSubmit:function(){
            console.log('test');
        },        
    },

問題がなければ、サーバー送信処理を書くことにします。
this.titlethis.filesはこの後に追加するよ!

main.js

new Vue({
    el:'.form',
    methods:{
        //ファイル送信処理
        onSubmit:function(){
            console.log('test');

            //送信データは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);
            })
        },        
    },

サーバー送信処理は、FormDataとaxiosを使っておこなっています。

ファイルを送る際にContent-Type=multipart/form-data を指定しよう!とか気にしなきゃいけないのですが、FormDataを使うとそのへんを勝手にやってくれるみたいです。

axiosの詳細な説明はあまりできないので、ググってほしいんだ!


dataを追加する

さきほど、サーバーに送信する処理を書きましたが、this.titlethis.filesを何も定義してないので、値が入ってくることがありません。

なので、データバインディングを使って、フォームの項目とひも付けてあげます。

main.jsに、こんな感じでdataを追加します。

main.js

new Vue({
    el:'.form',
    data:{
        //タイトル
        title:'',
        //ファイル
        files:[]
    },
    methods:{
        //ファイル送信処理
        onSubmit:function(){
           //省略
        },        
    },

タイトルをv-modelでひも付けてあげます。

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>
    </form

これで、タイトルに何かを入力すれば、this.titleに値が反映され、投稿ボタンを押したときにサーバーにタイトルを送るようになります。


filesを送る

肝心のファイルですが、ドラッグ&ドロップの機能はコンポートに記載するので、現段階でひも付ける対象がなかったりします。

後ほどコンポーネントで作成していくのですが、作成する前にファイルを取得するにあたって、特殊なことがあるのでここに記載します。

以下は、コンポーネントではなくcreate.htmlに、直接ファイル選択を書いた例になります。

create.html

    <form class="simple-form">
        <h2>ファイルアップロード</h2>
        <label for="title">タイトル</label>
        <input type="text" name="title" v-model="title">
    <!-- ファイルもここに書くとしたら-->
        <input type="file" mulitple="multiple"> 
        <button type="button" @click="onSubmit">投稿する</button>
    </form

このように書くと、titleと同様に、v-modelを使ってデータバインディングじゃ!となるに違いありません。

create.html

        <!-- だめな例 -->
        <input type="file" mulitple="multiple" v-model="file">  

しかし、input type="file"に対しては、v-modelでデータバインディングすることはできないです。
理由はわからないけれども、公式でいってるんだよ!

なので、inpuy type="file"の値が何かしらかわったときに発生するchangeイベントを使う必要があります。

create.html

        <!-- changeイベントで対応する-->
        <input type="file" mulitple="multiple" @change="onDrop" >  

そして、Vueには以下のようにonDropメソッドを追加することでデータが取得できるようになります。

main.js

new Vue({
    el:'.form',
    data:{
        //タイトル
        title:'',
        //ファイル
        files:[]
    },
    methods:{
        //ファイル送信処理
        onSubmit:function(){
           //省略
        }, 
        //ファイル選択で選んだファイルを取得する  
        onDrop:function(event){
                let file = event.target.files
       }       
    },
`


ようやく本題 コンポートを作成する

さてさて、ようやく本題です。
前述の通り、create.htmlinputタグを直接書いていくのももちろんいいんだけれどもコンポートにしていくよ。

コンポートにすることで、 いろんなページで使い回せたりするんだよ!


drop.vueをつくる

Vue.jsのコンポートですが、生成方法だったり書き方がいろいろあったりします。

※以前、こちらで生成方法を書いたりしました。
www.tohuandkonsome.site

ここでは、レイアウトもDOM要素も、Vueも部品ごとにひとつのファイルにかける方法で書いてみるよ!

drop.vue

//テンプレートにはDOM要素を書く
<template>
    <div>
         <input type="file" @change="onDrop">
    </div>
</template>
//sciprtにはVueインスタンスで書いたときのような処理を書く
<script>
    export default {
        methods:{
            //ファイルを選択したときの処理
            onDrop:function(event){
                let fileList = event.target.files 
            }
        },
    }
</script>
//styleにcssを書いていくんだけれども省略
<style lang="scss">
</style>

まずは、シンプルにファイル選択だけができる処理を書きました。

main.js側にコンポーネントとしてdrop.vueを登録してあげます。

main.js

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

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

こうすることで、create.htmlでdropタグが使えるようになります。

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:20180118222045p:plain
drop.vueコンポーネントが表示される

いつも肝心なところまでたどり着かないので不甲斐ないのですが、次回に続きます。

www.tohuandkonsome.site