BLOG

JavaScriptで最低限のバリデーションを実装する

Written by ogoe

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 - Web API | MDN

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

ValidityState.valueMissing - Web API | MDN

HTML 属性: pattern - HTML: ハイパーテキストマークアップ言語 | MDN