JavaScript を使ったフォームの検証(入力チェック)

JavaScript を使って送信時に入力された値を検証する基本的な方法についての解説やサンプルです。

入力時にリアルタイムに検証する方法や HTML5 のフォームの検証機能などより詳しい検証の方法やサンプルについては以下を御覧ください。

JavaScript フォームの検証・入力チェック(制約検証 API)

作成日:2021年11月10日

ベーシックサンプル

以下は JavaScript を使ってサーバーに送信する前にフォームに入力された値を検証するシンプルな例です。

それぞれの入力値に対しては以下のような検証を行います。

入力項目 検証の種類
名前 必須入力、最大文字数(10文字以内)
電話番号 電話番号の形式チェック
メールアドレス メールアドレスの形式チェック
メールアドレス 再入力 必須入力、値の一致
件名 必須入力、最大文字数(20文字以内)
お問い合わせ内容 必須入力、最大文字数(100文字以内)

いずれかの検証を満たさない場合はサーバーへは送信せずエラーを表示し、検証を全て満たす場合のみサーバーへ送信します(以下はサンプルですので実際にはデータはサーバーに送信されません)。

例えば、何も入力せずに送信ボタンをクリックすると、必須入力を指定していない電話番号以外の入力欄の下に「入力は必須です」というエラーが表示されます。

※ 以下のサンプルではアロー関数などの ES6 で追加された機能や addEventListener()、querySelectorAll() などを使用しているので古いブラウザには対応していません。また、NodeList の forEarch() を使っているので IE にも対応していません(IE 対応)。

サンプル(別ページで開く)

<body>
<div class="content">
  <form name="myForm" class="validationForm" novalidate>
    <div>
      <label for="name">名前 </label>
      <input class="required maxlength" data-maxlength="10" type="text" name="name" id="name">
    </div>
    <div>
      <label for="tel">電話番号 </label>
      <input class="tel" type="tel" name="tel" id="tel">
    </div>
    <div>
      <label for="email1">メールアドレス </label>
      <input class="required email" type="email" id="email1" name="email1" size="30">
    </div>
    <div>
      <label for="email2">メールアドレス 再入力(確認用)</label>
      <input class="required equal-to" data-equal-to="email1" type="email" id="email2" name="email2" size="30">
    </div>
    <div>
      <label for="subject">件名</label>
      <input type="text" class="required maxlength" data-maxlength="20" id="subject" name="subject">
    </div>
    <div>
      <label for="inquiry">お問い合わせ内容</label>
      <textarea class="required maxlength" data-maxlength="100" name="inquiry" id="inquiry" rows="5" cols="50"></textarea>
    </div>
    <button name="send">送信</button>
  </form>
</div>
<script>  
//class="validationForm" と novalidate 属性を指定した form 要素を独自に検証
document.addEventListener('DOMContentLoaded', () => {
  //.validationForm を指定した最初の form 要素を取得
  const validationForm = document.querySelector('.validationForm');
  //.validationForm を指定した form 要素が存在すれば
  if(validationForm) {
    //エラーを表示する span 要素に付与するクラス名(エラー用のクラス)
    const errorClassName = 'error';
    
    //required クラスを指定された要素の集まり  
    const requiredElems = document.querySelectorAll('.required');
    //email クラスを指定された要素の集まり
    const emailElems =  document.querySelectorAll('.email');
    //tel クラスを指定された要素の集まり
    const telElems =  document.querySelectorAll('.tel');
    //maxlength クラスを指定された要素の集まり
    const maxlengthElems =  document.querySelectorAll('.maxlength');
    //equal-to クラスを指定された要素の集まり
    const equalToElems = document.querySelectorAll('.equal-to'); 
    
    //エラーメッセージを表示する span 要素を生成して親要素に追加する関数
    //elem :対象の要素
    //errorMessage :表示するエラーメッセージ
    const createError = (elem, errorMessage) => {
      //span 要素を生成
      const errorSpan = document.createElement('span');
      //エラー用のクラスを追加(設定)
      errorSpan.classList.add(errorClassName);
      //aria-live 属性を設定
      errorSpan.setAttribute('aria-live', 'polite');
      //引数に指定されたエラーメッセージを設定
      errorSpan.textContent = errorMessage;
      //elem の親要素の子要素として追加
      elem.parentNode.appendChild(errorSpan);
    }

    //form 要素の submit イベントを使った送信時の処理
    validationForm.addEventListener('submit', (e) => {
      //エラーを表示する要素を全て取得して削除(初期化)
      const errorElems = validationForm.querySelectorAll('.' + errorClassName);
      errorElems.forEach( (elem) => {
        elem.remove(); 
      });
      
      //.required を指定した要素を検証
      requiredElems.forEach( (elem) => {
        //値(value プロパティ)の前後の空白文字を削除
        const elemValue = elem.value.trim(); 
        //値が空の場合はエラーを表示してフォームの送信を中止
        if(elemValue.length === 0) {
          createError(elem, '入力は必須です');
          e.preventDefault();
        }
      });
      
      //.email を指定した要素を検証
      emailElems.forEach( (elem) => {
        //Email の検証に使用する正規表現パターン
        const pattern = /^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ui;
        //値が空でなければ
        if(elem.value !=='') {
          //test() メソッドで値を判定し、マッチしなければエラーを表示してフォームの送信を中止
          if(!pattern.test(elem.value)) {
            createError(elem, 'Email アドレスの形式が正しくないようです。');
            e.preventDefault();
          }
        }
      });
      
      //.tel を指定した要素を検証
      telElems.forEach( (elem) => {
        //電話番号の検証に使用する正規表現パターン
        const pattern = /^\(?\d{2,5}\)?[-(\.\s]{0,2}\d{1,4}[-)\.\s]{0,2}\d{3,4}$/;
        //値が空でなければ
        if(elem.value !=='') {
          //test() メソッドで値を判定し、マッチしなければエラーを表示してフォームの送信を中止
          if(!pattern.test(elem.value)) {
            createError(elem, '電話番号の形式が正しくないようです。');
            e.preventDefault();
          }
        }
      });
      
      //.maxlength を指定した要素を検証
      maxlengthElems.forEach( (elem) => {
        //data-maxlength 属性から最大文字数を取得
        const maxlength = elem.dataset.maxlength;
        //または const maxlength = elem.getAttribute('data-maxlength');
        //値が空でなければ
        if(elem.value !=='') {
          //値が maxlength を超えていればエラーを表示してフォームの送信を中止
          if(elem.value.length > maxlength) {
            createError(elem, maxlength + '文字以内でご入力ください');
            e.preventDefault();
          }
        }
      });
      
      //.equal-to を指定した要素を検証
      equalToElems.forEach( (elem) => {
        //比較対象の要素の id 
        const equalToId = elem.dataset.equalTo;
        //または const equalToId = elem.getAttribute('data-equal-to');
        //比較対象の要素
        const equalToElem = document.getElementById(equalToId);
        //値が空でなければ
        if(elem.value !=='' && equalToElem.value !==''){
          if(equalToElem.value !== elem.value) {
            createError(elem, '入力された値が異なります');
            e.preventDefault();
          }
        }
      });

      //エラーの最初の要素を取得
      const errorElem =  validationForm.querySelector('.' + errorClassName);
      //エラーがあればエラーの最初の要素の位置へスクロール
      if(errorElem) {
        const errorElemOffsetTop = errorElem.offsetTop;
        window.scrollTo({
          top: errorElemOffsetTop - 40,  //40px 上に位置を調整
          //スムーススクロール
          behavior: 'smooth'
        });
      }
    }); 
  }
});
</script>
</body>

HTML

以下は前述のサンプルのフォーム部分の HTML です。

form 要素には novalidate 属性を指定してブラウザーの自動検証を無効にしています。novalidate 属性を指定しない場合、type="email" の input 要素はブラウザーの自動検証により送信時にメールアドレスの検証が行われます(input 要素の type 属性)。

form 要素には validationForm クラスを指定して JavaScript で検証する対象のフォームとしています。

また、検証を実施する要素にはその検証の内容により required や maxlength などのクラスを指定します。

<form name="myForm" class="validationForm" novalidate>
  <div>
    <label for="name">名前 </label>
    <input class="required maxlength" data-maxlength="10" type="text" name="name" id="name">
  </div>
  <div>
    <label for="tel">電話番号 </label>
    <input class="tel" type="tel" name="tel" id="tel">
  </div>
  <div>
    <label for="email1">メールアドレス </label>
    <input class="required email" type="email" id="email1" name="email1" size="30">
  </div>
  <div>
    <label for="email2">メールアドレス 再入力(確認用)</label>
    <input class="required equal-to" data-equal-to="email1" type="email" id="email2" name="email2" size="30">
  </div>
  <div>
    <label for="subject">件名</label>
    <input type="text" class="required maxlength" data-maxlength="20" id="subject" name="subject">
  </div>
  <div>
    <label for="inquiry">お問い合わせ内容</label>
    <textarea class="required maxlength" data-maxlength="100" name="inquiry" id="inquiry" rows="5" cols="50"></textarea>
  </div>
  <button name="send">送信</button>
</form>

このサンプルでは、検証の内容により以下のようなクラスや属性を指定することとしています。

クラス 意味 同時に指定する属性
required 必須入力(値が空でないか) なし
maxlength 最大文字数。data-maxlength で指定した文字数を超えていないか data-maxlength
email メールアドレスの妥当性の検証 なし
tel 電話番号の妥当性の検証 なし
equal-to 値が一致するかの検証。data-equal-to で指定した要素と比較 data-equal-to

CSS

このサンプルでは以下のようなスタイルを JavaScript により表示するエラーメッセージに設定しています。

/* エラーメッセージのスタイル */
.error {
  width : 100%;
  padding: 0;
  display: inline-block;
  font-size: 90%;
  color: red;
  box-sizing: border-box;
}

JavaScript

以下は前述のサンプルの JavaScript です。このサンプルでは </body> の直前に記述していますが、別ファイルにして読み込むこともできます。読み込む位置に関わらず機能するように DOMContentLoaded イベントを利用しています。

最初に validationForm クラスを指定した form 要素を取得し、その要素が存在すれば検証を行います。

エラーを表示する span 要素に付与するクラス名は7行目で error としていますが、この部分を変更すれば出力するエラーのクラスを変更できます。

スクリプトの概要は、検証用のクラスを指定した要素を全て取得し、フォームの送信時(submit イベント)にそれぞれの検証を行い、エラーがあれば表示して e.preventDefault() でフォームの送信を中断します。

それぞれの検証用のクラスを指定した要素は複数ある可能性があるので、NodeList のメソッド forEach() を使ってそれぞれの要素に対して処理します(それぞれの検証の詳細は後述)。

エラーを表示する処理はいずれの検証でも同じなので別途関数として定義しておきます。

form 要素の submit イベントを使った送信時の処理では、すでにエラーが表示されていれば、一度全てのエラーを削除してエラー表示を初期化しています(39〜42行目)。

また、エラーがあればエラー用のクラス(デフォルトは .error)が付与された要素の位置を取得してその位置までスクロールするようにしています。

document.addEventListener('DOMContentLoaded', () => {
  //.validationForm を指定した最初の form 要素を取得
  const validationForm = document.querySelector('.validationForm');
  //.validationForm を指定した form 要素が存在すれば
  if(validationForm) {
    //エラーを表示する span 要素に付与するクラス名(エラー用のクラス)
    const errorClassName = 'error';
    
    //required クラスを指定された要素の集まり  
    const requiredElems = document.querySelectorAll('.required');
    //email クラスを指定された要素の集まり
    const emailElems =  document.querySelectorAll('.email');
    //tel クラスを指定された要素の集まり
    const telElems =  document.querySelectorAll('.tel');
    //maxlength クラスを指定された要素の集まり
    const maxlengthElems =  document.querySelectorAll('.maxlength');
    //equal-to クラスを指定された要素の集まり
    const equalToElems = document.querySelectorAll('.equal-to'); 
    
    //エラーメッセージを表示する span 要素を生成して親要素に追加する関数
    //elem :対象の要素
    //errorMessage :表示するエラーメッセージ
    const createError = (elem, errorMessage) => {
      //span 要素を生成
      const errorSpan = document.createElement('span');
      //エラー用のクラスを追加(設定)
      errorSpan.classList.add(errorClassName);
      //aria-live 属性を設定
      errorSpan.setAttribute('aria-live', 'polite');
      //引数に指定されたエラーメッセージを設定
      errorSpan.textContent = errorMessage;
      //elem の親要素の子要素として追加
      elem.parentNode.appendChild(errorSpan);
    }

    //form 要素の submit イベントを使った送信時の処理
    validationForm.addEventListener('submit', (e) => {
      //エラーを表示する要素を全て取得して削除(初期化)
      const errorElems = validationForm.querySelectorAll('.' + errorClassName);
      errorElems.forEach( (elem) => {
        elem.remove(); 
      });
      
      //.required を指定した要素を検証
      requiredElems.forEach( (elem) => {
        //値(value プロパティ)の前後の空白文字を削除
        const elemValue = elem.value.trim(); 
        //値が空の場合はエラーを表示してフォームの送信を中止
        if(elemValue.length === 0) {
          createError(elem, '入力は必須です');
          e.preventDefault();
        }
      });
      
      //.email を指定した要素を検証
      emailElems.forEach( (elem) => {
        //Email の検証に使用する正規表現パターン
        const pattern = /^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ui;
        //値が空でなければ
        if(elem.value !=='') {
          //test() メソッドで値を判定し、マッチしなければエラーを表示してフォームの送信を中止
          if(!pattern.test(elem.value)) {
            createError(elem, 'Email アドレスの形式が正しくないようです。');
            e.preventDefault();
          }
        }
      });
      
      //.tel を指定した要素を検証
      telElems.forEach( (elem) => {
        //電話番号の検証に使用する正規表現パターン
        const pattern = /^\(?\d{2,5}\)?[-(\.\s]{0,2}\d{1,4}[-)\.\s]{0,2}\d{3,4}$/;
        //値が空でなければ
        if(elem.value !=='') {
          //test() メソッドで値を判定し、マッチしなければエラーを表示してフォームの送信を中止
          if(!pattern.test(elem.value)) {
            createError(elem, '電話番号の形式が正しくないようです。');
            e.preventDefault();
          }
        }
      });
      
      //.maxlength を指定した要素を検証
      maxlengthElems.forEach( (elem) => {
        //data-maxlength 属性から最大文字数を取得
        const maxlength = elem.dataset.maxlength;
        //または const maxlength = elem.getAttribute('data-maxlength');
        //値が空でなければ
        if(elem.value !=='') {
          //値が maxlength を超えていればエラーを表示してフォームの送信を中止
          if(elem.value.length > maxlength) {
            createError(elem, maxlength + '文字以内でご入力ください');
            e.preventDefault();
          }
        }
      });
      
      //.equal-to を指定した要素を検証
      equalToElems.forEach( (elem) => {
        //比較対象の要素の id 
        const equalToId = elem.dataset.equalTo;
        //または const equalToId = elem.getAttribute('data-equal-to');
        //比較対象の要素
        const equalToElem = document.getElementById(equalToId);
        //値が空でなければ
        if(elem.value !=='' && equalToElem.value !==''){
          if(equalToElem.value !== elem.value) {
            createError(elem, '入力された値が異なります');
            e.preventDefault();
          }
        }
      });

      //エラーの最初の要素を取得
      const errorElem =  validationForm.querySelector('.' + errorClassName);
      //エラーがあればエラーの最初の要素の位置へスクロール
      if(errorElem) {
        const errorElemOffsetTop = errorElem.offsetTop;
        window.scrollTo({
          top: errorElemOffsetTop - 40,  //40px 上に位置を調整
          //スムーススクロール
          behavior: 'smooth'
        });
      }
    }); 
  }
});
エラーを表示する関数

以下はエラーメッセージを表示する span 要素を生成して親要素に追加する関数の部分の抜粋です。

引数には対象の要素と表示するメッセージを受け取ります。エラー用のクラスは別途スクリプトの先頭付近で変数 errorClassName に文字列 error を設定しています。

この関数では、エラーメッセージを表示する span 要素を createElement() で毎回生成して、エラー用のクラス及び aria-live 属性を設定しています。aria-live 属性はスクリーンリーダーのユーザにエラーが発生した際にエラーの内容を伝えるためのアクセシビリティ確保のための属性です。

第2引数に指定されたエラーメッセージを span 要素の textContent に設定し、第1引数(elem)の親要素(この例の場合 div 要素)に appendChild() で子要素として追加しています。

エラーがあった場合に表示される span 要素は、送信時の処理の際に毎回削除され表示が初期化されます。

//elem :検証の対象の要素
//errorMessage :表示するエラーメッセージ
const createError = (elem, errorMessage) => {
  //span 要素を生成
  const errorSpan = document.createElement('span');
  //エラー用のクラス(errorClassName)を追加
  errorSpan.classList.add(errorClassName);
  //aria-live 属性を設定
  errorSpan.setAttribute('aria-live', 'polite');
  //引数に指定されたエラーメッセージを設定
  errorSpan.textContent = errorMessage;
  //elem の親要素の子要素として追加
  elem.parentNode.appendChild(errorSpan);
}
送信時の処理

送信時の処理は addEventListener() で form 要素の submit イベントに登録し、エラーの初期化(一度クリアする処理)やそれぞれの検証の処理を行います。

最初に、すでに表示されているエラーがあればクリアするために、エラー用のクラスを付与された要素を全て取得して削除しています。

form 要素(validationForm)のメソッドとして querySelectorAll() の引数にエラー用クラス(errorClassName)のセレクタを指定してエラーの要素を全て取得し、NodeList のメソッド forEach() を使ってそれぞれの要素を remove() で削除しています。

//送信時の処理(form 要素の submit イベント)
validationForm.addEventListener('submit', (e) => {
  //エラーを表示する要素を全て取得して削除(初期化)
  const errorElems = validationForm.querySelectorAll('.' + errorClassName);
  errorElems.forEach( (elem) => {
    //エラーを表示する要素を削除
    elem.remove(); 
  });
  
  //それぞれの検証の処理(省略)

}); 

4行目はイベントリスナーがアタッチされた要素(リスナーを登録した要素)を参照する currentTarget プロパティを使って以下のように記述することもできます。

const errorElems = e.currentTarget.querySelectorAll('.' + errorClassName);
//currentTarget プロパティはイベントリスナーを登録した要素を参照

removeChild() を使う

以下は要素自身を削除する remove() の代わりに子ノードを削除する removeChild() を使う例です、

//form 要素の直接の子要素の div 要素を全て取得
const parentDiv = document.querySelectorAll('form > div');
parentDiv.forEach( (elem) => {
  //div 要素を基点にエラー用クラスが指定されている要素を取得
  const errorElem = elem.querySelector('.' + errorClassName);
  if(errorElem) {
    //div 要素で removeChild() を使ってエラー用クラスが指定されている要素を削除
    elem.removeChild(errorElem); 
  }
});

値の検証

form 要素の submit イベントを使って、フォームに入力された値をサーバーに送信する前に検証します。

このサンプルの場合、検証用クラスを指定された要素を querySelectorAll() で全て取得して、NodeList のメソッド forEach() を使って、取得した各要素に対して検証を実行しています(検証の詳細は後述)。

//required クラスを指定された要素の集まりを取得 
const requiredElems = document.querySelectorAll('.required');
・・・中略・・・
            
//送信時の処理(form 要素の submit イベント)
validationForm.addEventListener('submit', (e) => {
  //エラーを一度クリア(初期化)
  const errorElems = validationForm.querySelectorAll('.' + errorClassName);
  errorElems.forEach( (elem) => {
    elem.remove(); 
  });
  
  //.required(検証用クラス)を指定した要素を検証
  requiredElems.forEach( (elem) => {
    //値(value プロパティ)の前後の空白文字を削除
    const elemValue = elem.value.trim(); 
    //値が空の場合はエラーを表示してフォームの送信を中止
    if(elemValue.length === 0) {
      createError(elem, '入力は必須です');
      e.preventDefault();
    }
  });
  
  //その他の検証の処理(省略)

}); 
エラーの位置までスクロール

エラーがあった場合は、最初のエラー用クラスを指定した要素の位置へスクロールするようにしています。

最初のエラー用クラスを指定した要素を取得するには form 要素を基点に querySelector() にエラー用クラスのセレクタを指定しています。

要素の位置を offsetTop で取得し、window.scrollTo() でその位置まで垂直方向にスクロールします。

//エラーの最初の要素を取得
const errorElem =  validationForm.querySelector('.' + errorClassName);
//エラーがあればエラーの最初の要素の位置へスクロール
if(errorElem) {
  const errorElemOffsetTop = errorElem.offsetTop;
  window.scrollTo({
    top: errorElemOffsetTop - 40,  //40px 上に位置を調整
    //スムーススクロール
    behavior: 'smooth'
  });
}

必須の検証

必須の検証は要素(コントロール)の種類により異なります。

テキストフィールド(type 属性が text や email などの input 要素)とテキストエリア(textarea 要素)の場合は、その要素の value プロパティの値が空かどうかで判定できます。

セレクトボックス(select 要素)の場合も、value プロパティの値が空かどうかで判定できます。

チェックチェック(type 属性が checkbox の input 要素)やラジオボタン(type 属性が radio の input 要素)の場合は、選択されている要素があるかどうかで判定します。

テキストフィールドとテキストエリア

以下はテキストフィールド(type 属性が text や email などの input 要素)とテキストエリア(textarea 要素)を必須とする例です。

サンプル(別ページで開く)

form 要素には validationForm クラスを、必須の検証をする要素には required クラスを指定します。

required クラスが指定された要素を querySelectorAll() で全て取得し、送信時に forEach() メソッドを使ってそれぞれの要素に対し値が空かどうかを検証して、検証を満たさない(値が空の)場合はエラーを表示して送信を中止します。

テキストフィールドテキストエリアに入力された値はその要素の value プロパティで取得できます。

取得した値が空白文字のみの場合は、エラーとなるように trim() で値の前後の空白文字を削除しています(30行目)。

値が空かどうかの判定は比較演算子(=== )を使い、値の文字列の長さが0かどうかを比較しています。値が空の場合、「elemValue.length === 0」は true が返るので(32行目)、createError() でエラーを表示(追加)し、 preventDefault() でフォームの送信(この場合のデフォルトの動作)を中止します。

<form name="myForm" class="validationForm" novalidate>
  <div>
    <label for="name">名前 </label>
    <input class="required" type="text" name="name" id="name">
  </div>
  <div>
    <label for="inquiry">お問い合わせ内容</label>
    <textarea class="required" name="inquiry" id="inquiry" rows="5" cols="50"></textarea>
  </div>
  <button name="send">送信</button>
</form>

<script> 
//.validationForm を指定した最初の form 要素を取得
const validationForm = document.querySelector('.validationForm');
//required クラスを指定された要素の集まり  
const requiredElems = document.querySelectorAll('.required');

//送信時の処理
validationForm.addEventListener('submit', (e) => {
  //エラーを表示する要素を全て取得して削除(初期化)
  const errorElems = validationForm.querySelectorAll('.error');
  errorElems.forEach( (elem) => {
    elem.remove(); 
  });

  //.required を指定した全ての要素を forEach() でそれぞれ検証
  requiredElems.forEach( (elem) => {
    //値(value プロパティ)の前後の空白文字を削除
    const elemValue = elem.value.trim(); 
    //値が空の場合はエラーを表示してフォームの送信を中止
    if(elemValue.length === 0) {
      createError(elem, '入力は必須です');
      e.preventDefault();
    }
  });
}); 
  
//エラーメッセージを表示する span 要素を生成して親要素に追加する関数
//elem :対象の要素
//errorMessage :表示するエラーメッセージ
const createError = (elem, errorMessage) => {
  //span 要素を生成
  const errorSpan = document.createElement('span');
  //エラー用のクラスを追加(設定)
  errorSpan.classList.add('error');
  //aria-live 属性を設定
  errorSpan.setAttribute('aria-live', 'polite');
  //引数に指定されたエラーメッセージを設定
  errorSpan.textContent = errorMessage;
  //elem の親要素の子要素として追加
  elem.parentNode.appendChild(errorSpan);
}
</script>

セレクトボックス

以下はセレクトボックス(select 要素)の選択を必須とする例です。

サンプル(別ページで開く)

HTML では選択を必須とするセレクトボックスの要素に required クラスを指定します。

select 要素の value プロパティは選択されている option 要素があれば、選択されている最初の option 要素の value プロパティの値を返し、選択されている option 要素がなければ、空文字列を返します。

但し、単一選択型(プルダウンリスト)のセレクトボックスの場合、option 要素に selected 属性を指定していない場合は、最初の option 要素が選択状態になるので、以下のサンプルでは初期状態で選択状態になる option の value を ""(空)にしています(10行目)。

このため、ユーザが何も操作しない場合、select 要素の value プロパティの値は空文字列になっているので、セレクトボックスの値が選択されているかの検証は(テキストの入力を必須にする検証と同様)、値が空かどうかを検証します。

この例では前述のテキストフィールドとテキストエリアの検証に追加する形で、value プロパティの値が空の場合に tagName プロパティで要素を判定し、select 要素の場合には「選択してください」と表示するようにしています。tagName プロパティの値(タグ名)は大文字で表現されます。

<form name="myForm" class="validationForm" novalidate>
  <div>
    <label for="name">名前 </label>
    <input class="required" type="text" name="name" id="name">
  </div>
  <div>
    <label for="season">連絡方法を選択してください</label>
    <select class="required" name="season" id="season">
      <!-- 初期状態で選択状態になる option の value を ""(空)に -->
      <option value="">選択してください</option>
      <option value="email">Email</option>
      <option value="tel">電話</option>
      <option value="mail">郵便</option>
    </select>
  </div>
  <button name="send">送信</button>
</form>

<script> 
//.validationForm を指定した最初の form 要素を取得
const validationForm = document.querySelector('.validationForm');
//required クラスを指定された要素の集まり  
const requiredElems = document.querySelectorAll('.required');

//送信時の処理
validationForm.addEventListener('submit', (e) => {
  //エラー表示の初期化(一度全てのエラーを削除)
  const errorElems = validationForm.querySelectorAll('.error');
  errorElems.forEach( (elem) => {
    elem.remove(); 
  });
  
  //.required を指定した要素を検証
  requiredElems.forEach( (elem) => {
    //値(value プロパティ)の前後の空白文字を削除
    const elemValue = elem.value.trim(); 
    //値が空の場合はエラーを表示してフォームの送信を中止
    if(elemValue.length === 0) {
      //その要素が select 要素の場合
      if(elem.tagName === 'SELECT') {
        createError(elem, '選択してください');
      }else{ //その要素が select 要素以外の場合
        createError(elem, '入力は必須です');
      }
      //フォームの送信を中止
      e.preventDefault();
    }
  });
}); 
  
//エラーメッセージを表示する span 要素を生成して親要素に追加する関数
const createError = (elem, errorMessage) => {
  const errorSpan = document.createElement('span');
  errorSpan.classList.add('error');
  errorSpan.setAttribute('aria-live', 'polite');
  errorSpan.textContent = errorMessage;
  elem.parentNode.appendChild(errorSpan);
}
</script>

関連ページ:JavaScript フォームとフォームコントロールの使い方(セレクトボックス)

チェックボックス

以下はチェックボックス(type 属性が checkbox の input 要素)の選択を必須とする例です。

サンプル(別ページで開く)

HTML では選択を必須とするチェックボックスの要素の1つに required クラスを指定します。

検証ではチェックボックスの場合とその他(テキストフィールドやセレクトボックスなど)で分岐します。

要素がチェックボックスの場合、tagName プロパティの値は INPUT で且つ type 属性の値が checkbox になります(35行目)。

選択状態にあるチェックボックスは、:checked 擬似クラスが適用されます。そのため、チェックボックスの親要素で :checked 擬似クラスを使ったセレクタを querySelectorAll() に指定すれば選択状態にあるチェックボックスを取得できます。

選択状態のチェックボックス要素を取得できない場合は、いずれのチェックボックスも選択されていないのでエラーを表示して e.preventDefault() で送信処理を中止します。

<form name="myForm" class="validationForm" novalidate>
  <div>
    <label for="name">名前 </label>
    <input class="required" type="text" name="name" id="name">
  </div>
  <div>
      <p>連絡方法を選択してください(複数選択可)</p>
      <input class="required" type="checkbox" name="contact[]" id="email" value="Email">
      <label for="email"> メール</label>
      <input type="checkbox" name="contact[]" id="telephone" value="Telephone">
      <label for="telephone"> 電話</label>
      <input type="checkbox" name="contact[]" id="mail" value="Mail">
      <label for="mail"> 郵便 </label> 
    </div>
  <button name="send">送信</button>
</form>

<script> 
//.validationForm を指定した最初の form 要素を取得
const validationForm = document.querySelector('.validationForm');
//required クラスを指定された要素の集まり  
const requiredElems = document.querySelectorAll('.required');

//送信時の処理
validationForm.addEventListener('submit', (e) => {
  //エラー表示の初期化(一度全てのエラーを削除)
  const errorElems = validationForm.querySelectorAll('.error');
  errorElems.forEach( (elem) => {
    elem.remove(); 
  });

  //.required を指定した要素を検証
  requiredElems.forEach( (elem) => {
    //チェックボックスの場合
    if(elem.tagName === 'INPUT' && elem.getAttribute('type') === 'checkbox') {
      //親要素を基点に選択状態の最初のチェックボックス要素を取得
      const checked = elem.parentElement.querySelector('input[type="checkbox"]:checked');
      //選択状態のチェックボックス要素を取得できない場合
      if(checked === null) {
        //エラーを表示
        createError(elem, '少なくとも1つを選択してください');
        //フォームの送信を中止
        e.preventDefault();
      }
    }else{
      const elemValue = elem.value.trim(); 
      //値が空の場合はエラーを表示してフォームの送信を中止
      if(elemValue.length === 0) {
        if(elem.tagName === 'SELECT') {
          createError(elem, '選択してください');
        }else{
          createError(elem, '入力は必須です');
        } 
        e.preventDefault();
      } 
    }  
  });
}); 
  
//エラーメッセージを表示する span 要素を生成して親要素に追加する関数
const createError = (elem, errorMessage) => {
  const errorSpan = document.createElement('span');
  errorSpan.classList.add('error');
  errorSpan.setAttribute('aria-live', 'polite');
  errorSpan.textContent = errorMessage;
  elem.parentNode.appendChild(errorSpan);
}
</script>

関連ページ:JavaScript フォームとフォームコントロールの使い方(チェックボックス)

ラジオボタン

以下はラジオボタン(type 属性が radio の input 要素)の選択を必須とする例です。

サンプル(別ページで開く)

HTML では選択を必須とするラジオボタンの要素の1つに required クラスを指定します。

検証では前述のチェエクボックスの検証にラジオボタン用の検証を追加します。

要素がラジオボタンの場合、tagName プロパティの値は INPUT で且つ type 属性の値が radio になります(35行目)。

選択状態にあるラジオボタンは、チェックボックス同様、:checked 擬似クラスが適用されます。そのため、ラジオボタンの親要素で :checked 擬似クラスを使ったセレクタを querySelectorAll() に指定すれば選択状態にあるラジオボタンを取得できます。

選択状態のラジオボタン要素を取得できない場合は、いずれのラジオボタンも選択されていないのでエラーを表示して e.preventDefault() で送信処理を中止します。

<form name="myForm" class="validationForm" novalidate>
  <div>
    <label for="name">名前 </label>
    <input class="required" type="text" name="name" id="name">
  </div>
  <div>
    <p>連絡方法を選択してください</p>
    <input type="radio" class="required" name="contact" value="Email" id="email">
    <label for="email"> メール </label>
    <input type="radio" name="contact" value="Tel" id="tel">
    <label for="tel"> 電話 </label>
    <input type="radio" name="contact" value="Mail" id="mail">
    <label for="mail"> 郵便 </label>
  </div>
  <button name="send">送信</button>
</form>

<script> 
//.validationForm を指定した最初の form 要素を取得
const validationForm = document.querySelector('.validationForm');
//required クラスを指定された要素の集まり  
const requiredElems = document.querySelectorAll('.required');

//送信時の処理
validationForm.addEventListener('submit', (e) => {
  //エラー表示の初期化(一度全てのエラーを削除)
  const errorElems = e.currentTarget.querySelectorAll('.error');
  errorElems.forEach( (elem) => {
    elem.remove(); 
  });

  //.required を指定した要素を検証
  requiredElems.forEach( (elem) => {
    //ラジオボタンの場合
    if(elem.tagName === 'INPUT' && elem.getAttribute('type') === 'radio') {
      //選択状態の最初のラジオボタン要素を取得
      const checkedRadio = elem.parentElement.querySelector('input[type="radio"]:checked');
      //選択状態のラジオボタン要素を取得できない場合
      if(checkedRadio === null) {
        createError(elem, 'いずれか1つを選択してください');
        e.preventDefault();
      }  
    }else if(elem.tagName === 'INPUT' && elem.getAttribute('type') === 'checkbox') {
      //選択状態の最初のチェックボックス要素を取得
      const checkedCheckbox = elem.parentElement.querySelector('input[type="checkbox"]:checked');
      //選択状態のチェックボックス要素を取得できない場合
      if(checkedCheckbox === null) {
        createError(elem, '少なくとも1つを選択してください');
        e.preventDefault();
      } 
    }else{
      const elemValue = elem.value.trim(); 
      //値が空の場合はエラーを表示してフォームの送信を中止
      if(elemValue.length === 0) {
        if(elem.tagName === 'SELECT') {
          createError(elem, '選択してください');
        }else{
          createError(elem, '入力は必須です');
        } 
        e.preventDefault();
      } 
    }  
  });
}); 
  
//エラーメッセージを表示する span 要素を生成して親要素に追加する関数
const createError = (elem, errorMessage) => {
  const errorSpan = document.createElement('span');
  errorSpan.classList.add('error');
  errorSpan.setAttribute('aria-live', 'polite');
  errorSpan.textContent = errorMessage;
  elem.parentNode.appendChild(errorSpan);
}
</script>

関連ページ:JavaScript フォームとフォームコントロールの使い方(ラジオボタン)

パターンの検証

JavaScript の正規表現を使って入力された文字列が期待する形式(パターン)に合致するかを検証することができます。

以下は email クラスを指定したテキストフィールド(input 要素)に入力された値が正しい形式のメールアドレスかを検証する例です。

正しい形式のメールアドレスを入力すれば送信されますが、正しくない形式のメールアドレスを入力して送信ボタンをクリックするとエラーが表示され送信されません。

但し、以下のサンプルでは必須の検証をしていないので、何も入力せずに送信することはできます。

サンプル(別ページで開く)

document.querySelectorAll('.email') で取得した全ての email クラスを指定した要素を forEach() メソッドを使ってそれぞれ検証します(28〜39行目)。

Email の検証に使用する正規表現パターンは必要に応じて(サーバー側の検証に合わせて)変更します。

また、空の値に対してパターンを検証するとマッチしないため、パターンの検証はその要素の値(elem.value.trim())が空でない場合に実行しています。

このサンプルでは test() メソッドを使って指定したパターンに入力された値がマッチするかを検証し、マッチしない場合はエラーを表示してフォームの送信を中止します。

test() メソッドは、引数に指定されて文字列が正規表現とマッチすれば true を返し、マッチしなければ false を返します。

<form name="myForm" class="validationForm" novalidate>
  <div>
    <label for="email1">メールアドレス 1 </label>
    <input class="email" type="email" id="email1" name="email1" size="30">
  </div>
  <div>
    <label for="email2">メールアドレス 2 </label>
    <input class="email" type="email" id="email2" name="email2" size="30">
  </div>
  <button name="send">送信</button>
</form>

<script> 
//.validationForm を指定した(最初の)要素を取得
const validationForm = document.querySelector('.validationForm');
//email クラスを指定された要素の集まり
const emailElems =  document.querySelectorAll('.email');

//form 要素の submit イベントを使った送信時の処理
validationForm.addEventListener('submit', (e) => {
  //エラーの初期化
  const errorElems = e.currentTarget.querySelectorAll('.error');
  errorElems.forEach( (elem) => {
    elem.remove(); 
  });

  //.email を指定した要素のパターンを検証
  emailElems.forEach( (elem) => {
    //Email の検証に使用する正規表現パターン
    const pattern = /^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ui;
    //値が空でなければ
    if(elem.value.trim() !=='') {
      //test() メソッドで値を判定し、マッチしなければエラーを表示してフォームの送信を中止
      if(!pattern.test(elem.value)) {
        createError(elem, 'Email アドレスの形式が正しくないようです。');
        e.preventDefault();
      }
    }
  });
}); 
  
//エラーメッセージを表示する span 要素を生成して親要素に追加する関数
const createError = (elem, errorMessage) => {
  const errorSpan = document.createElement('span');
  errorSpan.classList.add('error');
  errorSpan.setAttribute('aria-live', 'polite');
  errorSpan.textContent = errorMessage;
  elem.parentNode.appendChild(errorSpan);
}
</script>

この例や最初のサンプルでは、検証するパターンごとに .email や .tel などのクラスを指定していますが、data-* 属性(カスタムデータ属性)を利用すれば柔軟にパターンを指定して検証することができます。

カスタムデータ属性を利用

カスタムデータ属性(data-* 属性)は data- からはじまる任意の属性名を使って HTML の各要素に対し独自の値を設定することができる仕組みです。

設定したカスタムデータ属性の値は通常の属性同様 getAttribute() やカスタムデータ属性専用の dataset プロパティを使って取得できます。

<p id="foo" data-sample="FOO!">Sample Text</p>
          
<script>
const foo = document.getElementById('foo');
  
//getAttribute() で data-sample 属性の値を取得
const customAttr = foo.getAttribute('data-sample');
  
//dataset を使って値を取得(data- 以降の部分をプロパティ名としてアクセス)
const customData = foo.dataset.sample;
  
console.log(customAttr);  //FOO!
console.log(customData);  //FOO! 
</script>

パターンの検証

以下は data-pattern という独自の属性(カスタムデータ属性)に指定された値を使ってパターンを検証する例です。

サンプル(別ページで開く)

data-pattern 属性に特定の文字列が指定されていれば、予め用意したパターンを使って検証し、正規表現パターンが指定されていればそのパターンを使って検証します。

この方法では、パターンを検証する要素に class="pattern" と data-pattern 属性を指定します。

data-pattern 属性の値には特定の文字列(予め用意したパターンを使用する場合)や正規表現パターンを指定します。値に正規表現パターンを指定する場合は、パターンの前後にはスラッシュ(デリミタ)は付けません(付けてしまうとスラッシュ自体もパターンの一部になってしまいます)。

<form name="myForm" class="validationForm" novalidate>
  <div>
    <label for="sample">サンプル </label>
    <input class="pattern" data-pattern="^\S{3,10}$" type="text" id="sample" name="sample" size="40" placeholder="空白文字以外で3文字以上10文字以下">
  </div>
  <div>
    <label for="username">ユーザー名 </label>
    <input class="pattern" data-pattern="user" type="text" id="username" name="username" size="40" placeholder="4文字以上10文字以下の半角英数字">
  </div>
  <div>
    <label for="email">メールアドレス </label>
    <input class="pattern" data-pattern="email"  type="email" id="email" name="email" size="40" placeholder="@やドメインを含む形式">
  </div>
  <div>
    <label for="tel">電話番号 </label>
    <input class="pattern" data-pattern="tel"  type="tel" name="tel" id="tel" size="40" placeholder="半角数字(カッコやハイフン使用可能)">
  </div>
  <button name="send">送信</button>
</form>

以下の15行目以降がパターンの検証部分です。

data-pattern 属性の値を getAttribute() または dataset を使って取得して、その値によりパターンとエラーメッセージを生成しています。

data-pattern 属性の値に email などの特定の文字列が指定されている場合は、switch 文で分岐して対応するパターンとエラーメッセージを設定します。以下で使用しているパターンは一例です(サーバー側の検証に合わせて適宜変更します)。

値に case で指定した文字列以外が指定されている場合は、その値を正規表現の RegExp() コンストラクタに指定してパターンを生成します。

用意または生成したパターンを使って test() メソッドで入力された値を検証し、マッチしない場合はエラーを表示してフォームの送信を中止します。

//.validationForm を指定した(最初の)要素を取得
const validationForm = document.querySelector('.validationForm');
//pattern クラスを指定された要素の集まり
const patternElems =  document.querySelectorAll('.pattern');

//form 要素の submit イベントを使った送信時の処理
validationForm.addEventListener('submit', (e) => {
  //エラーの初期化
  const errorElems = e.currentTarget.querySelectorAll('.error');
  errorElems.forEach( (elem) => {
    elem.remove(); 
  });

  //.pattern を指定した要素のパターンを検証
  patternElems.forEach( (elem) => {
    //data-pattern 属性の値を取得
    let dataPattern = elem.getAttribute('data-pattern');
    //または let dataPattern = elem.dataset.pattern;
    //正規表現パターンを格納する変数
    let pattern;
    //デフォルトのエラーメッセージ
    let errorMessage = '入力された形式が正しくないようです。';
    //data-pattern 属性の値が設定されていれば
    if(dataPattern) {
      //data-pattern 属性の値により switch 文で分岐
      switch(dataPattern) {
        //data-pattern 属性の値が email の場合
        case 'email' :
          //検証に使用するパターンを指定
          pattern = /^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ui;
          //エラーメッセージを指定
          errorMessage = 'メールアドレスの形式が正しくありません。';
          break;
        //data-pattern 属性の値が tel の場合
        case 'tel' :
          pattern = /^\(?\d{2,5}\)?[-(\.\s]{0,2}\d{1,4}[-)\.\s]{0,2}\d{3,4}$/;
          errorMessage = '電話番号の形式が正しくありません。';
          break;
        //data-pattern 属性の値が zip の場合
        case 'zip' :
          pattern = /^\d{3}-{0,1}\d{4}$/;
          errorMessage = '郵便番号の形式が正しくありません。';
          break;
        //data-pattern 属性の値が user の場合
        case 'user' :
          pattern = /^[a-zA-Z0-9]{4,10}$/;
          errorMessage = 'ユーザ名は4文字以上10文字以下の半角英数字です';
          break;  
        //data-pattern 属性の値が上記以外の場合
        default :
          //data-pattern 属性の値を使って正規表現パターンを生成
          pattern = new RegExp(dataPattern);
      } 
    }
    
    //値が空でなければ
    if(elem.value.trim() !=='') {
      //上記で生成したパターンの test() メソッドで値を判定
      if(!pattern.test(elem.value)) {
        //パターンにマッチしなければエラーを表示してフォームの送信を中止
        createError(elem, errorMessage);
        e.preventDefault();
      }
    }
  });
  
}); 
  
//エラーメッセージを表示する span 要素を生成して親要素に追加する関数
const createError = (elem, errorMessage) => {
  const errorSpan = document.createElement('span');
  errorSpan.classList.add('error');
  errorSpan.setAttribute('aria-live', 'polite');
  errorSpan.textContent = errorMessage;
  elem.parentNode.appendChild(errorSpan);
}

文字数の制限

文字数の制限はパターンの検証を使っても可能ですが、以下は指定されたクラス名により最小及び最大文字数の検証を実装する例です。

最小文字数や最大文字数はカスタムデータ属性(data-minlength や data-maxlength)に指定します。

サンプル(別ページで開く)

最小文字数を検証する要素には minlength クラスを指定し、data-minlength 属性に最小文字数を、最大文字数を検証するには maxlength クラスを指定し、data-maxlength 属性に最大文字数を指定します。

最小及び最大文字数を指定するには両方を指定します(パターンを使う方が良いかも知れません)。

<form name="myForm" class="validationForm" novalidate>
  <div>
    <label for="sample1">サンプル(5文字以上) </label>
    <input class="minlength" data-minlength="5" type="text" id="sample1" name="sample1" size="20">
  </div>
  <div>
    <label for="sample2">サンプル(10文字以下) </label>
    <input class="maxlength" data-maxlength="10" type="text" id="sample2" name="sample2" size="20">
  </div>
  <div>
    <label for="sample3">サンプル(3文字以上10文字以下) </label>
    <input class="minlength maxlength" data-minlength="3" data-maxlength="10"  type="text" id="sample3" name="sample3" size="20">
  </div>
  <button name="send">送信</button>
</form>

最小文字数の検証では、minlength クラスを指定された要素を全て取得して forEach() でそれぞれの要素の data-minlength 属性の値と入力された文字数を比較して検証します。

エラーメッセージは data-minlength 属性の値を使って「xx文字以上で入力ください」のように表示するようにしています。最大文字数の検証も同様です。

//.validationForm を指定した(最初の)要素を取得
const validationForm = document.querySelector('.validationForm');
//minlength クラスを指定された要素の集まり
const minlengthElems =  document.querySelectorAll('.minlength');
//maxlength クラスを指定された要素の集まり
const maxlengthElems =  document.querySelectorAll('.maxlength');

//form 要素の submit イベントを使った送信時の処理
validationForm.addEventListener('submit', (e) => {
  //エラーの初期化
  const errorElems = e.currentTarget.querySelectorAll('.error');
  errorElems.forEach( (elem) => {
    elem.remove(); 
  });

  //.minlength を指定した要素のパターンを検証
  minlengthElems.forEach( (elem) => {
    //data-minlength 属性の値(最小文字数)を取得
    let minlength = elem.getAttribute('data-minlength');
    //または let minlength = elem.dataset.minlength;
    //エラーメッセージを作成
    let errorMessage = minlength + '文字以上で入力ください';
    //data-minlength 属性の値が設定されていて且つ値が空でなければ
    if(minlength && elem.value !=='') {
      //値が data-minlength 属性で指定された最小文字数より小さければ
      if(elem.value.length < minlength) {
        //エラーを表示してフォームの送信を中止
        createError(elem, errorMessage);
        e.preventDefault();
      }
    }
  });
  
  //.maxlength を指定した要素のパターンを検証
  maxlengthElems.forEach( (elem) => {
    //data-minlength 属性の値(最大文字数)を取得
    let maxlength = elem.getAttribute('data-maxlength');
    //または let maxlength = elem.dataset.maxlength;
    //エラーメッセージを作成
    let errorMessage = maxlength + '文字以内で入力ください';
    //data-minlength 属性の値が設定されていて且つ値が空でなければ
    if(maxlength && elem.value !=='') {
      //値が data-maxlength 属性で指定された最小文字数より大きければ
      if(elem.value.length > maxlength) {
        //エラーを表示してフォームの送信を中止
        createError(elem, errorMessage);
        e.preventDefault();
      }
    }
  });
}); 
  
//エラーメッセージを表示する span 要素を生成して親要素に追加する関数
const createError = (elem, errorMessage) => {
  const errorSpan = document.createElement('span');
  errorSpan.classList.add('error');
  errorSpan.setAttribute('aria-live', 'polite');
  errorSpan.textContent = errorMessage;
  elem.parentNode.appendChild(errorSpan);
}
入力文字数を表示

以下は最大文字数を検証する maxlength クラスと data-maxlength 属性を指定した要素に showCount クラスを指定すると、入力された文字数と data-maxlength 属性に指定した最大文字数を表示する例です。

data-maxlength 属性に指定した文字数を超えると、表示されるカウントの文字色を赤色で表示します。

サンプル(別ページで開く)

使い方は最大文字数を検証する maxlength クラスと data-maxlength 属性を指定した要素に showCount クラスを指定します。

showCount クラスを指定すれば、maxlength クラスを指定しない場合でも data-maxlength を指定すれば入力文字数は表示されますが、送信時の検証は行われません。

また、showCount クラスを指定しても data-maxlength を指定しなければ入力文字数は表示されません。

<form name="myForm" class="validationForm" novalidate>
  <div>
    <label for="subject">件名 </label>
    <input class="maxlength showCount" data-maxlength="20" type="text" id="subject" name="subject" size="20" placeholder="件名(20文字以内)">
  </div>
  <div>
    <label for="inquiry">お問い合わせ内容</label>
    <textarea class="maxlength showCount" data-maxlength="100" name="inquiry" id="inquiry" rows="5" cols="50" placeholder="お問い合わせ内容(100文字以内)"></textarea>
  </div>
  <button name="send">送信</button>
</form>

<script> 
const validationForm = document.querySelector('.validationForm');
const minlengthElems =  document.querySelectorAll('.minlength');
const maxlengthElems =  document.querySelectorAll('.maxlength');

validationForm.addEventListener('submit', (e) => {
  const errorElems = e.currentTarget.querySelectorAll('.error');
  errorElems.forEach( (elem) => {
    elem.remove(); 
  });

  minlengthElems.forEach( (elem) => {
    let minlength = elem.getAttribute('data-minlength');
    let errorMessage = minlength + '文字以上で入力ください';
    if(minlength && elem.value !=='') {
      if(elem.value.length < minlength) {
        createError(elem, errorMessage);
        e.preventDefault();
      }
    }
  });

  maxlengthElems.forEach( (elem) => {
    let maxlength = elem.getAttribute('data-maxlength');
    let errorMessage = maxlength + '文字以内で入力ください';
    if(maxlength && elem.value !=='') {
      if(elem.value.length > maxlength) {
        createError(elem, errorMessage);
        e.preventDefault();
      }
    }
  });
}); 
  
const createError = (elem, errorMessage) => {
  const errorSpan = document.createElement('span');
  errorSpan.classList.add('error');
  errorSpan.setAttribute('aria-live', 'polite');
  errorSpan.textContent = errorMessage;
  elem.parentNode.appendChild(errorSpan);
}

/* ★★★ 以下を追加 ★★★ */
//showCount クラスを指定された要素の集まり
const showCountElems =  document.querySelectorAll('.showCount');  
  
//data-maxlength属性を指定した要素でshowCountクラスが指定されていれば入力文字数を表示する処理
showCountElems.forEach( (elem) => {
  //data-maxlength 属性の値を取得
  const dataMaxlength = elem.getAttribute('data-maxlength');  
  //data-maxlength 属性の値が存在し、数値であれば
  if(dataMaxlength && !isNaN(dataMaxlength)) {
    //入力文字数を表示する p 要素を生成
    const countElem = document.createElement('p');
    //生成した p 要素にクラス countSpanWrapper を設定
    countElem.classList.add('countSpanWrapper');
    //p要素のコンテンツを作成(countSpanクラスを指定したspan要素にカウントを出力。初期値は0)
    countElem.innerHTML = '<span class="countSpan">0</span>/' + parseInt(dataMaxlength);
    //入力文字数を表示する p 要素を追加
    elem.parentNode.appendChild(countElem);
  }
  //input イベントを設定
  elem.addEventListener('input', (e) => {
    //上記で作成したカウントを出力する span 要素を取得
    const countSpan = elem.parentElement.querySelector('.countSpan');
    if(countSpan) {
      //入力されている文字数を取得(e.currentTarget は elem のこと)
      const count = e.currentTarget.value.length;
      //span 要素に文字数を出力
      countSpan.textContent = count;
      //文字数が dataMaxlength(data-maxlength 属性の値)より大きい場合
      if(count > dataMaxlength) {
        //文字を赤色に
        countSpan.style.setProperty('color', 'red');
        //span 要素に overMaxCount クラスを追加
        countSpan.classList.add('overMaxCount');
      }else{
        //dataMaxlength 未満の場合は文字を元に戻し
        countSpan.style.removeProperty('color');
        //span 要素から overMaxCount クラスを削除
        countSpan.classList.remove('overMaxCount');
      }
    }
  });
});
</script>

上記の57行目からが入力文字数は表示する処理です(その前の部分は前述の例と同じです)。

showCount クラスを指定された要素を取得し、それぞれの要素に対して data-maxlength 属性の値を取得してその値が存在し、数値であれば入力文字数を表示する p 要素を生成し、親要素に追加します。

そして showCount クラスを指定された要素のそれぞれに input イベントを設定し、入力されている文字数を取得して出力します(75〜96行目)。

文字数が data-maxlength 属性で指定された値より大きい場合はインラインスタイルで文字を赤色に変更し、span 要素に overMaxCount クラスを追加します。値が小さければ元に戻します。

この例では最大文字数を超えた場合、インラインスタイルで文字色を変更していますが、その部分を削除して追加される overMaxCount クラスを使ってスタイルを設定することもできます(この例では overMaxCount クラスを使って何も設定していません)。

値が同じかの検証

2つの input 要素に入力された値が同じかどうかを検証する例です。

検証する2つの要素のうち同じ値が期待される要素に equal-to クラスと、値が同じかどうかを比較する対象の要素の id を指定した data-equal-to 属性を指定します。

サンプル(別ページで開く)

以下は2つのテキストフィールドに入力された値が同じかどうかを検証する例です。同じ値が期待される要素に equal-to クラスと data-equal-to 属性を指定します。

data-equal-to 属性には値が同じかどうかを比較する対象の要素の id 属性の値を指定します。

<form name="myForm" class="validationForm" novalidate>
  <div>
    <label for="input1">Input 1</label>
    <input id="input1" name="input1">
  </div>
  <div>
    <label for="input2">Input 2</label>
    <input class="equal-to" data-equal-to="input1" id="input2" name="input2">
  </div>
  <button name="send">送信</button>
</form>

以下の15〜28行目が値が同じかどうかを検証する部分です。

equal-to クラスを指定した要素の data-equal-to 属性から比較対象の要素の id を取得して、その id を getElementById() の引数に指定して比較対象の要素を取得し、2つの値(それぞれの value プロパティ)が一致しなければエラーを表示して送信を中止します。

//.validationForm を指定した(最初の)要素を取得
const validationForm = document.querySelector('.validationForm');
//equal-to クラスを指定された要素の集まり
const equalToElems = document.querySelectorAll('.equal-to');  
  
//form 要素の submit イベントを使った送信時の処理
validationForm.addEventListener('submit', (e) => {
  //エラーの初期化
  const errorElems = e.currentTarget.querySelectorAll('.error');
  errorElems.forEach( (elem) => {
    elem.remove(); 
  });

  //.equal-to を指定した要素を検証
  equalToElems.forEach( (elem) => {
    //比較対象の要素の id 
    const equalToId = elem.getAttribute('data-equal-to');
    //または const equalToId = elem.dataset.equalTo; 
    //比較対象の要素
    const equalToElem = document.getElementById(equalToId);
    //値が空でなければ
    if(elem.value.trim() !=='' && equalToElem.value.trim() !==''){
      //2つの値(value)が一致しなければエラーを表示して送信を中止
      if(equalToElem.value !== elem.value) {
        createError(elem, '入力された値が異なります');
        e.preventDefault();
      }
    }
  });
}); 
  
//エラーメッセージを表示する span 要素を生成して親要素に追加する関数
const createError = (elem, errorMessage) => {
  const errorSpan = document.createElement('span');
  errorSpan.classList.add('error');
  errorSpan.setAttribute('aria-live', 'polite');
  errorSpan.textContent = errorMessage;
  elem.parentNode.appendChild(errorSpan);
}

data-* 属性にハイフンが含まれる場合

dataset プロパティを使う場合、data-* 属性の data- 以降の文字列にハイフンが含まれる場合は、ハイフンを削除してキャメルケースに変換します。

例えば、data-equal-to 属性の値を取得するには dataset.equalTo で取得します(上記18行目)。

エラーをカスタマイズ

カスタムデータ属性を使って検証する要素ごとに表示するエラーメッセージをカスタマイズすることもできます。

以下はテキストフィールドの必須の検証ですが、要素に data-error-required 属性を指定するとその値をエラーメッセージとして表示するようにしています。

data-error-required 属性を指定しない場合はデフォルトのエラーメッセージを表示します。

サンプル(別ページで開く)

この例では最初の2つの input 要素には data-error-required 属性を指定して独自のエラーメッセージを表示し、最後の「件名」の input 要素には data-error-required 属性をせず、デフォルトのエラーメッセージを表示するようにしています。

<form name="myForm" class="validationForm" novalidate>
  <div>
    <label for="name">名前 </label>
    <input class="required" data-error-required="名前は必須です" name="name" id="name">
  </div>
  <div>
    <label for="username">ユーザ名 </label>
    <input class="required" data-error-required="ユーザ名を入力してください" name="username" id="username">
  </div>
  <div>
    <label for="subject">件名 </label>
    <input class="required" name="subject" id="subject">
  </div>
  <button name="send">送信</button>
</form>

検証の処理の際に、data-error-required 属性の値を取得して、値が設定されていればその値をエラーメッセージとして使用し、値が設定されていなければデフォルトのエラーメッセージを使います。

//.validationForm を指定した最初の form 要素を取得
const validationForm = document.querySelector('.validationForm');
//required クラスを指定された要素の集まり  
const requiredElems = document.querySelectorAll('.required');

//送信時の処理
validationForm.addEventListener('submit', (e) => {
  //エラーを表示する要素を全て取得して削除(初期化)
  const errorElems = validationForm.querySelectorAll('.error');
  errorElems.forEach( (elem) => {
    elem.remove(); 
  });

  //.required を指定した要素を検証
  requiredElems.forEach( (elem) => {
    const elemValue = elem.value.trim(); 
    if(elemValue.length === 0) {
      //デフォルトのエラーメッセージを設定
      let errorMessage = '入力は必須です';
      //data-error-required 属性の値を取得
      const dataError = elem.getAttribute('data-error-required');
      //または const dataError = elem.dataset.errorRequired;
      if(dataError) {
        //data-error-required 属性に値が設定されていればその値をエラーメッセージに設定
        errorMessage = dataError;
      }
      createError(elem, errorMessage);
      e.preventDefault();
    }
  });
}); 
  
//エラーメッセージを表示する span 要素を生成して親要素に追加する関数
const createError = (elem, errorMessage) => {
  const errorSpan = document.createElement('span');
  errorSpan.classList.add('error');
  errorSpan.setAttribute('aria-live', 'polite');
  errorSpan.textContent = errorMessage;
  elem.parentNode.appendChild(errorSpan);
}

サンプル

以下はカスタムデータ属性を使って検証やエラーメッセージを HTML 側でカスタマイズできるようにしたサンプルです。

値を入力して送信ボタンをクリックすると指定された検証を実施し、検証を満たさない場合はエラーを表示し、送信を中止します。

サンプル(別ページで開く)

以下は上記サンプルの HTML です。

HTML の構造

この検証のサンプルのスクリプトを使用するには、form 要素に class="validationForm" と novalidate 属性を指定し、コントロール要素と label 要素(もしあれば)を div 要素で囲む必要があります。

そして検証を行う要素に required や pattern などの検証用クラスと検証用のカスタムデータ属性を指定します。検証用クラスを指定しなければ検証は行われません。

<!-- form 要素に class="validationForm" と novalidate 属性を指定 -->
<form name="myForm" class="validationForm" novalidate>
  <!--  div 要素でコントロールとラベルを囲む -->
  <div>
    <label for="name">名前 </label>
    <!--  検証用クラスや属性をコントロール要素に指定 -->
    <input class="required pattern" data-error-required="名前は必須です" data-pattern="^.{2,20}$" data-error-pattern="2文字以上20文字以内で入力ください" type="text" name="name" id="name">
  </div>
  <div>
    <label for="tel">電話番号 </label>
    <input class="pattern" data-pattern="tel" data-error-pattern="適切な桁数及び半角数字でご入力ください" type="tel" name="tel" id="tel">
  </div>
  <div>
    <label for="email1">メールアドレス </label>
    <input class="required pattern" data-pattern="email" data-error-required="メールアドレスは必須です" data-error-pattern="メールアドレスには@やドメインが必要です" type="email" id="email1" name="email1" size="30">
  </div>
  <div>
    <label for="email2">メールアドレス 再入力(確認用)</label>
    <input class="required equal-to" data-equal-to="email1" data-error-equal-to="メールアドレスが一致しません" type="email" id="email2" name="email2" size="30">
  </div>
  <div>
    <p>色を選択してください(必須)</p>
    <input class="required" data-error-required="いずれかの色を選択してください" type="radio" name="color" value="blue" id="blue">
    <label for="blue"> 青 </label>
    <input type="radio" name="color" value="red" id="red">
    <label for="red"> 赤 </label>
    <input type="radio" name="color" value="green" id="green">
    <label for="green"> 緑 </label>
  </div>
  <div>
    <p>連絡方法を選択してください(必須:複数選択可)</p>
    <input class="required" data-error-required="連絡方法の選択は必須です" type="checkbox" name="contact[]" id="byEmail" value="Email">
    <label for="byEmail"> メール</label>
    <input type="checkbox" name="contact[]" id="byTel" value="Telephone">
    <label for="byTel"> 電話</label>
    <input type="checkbox" name="contact[]" id="byMail" value="Mail">
    <label for="byMail"> 郵便 </label>
  </div>
  <div>
    <select class="required" data-error-required="季節が選択されていません" name="season" id="season">
      <!-- 初期状態で選択されている項目のvalue を空に -->
      <option value="">選択してください(必須)</option>
      <option value="spring">春</option>
      <option value="summer">夏</option>
      <option value="autumn">秋</option>
      <option value="winter">冬</option>
    </select>
  </div>
  <div>
    <label for="subject">件名</label>
    <input type="text" class="required maxlength" data-maxlength="30" id="subject" name="subject">
  </div>
  <div>
    <label for="inquiry">お問い合わせ内容</label>
    <textarea class="required maxlength showCount" data-maxlength="100" name="inquiry" id="inquiry" rows="5" cols="50"></textarea>
  </div>
  <button name="send">送信</button>
</form>

<!--  検証用の JavaScript(basicFormValidation.js)の読み込み -->
<script src="basicFormValidation.js"></script>

以下は検証する要素に指定するクラス名や属性です。

※ 必須の検証以外は検証に必要な値をカスタムデータ属性(data-*)で指定する必要があります。

カスタムエラー用の属性を指定すれば、独自のエラーメッセージを表示することができます(指定しない場合はデフォルトのエラーメッセージが表示されます)。

検証の種類 対象要素 指定するクラス 指定する属性 カスタムエラー用属性
必須 input, textarea, select required なし data-error-required
パターン input, textarea pattern data-pattern data-error-pattern
最大文字数 input, textarea maxlength data-maxlength data-error-maxlength
最小文字数 input, textarea minlength data-minlength data-error-minlength
値の一致 input, textarea equal-to data-equal-to data-error-equal-to

オプション(入力文字数の表示)

maxlength クラスと data-maxlength 属性を指定した要素に showCount クラス追加すると、入力された文字数と data-maxlength 属性に指定した最大文字数を表示します(例 5/100)。

スタイル用に入力文字数を表示する p 要素に countSpanWrapper クラスを指定しています。また、最大文字数を超えた場合は表示されるカウント部分の countSpan クラスを指定した span 要素に overMaxCount というクラスを追加します(入力文字数を表示)。

CSS

以下は上記サンプルで指定しているスタイルです。

/* エラーメッセージのスタイル */
.error {
  width : 100%;
  padding: 0;
  display: inline-block;
  font-size: 90%;
  color: red;
  box-sizing: border-box;
}
input[type="checkbox"]:not(input[type="checkbox"]:first-of-type ),
input[type="radio"]:not(input[type="radio"]:first-of-type ){
  margin-left: 20px;
}

検証用の JavaScript

以下がこのサンプルで使用している検証用の JavaScript です。

このスクリプトでは、class="validationForm" と novalidate 属性を指定した form 要素を独自に検証します。検証対象のフォームがそのドキュメントに1つのみであることを前提にしています。

エラーを表示する span 要素に付与するクラスは error としていますが、6行目で変更可能です。

basicFormValidation.js
//class="validationForm" と novalidate 属性を指定した form 要素を独自に検証するスクリプト
document.addEventListener('DOMContentLoaded', () => {
  const validationForm = document.querySelector('.validationForm');
  if(validationForm) {
    //エラーを表示する span 要素に付与するクラス名
    const errorClassName = 'error'; 
    const requiredElems = document.querySelectorAll('.required');
    const patternElems =  document.querySelectorAll('.pattern');
    const minlengthElems =  document.querySelectorAll('.minlength');
    const maxlengthElems =  document.querySelectorAll('.maxlength');
    const equalToElems = document.querySelectorAll('.equal-to'); 
    
    //エラーメッセージを表示する span 要素を生成して親要素に追加する関数
    const createError = (elem, errorMessage) => {
      const errorSpan = document.createElement('span');
      errorSpan.classList.add(errorClassName);
      errorSpan.setAttribute('aria-live', 'polite');
      errorSpan.textContent = errorMessage;
      elem.parentNode.appendChild(errorSpan);
    }

    //form 要素の submit イベントを使った送信時の処理
    validationForm.addEventListener('submit', (e) => {
      const errorElems = e.currentTarget.querySelectorAll('.' + errorClassName);
      errorElems.forEach( (elem) => {
        elem.remove(); 
      });
      
      //.required を指定した要素を検証
      requiredElems.forEach( (elem) => {
        const dataError = elem.getAttribute('data-error-required');
        if(elem.tagName === 'INPUT' && elem.getAttribute('type') === 'radio') {
          const checkedRadio = elem.parentElement.querySelector('input[type="radio"]:checked');
          if(checkedRadio === null) {   
            const errorMessage = dataError ? dataError : 'いずれか1つを選択してください';
            createError(elem, errorMessage);
            e.preventDefault();
          }  
        }else if(elem.tagName === 'INPUT' && elem.getAttribute('type') === 'checkbox') {
          const checkedCheckbox = elem.parentElement.querySelector('input[type="checkbox"]:checked');
          if(checkedCheckbox === null) {
            const errorMessage = dataError ? dataError : '少なくとも1つを選択してください';
            createError(elem, errorMessage);
            e.preventDefault();
          } 
        }else{
          const elemValue = elem.value.trim(); 
          if(elemValue.length === 0) {
            if(elem.tagName === 'SELECT') {
              const errorMessage = dataError ? dataError : '選択してください';
              createError(elem, errorMessage);
            }else{
              const errorMessage = dataError ? dataError : '入力は必須です';
              createError(elem, errorMessage);
            } 
            e.preventDefault();
          } 
        }  
      });
      
      //.pattern を指定した要素のパターンを検証
      patternElems.forEach( (elem) => {
        let dataPattern = elem.getAttribute('data-pattern');
        let pattern;
        const dataError = elem.getAttribute('data-error-pattern');
        let errorMessage = '';
        if(dataPattern) {
          switch(dataPattern) {
            case 'email' :
              pattern = /^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ui;
              errorMessage = dataError ? dataError : 'メールアドレスの形式が正しくありません。';
              break;
            case 'tel' :
              pattern = /^\(?\d{2,5}\)?[-(\.\s]{0,2}\d{1,4}[-)\.\s]{0,2}\d{3,4}$/;
              errorMessage = dataError ? dataError : '電話番号の形式が正しくありません。';
              break;
            case 'zip' :
              pattern = /^\d{3}-{0,1}\d{4}$/;
              errorMessage = dataError ? dataError : '郵便番号の形式が正しくありません。';
              break;
            default :
              pattern = new RegExp(dataPattern);
              errorMessage = dataError ? dataError : '入力された形式が正しくないようです。';
          } 
        }
        if(elem.value.trim() !=='') {
          if(!pattern.test(elem.value)) {
            createError(elem, errorMessage);
            e.preventDefault();
          }
        }
      });
      
      //.minlength を指定した要素のパターンを検証
      minlengthElems.forEach( (elem) => {
        let minlength = elem.getAttribute('data-minlength');
        if(minlength && elem.value !=='') {
          if(elem.value.length < minlength) {
            const dataError = elem.getAttribute('data-error-minlength');
            const errorMessage = dataError ? dataError : minlength + '文字以上で入力ください';
            createError(elem, errorMessage);
            e.preventDefault();
          }
        }
      });

      //.maxlength を指定した要素のパターンを検証
      maxlengthElems.forEach( (elem) => {
        let maxlength = elem.getAttribute('data-maxlength');       
        if(maxlength && elem.value !=='') {
          if(elem.value.length > maxlength) {
            const dataError = elem.getAttribute('data-error-maxlength');
            const errorMessage = dataError ? dataError : maxlength + '文字以内で入力ください';
            createError(elem, errorMessage);
            e.preventDefault();
          }
        }
      });
      
      //.equal-to を指定した要素を検証
      equalToElems.forEach( (elem) => {
        const equalToId = elem.getAttribute('data-equal-to');        
        const equalToElem = document.getElementById(equalToId);
        if(elem.value.trim() !=='' && equalToElem.value.trim() !==''){
          if(equalToElem.value !== elem.value) {
            const dataError = elem.getAttribute('data-error-equal-to'); 
            const errorMessage = dataError ? dataError : '入力された値が異なります';
            createError(elem, errorMessage);
            e.preventDefault();
          }
        }
      });
      
      //エラーがあればエラーの最初の要素の位置へスクロール
      const errorElem =  validationForm.querySelector('.' + errorClassName); 
      if(errorElem) {
        const errorElemOffsetTop = errorElem.offsetTop;
        window.scrollTo({
          top: errorElemOffsetTop - 40,
          behavior: 'smooth'
        });
      }
    }); 
    
    //showCount クラスを指定された要素の集まり
    const showCountElems =  document.querySelectorAll('.showCount');  
    //data-maxlengthを指定した要素でshowCountクラスが指定されていれば入力文字数を表示
    showCountElems.forEach( (elem) => {
      const dataMaxlength = elem.getAttribute('data-maxlength');  
      if(dataMaxlength && !isNaN(dataMaxlength)) {
        const countElem = document.createElement('p');
        countElem.classList.add('countSpanWrapper');
        countElem.innerHTML = '<span class="countSpan">0</span>/' + parseInt(dataMaxlength);
        elem.parentNode.appendChild(countElem);
      }
      elem.addEventListener('input', (e) => {
        const countSpan = elem.parentElement.querySelector('.countSpan');
        if(countSpan) {
          const count = e.currentTarget.value.length;
          countSpan.textContent = count;
          if(count > dataMaxlength) {
            countSpan.style.setProperty('color', 'red');
            countSpan.classList.add('overMaxCount');
          }else{
            countSpan.style.removeProperty('color');
            countSpan.classList.remove('overMaxCount');
          }
        }
      });
    });
  }
});
//class="validationForm" と novalidate 属性を指定した form 要素を独自に検証
document.addEventListener('DOMContentLoaded', () => {
  //validationForm クラスを指定した最初の form 要素を取得
  const validationForm = document.querySelector('.validationForm');
  //validationForm クラス を指定した form 要素が存在すれば
  if(validationForm) {
    //エラーを表示する span 要素に付与するクラス名(エラー用のクラス)
    const errorClassName = 'error'; 
    //required クラスを指定された要素の集まり  
    const requiredElems = document.querySelectorAll('.required');
    //pattern クラスを指定された要素の集まり
    const patternElems =  document.querySelectorAll('.pattern');
    //minlength クラスを指定された要素の集まり
    const minlengthElems =  document.querySelectorAll('.minlength');
    //maxlength クラスを指定された要素の集まり
    const maxlengthElems =  document.querySelectorAll('.maxlength');
    //equal-to クラスを指定された要素の集まり
    const equalToElems = document.querySelectorAll('.equal-to'); 
    
    //エラーメッセージを表示する span 要素を生成して親要素に追加する関数
    //elem :対象の要素
    //errorMessage :表示するエラーメッセージ
    const createError = (elem, errorMessage) => {
      //span 要素を生成
      const errorSpan = document.createElement('span');
      //error 及び引数に指定されたクラスを追加(設定)
      errorSpan.classList.add(errorClassName);
      //aria-live 属性を設定
      errorSpan.setAttribute('aria-live', 'polite');
      //引数に指定されたエラーメッセージを設定
      errorSpan.textContent = errorMessage;
      //elem の親要素の子要素として追加
      elem.parentNode.appendChild(errorSpan);
    }

    //form 要素の submit イベントを使った送信時の処理
    validationForm.addEventListener('submit', (e) => {
      //エラーを表示する要素を全て取得して削除(初期化)
      const errorElems = e.currentTarget.querySelectorAll('.' + errorClassName);
      errorElems.forEach( (elem) => {
        elem.remove(); 
      });
      
      //.required を指定した要素を検証
      requiredElems.forEach( (elem) => {
        //data-error-required 属性(カスタムエラーメッセージ)の値を取得
        const dataError = elem.getAttribute('data-error-required');
        //ラジオボタンの場合
        if(elem.tagName === 'INPUT' && elem.getAttribute('type') === 'radio') {
          //選択状態の最初のラジオボタン要素を取得
          const checkedRadio = elem.parentElement.querySelector('input[type="radio"]:checked');
          //選択状態のラジオボタン要素を取得できない場合
          if(checkedRadio === null) {   
            //エラーメッセージを設定(data-error-required が設定されていればその値を使用)
            const errorMessage = dataError ? dataError : 'いずれか1つを選択してください';
            //エラーを表示する span 要素を追加して表示
            createError(elem, errorMessage);
            //送信を中止
            e.preventDefault();
          }  
        }else if(elem.tagName === 'INPUT' && elem.getAttribute('type') === 'checkbox') {
          //選択状態の最初のチェックボックス要素を取得
          const checkedCheckbox = elem.parentElement.querySelector('input[type="checkbox"]:checked');
          //選択状態のチェックボックス要素を取得できない場合
          if(checkedCheckbox === null) {
            const errorMessage = dataError ? dataError : '少なくとも1つを選択してください';
            createError(elem, errorMessage);
            e.preventDefault();
          } 
        }else{
          const elemValue = elem.value.trim(); 
          //値が空の場合はエラーを表示してフォームの送信を中止
          if(elemValue.length === 0) {
            if(elem.tagName === 'SELECT') {
              const errorMessage = dataError ? dataError : '選択してください';
              createError(elem, errorMessage);
            }else{
              const errorMessage = dataError ? dataError : '入力は必須です';
              createError(elem, errorMessage);
            } 
            e.preventDefault();
          } 
        }  
      });
      
      //.pattern を指定した要素のパターンを検証
      patternElems.forEach( (elem) => {
        //data-pattern 属性の値を取得
        let dataPattern = elem.getAttribute('data-pattern');
        //正規表現パターンを格納する変数
        let pattern;
        //data-error-pattern 属性の値を取得
        const dataError = elem.getAttribute('data-error-pattern');
        //エラーメッセージの初期化
        let errorMessage = '';
        //data-pattern 属性の値が設定されていれば
        if(dataPattern) {
          //data-pattern 属性の値により switch 文で分岐
          switch(dataPattern) {
            //data-pattern 属性の値が email の場合
            case 'email' :
              //検証に使用するパターンを指定
              pattern = /^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ui;
              errorMessage = dataError ? dataError : 'メールアドレスの形式が正しくありません。';
              break;
            //data-pattern 属性の値が tel の場合
            case 'tel' :
              pattern = /^\(?\d{2,5}\)?[-(\.\s]{0,2}\d{1,4}[-)\.\s]{0,2}\d{3,4}$/;
              errorMessage = dataError ? dataError : '電話番号の形式が正しくありません。';
              break;
            //data-pattern 属性の値が zip の場合
            case 'zip' :
              pattern = /^\d{3}-{0,1}\d{4}$/;
              errorMessage = dataError ? dataError : '郵便番号の形式が正しくありません。';
              break;
            //data-pattern 属性の値が上記以外の場合
            default :
              //data-pattern 属性の値を使って正規表現パターンを生成
              pattern = new RegExp(dataPattern);
              //デフォルトのエラーメッセージ
              errorMessage = dataError ? dataError : '入力された形式が正しくないようです。';
          } 
        }

        //値が空でなければ
        if(elem.value.trim() !=='') {
          //上記で生成したパターンの test() メソッドで値を判定
          if(!pattern.test(elem.value)) {
            //パターンにマッチしなければエラーを表示してフォームの送信を中止
            createError(elem, errorMessage);
            e.preventDefault();
          }
        }
      });
      
      //.minlength を指定した要素のパターンを検証
      minlengthElems.forEach( (elem) => {
        //data-minlength 属性の値(最小文字数)を取得
        let minlength = elem.getAttribute('data-minlength');
        
        //data-minlength 属性の値が設定されていて且つ値が空でなければ
        if(minlength && elem.value !=='') {
          //値が data-minlength 属性で指定された最小文字数より小さければ
          if(elem.value.length < minlength) {
            //data-error-minlength 属性の値を取得
            const dataError = elem.getAttribute('data-error-minlength');
            //エラーメッセージを作成
            const errorMessage = dataError ? dataError : minlength + '文字以上で入力ください';
            createError(elem, errorMessage);
            e.preventDefault();
          }
        }
      });

      //.maxlength を指定した要素のパターンを検証
      maxlengthElems.forEach( (elem) => {
        //data-minlength 属性の値(最大文字数)を取得
        let maxlength = elem.getAttribute('data-maxlength');       
        //data-minlength 属性の値が設定されていて且つ値が空でなければ
        if(maxlength && elem.value !=='') {
          //値が data-maxlength 属性で指定された最小文字数より大きければ
          if(elem.value.length > maxlength) {
            //data-error-maxlength 属性の値を取得
            const dataError = elem.getAttribute('data-error-maxlength');
            //エラーメッセージを作成
            const errorMessage = dataError ? dataError : maxlength + '文字以内で入力ください';
            createError(elem, errorMessage);
            e.preventDefault();
          }
        }
      });
      
      //.equal-to を指定した要素を検証
      equalToElems.forEach( (elem) => {
        //比較対象の要素の id 
        const equalToId = elem.getAttribute('data-equal-to');        
        //比較対象の要素
        const equalToElem = document.getElementById(equalToId);
        //値が空でなければ
        if(elem.value.trim() !=='' && equalToElem.value.trim() !==''){
          //2つの値(value)が一致しなければエラーを表示して送信を中止
          if(equalToElem.value !== elem.value) {
            //data-error-equal-to 属性の値を取得
            const dataError = elem.getAttribute('data-error-equal-to'); 
            const errorMessage = dataError ? dataError : '入力された値が異なります';
            createError(elem, errorMessage);
            e.preventDefault();
          }
        }
      });

      //エラーの最初の要素を取得
      const errorElem =  validationForm.querySelector('.' + errorClassName);
      //エラーがあればエラーの最初の要素の位置へスクロール
      if(errorElem) {
        const errorElemOffsetTop = errorElem.offsetTop;
        window.scrollTo({
          top: errorElemOffsetTop - 40,  //40px 上に位置を調整
          //スムーススクロール
          behavior: 'smooth'
        });
      }
    }); 
    
    //showCount クラスを指定された要素の集まり
    const showCountElems =  document.querySelectorAll('.showCount');  

    //data-maxlengtを指定した要素でshowCountクラスが指定されていれば入力文字数を表示する処理
    showCountElems.forEach( (elem) => {
      //data-maxlength 属性の値を取得
      const dataMaxlength = elem.getAttribute('data-maxlength');  
      //data-maxlength 属性の値が存在し、数値であれば
      if(dataMaxlength && !isNaN(dataMaxlength)) {
        //入力文字数を表示する p 要素を生成
        const countElem = document.createElement('p');
        //生成した p 要素にクラス countSpanWrapper を設定
        countElem.classList.add('countSpanWrapper');
        //p要素のコンテンツを作成(.countSpanを指定したspan要素にカウントを出力。初期値は0)
        countElem.innerHTML = '<span class="countSpan">0</span>/' + parseInt(dataMaxlength);
        //入力文字数を表示する p 要素を追加
        elem.parentNode.appendChild(countElem);
      }
      //input イベントを設定
      elem.addEventListener('input', (e) => {
        //上記で作成したカウントを出力する span 要素を取得
        const countSpan = elem.parentElement.querySelector('.countSpan');
        if(countSpan) {
          //入力されている文字数を取得(e.currentTarget は elem のこと)
          const count = e.currentTarget.value.length;
          //span 要素に文字数を出力
          countSpan.textContent = count;
          //文字数が dataMaxlength(data-maxlength 属性の値)より大きい場合
          if(count > dataMaxlength) {
            //文字を赤色に
            countSpan.style.setProperty('color', 'red');
            //span 要素に overMaxCount クラスを追加
            countSpan.classList.add('overMaxCount');
          }else{
            //dataMaxlength 未満の場合は文字を元に戻し
            countSpan.style.removeProperty('color');
            //span 要素から overMaxCount クラスを削除
            countSpan.classList.remove('overMaxCount');
          }
        }
      });
    });
  }
});

関連ページ