🐦

【HTML】フォームバリデーションの実装方法をもう一度見直してみる【生JavaScript】

2023/05/09に公開

はじめに

htmlのフォームのバリデーションに関してWeb APIに標準搭載されている機能を改めて見直し、どのように標準機能を用いてフォームのバリデーションを実装できるかという検討の備忘を実装例とともに記事に残します。
ライブラリを使わないで実装します。
htmlとバニラ(生)のjsで実装します。

実装方針

Web API標準のバリデーション機能を存分に使っていきます。

こちらは入力項目の必須チェックのよくある実装例です。

AsIs
// 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標準機能を使えばその必要はありません。

ToBe
//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.)

  1. メールアドレス形式チェック
    メールアドレス形式チェックの実装例
    メールアドレス形式チェック

  2. URL形式チェック
    URL形式チェックの実装例
    URL形式チェック

エラーメッセージも標準で用意されており、自前で設定する必要もありません。
(文言をカスタムすることも可能です。 - 後述

具体実装例

ここから具体的な実装例を順を追って見ていきます。

step1. ベーシックなバリデーション

まずは超ベーシックなフォームバリデーションを実装します。
バリデーションに関するjsは一切書いていません。htmlの標準機能のみで実現可能です。

ここでは以下のチェックを行なっています。

  • 必須チェック
    • required属性を付与するだけ
  • メールアドレス形式チェック
    • input[type="email"]にするだけ
  • 任意の形式チェック
    • pattern属性に任意の形式を正規表現で設定
    • ここではbananaもしくはcherryと入力するとvalid

標準機能で実装可能なチェック項目は以下です。

https://developer.mozilla.org/ja/docs/Web/API/ValidityState

必須チェック・メールアドレス形式チェック以外にも、maxlength, minlengthによる最大|最小文字数チェック・pattern属性による任意の形式チェックなど様々なチェックが可能です。
基本的なバリデーションであれば、自前で実装するよりも標準機能を使う方がよいでしょう。

step2. エラー表示をカスタマイズする

標準のエラー通知はポップアップを表示する形式となっています。

macOS Chrome の必須エラー
macOS Chrome の必須エラー

このままでOKならOKなのですが、サイトのトンマナにそぐわない、ユーザーエージェントによって表示が異なる、などの理由からデザイナー観点のOKが出ない場合があります。
ではこのstep2では、エラー表示をカスタマイズしていきます。

標準のポップアップによるエラー通知を表示させずにカスタマイズするには以下の方法で実装します。

  • form要素にnovalidate属性を付与しcheckValidity()メソッドでバリデーションする
  • input要素のinvalidイベントのハンドラにエラー表示処理を実装する

(調べた限り)標準のポップアップによるエラー表示をカスタムするというAPIは用意されていないため、まずはform要素にnovalidate属性を付与し送信時に標準のバリデーション機能を発火させないようにします。
このままではせっかくの標準のバリデーション機能が使えないため、jsからcheckValidity()メソッドを実行しエラーを通知させずに標準のバリデーションチェックだけ実行させます。

https://developer.mozilla.org/ja/docs/Web/API/HTMLInputElement/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プロパティで標準機能のエラーメッセージを取得できます。今回は入力項目下部に赤字で表示させました。

https://developer.mozilla.org/ja/docs/Web/API/HTMLObjectElement/validationMessage

step3. エラーメッセージをカスタマイズする

pattern属性にて指定された正規表現と入力値が一致しない場合patternMismatchエラーとなり、macOS Chromeブラウザにおける標準のエラーメッセージは「要求された形式に一致させてください。」です。

patternMismatchエラーのエラーメッセージ
patternMismatchエラーのエラーメッセージ

しかしこのエラー通知ではどのような入力が有効なのかがユーザーに伝わらず少々不親切であるため、エラーメッセージをカスタムしたいです。

標準機能であるsetCustomValidity()メソッドに文字列を渡すことで、エラーメッセージをカスタムできます。またそのメッセージはデフォルトのエラーメッセージと同様にvalidationMessageプロパティから取得可能です。

https://developer.mozilla.org/ja/docs/Web/API/HTMLInputElement/setCustomValidity

エラーの種類はValidityStateから判別可能であり、patternMismatchの場合、つまりinput.validity.patternMismatchtrueの場合に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標準のバリデーション機能を使ってバリデーション機能を実装してきました。
最後にこれら実装を全部乗せしたサンプル実装を載せます。

ソースコードは以下から確認できます。

https://github.com/ueda-kio/html-vanilla-validation/blob/main/main.js

課題点

1. validationMessageがユーザーエージェント依存

validationMessageを使用すればエラーメッセージを自分で考える必要がないという利点がありますが、その標準エラーメッセージはユーザーエージェント依存でありブラウザ・OS間で文言が異なります。
とくに句読点の有無にばらつきがあり、表記揺れの問題があります。

各OS毎の必須エラーメッセージの参考画像
OS 参考画像
macOS Chrome C915EED2-2E73-405C-8DBA-0E30B4C50404.png
macOS Safari 739C0B96-86E2-434E-94BA-E66CD4EE4F06.png
macOS Firefox F634A40C-F389-4943-A8DB-BE58EB75A184.png
Android Chrome 375F04A4-04F4-4DD0-B1BC-13F8C8DAEFC5.png
iOS Safari unnamed.jpg

2. 複数のエラーを同時に表示できない

エラーメッセージをvalidationMessageプロパティから取得・表示していますが、プロパティの仕様上複数のメッセージを同時に取得することができないため、同時に表示できるエラーメッセージは1つのみです。複数エラーとなっている場合は全てのエラーメッセージを通知した方が手戻りを防ぐことができユーザーにとってはうれしいため改善が必要そうです。
invalid時にエラー毎にvalidationMessageを取得し得られたメッセージを全て表示する、という実装がよさそう)

さいごに

標準APIによるフォームバリデーションの機能をおさらいし、それを踏まえた実装方法を自身の備忘を兼ねてまとめてきました。
どこか大手の実装例を参考にしたり、有名ライブラリのソースコードを参照したりしたわけではなく、あくまで私個人がゼロから書いたものであり、果たして実用性があるかはあやしいです。
適切でない実装、アンチパターン、仕様の誤解などありましたらご指摘いただけると幸いです。

GitHubで編集を提案

Discussion