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:value
と v-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>
props
に value
を追加。
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; } } },