完全な手順!LaravelでAjaxコンテンツをつくる方法

最近ではホントにAjaxを使ったウェブコンテンツが多くなってきましたね。

個人的にはSPA(Single-page Application)っていうのは、「かっこいいけど複雑すぎて保守に向かないやりかた」という位置づけに落ち着いたんですけど、それでもやっぱりAjaxを使って開発をすると時短になりますし、とても重宝しています。

そこで、今回は開発者として、

今からAjaxを使ってお問い合わせ機能を作れ!

と言われたらどんな流れで開発を進めるのか?を順を追って説明してみたいと思います。

開発環境は、

  • PHP: Laravel 5+
  • JavaScript: Vue.js, axios

です。では行ってみましょう!

サンプルはこちら

1.土台をつくる

まずは土台となる3つのファイルをつくらないといけません。
この場合、以下の3つですね。

  • コントローラー(Controller)
  • ルーティング(Routing)
  • ビュー(View)

ひとつひとつみていきましょう。

(1)コントローラー

Laravelにはコントローラー・ファイルを自動で作ってくれるコマンドが用意されています。
以下のように作成しましょう。

php artisan make:controller ContactController

※本来ならすでにあるHomeControllerに書くんですけど、今回はテストということで専用コントローラーです。

で、コントローラーが作成されたら以下のように、Viewを設定します。
(Viewはあとでつくります)

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;

class ContactController extends Controller
{
    public function index() {

        return view('contact');

    }
}

で、Ajaxを使ってデータを送受信するので、さっきの要領でAjax専用のコントローラーもつくりましょう。(Ajaxの後はバックスラッシュ2連続です)

php artisan make:controller Ajax\\ContactController

中身としては、メール送信になるんですけど、今回は省略しています。
また、レスポンスはjsonで返しています。

<?php

namespace App\Http\Controllers\Ajax;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

class ContactController extends Controller
{
    public function store() {

        // メールを送信する
        return response()->json([
            'result' => true
        ]);

    }
}

はい、これでコントローラーは完了です!

(2)Route

ルーティングなんですけど、Laravelのバージョンでファイルが違うので、注意が必要です。

Laravel 5.3 以降

/routes/web.php

Laravel 5.2 以前

/app/Http/routes.php

Laravelも日々進化してるんですね。

で、実際の設定は以下のとおりです。

Route::get('/contacts', 'ContactController@index');

Route::group(['prefix' => 'ajax'], function() {

    Route::resource('contacts', 'Ajax\ContactController', [
        'only' => ['store']
    ]);

});

2つ目ちょっとがちょっとわかりにくいかもしれませんけど、要するにpostで /ajax/contacts にアクセスするようにしています。シンプルなほうがいい場合は、

Route::get('/contacts', 'ContactController@index');
Route::post('/ajax/contacts', 'Ajax\ContactController@store');

でもOKです。(この辺はRESTfulとかを調べてください)

さぁ、これでルーティングも完了です。

(3)ビュー

ビューは簡単で、さっきコントローラーで設定した場所にファイルをつくるだけです。
(今回はテストなので@extendsなどは使わず、直にHTMLタグを書いてますし、metaタグなどは省略しています。)

Vueとaxiosはcdnを使ってロードし、axiosをvue内で使いやすくするために$httpにセットしています。

※ちなみになぜビューには作成コマンドがないのか不思議です。わざわざこんなパッケージを公開してる人がいるぐらいなんだから、つければいいのに・・・・・・。

/views/contact.blade.php

<html>
<head>
    <title>お問い合わせ</title>
</head>
<body>
    <div id="app">
        <label>名前</label>
        <input type="text">
        <br>
        <label>メールアドレス</label>
        <input type="text">
        <br>
        <label>内容</label>
        <textarea></textarea>
        <br>
        <button type="button">送信する</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>
        Vue.prototype.$http = axios;

        new Vue({
            el: '#app'
        })

    </script>
</body>
</html>

さぁ、これで土台が完了しました!
次からJavaScript部分を見ていくことにしましょう!

Vueを使ったAjax部分

まずはinputにv-modelを使って変数をバインディングしていきましょう。

(JS)

        new Vue({
            el: '#app',
            data: {
                params: {
                    name: '',
                    email: '',
                    body: ''
                }
            }
        });

(HTML)

<label>名前</label>
<input type="text" v-model="params.name">
<br>
<label>メールアドレス</label>
<input type="text" v-model="params.email">
<br>
<label>内容</label>
<textarea v-model="params.body"></textarea>

はい!
これで入力ボックスに変更があったら、自動でparamsの中身が書きかわりますし、逆にparamsを変更すれば入力値が自動更新されます。(ツー・ウェイ・データ・バインディングですね。マジ便利!)


そしたら次は送信ボタンがクリックされたときのイベントです。
これもVueなら簡単ですよ!

(JS)

new Vue({
    el: '#app',
    data: {
        params: {
            name: '',
            email: '',
            body: ''
        }
    },
    methods: {
        onClick: function() {

            // ここにAjax

        }
    }
});

※そろそろアロー関数とか使うべきなんでしょうけど、例の問題児、IEがまだサポートしてないらしいので、あえてfunctionを使っています(笑)

(HTML)

<button type="button" @click="onClick">送信する</button>

 

では、次にAjax通信部分です。
これもaxiosなら簡単ですよ。

onClick: function() {

    this.$http.post('/ajax/contacts', this.params)
        .then(function(response){

            // 成功したとき

        }).catch(function(error){

            // 失敗したとき

        });

}

さっき、Vueにaxiosを設定したんで、this.$httpとして呼び出すことができます。
しかも、データ・バインディングでthis.paramsの中には入力値がすべて含まれているんですね。

では、通信が成功したときのコードを見ていきましょう。
アラートを出して、入力値をクリアします。

onClick: function() {

    var self = this;

    this.$http.post('/ajax/contacts', this.params)
        .then(function(response){

            // 成功したとき
            self.params = {
                name: '',
                email: '',
                body: ''
            };
            alert('送信が完了しました。');

        }).catch(function(error){

            // 失敗したとき

        });

}

ここで重要なのは「self」の部分です。axiosのthen、catchの中身はコールバック関数なんで、thisだとスコープ違いでデータを参照できないんですね。

では、次に通信に失敗した場合なんですけど、今のところ/ajax/contactsではエラーは発生しません。なので、先にバリデーションを作っていきましょう。
これもLaravelにはいいコマンドがあります。

php artisan make:request ContactRequest

ContactRequestは「app/Http/Requests/ContactRequest.php」にできているのでファイルを開いてバリデーション設定をします。

<?php

namespace App\Http\Requests;

use App\Http\Requests\Request;

class ContactRequest extends Request
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required',
            'email' => 'required|email',
            'body' => 'required'
        ];
    }
}

デフォルトから変更したのは、autorize()returntrueへ。
それから、rules()内で各種バリデーション・ルールを書き込んでいます。

これが完了したらAjax\ContactControllerに設置しましょう。
こんなカンジです。

<?php

namespace App\Http\Controllers\Ajax;

use Illuminate\Http\Request;

use App\Http\Requests\ContactRequest;
use App\Http\Controllers\Controller;

class ContactController extends Controller
{
    public function store(ContactRequest $request) {

        // メールを送信する
        return response()->json([
            'result' => true
        ]);

    }
}

useを使ってネームスペースを解決しているのに注意して下さいね。
これで、$requestを使って入力値を取得できますし、自動でバリデーションも機能します。

※ちなみにバリデーションに失敗した場合はHTTPステータスコードは422が返ってきます。

さぁ、ではエラー内容を入力ボックスに表示してみましょう!

(JS)

new Vue({
    el: '#app',
    data: {
        params: {
            name: '',
            email: '',
            body: ''
        },
        errors: {
            name: '',
            email: '',
            body: ''
        }
    },
    methods: {
        onClick: function() {

            var self = this;
            this.errors = {
                name: '',
                email: '',
                body: ''
            };

            this.$http.post('/ajax/contacts', this.params)
                .then(function(response){

                    // 成功したとき
                    // (コード省略)

                }).catch(function(error){

                    // 失敗したとき
                    for(var key in error.response.data) {

                        self.errors[key] = error.response.data[key][0];

                    }

                });

        }
    }
});

ちょっとコードが多くなっちゃいましたけど、やってることはさっきと一緒で、errorsという変数を用意してあげて、エラー文章をそこへ格納してるだけです。

で、errorsはAjax送信する直前に、必ずerrorsを初期化するようにしています。
つづいてHTMLのエラー表示です。

(HTML)

<label>名前</label>
<input type="text" v-model="params.name">
<div v-if="errors.name">@{{ errors.name }}</div>
<br>
<label>メールアドレス</label>
<input type="text" v-model="params.email">
<div v-if="errors.email">@{{ errors.email }}</div>
<br>
<label>内容</label>
<textarea v-model="params.body"></textarea>
<div v-if="errors.body">@{{ errors.body }}</div>

注目すべきところは「v-if」でエラーが存在してるときは表示、そうじゃなければ表示しないようにしているところです。

さっきも書きましたけど、Vueのバインディングはデータが更新されたらすぐ表示も変更になるので、こんなシンプルな書き方ができるわけです。

ちなみにバリデーション・エラーの日本語化をしたい場合は/resources/lang/jaというフォルダを作りそこにここから持ってきた翻訳データを格納すればOKです。

もし切り替わらない場合は、

app()->setLocale('ja');

などとして言語を変更してください。

そして、このままでは「nameは、必ず指定してください。」という具合にアトリビュートが変数名になってしまうので、validation.php内のattributes項目に以下のように追加してください。

'attributes' => [
    'name' => '名前',
    'email' => 'メールアドレス',
    'body' => '問い合わせ内容'
],

はい!これでエラー部分も完了です。

では、最後にAjax部分の完全版コードを紹介しておしまいにします。

<html>
<head>
    <title>お問い合わせ</title>
</head>
<body>
    <div id="app">
        <label>名前</label>
        <input type="text" v-model="params.name">
        <div v-if="errors.name">@{{ errors.name }}</div>
        <br>
        <label>メールアドレス</label>
        <input type="text" v-model="params.email">
        <div v-if="errors.email">@{{ errors.email }}</div>
        <br>
        <label>内容</label>
        <textarea v-model="params.body"></textarea>
        <div v-if="errors.body">@{{ errors.body }}</div>
        <br>
        <button type="button" @click="onClick">送信する</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.5.13/dist/vue.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script>

        Vue.prototype.$http = axios;

        new Vue({
            el: '#app',
            data: {
                params: {},
                errors: {}
            },
            methods: {
                initParams: function(){

                    this.params = {
                        name: '',
                        email: '',
                        body: ''
                    };

                },
                initErrors: function(){

                    this.errors = {
                        name: '',
                        email: '',
                        body: ''
                    };

                },
                onClick: function() {

                    var self = this;
                    this.initErrors();

                    this.$http.post('/ajax/contacts', this.params)
                        .then(function(response){

                            // 成功したとき
                            self.initParams();
                            alert('送信が完了しました。');

                        }).catch(function(error){

                            // 失敗したとき
                            for(var key in error.response.data) {

                                self.errors[key] = error.response.data[key][0];

                            }

                        });

                }
            },
            mounted: function() {

                this.initParams();
                this.initErrors();

            }
        });

    </script>
</body>
</html>

重複するコードがあったんで、ひとつにまとめてますけど、内容は一緒です。

vueが初めての方に説明が必要だとしたらmountedの部分。
これはページが読み込まれたらすぐに起動する場所です。window.onloadみたいなもんですね。

と、いうことで今回はちょっと長めの記事を書いてみました。
この記事で、Laravelの和が広がればうれしいです!

ではでは(^^)



にほんブログ村 IT技術ブログへ  にほんブログ村 IT技術ブログ プログラム・プログラマーへ


BugGUI バグ報告を効率化
たった3分でバグ報告完了!? BugGUI