[Vue] HTML5 Form Validation を利用するフォーム要素コンポーネント

2年前に書いた HTML5 Form Validationサンプル(jQuery + Bootstrap 4.0 + Constraint Validation API) の Vue版です。

HTML5のフォーム要素に備わっているValidation機能(Constraint Validation API)を利用するフォーム要素のコンポーネントと、それを利用したフォームのサンプルを作ってみた。

Form Validation Library

カスタムバリデーションについては公式のドキュメントに説明があります
著名なライブラリを使う手もあります。

サンプルコード

概要

スタイルはBulmaです。
components/email.vue でメールアドレス用のコンポーネントを作ってあります。

APIによるバリデーション

フォーム要素はApp.vueにあります。
このフォーム要素にはnovalidate属性でブラウザのエラーメッセージ表示をオフにしてあります。

<form class="form" ref="form" @submit.prevent="onSubmit" novalidate>
  <email-input v-model="email"></email-input>
  <button type="submit" class="button is-primary">Submit</button>
</form>

このままだとエラーがあっても通ってしまうので、submitイベントハンドラ内で checkValidity を見ます。

  methods: {
    async onSubmit() {
      if (!this.$refs.form.checkValidity()) {
        return await false;
      }

      try {
        await fetch('https://jsonplaceholder.typicode.com/user/1')
        alert('submit!');
        this.email = null;
      } catch (e) {
        alert('error!');
      }
      
    }
  }

checkValidity はエラーがあると false になるので、 先頭でreturnすれば送信を停止できる。

コンポーネントで v-model を使う

email.vueはコンポーネントとしてApp.vueに登録してあります。

<email-input v-model="email"></email-input>

入力された値を送信時に使うので、App.vueのdata()にあるemailをmodelとして使いたいです。
コンポーネントで v-model を使う方法は公式ドキュメントに説明があるので、この通り実装します。

input要素に v-bind:valuev-on:input を追加する。

<input type="email" id="input-email" name="email" 
      class="input" :class="stateClass" 
      ref="input" :value="value"
      @input="onInput"
      @invalid="showFeedback"
      :placeholder="placeholder" required>

propsvalue を追加。

props: {
    value: {
      type: String,
      default: null
    },
}

v-on:input$emit('input', $event.target.value) を設定する。
サンプルはついでにバリデーションもするので、onInputイベントハンドラを利用してます。

onInput(e) {
  this.showFeedback(e);
  this.$emit('input', e.target.value);
},

ValidityStateの確認

フォーム要素にはvalidityというプロパティにValidityStateオブジェクトがあります。
これにバリデーションの結果がbooleanで納めてあり、trueだとエラーという意味です。

{
   //ユーザーがUIが値に変換できない入力をした場合に true
  badInput: '',
   
  //要素の値が与えられたパターンにマッチしない場合に true
  //[pattern]
  patternMismatch: '',
   
  //要素の値が与えられた最大値を超える場合に true
  //[max]
  rangeOverflow: '',
   
  //要素の値が与えられた最小値を下回る場合に true
  //[min]
  rangeUnderflow: '',
   
  //要素の値が step 属性で与えられた規則に合わない場合に true
   //[step]
  stepMismatch: '',
   
  //要素の値が与えられた長さより長い場合に true
  //[maxlength]
  tooLong: '',
   
  //要素の値が与えられた長さより短い場合に true
  //[minlength]
  tooShort: '',
   
  //要素の値が正しい構文ではない場合に true
  //[type=url][type=email]
  typeMismatch: '',
   
  //要素が入力必須のフィールドであるのに値がない場合に true
  //[required]
  valueMissing : ''
}

全部チェックしてtrueがあれば、そのkeyに対応するメッセージを表示すればいいですが、
ValidityStateにはObject.entriesやObject.keysなどObjectメソッドは利用できないのでfor-inを使います。

for(const key in e.target.validity){
  if (e.target.validity[key]) {
    this.invalidFeedback = this.feedbackMessage[key];
    this.state = false;
  }
};

サンプルのemail要素に対してAPIが吐くエラーは valueMissing(requiredの未入力) と、 typeMismatch(書式ミス) です。
これに対するメッセージをdataで持っておけば、エラーがあった場合にkeyを使って得られます。

feedbackMessage: {
  valueMissing: 'This field is required',
  typeMismatch: 'This email is invalid'
},

バリデーションエラーがなかった場合は、validity.valid が trueなので、
for-inでエラーをチェックする前にvalidのチェックをします。
これを忘れるとエラーがないのにエラーになります…。

リセット

送信後にフォームの内容をリセットするのは、v-modelにnullを設定するだけです。

this.email = null;

form要素のreset()はinput要素の内容は空になるものの、v-model変数にnullを追加するわけではないので内部では値が残ったままになります。

email.vueではwatchでvalueを監視して、nullだった場合にstateとエラーメッセージをクリアします。

watch: {
    value(val, oldVal) {
      if (val === null) {
        this.state = null;
        this.invalidFeedback = null;
      }
    }
  },

コメントを残す

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください