Laravel + Vue で問い合わせフォームをつくる

さてさて、ウェブ開発をしているとよく必要になる機能のひとつにお問い合わせフォームがあります。

もちろんクライアントさんだけが利用するタイプのサイトなら不要ですが、それ以外はだいたいどのようなサイトでもユーザーが運営者に連絡ができるようお問い合わせフォームを開設しています。

そこで、今回は比較的ご要望が多いお問い合わせフォームを Larave + Vueで作ってみます。

いつもの内容より基本的な内容が多くなっているので、ぜひ参考にしてくださいね。(最後に今回のプログラムコード一式をダウンロードできます!)

開発環境: Laravel 5.7

やりたいこと

今回開発する内容は以下のようなお問い合わせフォームです。

  • 問い合わせ内容をメールで送信
  • バリデーション(入力チェック)をする
  • Ajax通信で送信(ページの移動はなし)

では実際に開発していきましょう!

お問い合わせの入力ページをつくる(デザイン部分)

ルーティングをつくる

今回必要なルーティングは以下の2つです。

  • お問い合わせの入力ページ
  • Ajaxでお問い合わせ内容を受信するページ

そのため、以下のような内容を追加しましょう。

Route::get('contact', 'ContactController@input'); // 入力ページ
Route::post('contact', 'ContactController@send'); // 送信ページ(Ajax)

※ 本来はHTTPとAjaxなのでコントローラーは分ける方がいいですが、今回は説明のためシンプルにコントローラーはひとつだけで作成しています。

コントローラーをつくる

では、次にコントローラーです。
以下のコマンドで作成しましょう。

php artisan make:controller ContactController

これでapp/Http/Controllers/ContactController.phpが作成されました。
なお、今の段階では、入力ページのためにinput()だけを作成しておきましょう。

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ContactController extends Controller
{
    public function input() {

        return view('contact.input');

    }
}

ビューを作成する

続いては、実際にユーザーが目にするビューです。
resources/views/contact/input.blade.phpを作成します。

※ なお、今回はいつものbootstrapではなくZurb Foundationを使ってみます。こちらのデザインを使わせてもらいました。m_ _m

<html>
<head>
    <link href="//cdnjs.cloudflare.com/ajax/libs/foundation/6.3.1/css/foundation.min.css" rel="stylesheet" id="bootstrap-css">
    <style>

        .error_txt{margin-bottom: 10px; display: block; color: #f00; font-size: 13px;}

        .foundation-example__ex-wrapper{
            background-color: lightblue;
        }
        .foundation-example__in-wrapper{
            margin: auto;
            max-width: 960px;
            background: white;
            padding: 30px;
        }

        .foundation-example__title{
            font-size: 40px;
            margin-bottom: 25px;
        }

    </style>
</head>
<body>
    <section>
        <div style="height: 50px;background: lightblue;"></div>
        <div class="foundation-example__ex-wrapper">
            <div class="foundation-example__in-wrapper">
                <div class="foundation-example__title"> お問い合わせ</div>
                <form>
                    <div class="row">
                        <div class="large-6 columns">
                            <label>名
                                <input type="text">
                                <div class="error_txt"></div>
                            </label>
                            <label>メールアドレス
                                <input type="text">
                                <div class="error_txt"></div>
                            </label>
                        </div>

                        <div class="large-6 columns">
                            <label>姓
                                <input type="text">
                                <div class="error_txt"></div>
                            </label>
                            <label>ご用件
                                <select></select>
                                <div class="error_txt></div>
                            </label>
                        </div>
                    </div>
                    <div class="row">
                        <div class="large-12 columns">
                            <label>お問い合わせ内容
                                <textarea rows="10" placeholder="お問い合わせの内容をご入力ください。"></textarea>
                                <div class="error_txt></div>
                            </label>
                        </div>
                    </div>
                    <div class="row">
                        <div class="large-12 columns text-center">
                            <button class="button expanded" type="button">送信する</button>
                        </div>
                    </div>
                </form>
            </div>
        </div>
        <div style="height: 50px;background: lightblue;"></div>
    </section>
</body>
</html>

では、ページを開いて確認してみましょう。

では、次の項目からこのフォームで問い合わせフォームを作っていきましょう。(Foundationもきれいなデザインになりますね! ^^)

お問い合わせの入力ページをつくる(JavaScript部分)

Vueを使えるようにする

まず、Vueを使えるようにcdnでファイルを読み込んで基本形をつくります。

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

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

</script>

そして、Vueがアクセスするエリアにidを追加。

<section id="app">
<!-- 省略 -->
</section>

データ変数をつくってバインディングする

まずは、dataに次のような変数を追加します。

new Vue({
    el: '#app',
    data: {
        firstName: '',
        lastName: '',
        email: '',
        subjectId: '',
        body: '',
        subjects: {
            100: '商品に関するご質問',
            200: 'お支払に関するご質問',
            300: 'ショッピングに関するご質問',
            400: 'ポイントに関するご質問',
            500: 'ご質問・その他',
        }
    }
});

そして、各<input><select><textarea>タグにv-modelを使ってデータ・バインディングします。

<label>姓
    <input type="text" v-model="lastName">
<!--省略-->

<label>メールアドレス
    <input type="text" v-model="email">
<!--省略-->

<label>名
    <input type="text" v-model="firstName">
<!--省略-->

<label>ご用件
    <select v-model="subjectId"></select>
<!--省略-->

<label>お問い合わせ内容
    <textarea rows="10" placeholder="お問い合わせの内容をご入力ください。" v-model="body"></textarea>

これで、入力内容変更になると同時にVueの変数も自動的に更新されます。(これが、データ・バインディング)

そして、「ご用件」のセレクトボックスですが、まだ選択肢が無いのでこれもVueで準備しておきましょう。

<select v-model="subjectId">
    <option value=""></option>
    <option v-for="(subject,id) in subjects" :value="id" v-text="subject"></option>
</select>

未選択の場合は空白にしておきたいので、1行目の<option>はvalueが空にしています。

そして、2行目はv-forを使って先ほどdataに書いたsubjectsをループし、valueと表示内容をセットしています。

これをVueに表示させると次のようになります。

<select>
    <option value=""></option>
    <option value="100">商品に関するご質問</option>
    <option value="200">お支払に関するご質問</option>
    <option value="300">ショッピングに関するご質問</option>
    <option value="400">ポイントに関するご質問</option>
    <option value="500">ご質問・その他</option>
</select>

では実際にうまく表示されているか確認しておきましょう。

うまくいきました!

Ajaxで送信する部分をつくる

では、送信ボタンをクリックした後のプログラムをつくっていきましょう。(IE11のことも考えてES6は使わないでコーディングしています ^^;)

まず、Ajax通信ができるようにaxiosというJSライブラリをcdnから読み込みます。

<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>

次に送信ボタンにクリックイベントを追加し、onSubmit()を実行できるようにします。

<text class="button" type="text" @click="onSubmit()">送信する</text>

onSubmit()の中身はこうなります。

methods: {
    onSubmit: function() {

        if(!confirm('送信します。よろしいですか?')) {

            return;

        }

        var params = {
            first_name: this.firstName,
            last_name: this.lastName,
            email: this.email,
            subject_id: this.subjectId,
            body: this.body
        };
        axios.post('/contact', params)
            .then(function(response){

                // 成功した時

            })
            .catch(function(error){

                // 失敗したとき

            });

    }
}

バリデーション部分をつくる

後で必要になるので、Ajaxで受信をする部分をつくる前にバリデーション(入力チェック)を作っておきましょう。次のコマンドで専用のRequestを作成します。

php artisan make:request ContactRequest

そして、ここに入力チェック・ルールを書いていきましょう。(太字の部分が変更した場所です)

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class ContactRequest extends FormRequest
{
    /**
     * 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 [
            'first_name' => 'required',
            'last_name' => 'required',
            'email' => 'required|email',
            'subject_id' => 'required|in:100,200,300,400,500',
            'body' => 'required'
        ];
    }
}

※ 本来はsubject_idinは一元管理したデータから呼び出すべき(Vue部分も)ですが、今回は分かりやすい説明のために直書きしています。

ただし、このままではエラー内容は英語のみになってしまうので、resources/lang/ja/validation.phpを作成して、Laravel-lang(バリデーション翻訳)の内容をコピーしましょう。

そして、さらに一番下のattributesに入力値の名前を追加します。

'attributes' => [
    'first_name' => '名',
    'last_name' => '姓',
    'email' => 'メールアドレス',
    'subject_id' => 'ご用件',
    'body' => 'お問い合わせ内容'
],

メール部分をつくる

LaravelにはMailableというクラスが用意されていて、これを利用すると簡単にメール送信ができるようになっています。

では、次のコマンドでContactedという名前のMailableクラスをつくりましょう。

php artisan make:mail Contacted

作成されたapp/Mail/Contacted.phpを開いて、中身を変更します。

<?php

namespace App\Mail;

use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;

class Contacted extends Mailable
{
    use Queueable, SerializesModels;

    private $_params = [];

    /**
     * Create a new message instance.
     *
     * @return void
     */
    public function __construct($params)
    {
        $this->_params = $params;
    }

    /**
     * Build the message.
     *
     * @return $this
     */
    public function build()
    {
        return $this->subject('お問い合わせがありました')
            ->with('params', $this->_params)
            ->view('mail.contact');
    }
}

さらに、まだmail.contactというビューがないので、resources/views/mail/contact.blade.phpを作成し以下の内容を追加します。

以下のお問い合わせを受け付けました。<br>
<br><br>
名前: {{ $params['last_name'] }} {{ $params['first_name'] }}<br>
メールアドレス: {{ $params['email'] }}<br>
ご用件: {{ $params['subject'] }}<br>
お問い合わせ内容: <pre>{{ $params['body'] }}</pre>

Ajaxで受信する部分をつくる

続いて、送信されたデータを受信する部分をつくっていきましょう。
ここからは、Laravel(PHP)部分になります。

先ほど作ったContactControllerを開いてsend()メソッドを追加します。

<?php

namespace App\Http\Controllers;

use App\Http\Requests\ContactRequest;
use Illuminate\Http\Request;

class ContactController extends Controller
{
    // 省略

    public function send(ContactRequest $request) {

        // ここでメール送信

    }
}

ここで、引数の中にContactRequestを使っています。こうすることで自動的に先ほど作ったバリデーションを実行し、問題があればエラーを返してくれます。

では、テストで何も入力せずにデータを送信して、レスポンスがどうなるかをチェックしておきましょう。

うまくいきました。
エラー内容も日本語化されています。

メール送信部分をつくる

では、実際にメールを送信する部分です。

同じくContactControllerを開いてsend()メソッドに以下の内容を追加します。

public function send(ContactRequest $request) {

    $subjects = [
        '100' => '商品に関するご質問',
        '200' => 'お支払に関するご質問',
        '300' => 'ショッピングに関するご質問',
        '400' => 'ポイントに関するご質問',
        '500' => 'ご質問・その他',
    ];
    $params = [
        'first_name' => $request->first_name,
        'last_name' => $request->last_name,
        'email' => $request->email,
        'subject_id' => $request->subject_id,
        'body' => $request->body,
        'subject' => $subjects[$request->subject_id]
    ];
    \Mail::to('admin@example.com')->send(new Contacted($params));
}

※ ここでも説明を分かりやすくするために$subjectsは直書きしていますが、どこかで共通データにすべきです。

テスト送信する

では、今の時点で以下のような内容を入力し、メール送信できるかをチェックしておきましょう。

うまくいけば、以下のようなメッセージが送信されます。

Ajax送信が失敗した時の部分をつくる

では最後に、入力エラーが発生したときの表示も実装しておきましょう。

まず、dataに各入力エラーが入るerrorsという変数をつくります。

data: {

    // 省略

    errors: {}
},

そして、Ajax送信の直前に必ず初期化し、エラーの場合のみそれぞれのエラー内容を格納します。

this.errors = {};

// 省略

axios.post('/contact', params)
    .then(function(){

        // 省略

    })
    .catch(function(error){

        var errors = {};

        for(var key in error.response.data.errors) {

            errors[key] = error.response.data.errors[key].join('<br>');

        }

        self.errors = errors;

    });

※ なお、一旦ローカル変数のerrorsに全エラーを格納しているのは、Vueは配列の中身が変更されてもコンテンツの自動変更は行われないからです。そのため、最後に一気にデータを変更しています。

では、このエラーをv-htmlを使ってそれぞれの位置で表示できるようにしましょう。

<div class="error_txt" v-html="errors.last_name"></div>

// 省略

<div class="error_txt" v-html="errors.email"></div>

// 省略

<div class="error_txt" v-html="errors.first_name"></div>

// 省略

<div class="error_txt" v-html="errors.subject_id"></div>

// 省略

<div class="error_txt" v-html="errors.body"></div>

<br>タグが入ってくる可能性があるのであえてv-htmlにしています。

これで以下のようにエラー内容が表示されるようになりました。

Ajax送信が成功した時の部分をつくる

では、メッセージの送信が完了した場合に入力フォームを消し、「メッセージが送信されました。ありがとうございました。」と表示するようにしてみましょう。

まず、dataにメールが送信されたかどうかが分かるemailSentを追加します。

data: {

    // 省略

    emailSent: false
},

そして、メール送信が完了した時点でemailSendtrueに切り替えます。

var self = this;

// 省略

axios.post('/contact', params)
    .then(function(){

        self.emailSent = true;

    })
    .catch(function(error){

        // 省略

    });

※ 関数内はthisが違う場所を指してしまうので、selfに格納していることに注意してください。(ES6のアロー関数が使えば無視できるのですが、IE対応しているのでこうなりました ^^;)

そして、Vueのv-ifで表示の切り替えです。
まず<form>タグをメールが送信されていない場合だけ表示するようにします。

<form v-if="!emailSent">

逆に、メールが送信された場合には完了メッセージを表示するようにします。(間に何も入れる予定がないならv-elseでもいいでしょう)

<!-- 省略 -->

</form>
<div v-if="emailSent">メッセージが送信されました。ありがとうございました。</div>

これで送信が完了したら以下のようになります。

おまけ

途中で何度か書きましたが、やはり以下のデータはどこかで一元管理すべきなので、以Laravelのモデルから取得できるようにしてみましょう。

subjects: {
    100: '商品に関するご質問',
    200: 'お支払に関するご質問',
    300: 'ショッピングに関するご質問',
    400: 'ポイントに関するご質問',
    500: 'ご質問・その他',
},

まず、次のコマンドでEmailSubjectというモデルを作ります。

php artisan make:model EmailSubject

そして、中身を次のように変更します。(今回はDBへ格納はしないのでextends部分は削除しました)

<?php

namespace App;

class EmailSubject
{
    public static function all() {

        return collect([
            '100' => '商品に関するご質問',
            '200' => 'お支払に関するご質問',
            '300' => 'ショッピングに関するご質問',
            '400' => 'ポイントに関するご質問',
            '500' => 'ご質問・その他',
        ]);

    }
}

これで、\App\Email::all()とするとLaravelのコレクションで全ご用件データを取得することができます。

では、先ほどのinput.balde.phpの中でこのモデルを使ってみましょう。

data: {
    firstName: '',
    lastName: '',
    email: '',
    subjectId: '',
    body: '',
    subjects: {!! \App\EmailSubject::all() !!},
    emailSent: false,
    errors: {}
},

※ Laravelのコレクションはそのものを表示しようとすると自動的にJSON化されます。

また、ContactController$subjectsも置き換えます。

$subjects = \App\EmailSubject::all();

最後に、ContactRequestのバリデーション・ルールも変更すればご用件の一元管理は完了です。

'subject_id' => 'required|in:'. \App\EmailSubject::all()->keys()->implode(','),

今回のプログラム・コードをダウンロード

今回の開発で実際に作成したプログラム・コード一式を以下からダウンロードできます。ぜひ学習に役立ててください。

※ なお、validation.phpはご自身でコピーして使用してください。

Laravel + Vue で問い合わせフォーム: プログラム・コード一式