v8nでフォームのバリデーションを実装してみる

以前にバリデーションライブラリのv8nの使い方の記事を投稿しましたが、今回はv8nを使って実際にフォームのバリデーションを実装してみます。

サンプルコード

今回は入力欄からフォーカスを外した際、もしくはセレクトボックスやチェックボックスの値を変更した際にバリデーションを実行する形で試してみます。
まずはサンプルのフォームです。

<form novalidate>
  <div class="form-item">
    <div class="form-key">名前(必須)</div>
    <div class="form-val">
      <input type="text" name="name" data-validate="required">
    </div>
  </div>
  <div class="form-item">
    <div class="form-key">メールアドレス(必須)</div>
    <div class="form-val">
      <input type="email" name="mailaddress" data-validate="required,email">
    </div>
  </div>
  <div class="form-item">
    <div class="form-key">メールアドレス確認(必須)</div>
    <div class="form-val">
      <input type="email" name="mail-conf" data-validate="required,email,email-conf" data-validate_email_conf="mailaddress">
    </div>
  </div>
  <div class="form-item">
    <div class="form-key">パスワード(任意・半角英数字8〜16文字)</div>
    <div class="form-val">
      <input type="password" name="password" data-validate="password">
    </div>
  </div>
  <div class="form-item">
    <div class="form-key">お問い合わせ内容(必須)</div>
    <div class="form-val">
      <textarea name="message" data-validate="required"></textarea>
    </div>
  </div>
  <div class="form-item">
    <div class="form-key">都道府県(必須)</div>
    <div class="form-val">
      <select name="prefectures" data-validate="required">
        <option value="">-----</option>
        <option value="01">北海道</option>
        <option value="47">沖縄</option>
      </select>
    </div>
  </div>
  <div class="form-item">
    <div class="form-key">連絡方法(必須)</div>
    <div class="form-val" data-check_validate="required">
      <input type="checkbox" id="contact1" name="contact[]" value="メール">
      <label for="contact1">メール</label>
      <input type="checkbox" id="contact2" name="contact[]" value="電話">
      <label for="contact2">電話</label>
      <input type="checkbox" id="contact3" name="contact[]" value="郵送">
      <label for="contact3">郵送</label>
    </div>
  </div>
</form>

1行テキストとテキストエリア、セレクトボックスはdata-validateという属性でバリデーション内容を設定する想定です。
バリデーションはカンマ区切りでの指定で、必須(required)、メールアドレス(email)、パスワード(password)など用意しています。
例外として確認用メールアドレスは元のメールアドレスも必要になるため、data-validate_email_confという属性を別途用意して、元にするinputのname属性の値を設定する想定にしています。

チェックボックスに関しては複数個のinputになるため、ラッパーの要素に対してdata-check_validateという属性を設定しています。
ラジオボタンは選択後に外すということがなく、今回の仕様だとバリデーションは特に不要そうなので特に対応していません。

次にJavaScriptです。

// バリデーションエラー時の各メッセージ
const errorMessagees = [
  {
    type: 'required',
    message: '必須項目です。'
  }, {
    type: 'email',
    message: 'メールアドレスの形式が正しくありません。'
  }, {
    type: 'password',
    message: 'パスワードが正しくありません。'
  }, {
    type: 'email-conf',
    message: 'メールアドレスが異なります。'
  }
];

/**
 * 必須項目のバリデーション
 * @param {object} event - 対象要素のイベントオブジェクト
 * @return {boolean} バリデーションの結果
 */
const validate_required = event => {
  const result = verify_required(event.currentTarget.value);
  if(!result) show_error_message(event, 'required');
  return result;
}

/**
 * 必須項目のバリデーション(チェックボックス用)
 * @param {object} event - 対象要素のイベントオブジェクト
 * @return {boolean} バリデーションの結果
 */
const validate_checkbox_required = event => {
  let result = false;
  const wrapper = event.currentTarget.closest('[data-check_validate]');
  wrapper.querySelectorAll('input[type = "checkbox"]').forEach(element => {
    if(element.checked) result = true;
  });
  if(!result) show_error_message(event, 'required', 'checkbox');
  return result;
}

/**
 * 必須項目のバリデーション処理
 * @param {string} value - バリデーションを行う文字列
 * @return {boolean} バリデーションの結果
 */
const verify_required = value => {
  return v8n()
    .not.empty()
    .test(value);
}

/**
 * メールアドレスのバリデーション
 * @param {object} event - 対象要素のイベントオブジェクト
 * @return {boolean} バリデーションの結果
 */
const validate_email = event => {
  const result = verify_email(event.currentTarget.value);
  if(!result) show_error_message(event, 'email');
  return result;
}

/**
 * メールアドレスのバリデーション処理
 * @param {string} value - バリデーションを行う文字列
 * @return {boolean} バリデーションの結果
 */
const verify_email = value => {
  return v8n()
    .pattern(/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/)
    .test(value);
}

/**
 * メールアドレス確認のバリデーション
 * @param {object} event - 対象要素のイベントオブジェクト
 * @return {boolean} バリデーションの結果
 */
const validate_email_conf = event => {
  // 確認対象のメールアドレス取得
  const targetName = event.currentTarget.getAttribute('data-validate_email_conf');
  const targetInput = document.querySelector('[name = "'+targetName+'"]');
  if(!targetInput) return true; // 対象のinputが見つからない場合は処理終了
  const mainAddress = targetInput.value;

  const result = verify_email_conf(mainAddress, event.currentTarget.value);
  if(!result) show_error_message(event, 'email-conf');
  return result;
}

/**
 * メールアドレス確認のバリデーション処理
 * @param {string} value - バリデーションを行う文字列
 * @return {boolean} バリデーションの結果
 */
const verify_email_conf = (main, conf) => {
  return v8n()
    .exact(main)
    .test(conf);
}

/**
 * パスワードのバリデーション
 * @param {object} event - 対象要素のイベントオブジェクト
 * @return {boolean} バリデーションの結果
 */
const validate_password = event => {
  const result = verify_password(event.currentTarget.value);
  if(!result) show_error_message(event, 'password');
  return result;
}

/**
 * パスワードのバリデーション処理
 * @param {string} value - バリデーションを行う文字列
 * @return {boolean} バリデーションの結果
 */
const verify_password = value => {
  return v8n()
    .minLength(8)
    .maxLength(16)
    .pattern(/^[0-9a-zA-Z]+$/)
    .test(value);
}

/**
 * 1行テキスト・テキストエリア・セレクトボックスの各種バリデーション実行
 * @param {object} event - 対象要素のイベントオブジェクト
 */
const execute_validation = (event) => {
  // エラーメッセージを表示済みの場合は削除
  if(event.currentTarget.nextElementSibling) event.currentTarget.parentNode.removeChild(event.currentTarget.nextElementSibling);

  // 実行するバリデーションを取得
  const validationList = event.currentTarget.getAttribute('data-validate').split(',');
  let validationStatus = true; // バリデーションの実行状態を管理(falseになったらその時点でバリデーション終了)

  // 必須項目のバリデーション
  if(validationList.includes('required')) {
    validationStatus = validate_required(event);
    if(!validationStatus) return;
  // 必須項目ではなく、未入力の場合はバリデーション処理を行わない
  } else if(event.currentTarget.value === '') {
    return;
  }

  // メールアドレスのバリデーション
  if(validationList.includes('email')) {
    validationStatus = validate_email(event);
    if(!validationStatus) return;
  }

  // メールアドレス確認のバリデーション
  if(validationList.includes('email-conf')) {
    validationStatus = validate_email_conf(event);
    if(!validationStatus) return;
  }

  // パスワードのバリデーション
  if(validationList.includes('password')) {
    validationStatus = validate_password(event);
    if(!validationStatus) return;
  }
}

/**
 * チェックボックスのバリデーション実行
 * @param {object} event - 対象要素のイベントオブジェクト
 */
const execute_checkbox_validation = (event) => {
  const wrapper = event.currentTarget.closest('[data-check_validate]');

  // // エラーメッセージを表示済みの場合は削除
  const errorTag = wrapper.querySelector('.error');
  if(errorTag) errorTag.parentNode.removeChild(errorTag);

  // 実行するバリデーションを取得
  const validationList = wrapper.getAttribute('data-check_validate').split(',');
  let validationStatus = true; // バリデーションの実行状態を管理(falseになったらその時点でバリデーション終了)

  // 必須項目のバリデーション
  if(validationList.includes('required')) {
    validationStatus = validate_checkbox_required(event);
    if(!validationStatus) return;
  }
}

/**
 * バリデーション結果に応じてエラーメッセージを表示
 * @param {object} event - 対象要素のイベントオブジェクト
 * @param {string} type  - エラーメッセージの種類(変数errorMessageesのtypeの値)
 * @param {string} parts - 対象要素の種類(text / checkbox)
 */
const show_error_message = (event, type, parts = 'text') => {
  // エラーメッセージの取得
  const error = errorMessagees.find(element => element.type === type);
  if(error === undefined) return;

  // エラーメッセージの要素生成
  let div = document.createElement('div');
  div.classList.add('error');
  div.append(error.message);

  // 対象要素に応じて追加
  switch (parts) {
    case 'checkbox':
      const wrapper = event.currentTarget.closest('[data-check_validate]');
      wrapper.append(div);
      break;
    default:
      event.currentTarget.parentNode.append(div);
      break;
  }
}

document.addEventListener('DOMContentLoaded', () => {
  // 対象の1行テキスト・テキストエリアにイベントを設定
  document.querySelectorAll('input[data-validate], textarea[data-validate]').forEach(element => {
    // フォーカスを外した際にバリデーション実行
    element.addEventListener('blur', execute_validation, false);
  });

  // 対象のセレクトボックスにイベントを設定
  document.querySelectorAll('select[data-validate]').forEach(element => {
    // 選択を変更した際にバリデーション実行
    element.addEventListener('change', execute_validation, false);
  });

  // 対象のチェックボックスにイベントを設定
  document.querySelectorAll('[data-check_validate] input[type = "checkbox"]').forEach(element => {
    // 選択を変更した際にバリデーション実行
    element.addEventListener('change', execute_checkbox_validation, false);
  });
}, false);

基本的な実装に関してはコメントに記載しているので省略します。
フォームバリデーションのデモページ

エラー時のメッセージはdata-validate属性を設定した要素の後、もしくはdata-check_validateを設定した要素内の末尾に追加されます。
メールアドレスの正規表現はHTML5のtype=emailを参考に設定、パスワードは半角英数字のみの設定となっているので、実際に使用する際はそのフォームの仕様に合わせて適宜変更ください。

このエントリーをはてなブックマークに追加

関連記事

コメントを残す

メールアドレスが公開されることはありません。
* が付いている欄は必須項目です

CAPTCHA


コメントが承認されるまで時間がかかります。

2024年10月
 12345
6789101112
13141516171819
20212223242526
2728293031