【HTML】フォームバリデーションの実装方法をもう一度見直してみる【生JavaScript】
はじめに
htmlのフォームのバリデーションに関してWeb APIに標準搭載されている機能を改めて見直し、どのように標準機能を用いてフォームのバリデーションを実装できるかという検討の備忘を実装例とともに記事に残します。
ライブラリを使わないで実装します。
htmlとバニラ(生)のjsで実装します。
実装方針
Web API標準のバリデーション機能を存分に使っていきます。
こちらは入力項目の必須チェックのよくある実装例です。
// html
<input type="text" id="text">
<span id="errorMessage"></span>
// js
const input = document.getElementById('text');
const errorMessage = document.getElementById('errorMessage');
if (input.value === '') {
errorMessage.textContent = '必須項目です。';
}
invalid
の条件もエラーメッセージも自分で設定する必要があります。
自前のバリデーション
Web API標準機能を使えばその必要はありません。
//html
<input type="text" id="text" required>
<span id="errorMessage"></span>
//js
const input = document.getElementById('text');
const errorMessage = document.getElementById('errorMessage');
if (input.checkValidity()) { // 標準のバリデーション機能
errorMessage.textContent = input.validationMessage; // 標準のバリデーションメッセージ
}
標準機能を用いたバリデーション
なにが嬉しいの?
Web APIにはクライアントのバリデーションに必要な機能が標準でいくつも実装されており、これらを利用することで「自分で書くコード」を減らすことが可能です。
e.g.)
-
メールアドレス形式チェック
メールアドレス形式チェック -
URL形式チェック
URL形式チェック
エラーメッセージも標準で用意されており、自前で設定する必要もありません。
(文言をカスタムすることも可能です。 - 後述)
具体実装例
ここから具体的な実装例を順を追って見ていきます。
step1. ベーシックなバリデーション
まずは超ベーシックなフォームバリデーションを実装します。
バリデーションに関するjsは一切書いていません。htmlの標準機能のみで実現可能です。
ここでは以下のチェックを行なっています。
- 必須チェック
-
required
属性を付与するだけ
-
- メールアドレス形式チェック
-
input[type="email"]
にするだけ
-
- 任意の形式チェック
-
pattern
属性に任意の形式を正規表現で設定 - ここでは
banana
もしくはcherry
と入力するとvalid
-
標準機能で実装可能なチェック項目は以下です。
必須チェック・メールアドレス形式チェック以外にも、maxlength
, minlength
による最大|最小文字数チェック・pattern
属性による任意の形式チェックなど様々なチェックが可能です。
基本的なバリデーションであれば、自前で実装するよりも標準機能を使う方がよいでしょう。
step2. エラー表示をカスタマイズする
標準のエラー通知はポップアップを表示する形式となっています。
macOS Chrome の必須エラー
このままでOKならOKなのですが、サイトのトンマナにそぐわない、ユーザーエージェントによって表示が異なる、などの理由からデザイナー観点のOKが出ない場合があります。
ではこのstep2では、エラー表示をカスタマイズしていきます。
標準のポップアップによるエラー通知を表示させずにカスタマイズするには以下の方法で実装します。
-
form
要素にnovalidate
属性を付与しcheckValidity()
メソッドでバリデーションする -
input
要素のinvalid
イベントのハンドラにエラー表示処理を実装する
(調べた限り)標準のポップアップによるエラー表示をカスタムするというAPIは用意されていないため、まずはform
要素にnovalidate
属性を付与し送信時に標準のバリデーション機能を発火させないようにします。
このままではせっかくの標準のバリデーション機能が使えないため、jsからcheckValidity()
メソッドを実行しエラーを通知させずに標準のバリデーションチェックだけ実行させます。
検証結果に問題がある場合はfalse
を返し、input
要素のinvalid
イベントを発火させます。
ここでカスタムしたエラー通知を表示させたいため、input
要素のinvalid
イベントのハンドラにエラー表示処理を実装します。
const form = document.getElementById('form');
form.addEventListener(
'submit',
(e) => {
e.preventDefault();
const isValid = form.checkValidity(); // バリデーション実行
if (isValid) {
alert('submit!');
}
},
{ passive: false }
);
const input = document.getElementById('text');
input.addEventListener('invalid', () => {
input.classList.add('is-error');
errorMessage.textContent = input.validationMessage;
});
ざっくりとこんな実装内容です。
validationMessage
プロパティで標準機能のエラーメッセージを取得できます。今回は入力項目下部に赤字で表示させました。
step3. エラーメッセージをカスタマイズする
pattern
属性にて指定された正規表現と入力値が一致しない場合patternMismatch
エラーとなり、macOS Chromeブラウザにおける標準のエラーメッセージは「要求された形式に一致させてください。」です。
patternMismatch
エラーのエラーメッセージ
しかしこのエラー通知ではどのような入力が有効なのかがユーザーに伝わらず少々不親切であるため、エラーメッセージをカスタムしたいです。
標準機能であるsetCustomValidity()
メソッドに文字列を渡すことで、エラーメッセージをカスタムできます。またそのメッセージはデフォルトのエラーメッセージと同様にvalidationMessage
プロパティから取得可能です。
エラーの種類はValidityState
から判別可能であり、patternMismatch
の場合、つまりinput.validity.patternMismatch
がtrue
の場合にsetCustomValidity()
にエラーメッセージとして表示したい文字列を渡します。
input.addEventListener('invalid', () => {
input.classList.add('is-error');
if (input.validity.patternMismatch) {
input.setCustomValidity('「banana」もしくは「cherry」を入力してください。');
}
errorMessage.textContent = input.validationMessage;
input.setCustomValidity(''); //! 通知が完了したら`setCustomValidity`に空文字列を渡す。
});
[IMO]カスタムエラーメッセージの管理方法について
patternMismatch
エラーの際、input
要素にtitle
属性が指定されている場合その文字列も標準のエラー通知に表示されます。
title
属性に「banana」もしくは「cherry」を入力してください。
を設定した場合
よってこの仕様に準じるのであれば、カスタムメッセージもhtmlのtitle
属性にて管理し、jsではそれを取得・表示する実装方法がより適していると思われます。
input.addEventListener('invalid', () => {
input.classList.add('is-error');
if (input.validity.patternMismatch) {
input.setCustomValidity(input.title); // `title`属性の文字列を渡す
}
errorMessage.textContent = input.validationMessage;
input.setCustomValidity('');
});
step4. 独自のバリデーション要件を設定する
最後に標準のバリデーション機能ではチェックできない独自のバリデーション要件(以降「カスタムルール」と呼称)がある場合の実装方法を見ていきます。
カスタムルールはこれまでと異なりinvalid
イベントハンドラ内で条件分岐させるだけでは実装できません。カスタムルールのバリデーション結果がfalse
であっても、form.checkValidity()
の結果がfalse
とならずそもそもinvalid
イベントが走らないためです。
そのため、フォーム送信時に各入力項目要素それぞれでinputElement.checkValidity()
を実行し、入力項目がカスタムルール検査の対象の場合その条件式に通し、invalid
の場合setCustomValidity()
メソッドからエラーメッセージをセットする、という実装を行います。
const form = document.getElementById('form');
const input = document.getElementById('text');
const validate = (input) => {
if (input.hasAttribute('data-validate')) {
if (input.value !== '1') { // カスタムルール
input.setCustomValidity('カスタムルールに反している旨のエラーメッセージ');
} else {
input.setCustomValidity('');
}
}
input.checkValidity();
};
form.addEventListener(
'submit',
(e) => {
e.preventDefault();
validate(input); // 各入力項目にバリデーション実行
const isValid = form.checkValidity();
if (isValid) {
alert('submit!');
}
},
{ passive: false }
);
input.addEventListener('invalid', () => {
input.classList.add('is-error');
errorMessage.textContent = input.validationMessage;
});
setCustomValidity()
に空文字列以外がセットされていればinput.checkValidity()
からinvalid
イベントを発火させることができ、標準のバリデーション要件と同様にエラーを表示できます。
かつフォーム要素に対するform.checkValidity()
メソッドでもfalse
を返すため、「すべての入力項目がvalid
の場合にフォーム送信実行」という条件分岐も実装することが可能です。
全部乗せ実装例
ここまでstep1 ~ step4でWeb API標準のバリデーション機能を使ってバリデーション機能を実装してきました。
最後にこれら実装を全部乗せしたサンプル実装を載せます。
ソースコードは以下から確認できます。
課題点
validationMessage
がユーザーエージェント依存
1. validationMessage
を使用すればエラーメッセージを自分で考える必要がないという利点がありますが、その標準エラーメッセージはユーザーエージェント依存でありブラウザ・OS間で文言が異なります。
とくに句読点の有無にばらつきがあり、表記揺れの問題があります。
各OS毎の必須エラーメッセージの参考画像
OS | 参考画像 |
---|---|
macOS Chrome | |
macOS Safari | |
macOS Firefox | |
Android Chrome | |
iOS Safari |
2. 複数のエラーを同時に表示できない
エラーメッセージをvalidationMessage
プロパティから取得・表示していますが、プロパティの仕様上複数のメッセージを同時に取得することができないため、同時に表示できるエラーメッセージは1つのみです。複数エラーとなっている場合は全てのエラーメッセージを通知した方が手戻りを防ぐことができユーザーにとってはうれしいため改善が必要そうです。
(invalid
時にエラー毎にvalidationMessage
を取得し得られたメッセージを全て表示する、という実装がよさそう)
さいごに
標準APIによるフォームバリデーションの機能をおさらいし、それを踏まえた実装方法を自身の備忘を兼ねてまとめてきました。
どこか大手の実装例を参考にしたり、有名ライブラリのソースコードを参照したりしたわけではなく、あくまで私個人がゼロから書いたものであり、果たして実用性があるかはあやしいです。
適切でない実装、アンチパターン、仕様の誤解などありましたらご指摘いただけると幸いです。
Discussion