BLOG
JavaScriptで最低限のバリデーションを実装する
INDEX
最低限のバリデーションであれば素のJavaScriptのみで実装できる時代になりました。
プラグインを使えばいいものを、今回は自力で実装します。
本記事ではinput
要素やtextarea
要素など、フォーム関連の要素を「フォーム要素」と呼びます。
バリデーションで使用するあれこれ
バリデーションの状態は、ValidityState
を実装するvalidity
オブジェクトから取得します。
validity
オブジェクトは以下のプロパティを持ちます。
ValidityState.valid
以外は、それぞれバリデーションにエラーがあればtrue
を、なければfalse
を返します。
ValidityState.badInput
ブラウザが解析できない場合にtrue
を返す
ValidityState.customError
setCustomValidity()
メソッドの設定がある場合にtrue
を返す
ValidityState.patternMismatch
pattern
属性の正規表現に一致しない場合にtrue
を返す
ValidityState.rangeOverflow
max
属性の値を上回った場合にtrue
を返す
ValidityState.rangeUnderflow
min
属性の値を下回った場合にtrue
を返す
ValidityState.stepMismatch
step
属性の値で割り切れない場合にtrue
を返す
ValidityState.tooLong
maxlength
属性の値を上回った場合にtrue
を返す
ValidityState.tooShort
minlength
属性の値を下回った場合にtrue
を返す
ValidityState.typeMismatch
type
属性の制約を満たさない場合にtrue
を返す
ValidityState.valueMissing
required
属性を持ち、値が空の場合にtrue
を返す
ValidityState.valid
バリデーションにエラーがある場合にfalse
を返す
完成したコード
コードを拡張する
せっかくなのでもう少し機能をつけます。
エラー文言を変更する
+ const customErrorTexts = {
+ valueMissing: '必須項目です', // required属性を持つが値が入力されていない場合
+ patternMismatch: {
+ tel: '電話番号の形式で入力してください',
+ url: 'https://evoworx.co.jpを入力してください',
+ }, // pattern属性の値と一致しない場合
+ }; // エラーテキストの一覧
+
+ // バリデーションのエラーテキストを取得する
+ const getCustomErrorText = (validity, type = '') => {
+ for (const key in validity) {
+ if (!validity[key]) continue;
+ const errorTextEntry = customErrorTexts[key];
+ if (typeof errorTextEntry === 'string') {
+ return errorTextEntry;
+ } else if (typeof errorTextEntry === 'object' && errorTextEntry[type]) {
+ return errorTextEntry[type];
+ }
+ }
+ return undefined;
+ };
~~~
const handleValidationError = (element) => {
const validity = element.validity;
- const showError = validity.patternMismatch || validity.rangeOverflow || validity.rangeUnderflow || validity.tooLong || validity.tooShort || validity.typeMismatch || validity.valueMissing;
+ const showError = validationTypes.some((type) => validity[type]);
+ const type = element.type || '';
- updateErrorState(element, showError);
+ updateErrorState(element, showError, getCustomErrorText(validity, type));
};
customErrorTexts
オブジェクトを追加して、任意のエラーメッセージを指定できるようにしました。
直下のプロパティはValidityState
が持つプロパティを指定します。
ネストされたプロパティはお好みで、input
要素のtype
属性を指定すると該当type
の要素に適用されます。
getCustomErrorText()
はcustomErrorTexts
オブジェクトに一致する値があればその値を返します。
実行するバリデーションを管理する
+ // 実施するバリデーション
+ const validationTypes = ['patternMismatch', 'rangeOverflow', 'rangeUnderflow', 'tooLong', 'tooShort', 'typeMismatch', 'valueMissing'];
~~~
+ const handleErrorValidation = (element) => {
+ const validity = element.validity;
- const showError = validity.patternMismatch || validity.rangeOverflow || validity.rangeUnderflow || validity.tooLong || validity.tooShort || validity.typeMismatch || validity.valueMissing;
+ const showError = validationTypes.some((type) => validity[type]);
+ updateErrorState(element, showError);
+ };
validationTypes
オブジェクトを追加して、実行するバリデーションを指定できるようにしました。
ValidityState
が持つプロパティを文字列として格納し、値のあるバリデーションを実行します。
name
属性が一致するチェックボックスのうち、1つ以上の選択を必須にする
// HTML
<ul>
<li class="flex">
- <input type="checkbox" value="いぬ" name="favorite_animal" id="dog" />
+ <input type="checkbox" value="いぬ" name="favorite_animal" id="dog" class="form_element" required />
<label for="dog">いぬ</label>
</li>
<li class="flex">
- <input type="checkbox" value="ねこ" name="favorite_animal" id="cat" />
+ <input type="checkbox" value="ねこ" name="favorite_animal" id="cat" class="form_element" required />
<label for="cat">ねこ</label>
</li>
<li class="flex">
- <input type="checkbox" value="うさぎ" name="favorite_animal" id="rabbit" />
+ <input type="checkbox" value="うさぎ" name="favorite_animal" id="rabbit" class="form_element" required />
<label for="rabbit">うさぎ</label>
</li>
</ul>
+ <span class="error_message" aria-live="off" aria-hidden="true"></span>
~~~
// CSS
- .form_parent:has(:where(input, textarea, select):required) .form_title
+ .form_parent.is_required .form_title
~~~
// JavaScript
+ // チェックボックスのrequired属性を更新する
+ const updateRequiredAttribute = (element) => {
+ const checkboxes = document.querySelectorAll(`input[type="checkbox"][name="${element.name}"]`);
+ const checked = [...checkboxes].some((checkbox) => checkbox.checked);
+ const requiredValue = checked ? undefined : 'required';
+
+ // name属性が一致するチェックボックスのいずれか1つが選択されていたらrequired属性を削除する
+ checkboxes.forEach((checkbox) => (checkbox.required = requiredValue));
+ };
~~~
+ // フォーム要素固有のバリデーションのハンドリング
+ const handleUniqueValidation = (element) => {
+ const elementType = element.type;
+ if (elementType === 'checkbox') {
+ updateRequiredAttribute(element);
+ }
+ };
~~~
+ // 必須項目があれば親要素にクラスを付与する
+ const addRequiredClass = (element) => {
+ const parentElement = element.closest(`.${PARENT_CLASS}`);
+
+ if (element.required) {
+ parentElement.classList.add(REQUIRED_CLASS);
+ }
+ };
~~~
(() => {
const formElements = document.querySelectorAll(`.${FORM_ELEMENT_CLASS}`);
formElements?.forEach((element) => {
attachValidationHandlers(element);
+ addRequiredClass(element);
});
})();
handleUniqueValidation()
を追加して、フォーム要素ごとに固有のバリデーションを実装できるようにしました。
name
属性が一致するチェックボックスを取得し、うち1つでも選択されていたらrequired
属性、1つも選択されていなければrequired
属性を付与します。
チェックボックスの仕様変更に伴い、必須項目用のクラス付与をする処理を追加し周辺の記述を変更しました。
拡張したコード
※ 一部CodePen上で動かない処理がありますが、ブラウザで実行すると正常に処理されます。
完成したコードにすべての拡張を適用しました。
ほかにもsetCustomValidity()
メソッドを使用して細かくエラーメッセージを表示したり、エラーメッセージが複数あれば全て表示したり、など。
まだまだ改善の余地はありますが、ひとまず最低限であればなんとか実装できました。
参考
ValidityState.badInput - Web API | MDN
ValidityState.patternMismatch - Web API | MDN
ValidityState.rangeOverflow - Web API | MDN
ValidityState.rangeUnderflow - Web API | MDN
ValidityState.stepMismatch - Web API | MDN
ValidityState.tooLong - Web API | MDN
ValidityState.tooShort - Web API | MDN
ValidityState.typeMismatch - Web API | MDN