Laravel5 + Vue.js + Axios でAjaxのpost送信

Laravel Ajax Validation

  • アジェンダ
    • Vue.js + AxiosのAjax通信の骨組み
    • Laravelのバリデーション返り値のAjaxでの受け取り方
    • テキストのpost送信
    • ファイルのpost送信
  • 話さないこと
    • 開発環境などの作り方

    • Vue.jsの詳しい使い方

    • axiosの詳しい使い方

    • Laravelのバリデーションのカスタマイズや日本語化。

Laravelは通常のsubmit方式でも十分にValidationを行える機能がすでに揃っていますが、Ajaxでやったほうが動かきも軽快で気持ちがいいです。ここではLaravelのバリデーションをVue.jsを使って行う方法の覚書です。

少々特殊なのは、Laravelのバリデーションはhttpステータス422で返ってくるということです。postの返り値として200で待ち受けていたら全然違うところから来たので少々びっくりでした。

送信フォームの雛形

<div id="test">
  <input type=text v-model="name">
  <button type="button" @click="post($event)">Submit</button>
</div>
<script>
  var app = new Vue({
    el: '#test',
    data: {
      name: "default name"
    },
    methods: {
      post: function(){
        console.log("submit");
      }
    }
  });
</script>

nameというフォームの値をバインド、Submitボタンでmethodを叩く準備までできました。method内にaxiosを使ってAjax通信をします。ここではpost先のAPIもLaravel側に作っておく必要があります。

Laravel側のルーティングとコントローラ

ルーティングの設定をします。適当なURLを作成してpostできるようにしておきます。

Route::get('/v2/form/tutorial/index', 'Develop\FormController@tutorial_index');
Route::post('/v2/form/tutorial/store', 'Develop\FormController@tutorial_store');

コントローラもまずは適当なものを用意しておきます。

<?php

namespace App\Http\Controllers\Develop;

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;

class FormController extends Controller
{
    public function tutorial_index(Request $request) {
      return view("develop.form_tutorial_index");
    }

    public function tutorial_store(Request $request) {
      return view("develop.form_tutorial_store");
    }
}

tutorial_indexでは先程のHTMLを作成して、tutorial_store()メソッドでバリデーションの処理と保存の処理をすればよいかと思います。

Vue.js側ではaxiosを使ってpostするコードを追加しておきます。骨組みは以下のような感じ。ちょっと補足をしておくとpostのパラメータ渡しはFormData()を使っておくとよい。テキストも画像もこれで送信できる。postのヘッダにはcontent-typeにmultipart/form-dataを入れておく。これも画像送信用。画像の送信がなければ、ヘッダはデフォルトのものでいいので特に設定しなくてもよい。

CSRFトークンを送信する方法はいろいろあるのだけど、ヘッダに設定してやるか、post値に混ぜて送信するのが一般的。どっちでもよい。

<script>
  var app = new Vue({
    el: '#test',
    data: {
      name: "default name"
    },
    methods: {
      post: function(){
        console.log("submit");

        let params = new FormData();
        params.append('name', this.name);
        params.append('_token', '{{csrf_token()}}');

        var config = {
          headers: {
            'content-type': 'multipart/form-data'
          }
        };
        axios.post(
          '/v2/form/tutorial/store',
          params,
          config
        ).then(response => {
            if (response.status === 200) {
            console.log(response);
          }
        }).catch(e => {

        });
      }
    }
  });
</script>

CSRFトークンは、通常のForm送信の場合は以下のようにhiddenで送信している。

<input type="hidden" name="_token" value="L4AbiYBHVQKBvz4zMhH4KmWdsCEEjeEBnIqqKRcZ">

これをAjaxで送信するので単にpost値に_tokenで値を設定してあげればOK。{{csrf_token()}}はLaravelが展開してくれる。これは後ほど設定します。

postを受けるコントローラ側では値をデバックしづらいので私は個人的にログを仕込んだほうがわかりやすいと思っているのでだいたいmonologを引っ張り込んで開発します。(やり方は割愛)vue側でコントローラの返り値を出力するこでもある程度開発できるのだけどやりづらいです。

ここまでで値のpostはできるようになっています。

LaravelのValidationの設定

コントローラのメソッドで以下のようにバリデーションします。やり方はいろいろあるので割愛します。とりあえずわかりやすいように。

public function tutorial_store(Request $request) {

  $this->validate($request, [
    'name' => 'required',
  ]);

  return view("develop.form_tutorial_store");
}

フォームの入力内容を空にしてリクエストすると422が返ってきます。Laravelから返ってきたバリデーションメッセージはe.response.dataで拾うことができます。なので、nameのメッセージは

e.response.data.errors.name

で取得できます。Vueではdataに双方向バインドするのが十八番(オハコ)なので、バリデーションメッセージを格納する変数を作ってあげます。これでエラーメッセージが表示できるようになります。ざっと骨組みだけを作るとこんな感じになります。

<div id="test">
  <input type=text v-model="name">
  <p>@{{error_message_name}}</p>
  <button type="button" @click="post($event)">Submit</button>
</div>

<script>
  var app = new Vue({
    el: '#test',
    data: {
      name: "default name",
      error_message_name: ""
    },
    methods: {
      post: function(){
        console.log("submit");

        let params = new FormData();
        params.append('name', this.name);
        params.append('_token', '{{csrf_token()}}');

        var config = {
          headers: {
            'content-type': 'multipart/form-data'
          }
        };
        axios.post(
          '/v2/form/tutorial/store',
          params,
          config
        ).then(response => {
          if (response.status === 200) {
            console.log(response);
          }
        }).catch(e => {
         console.log(e.response.data.errors.name);
          this.error_message_name = e.response.data.errors.name;
        });
      }
    }
  });
</script>
public function tutorial_store(Request $request) {

  $this->validate($request, [
    'name' => 'required',
  ]);

  // なんか保存の処理

  return view("develop.form_tutorial_store");
}

画像(メディア)をアップロードする

画像を送信する場合はまたひとくせある感じでした。先のコードで予めmultipart/form-dataを設定しておいたのでOKです。

Vueでのやり方は@changeでファイル情報を取得してdataにバインドしてあげるというやり方になります。とても楽です。取得したデータはaxiosのpostパラメータに格納(append)してあげるとLaravelのコントローラ側でリクエストを受け取ることができます。

ファイルのアップロードのためのinputを追加します。

<div id="test">
  <input type=text v-model="name">
  <p>@{{error_message_name}}</p>
  <input @change="selected_file" type="file" name="file">
  <button type="button" @click="post($event)">Submit</button>
</div>

axiosのpostパラメータにもファイルを追加します。バインドするdataと@changeで動作するメソッドを追加します。eventからアップロードするファイルを取得するんだなぁ。

...(省略)

data: {
  name: "default name",
  error_message_name: "",
  file: null
},
methods: {
    selected_file: function(event){
      event.preventDefault();
      let files = event.target.files;
      this.file = files[0];
      console.log(this.file);
    },
    post: function(){

...(省略)

 let params = new FormData();
 params.append('name', this.name);
 params.append('file', this.file);
 params.append('_token', '{{csrf_token()}}');

コントローラ側では画像の保存をすればOKです。本来はバリデーションもちゃんとつけないといけけないと思いますがここではいったん割愛。

...
$this->validate($request, [
  'name' => 'required',
  'file' => 'file|image|mimes:jpeg,gif,png|dimensions:min_width=100,min_height=100,max_width=3600,max_height=3600',
]);
$path = $request->file('file')->store('public/pics');
...

概ねこういう感じになります。

全体ではこう。

<div id="test">
  <input type=text v-model="name">
  <p>@{{error_message_name}}</p>
  <input @change="selected_file" type="file" name="file">
  <p>@{{error_message_file}}</p>
  <button type="button" @click="post($event)">Submit</button>
</div>

<script>
  var app = new Vue({
    el: '#test',
    data: {
      name: "default name",
      error_message_name: "",
      file: null,
      error_message_file: "",
    },
    methods: {
      selected_file: function(event){
        event.preventDefault();
        let files = event.target.files;
        this.file = files[0];
        console.log(this.file);
      },
      post: function(){
        console.log("submit");

        let params = new FormData();
        params.append('name', this.name);
        params.append('file', this.file);
        params.append('_token', '{{csrf_token()}}');

        var config = {
          headers: {
            'content-type': 'multipart/form-data'
          }
        };
        axios.post(
          '/v2/form/tutorial/store',
          params,
          config
        ).then(response => {
          if (response.status === 200) {
            console.log(response);
          }
        }).catch(e => {
          console.log(e.response.data.errors.file);
          console.log(e.response.data.errors.name);
          this.error_message_name = e.response.data.errors.name;
          this.error_message_file = e.response.data.errors.file;
        });
      }
    }
  });
</script>

[Laravel5のCSRFトークンの使い方いろいろ]((https://saba.omnioo.com/note/4150/laravel5%e3%81%aecsrf%e3%83%88%e3%83%bc%e3%82%af%e3%83%b3%e3%81%ae%e4%bd%bf%e3%81%84%e6%96%b9%e3%81%84%e3%82%8d%e3%81%84%e3%82%8d/)

Vue.js でファイルをポストしたいとき

Last update: 2019.06.14 (金)