Ajax Form Example for Laravel (With request validation)

View:

@extends('layouts.front')


@section('title', '註冊')


@push('styles')
<style>
form .row > div:first-child{
  color: #FFF;
}
.no-padding{
  padding: 0;
}
.flex{
  display: flex;
}
.mr-20{
  margin-right: 20px;
}
</style>
@endpush


@push('scripts')
<script charset="utf-8" src="{{BASE_URL}}assets/front/js/signup.js?v=1.0"></script>
@endpush


@push('modals')

@endpush


@section('content')
<!-- Contact -->
<section id="content">
  <div class="container">
    <div class="row">
      <div class="col-lg-12 text-center">
        <h2 class="section-heading text-uppercase tx-white">註冊會員</h2>
        <h3 class="section-subheading text-muted tx-white">請於下方表單完整輸入</h3>
      </div>
    </div>
    <div class="row">
      <div class="col-lg-12">
        <form id="signup_form" name="signup_form" novalidate action="#" method="post">
          @if ($errors->any())
            <div class="alert alert-danger">
                <ul>
                    @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                    @endforeach
                </ul>
            </div>
          @endif

          <div class="row justify-content-center">
            <div class="col-4 col-sm-3">
              請輸入姓名
            </div>
            <div class="col-8 col-sm-4">
              <div class="form-group">
                <input class="form-control" id="name" name="name" type="text" placeholder="姓名" required data-validation-required-message="請輸入姓名">
                <p class="help-block text-danger"></p>
              </div>
            </div>
          </div>
          <div class="row justify-content-center">
            <div class="col-4 col-sm-3">
              請輸入身份證字號
            </div>
            <div class="col-8 col-sm-4">
              <div class="form-group">
                <input class="form-control" id="pid" name="pid" type="text" placeholder="身份證字號" required data-validation-required-message="請輸入身份證字號" data-validation-callback-callback="check_id_callback">
                <p class="help-block text-danger"></p>
              </div>
            </div>
          </div>
          <div class="row justify-content-center">
            <div class="col-4 col-sm-3">
              請輸入Email
            </div>
            <div class="col-8 col-sm-4">
              <div class="form-group">
                <input class="form-control" id="email" name="email" type="email" placeholder="Email" required data-validation-required-message="請輸入Email" data-validation-email-message="Email格式錯誤">
                <p class="help-block text-danger"></p>
              </div>
            </div>
          </div>
          <div class="row justify-content-center">
            <div class="col-4 col-sm-3">
              聯絡電話
            </div>
            <div class="col-8 col-sm-4">
              <div class="form-group">
                <input class="form-control" id="phone" name="phone" type="tel" placeholder="電話" required data-validation-required-message="請輸入電話">
                <p class="help-block text-danger"></p>
              </div>
            </div>
          </div>
          <div class="row justify-content-center">
            <div class="col-4 col-sm-3">
              手機號碼
            </div>
            <div class="col-8 col-sm-4">
              <div class="form-group">
                <input class="form-control" id="mobile" name="mobile" type="tel" placeholder="手機號碼" required data-validation-required-message="請輸入手機號碼" pattern="\d{10}" data-validation-pattern-message="請輸入10位數字">
                <p class="help-block text-danger"></p>
              </div>
            </div>
          </div>
          <div class="row justify-content-center">
            <div class="col-4 col-sm-3">
              傳真號碼
            </div>
            <div class="col-8 col-sm-4">
              <div class="form-group">
                <input class="form-control" id="fax" name="fax" type="tel" placeholder="傳真號碼">
                <p class="help-block text-danger"></p>
              </div>
            </div>
          </div>
          <div class="row justify-content-center">
            <div class="col-4 col-sm-3">
              聯絡住址
            </div>
            <div class="col-8 col-sm-4">
              <div class="form-group flex">
                <div class="col-md-5 no-padding mr-20">
                  <select class="form-control" id="city" name="city" required data-validation-required-message="請選擇縣市">
                    <option value="">--選擇縣市--</option>
                    @foreach($cities as $item)
                      <option value="{{$item->city}}">{{$item->city}}</option>
                    @endforeach
                  </select>
                  <p class="help-block text-danger"></p>
                </div>
                <div class="col-md-5 no-padding">
                  <select class="form-control" id="area" name="area" required data-validation-required-message="請選擇地區">
                    <option value="">--選擇地區--</option>
                  </select>
                </div>
              </div>
              <div class="form-group">
                <input class="form-control" id="address" name="address" type="text" placeholder="地址" required data-validation-required-message="請輸入地址">
                <p class="help-block text-danger"></p>
              </div>
            </div>
          </div>
          <div class="row justify-content-center">
            <div class="col-4 col-sm-3">
              銀行代碼
            </div>
            <div class="col-8 col-sm-4">
              <div class="form-group">
                <select class="form-control" id="bank" name="bank" required data-validation-required-message="請選擇銀行">
                  <option value="">--選擇--</option>
                  @foreach($banks as $item)
                    <option value="{{$item->bank_id}}">{{$item->bank_id . ' ' . $item->bank_name}}</option>
                  @endforeach
                </select>
                <p class="help-block text-danger"></p>
              </div>
            </div>
          </div>
          <div class="row justify-content-center">
            <div class="col-4 col-sm-3">
              分行代碼
            </div>
            <div class="col-8 col-sm-4">
              <div class="form-group">
                <select class="form-control" id="branch_id" name="branch_id" required data-validation-required-message="請選擇分行">
                  <option value="">--選擇--</option>
                </select>
                <p class="help-block text-danger"></p>
              </div>
            </div>
          </div>
          <div class="row justify-content-center">
            <div class="col-4 col-sm-3">
              銀行帳號
            </div>
            <div class="col-8 col-sm-4">
              <div class="form-group">
                <input class="form-control" id="bank_account" name="bank_account" type="text" placeholder="銀行帳號" required data-validation-required-message="請輸入銀行帳號">
                <p class="help-block text-danger"></p>
              </div>
            </div>
          </div>
          <div class="row justify-content-center">
            <div class="col-4 col-sm-3">
              密碼 (至少5碼)
            </div>
            <div class="col-8 col-sm-4">
              <div class="form-group">
                <input class="form-control" id="password" name="password" type="password" placeholder="密碼 (至少5碼)" required data-validation-required-message="請輸入密碼 (至少5碼)" minlength="5" data-validation-minlength-message="密碼至少5碼">
                <p class="help-block text-danger"></p>
              </div>
            </div>
          </div>
          <div class="row justify-content-center">
            <div class="col-4 col-sm-3">
              確認密碼
            </div>
            <div class="col-8 col-sm-4">
              <div class="form-group">
                <input class="form-control" id="passwordAgain" name="passwordAgain" type="password" required data-validation-required-message="請再次輸入密碼" data-validation-matches-match="password"   data-validation-matches-message="必須和上方輸入的密碼相同" minlength="5" data-validation-minlength-message="密碼至少5碼">
                <p class="help-block text-danger"></p>
              </div>
            </div>
          </div>
          <div class="row">
            <div class="col-lg-12 text-center">
              <div id="success"></div>
              <button class="btn btn-primary btn-xl text-uppercase" type="submit">確定註冊</button>
              {{ csrf_field() }}

              <!--線上遊戲-->
              <input type="hidden" name="cat" value="34">
            </div>
          </div>
        </form>
      </div>
    </div>
  </div>
</section>
@endsection

JS:

$.ajaxSetup({
  headers: {
    'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
  }
});

$(document).ready(function(){
	$('#city').on('change', function(e){
        var $this = $(this);
		var city = $this.val();
        member_app.show_areas(city);
	});

    $('#bank').on('change', function(e){
        var $this = $(this);
        var bank_id = $this.val();
        member_app.show_branches(bank_id);
    });

    /*
    $("input,select,textarea").not("[type=submit]").jqBootstrapValidation({
        submitError: function ($form, event, errors) {
            console.log(errors);
        }, 
        submitSuccess: function ($form, event) {
            console.log(event);
        }
    });
    */

    $('#signup_form').on('submit', function(e){
        e.preventDefault();
        member_app.create_member();
    });

    member_app.init();
});

function check_id_callback($el, value, callback) {
    var result = checkID(value);

    callback({
        value: value,
        valid: result,
        message: "身份證號碼格式錯誤"
    });
}

function checkID(id){
    //建立字母分數陣列(A~Z)
    var city = new Array(
         1,10,19,28,37,46,55,64,39,73,82, 2,11,
        20,48,29,38,47,56,65,74,83,21, 3,12,30
    )
    id = id.toUpperCase();
    // 使用「正規表達式」檢驗格式
    if (id.search(/^[A-Z](1|2)\d{8}$/i) == -1) {
        //alert('基本格式錯誤');
        return false;
    } else {
        //將字串分割為陣列(IE必需這麼做才不會出錯)
        id = id.split('');
        //計算總分
        var total = city[id[0].charCodeAt(0)-65];
        for(var i=1; i<=8; i++){
            total += eval(id[i]) * (9 - i);
        }
        //補上檢查碼(最後一碼)
        total += eval(id[9]);
        //檢查比對碼(餘數應為0);
        return ((total%10 == 0 ));
    }
}

var member_app = function(){
	var $c = null;
    var $form = null;
	var member_id = 0;
	var parent_id = 0;
	var action = 'new';

	function init(){
		$form = $('#signup_form');
	}

    function show_areas(city){
        var params = {
            'city': city
        };

        $.get(base_url + 'ajax/areas', params, function(data){
            if(typeof data.status == 'undefined'){
                alert(data);
            }else if(data.status == 'fail'){
                alert(data.message);
            }else{
                refresh_areas(data.data);
            }
        });
    }

    function refresh_areas(data){
        var $s = $('#area');
        $s.find('option:gt(0)').remove();
        for(var i = 0; i < data.length; i++){
            var area = data[i];
            $s.append('<option value="' + area.id + '">' + area.zip_code + ' ' + area.area + '</option>');
        }
    }

    function show_branches(bank_id){
        var params = {
            'bank_id': bank_id
        };

        $.get(base_url + 'ajax/branches', params, function(data){
            if(typeof data.status == 'undefined'){
                alert(data);
            }else if(data.status == 'fail'){
                alert(data.message);
            }else{
                refresh_branches(data.data);
            }
        });
    }

    function refresh_branches(data){
        var $s = $('#branch_id');
        $s.find('option:gt(0)').remove();
        for(var i = 0; i < data.length; i++){
            var item = data[i];
            $s.append('<option value="' + item.branch_id + '">' + item.branch_id + ' ' + item.branch + '</option>');
        }
    }

    function show_response_error(errors){
        var str = '';
        //console.log(errors);

        for(var key in errors){
            var err = errors[key];
            str += err[0] + '\n';
        }
        
        alert(str);
    }

    function create_member(){
        var params = $form.serialize();
        /*
        var extra = {
            id: id
        }
        params += '&' + $.param(extra);
        */
        //console.log(params);
        //return;

        $.ajax({
            type: 'post',
            url: base_url + 'member/signup_save',
            data: params,
            dataType: 'json',
            success: function(data){
                if(typeof data.status == 'undefined'){
                    alert(data);
                }else if(data.status == 'fail'){
                    alert(data.message);
                }else{
                    //console.log(data);
                    alert('新增成功');
                }
            },
            error: function(data){
                var errors = data.responseJSON;
                show_response_error(errors);
            }
        });
    }

	return {
		init: init, 
		show_areas: show_areas, 
        show_branches: show_branches, 
        create_member: create_member
	}
}();

Conroller:

    public function signup_save(NewMemberReq $req){
    	$fields = Config::get('front.member_fields');
        $data = $req->input();
        logg($data);
    	$data = request_data($req, $fields);
    	if(is_array($data) == false){
    		show_alert($data);
    	}
    	logg($data);

    	if($this->check_nid($data['pid']) == false){
    		show_alert('身份證錯誤');
    	}

    	//Check email
    	$user = MemberM::user_by_email($data['email']);
    	if($user != false){
    		show_alert('Email已經存在');
    	}

    	$member_id = MemberM::new_member($data);
    	if($member_id == false){
    		show_alert('無法新增會員');
    	}

    	$login = [
            'id' => $member_id, 
            'name' => $data['name']
        ];
        session($login);

        show_alert('註冊成功!', BASE_URL);
    }

Form Request Validator:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class NewMemberReq 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 [
            'action' => 'required|' . Rule::in(["new", "update"]), 
            'name' => 'required', 
            'pid' => 'required', 
            'email' => 'required|email|unique:member', 
            'phone' => 'required', 
            'mobile' => 'required|numeric|digits:10', 
            'city' => 'required', 
            'area' => 'required|numeric', 
            'address' => 'required', 
            'bank' => 'required', 
            'branch_id' => 'required', 
            'bank_account' => 'required', 
            'password' => 'required|min:5', 
            'cat' => 'required|numeric', 
        ];
    }

    public function messages()
    {
        return [
            'action.in' => '動作代碼錯誤', 
            'name.required' => '姓名為必填',
            'pid.required' => '身份證號為必填',
            'email.required' => 'Email為必填',
            'email.email' => 'Email格式不正確',
            'email.unique' => 'Email已經存在',
            'phone.required' => '電話為必填',
            'mobile.required' => '手機號碼為必填',
            'mobile.numeric' => '手機號碼為10位數字',
            'mobile.digits' => '手機號碼為10位數字',
            'city.required' => '縣市為必填',
            'area.required' => '地區為必填',
            'area.numeric' => '地區為數字',
            'address.required' => '地址為必填',
            'bank.required' => '銀行為必填',
            'branch_id.required' => '分行為必填',
            'bank_account.required' => '銀行帳號為必填',
            'password.required' => '密碼為必填',
            'password.min' => '密碼至少要5碼',
            'cat.required' => '產品分類為必填',
            'cat.numeric' => '產品分類為數字',
        ];
    }
}