前回作成したVue/Vuetifyのフォームにおける登録処理時のLaravel側での非同期バリデーションの実装です。具体的にはAxiosで非同期でフォーム値を送信し、LaravelのRequestでバリデーションを行いエラーがあればAxiosのレスポンスに返してコンポーネントにエラー表示させます。
目次
開発環境
Laradock v10
Laravel 7
Vue 2.6
Laravel:モデル作成
登録もできるようにテーブルとモデルも作っておきましょう。
# php artisan make:model Models/Item --migration
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateItemsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('items', function (Blueprint $table) {
$table->id();
$table->string('textbox', 255);
$table->text('textarea');
$table->tinyInteger('radiobtn');
$table->tinyInteger('select');
$table->string('checkbox', 255);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('items');
}
}
※何か違うと思ったら、Laravel7では $table->id() は $table->bigIncrements(‘id’) のエイリアスらしい。
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Item extends Model
{
protected $fillable = [
'textbox', 'textarea', 'radiobtn', 'select', 'checkbox',
];
}
Laravel:フォームリクエスト作成
# php artisan make:request Ajax/User/ItemRequest
<?php
namespace App\Http\Requests\Ajax\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
class ItemRequest 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 [
'textbox' => 'required|max:16',
'textarea' => 'required|max:100',
'radiobtn' => 'required',
'select' => 'required',
'checkbox' => 'required',
];
}
public function messages()
{
return [
'textbox.required' => ':attributeは必須項目です。',
'textbox.max' => ':attributeは16文字以内です。',
'textarea.required' => ':attributeは必須項目です。',
'textarea.max' => ':attributeは100文字以内です。',
'radiobtn.required' => ':attributeは必須項目です。',
'select.required' => ':attributeは必須項目です。',
'checkbox.required' => ':attributeは1つ必ず選択してください。',
];
}
public function attributes()
{
return [
'textbox' => 'テキストボックス',
'textarea' => 'テキストエリア',
'radiobtn' => 'ラジオボタン',
'select' => 'セレクトボックス',
'checkbox' => 'チェックボックス',
];
}
/**
* [override] バリデーション失敗時ハンドリング
*
* @param Validator $validator
* @throw HttpResponseException
* @see FormRequest::failedValidation()
*/
protected function failedValidation(Validator $validator) {
$response['status'] = 400;
$response['statusText'] = 'Failed validation.';
$response['errors'] = $validator->errors();
throw new HttpResponseException(
response()->json( $response, 200 )
);
}
}
failedValidationメソッドをオーバーライドしてバリデーション失敗時のレスポンスを設定します。
Laravel:コントローラー作成
RESTfulなコントローラーを作成します。
# php artisan make:controller Ajax/User/ItemController --resource
登録処理(storeメソッド)を作成
<?php
namespace App\Http\Controllers\Ajax\User;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Http\Requests\Ajax\User\ItemRequest;
use App\Models\Item;
class ItemController extends Controller
{
public function store(ItemRequest $request)
{
$status = 200;
$message = null;
$data = $request->all();
$data['checkbox'] = implode(',', $data['checkbox']); // 配列をカンマ区切りのテキストに変換
$item = new Item();
$result = $item->fill($data)->save();
return response()->json(['post' => $data, 'status' => $status, 'message' => $message]);
}
}
Laravel:ルーティング
Route::middleware('verified')->group(function() {
Route::get('/user/{any?}', function() {
return view('user');
})->where('any', '.*');
Route::resource('/ajax/user/item', 'Ajax\User\ItemController'); // 追加
});
Vue:フォームデータ送信/レスポンス処理
・各フォーム部品のエラー初期化
・フォーム送信機能(submit)を実装し、バリデーションエラーが帰ってきたらエラー情報にセット。
・次項のエラー表示で使用するエラーのリセット機能(clearError)の実装
<script>
export default {
data() {
return {
・・・
// エラー情報初期化
errors: {
textbox: false, // 追加
textarea: false, // 追加
radiobtn: false, // 追加
select: false, // 追加
checkbox: false,
},
messages: {
textbox: null, // 追加
textarea: null, // 追加
radiobtn: null, // 追加
select: null, // 追加
checkbox: null,
}
}
},
・・・
methods: {
// 送信ボタンを押したとき
submit() {
// 全てのエラーをリセット
Object.keys(this.errors).forEach((key) => {
this.errors[key] = false;
this.messages[key] = null;
})
// 送信処理
axios.post('/ajax/user/item', this.forms)
.then((res) => {
let response = res.data;
if (response.status == 400) {
// バリデーションエラー
Object.keys(response.errors).forEach((key) => {
this.errors[key] = true;
this.messages[key] = response.errors[key];
})
} else {
// 成功したらUserItemコンポーネントを表示
this.$router.push('/user/item');
}
})
.catch((error) => {
console.log(error.response)
})
},
// 各エラーのリセット
clearError(item) {
this.errors[item] = false;
this.messages[item] = null;
},
・・・
Vuetify:エラー表示
各フォーム部品に :error と :error-messages を追加します。
また、一度エラーを表示させると表示されたままになります。
エラーをリセットしないと送信ボタンが有効にならないので
テキストボックスとテキストエリアには @keydown
ラジオボタンとセレクトボックスには @change
で入力値の変更を監視してエラーをリセットします。
なお、チェックボックスについて変更の監視は 前回実装済です。
<v-text-field
v-model="forms.textbox"
label="テキストボックス"
:rules="[rules.required, rules.max_16]"
:error="errors.textbox" // 追加
:error-messages="messages.textbox" // 追加
@keydown="clearError('textbox')" // 追加
></v-text-field>
<v-textarea
v-model="forms.textarea"
label="テキストエリア"
auto-grow
:rules="[rules.required, rules.max_100]"
:error="errors.textarea" // 追加
:error-messages="messages.textarea" // 追加
@keydown="clearError('textarea')" // 追加
></v-textarea>
<v-radio-group
v-model="forms.radiobtn"
label="ラジオボタン"
row
:rules="[rules.required]"
:error="errors.radiobtn" // 追加
:error-messages="messages.radiobtn" // 追加
@change="clearError('radiobtn')" // 追加
>
<v-radio
v-for="(name, index) in constant.RADIOS"
:key=index
:label=name
:value=index
></v-radio>
</v-radio-group>
<v-select
v-model="forms.select"
label="セレクトボックス"
:items="constant.SELECTS"
item-text=name
item-value=id
:rules="[rules.required]"
:error="errors.select" // 追加
:error-messages="messages.select" // 追加
@change="clearError('select')" // 追加
></v-select>
<v-container>
<v-row>
<div
:class="errors.checkbox ? `theme--light v-label error--text` : `theme--light v-label`"
style="margin-bottom: 0.5rem;"
>チェックボックス</div>
</v-row>
<v-row>
<v-checkbox
v-model="forms.checkbox"
v-for="(name, index) in constant.CHECKS"
:key=index
:label=name
:value=index
style="margin: 0 16px 0 0;"
:rules="[rules.check_least_1]"
:error="errors.checkbox"
hide-details
@change="changeCheckbox"
></v-checkbox>
</v-row>
<div
v-for="(message, index) in messages.checkbox"
:key=index
class="v-messages error--text row"
>{{ message }}</div>
</v-container>
バリデーションテスト
Vue/Vuetify側のバリデーションを無効化してテストします。
rules: {
/*
required: value => !!value || '必須です。',
max_16: value => value.length <= 16 || '16文字以内です。',
max_100: value => value.length <= 100 || '100文字以内です。',
check_least_1: value => {
return value.length > 0 || '1つは必須選択です。'
},
*/
required: true,
max_16: true,
max_100: true,
check_least_1: true,
ラジオボタンをクリアして送信ボタンを押したら、Laravelのバリデーションで設定したメッセージが出たらOKです。