LaravelでVue/Vuetifyを使ってFormを作成

前回作成した管理画面で、Vue/Vuetifyを使用してテキストボックス、テキストエリア、ラジオボタン、チェックボックス、セレクトボックスの送信フォームを作成します。ラジオボタン、チェックボックス、セレクトボックスのリストはAxiosを利用してLaravelの定義ファイルから非同期で取得します。

開発環境

Laradock v10
Laravel 7
Vue 2.6

Laravel:定義ファイル作成

ラジオボタン、セレクトボックス、チェックボックスの配列を定義します。

config/const.php
-----
<?php

return [

    /*
    |--------------------------------------------------------------------------
    | 定数定義
    |--------------------------------------------------------------------------
    |
    |
    */

    'RADIOS' => [
        '1' => 'Radio1',
        '2' => 'Radio2',
        '3' => 'Radio3',
    ],
    'SELECTS' => [
        '1' => 'Select1',
        '2' => 'Select2',
        '3' => 'Select3',
        '4' => 'Select4',
    ],
    'CHECKS'  => [
        '1' => 'Check1',
        '2' => 'Check2',
        '3' => 'Check3',
        '4' => 'Check4',
        '5' => 'Check5',
        '6' => 'Check6',
        '7' => 'Check7',
        '8' => 'Check8',
    ],

];
-----

Laravel:Controller作成

定義したラジオボタン、セレクトボックス、チェックボックスの配列をJSON形式で返すだけのコントローラーです。

# php artisan make:controller ConstController
app/Http/Controllers/ConstController.php
-----
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class ConstController extends Controller
{
    public function __invoke() {
        $list = \Config::get('const');
        return response()->json(['const' => $list]);
    }
}
-----

Laravel:Route設定

routes/web.php
-----
Route::get('/const', 'ConstController');
-----

ここまでで
http://localhost/const
にアクセスすると定義データがJSON形式で表示されると思います。

管理画面にメニュー登録

HeaderComponentにリンク先を/user/formとしてメニューを追加します。

resources/js/components/User/HeaderComponent.vue
-----
<script>
  export default {
    data () {
      return {
        drawer: null,
        items: [
          { title: 'Dashboard', icon: 'mdi-view-dashboard', to: "/user/dashboard" },
          { title: 'Account', icon: 'mdi-account-box', to: "/user/account" },
          { title: 'Admin', icon: 'mdi-gavel', to: "/user/admin" },
          { title: 'Item', icon: 'mdi-view-list', to: "/user/item" },	// 追加★
        ],
        csrf_token: document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
      }
    },
    methods: {
      logout() {
        document.getElementById('logout-form').submit();
      },
    },
  }
</script>
-----

User/FormComponent作成

router.jsの設定だけ済ませたいので、とりあえず、簡単なコンポーネントを作成しておきます。

一覧用:登録フォームへのリンクを作成しておきます。

resources/js/components/User/ItemComponent.vue
-----
<template>
<div>
  <h1>UserItem</h1>

  <router-link to="/user/item/add">
    <v-btn>新規登録</v-btn>
  </router-link>

</div>
</template>
-----

登録フォーム用

resources/js/components/User/ItemAddComponent.vue
-----
<template>
<div>
  <h1>UserItemAdd</h1>

</div>
</template>
-----

router.jsにコンポーネントを追加

ItemComponentとItemAddComponentをインポートしてルートを追加します。

resources/js/router.js
-----
import Vue from 'vue'
import Router from 'vue-router'

import UserDashboard from './components/User/DashboardComponent'
import UserAccount from './components/User/AccountComponent'
import UserAdmin from './components/User/AdminComponent'
import UserItem from './components/User/ItemComponent'			// 追加★
import UserItemAdd from './components/User/ItemAddComponent'	// 追加★

Vue.use(Router)

export default new Router({
    mode: 'history',
    routes: [
        {
            path: '/user',
            redirect: '/user/dashboard'
        },
        {
            path: '/user/dashboard',
            name: 'user-dashboard',
            component: UserDashboard,
        },
        {
            path: '/user/account',
            name: 'user-account',
            component: UserAccount,
        },
        {
            path: '/user/admin',
            name: 'user-admin',
            component: UserAdmin,
        },
        // 以下追加★
        {
            path: '/user/item',
            name: 'user-item',
            component: UserItem,
        },
        {
            path: '/user/item/add',
            name: 'user-item-add',
            component: UserItemAdd,
        },
    ],
})
-----
laravel-vue-vuetify-form-1

Vuetifyでフォームを作成

UserItemAddComponent.vueにフォームを作成してテキストボックス、テキストエリア、ラジオボタン、セレクトボックス、チェックボックスと送信ボタンを設置します。

Vuetifyのサイト
https://vuetifyjs.com/ja/getting-started/quick-start/
のUIコンポーネント「Form input & controls」を参考に各部品を作成します。

【バリデーションについて】
すべて必須とし、
テキストボックスについては16文字以内、
テキストエリアについては100文字以内、
チェックボックスについては1つ以上の選択を必須
としています。
ラジオボタンのテストができないのでラジオボタンの選択を解除するボタンを追加しました。

【セレクトボックスについて】
ラジオボタンやチェックボックスと異なり、値リストは配列限定なのでLaravel側で定義するか、Javascript側で変換する必要があります。

【チェックボックスについて】
チェックボックスはラジオボタンと違ってgroupがないので他のフォーム部品に似せて作成してみました。
バリデーションは各項目にエラー表示されてしまうのでhide-detailsで非表示にしてエラーメッセージは選択の変更時に別途エラー表示させます。
エラー時のタイトルも他の部品と同様に赤になるようにしました。

resources/js/components/User/ItemAddComponent.vue
-----
<template>
<div>
  <h1>UserItemAdd</h1>

  <v-form
    ref="form"
    v-model="valid"
  >

    <v-text-field
      v-model="forms.textbox"
      label="テキストボックス"
      :rules="[rules.required, rules.max_16]"
    ></v-text-field>

    <v-textarea
      v-model="forms.textarea"
      label="テキストエリア"
      auto-grow
      :rules="[rules.required, rules.max_100]"
    ></v-textarea>

    <v-radio-group
      v-model="forms.radiobtn"
      label="ラジオボタン"
      row
      :rules="[rules.required]"
    >
      <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]"
    ></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]"
          hide-details
          @change="changeCheckbox"
        ></v-checkbox>
      </v-row>
      <div class="v-messages error--text row">{{ messages.checkbox }}</div>
    </v-container>

    <v-btn
      :disabled="!valid"
      color="success"
      @click="submit"
    >
      送信
    </v-btn>

    <v-btn
      @click="radioClear"
    >
      ラジオボタンクリア
    </v-btn>

  </v-form>

</div>
</template>

<script>
  export default {
    data() {
      return {
        valid: true,			// ①
        constant: {				// ②
          RADIOS: {},
          SELECTS: [],
          CHECKS: {},
        },
        forms: {				// ③
          textbox: '',
          textarea: '',
          radiobtn: '1',
          select: '',
          checkbox: [],
        },
        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つは必須選択です。'
          }
        },
        errors: {
          checlbox: false,
        },
        messages: {
          checkbox: null,
        }
      }
    },
    created() {					// ⑤
      axios.get('/const').then((res) => {
        let constData = res.data.const;
        this.constant.RADIOS = constData.RADIOS;
        this.constant.SELECTS = Object.entries(constData.SELECTS).map(([key, name]) => ({'id': key, 'name': name}))
        this.constant.CHECKS = constData.CHECKS;
      })
      .catch(function(error) {
        console.log(error)
        // エラー処理
      })
    },
    methods: {
      submit() {
        console.log("submit!")
        // 送信処理
      },
      changeCheckbox() {		// ⑥
        this.errors.checkbox = false;
        this.messages.checkbox = '';
        if (this.forms.checkbox.length == 0) {
          this.errors.checkbox = true;
          this.messages.checkbox = '1つは必須選択です。';
        }
      },
      radioClear() {			// ⑦
        this.forms.radiobtn = '';
      }
    },
  }
</script>
-----

①フォームのv-modelの初期値を設定して送信ボタン(v-btn)の制御に使用します。
各フォーム部品でエラーが出たときに自動的に送信ボタンを無効にしてくれます。

②constantオブジェクトでラジオボタン、セレクトボックス、チェックボックスのリスト値の初期値を設定します。※v-selectは配列限定です。

③formsオブジェクトで各フォーム部品の初期値を設定します。
ラジオボタンはデフォルトでRadio1を選択させています。
※ラジオボタンやセレクトボックスは文字列(String型)しか受け付けないようです。

④バリデーション設定です。

⑤Axiosによる非同期処理でLaravelのConstControllerからラジオボタン、セレクトボックス、チェックボックスのリストを取得しています。
なお、②で書きましたがv-selectは配列限定なのでオブジェクトを配列に変換しています。

http://localhost/const でConstControllerから取得できる値はJSON形式で以下のようになります。

-----
{
  "const":{
    "RADIOS":{
      "1":"Radio1",
      "2":"Radio2",
      "3":"Radio3"
    },
    "SELECTS":{
      "1":"Select1",
      "2":"Select2",
      "3":"Select3",
      "4":"Select4"
    },
    "CHECKS":{
      "1":"Check1",
      "2":"Check2",
      "3":"Check3",
      "4":"Check4",
      "5":"Check5",
      "6":"Check6",
      "7":"Check7",
      "8":"Check8"
    }
  }
}
-----

セレクトボックスについては以下のように変換して設定しています。

-----
    "SELECTS":[
      {"1":"Select1"},
      {"2":"Select2"},
      {"3":"Select3"},
      {"4":"Select4"}
    ],
-----

⑥チェックボックスの選択が変更されたときのエラーチェックです。

⑦ラジオボタンクリアのボタンが押されたときに選択を解除します。

CSSでデザイン調整

public/css/user.css
-----
/* Vuetify: Scroll Bar Auto */
html {
  overflow: auto !important;
}
/* コンテンツの下余白設定 */
main {
  margin-bottom: 100px;
}
/* ラジオボタン、チェックボックスのラベル位置の調整 */
label {
  margin-bottom: 0 !important;
}
-----

完成

laravel-vue-vuetify-form-2

バリデーションの動作

laravel-vue-vuetify-form-3

Laravelで設定したラジオボタン、セレクトボックス、チェックボックスのリスト値を取得してVue/VuetifyでFormを作成し、簡単にバリデーションも入れてみました。

セレクトボックスの値リストが配列限定なのと、チェックボックスのエラー関連が特殊なので注意が必要ですね!
Laravel側でも当然バリデーションは必要になりますが、また次回に。

コメント

タイトルとURLをコピーしました