jQueryのajaxを使って非同期通信を導入したい

今参加しているプログラミングスクールでの課題で、LaravelでECsiteを作っているのですが、ユーザー情報を登録する際に、validationを非同期通信で行ったら便利そうということで導入します

## 作りたいもの
- ECsite（の一部）
- ユーザー情報登録画面で未入力等の項目があった場合にvalidationで弾く
- そのvalidationを非同期で行う（エラーが出たら画面遷移をせずにエラー文を表示）
- エラーがなかったら、入力内容確認画面へ自動遷移

## 非同期通信とは？
以下の記事がとてもわかりやすかったです。</br>
[初心者目線でAjaxの説明](https://qiita.com/hisamura333/items/e3ea6ae549eb09b7efb9)

なんとなくわかった気にはなった...

## 何を使う？
jQueryのajax関数を使うのが簡単でいいらしいです</br>
[はじめてのAjax(jQuery) 2018年版](https://qiita.com/zakiyamaaaaa/items/bdda422db2ccbaea60d9)

## 早速作成
### まずはひな形を作成
フォームの作り方は省略で、以下のものができているとします

```php:user_regist.blade.php
@extends('common.layout')

@section('title')
    ユーザー新規登録
@endsection

@section('header')
@endsection

@section('bodyTitle')
    <h2>新規登録画面</h2>
@endsection

@section('body')
    <form action="user_confirm" method="post" id="form">
    @csrf
        名前（必須）：<br />
        <input type="text" name="user_name" size="50" value="{{old('user_name')}}" /><br /><br />

        パスワード（必須）：<br />
        <input type="password" name="password" size="50" value="{{old('password')}}" /><br /><br />

        Eメール（必須）：<br />
        <input type="text" name="email" size="50" value="{{old('email')}}" /><br /><br />

        住所（必須）：<br />
        <input type="text" name="address" size="50" value="{{old('address')}}" /><br /><br />

        <input type="submit" name="confirm" value="確認"/>
    </form>
@endsection
```
```php:user_confirm.blade.php
@extends('common.outerLayout')

@section('title')
    ユーザー新規登録
@endsection

@section('bodyTitle')
    <h2>確認画面</h2>
@endsection

@section('body')
    <form action="user_complete" method="post">
        @csrf
        <table border="1">
            <tr>
                <td>名前</td>
                <td>{{ $data['user_name'] }}</td>
            </tr>
            <tr>
                <td>Eメールアドレス</td>
                <td>{{ $data['email'] }}</td>
            </tr>
            <tr>
                <td>パスワード</td>
                <td>{{ $data['password'] }}</td>
            </tr>
            <tr>
                <td>住所</td>
                <td>{{ $data['address'] }}</td>
            </tr>
        </table>
        <input type="submit" name="submit" value="送信" />
        <input type="submit" name="submit" value="戻る" />
    </form>
@endsection
```
```php:web.php
<?php

Route::get('/user_regist', 'UserController@getUserRegist');
Route::post('/user_confirm', 'UserController@getUserConfirm');
Route::post('/user_complete', 'UserController@postUserComplete');
```
```php:UserController.php
<?php

namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests\UserRegistRequest;

class UserController extends Controller
{
    public function getUserRegist(){
        return view('user_regist');
    }

    public function getUserConfirm(Request $request){
        $data = $request->all();
        return view('user_confirm', compact('data'));
    }
```
```php:UserRequest.php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class UserRegistRequest 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 [
            'user_name' => 'required',
            'email' => 'required',
            'password' => 'required',
            'address' => 'required'
        ];
    }

    public function messages()
    {
        return [
            'user_name.required' => '名前を入力してください',
            'email.required' => 'メールアドレスを入力してください',
            'password.required' => 'パスワードを入力してください',
            'address.required' => '住所を入力してください'//,
        ];
    }
}
```
本当に必要なものしか書いていません。やっぱりフレームワーク使うと簡単にできていいですね。

### ajax関数の利用
このままだと、入力画面で確認ボタンを押すとPOST通信が開始されてしまうので、ボタンのtype属性をsubmitから、ただのbutton属性にします。そして、このボタンが押されたときにイベントを発生させたいので、id属性を付与しておきます


```php:user_regist.blede.php
        <input type="button" name="confirm" value="確認" id="button"/>
```
次に、このボタンが押されたときに関数が実行されるように、JSを記述します。

```php:user_regist.blede.php
    <script type="text/javascript">
        $('#button').click(function(event) {
            //ここに行いたい処理を記述
        });
    </script>
```
次に処理の内容を書いていきます。
まずは入力された値をJS内の変数に確保しておきます。この時、入力内容の取得はjQueryのval関数を使えば、対象要素のvalueを取得できます。
jQueryはダウンロードしてもよいですが、面倒なのでwebから直接読み込みます。

```php:user_regist.blede.php
@section('header')
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
@endsection

//省略

    <script type="text/javascript">
        $('#button').click(function(event) {
            var user_name = $('input[name="user_name"]').val();
            var password = $('input[name="password"]').val();
            var email = $('input[name="email"]').val();
            var address = $('input[name="address"]').val();

            var data = {'user_name': user_name,
                        'password': password,
                        'email': email,
                        'address': address};
        });
    </script>
```
valを使うセレクタは、’タグ名[属性名＝”属性値”]’ で指定しました。
また、iQueryのajaxによってJSON方式でデータを渡すには、各々のデータを連想配列で渡さないとなので、これをdata変数に作っておきます。

そしていよいよajaxを書いていきます。まずはheaderにCSRFトークンの設定を書きつつ、ajaxSetup関数でajax通信の設定を行います

```php:user_regist.blede.php
@section('header')
    <meta name="csrf-token" content="{{ csrf_token() }}">　　//追加
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
@endsection

//省略

    <script type="text/javascript">
        $('#button').click(function(event) {
            //省略

            $.ajaxSetup({
                headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}
            });
        });
    </script>
```
CRSFはちょっとまだ勉強不足です。。。

次にajax関数を書いていきます

```php:user_regist.blede.php

@section('body')
    <ul id="error_message">    //追加
    </ul>                      //追加

    //省略

    <form action="user_confirm" method="post" id="form">
    <script type="text/javascript">
        $('#button').click(function(event) {
            //省略

            $.ajax({
                url: 'user_confirm',
                type: 'POST',
                dataType: "json",
                data: data,
            }).done(function (results) {
                //通信が成功したときの処理
                console.log(results);
            }).fail(function (jqXHR, textStatus, errorThrown) {
                //通信が失敗したときの処理
                $('#error_message').empty();

                var text = $.parseJSON(jqXHR.responseText);
                var errors = text.errors;
                for (key in errors) {
                    var errorMessage = errors[key][0];
                    $('#error_message').append(`<li>${errorMessage}</li>`);
                }
            });
        });
    </script>
```
まずはurlで送信先のアドレスを指定します。送信方式(type)はPOSTで行います。dataTypeはjsonで、送るデータ(data)は先ほど作成したdata変数を送ります。
そして、doneとfailの中に通信後の処理を書きます。成功時は取り敢えず、console.logで結果を表示するようにします。
失敗時は箇条書き形式でメッセージを表示します。ulタグにid値をつけておいて、jQueryで指定できるようにします。

### エラー内容がうまくいかないとき
次に、エラー内容がうまくいかないときは、フォームリクエストに以下を追加して、エラー内容をJSON形式で返すようにします（最初はうまくいかなかったのに、これを書いているときに再現しようとしたら、エラーがでてこなかった。。。）。

```php:UserRegistRequest.php
use Illuminate\Foundation\Http\FormRequest;

use Illuminate\Contracts\Validation\Validator;           //追加
use Illuminate\Http\Exceptions\HttpResponseException;    //追加

//省略

    protected function failedValidation( Validator $validator )
    {
        $response['data']    = [];
        $response['status']  = 'NG';
        $response['summary'] = 'Failed validation.';
        $response['errors']  = $validator->errors()->toArray();

        throw new HttpResponseException(
            response()->json( $response, 422 )
        );
    }
```

### 成功したときの処理をどうするか？
次にvalidationを通るように入力して送信しますが...</br>
<img width="420" alt="not_json_return.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/422722/ab4d883c-2305-8b47-1fbb-2d8fb3509f13.png">
と、エラーが返ってきて、画面遷移をしてくれません。
これは恐らく、成功時に返している内容が

```php:UserController.php
    public function getUserConfirm(Request $request){
        $data = $request->all();
        return view('user_confirm', compact('data'));
    }
```
と、JSON形式のファイルではなく、viewが返っているからだと思います。
そこで、JSONのresponseをするようにcontrollerを変えますが、getUserConfirmのreturnを変えるとページ遷移ができなくなってしまうので、
- user_regist.blade -> postUserValidation へJSONで送る
- 結果を user_regist.blade へ送る
- エラーが無かったら user_confirm.blade へ遷移する
これを実装していきます。

```php:web.php
Route::get('/user_regist', 'UserController@getUserRegist');
Route::post('/user_validation', 'UserController@postUserValidation');   //追加
Route::get('/user_confirm', 'UserController@getUserConfirm');           //変更
Route::post('/user_complete', 'UserController@postUserComplete');
```
```php:UserController.php
    public function getUserRegist(){
        return view('user_regist');
    }

    public function postUserValidation(UserRegistRequest $request){
        $data = $request->all();
        $request->session()->put('data', $data);
        return response()->json([
            'data'=> $data
        ]);
    }

    public function getUserConfirm(Request $request){
        $data = $request->session()->get('data');

        return view('user_confirm', compact('data'));
    }
```
```php:user_regist.blade.php
//省略

            $.ajax({
                url: 'user_validation',  //変更
                type: 'POST',
                dataType: "json",
                data: data,
            }).done(function (results) {
                //console.log(results)
                window.location.href = 'user_confirm';  //'user_confirm'へ遷移
            }).fail(function (jqXHR, textStatus, errorThrown) {
```
これで無事送れました！

## 反省
この方法だとsessionに入れなくてはいけないので、本当は直接遷移させたかったが、いろいろ調べても分からなかった...

# 参考にしたサイト
[Laravel学習帳 Ajax入門](https://laraweb.net/surrounding/4217/)</br>
[【Laravel5】FormRequestのバリデーション結果をJSON APIで返す](https://qiita.com/junsan50/items/ec7f810decd3b82d3d76)</br>