やりたいこと

一覧形式のフォームで、全データのバリデーション後に送信

この記事では、Vuetifyのv-data-tableを使って、一覧形式で表示されたユーザーの入力欄を持つフォームでデータ送信前に一括でバリデーションを行う方法を記載します。
今回は各ユーザーの「項目A(下限・上限)」と「項目B(下限・上限)」の各入力欄で「下限値が上限値より大きい」という矛盾が発生していないかをチェックし全ての入力のエラーが一切ない場合にのみデータを送信します。

一括バリデーションのメリット

  • データの整合性: 送信前にまとめてエラーをチェックするので、サーバーに送るデータの整合性を保証できる。
  • ユーザーの負担軽減: ユーザーはまとめてエラーを確認・修正できる。
  • サーバー負荷の軽減: 不正なデータを含むリクエストを減らせるので、サーバーの負荷を減らせる。

実装のポイント:メソッドでバリデーションチェック

各入力欄の値(下限値と上限値)を取得し「下限値が上限値より大きい」というエラーがないか確認します。
エラーが見つかった場合は、エラーチェック用のオブジェクトに記録します。

コード全体

<template>
  <v-data-table
    :items="localItems"
  >
    // 入力欄
    <template v-slot:item.itemA="{ item }">
      <div>
        <v-text-field
          v-model.number="item.itemA.value1"
          :rules="itemRulesA[item.id] || []"
          @input="checkInput(item, 'itemA')"
        />
        <div>〜</div>
        <v-text-field
          v-model.number="item.itemA.value2"
          :rules="itemRulesA[item.id] || []"
          @input="checkInput(item, 'itemA')"
        />
      </div>
    </template>
    <template v-slot:item.itemB="{ item }">
      <div>
        <v-text-field
          v-model.number="item.itemB.value1"
          :rules="itemRulesB[item.id] || []"
          @input="checkInput(item, 'itemB')"
        />
        <div>〜</div>
        <v-text-field
          v-model.number="item.itemB.value2"
          :rules="itemRulesB[item.id] || []"
          @input="checkInput(item, 'itemB')"
        />
      </div>
    </template>
  </v-data-table>
</template>
-------------------
<script>
export default {
  data() {
    return {
      errorsObject: {}, // エラー情報を記録するオブジェクト
      localItems: [], // データテーブルのアイテム
      itemRulesA: {}, // 項目Aのルール(エラーメッセージ表示用)
      itemRulesB: {}, // 項目Bのルール(エラーメッセージ表示用)
    };
  },
  methods: {
    /**
     * 項目A・項目Bが[下限 > 上限]の場合のバリデーションチェック
     * @param {object} item - 各ユーザーのデータ
     * @param {string} itemHeadName - 入力した項目名('itemA' or 'itemB')
     */
    checkInput(item: any, itemHeadName: string) {
      const lowerValue = Number(item[itemHeadName].value1); // 下限値
      const upperValue = Number(item[itemHeadName].value2); // 上限値

      // エラーオブジェクトの初期化(必要時のみ)
      this.errorsObject[item.id] = this.errorsObject[item.id] || {};

      const error = lowerValue > upperValue;

      // エラーの有無とエラーメッセージを設定
      this.errorsObject[item.id][itemHeadName] = error;
      if (itemHeadName === 'itemB') {
        this.itemRulesB[item.id] = error
          ? ['ルール:下限が上限を超えてはいけません']
          : [];
      } else if (itemHeadName === 'itemA') {
        this.itemRulesA[item.id] = error
          ? ['ルール:下限が上限を超えてはいけません']
          : [];
      }

      // 全てのエラーをチェックして問題なければ送信
      if (!this.checkErrors()) {
        this.$emit('itemInput', this.localItems);
      }
    },

    /**
     * 全てのユーザーの入力データにエラーが存在するかどうかを判定する
     * @returns {boolean} エラーが存在する場合はtrue、そうでない場合はfalse
     */
    checkErrors() {
      return Object.values(this.errorsObject).some(rowErrors =>
        Object.values(rowErrors).some(error => error)
      );
    },
  }
};
</script>

コード解説

template:

  • v-data-table:localItemsのデータを表示します。
  • v-slot:item.itemA, v-slot:item.itemB:「項目A」と「項目B」の列の表示内容を定義
  • v-text-field: テキストフィールドコンポーネント
    • v-model.number:number修飾子で数値として扱い、ユーザーの入力値をlocalItems内のデータと紐づけ。
    • @input:項目に入力があるたびにcheckInputメソッドを呼び出す。
    • :rules:エラーメッセージを表示するためのルールを設定します。
      itemRulesAとitemRulesBからエラーがあればエラーメッセージを返します。

script:

  • data:
    • errorsObject:エラー情報を記録するオブジェクト。ユーザーIDと項目名をキーにして、エラーがあるかどうか (true or false)を記録
    • localItems:データテーブルに表示するユーザーデータ。
    • itemRulesA, itemRulesB:エラーメッセージ表示用のルールを格納するオブジェクト。
      ユーザーIDをキーにエラーメッセージを返す関数を配列を保持
  • methods:
    • checkInput:
       1. 入力された「下限値」と「上限値」を取得
       2. エラー情報を記録するerrorsObjectを、必要に応じて初期化
       3. 「下限値 > 上限値」ならエラーの判別でerrorsObjectにtrueを記録
       4. エラーがあればitemRulesA または itemRulesBにエラーメッセージ表示用の関数を追加
       5. checkErrorsメソッドを呼び出し、全てのエラーをチェックして問題なければ親コンポーネントに送信
    • checkErrors:
       1. errorsObjectをチェックして、エラーが記録されているかどうかを判定
       2. エラーがあればtrueを返し、なければfalseを返す。

一覧フォームの入力バリデーションのポイント

  1. エラー情報をerrorsObjectに記録:エラーがあったら場合、errorsObjectに記録します。
       // errorsObject の構造
        {
          "user1のID": {
            "itemA": false, // 項目Aはエラー:なし
            "itemB": true // 項目Bはエラー:あり
          },
          "user2のID": {
            "itemA": false, // 項目Aはエラー:なし
            "itemB": false // 項目Bはエラー:なし
          }
        }
  2. 全てのエラーをチェックして問題なければ送信:checkErrorsでエラーがないことを確認してから親コンポーネントにデータを送信します。

まとめ

この記事では一覧形式のフォームでバリデーションを行う方法 を解説いたしました。

この方法なら、複数の入力欄でも間違ったデータを送信することなくすべての入力欄をまとめて修正できるため、ユーザーフレンドリー且つデータの整合性も保証できると思います。ぜひ活用してみてください!