Recipe 7.8

Using Custom Validation Logic

You can perform custom validation logic to fit your business logic, and set a custom validation message on an element.

Demo

Code

JavaScript
const form = document.querySelector('#signup-form');

/**
 * Custom validation function that ensures the password and confirmPassword fields have the same value.
 * @param form the form containing the two fields
 */
function validatePasswordsMatch(form) {
  const { password, confirmPassword } = form.elements;

  if (password.value !== confirmPassword.value) {
    // A non-empty string marks the field as invalid
    confirmPassword.setCustomValidity('Passwords do not match.');
  } else {
    // An empty string marks the field as valid
    confirmPassword.setCustomValidity('');
  }
}

/**
 * Updates the custom validation on input events.
 */
form.elements.confirmPassword.addEventListener('input', () => {
  if (form.elements.confirmPassword.dataset.shouldValidate) {
    validatePasswordsMatch(form);
  }
});

/**
 * Adds the necessary event listeners to an element to participate in form validation.
 * It handles setting and clearing error messages depending on the validation state.
 * @param element The input element to validate
 */
function addValidation(element) {
  const errorElement = document.getElementById(`${element.id}-error`);

  /**
   * Fired when the form is validated and the field is not valid.
   * Sets the error message and style, and also sets the shouldValidate flag.
   */
  element.addEventListener('invalid', () => {  
    element.classList.add('border-danger');
    errorElement.textContent = element.validationMessage;
    element.dataset.shouldValidate = true;
  });

  /**
   * Fired when user input occurs in the field. If the shouldValidate flag is set,
   * it will re-check the field's validity and clear the error message if it becomes valid.
   */
  element.addEventListener('input', () => {
    if (element.dataset.shouldValidate) {
      if (element.checkValidity()) {
        element.classList.remove('border-danger');
        errorElement.textContent = '';
      }
    }
  });

  /**
   * Fired when the field loses focus, applying the shouldValidate flag.
   */
  element.addEventListener('blur', () => {
    // This field has been touched, it will now be validated on subsequent
    // `input` events.
    element.dataset.shouldValidate = true;
  });
}

addValidation(form.elements.username);
addValidation(form.elements.password);
addValidation(form.elements.confirmPassword);

form.addEventListener('submit', event => {
  event.preventDefault();
  validatePasswordsMatch(form);
  console.log(form.checkValidity());
});
HTML
<form id="signup-form" novalidate>
  <div class="mb-3">
    <label class="form-label" for="username">Username</label>
    <input required type="text" id="username" name="username" class="form-control">
    <div class="error-message text-danger" id="username-error"></div>
  </div>
  <div class="mb-3">
    <label class="form-label" for="password">Password</label>
    <input required type="password" id="password" name="password" class="form-control">
    <div class="error-message text-danger" id="password-error"></div>
  </div>
  <div class="mb-3">
    <label class="form-label" for="confirmPassword">Confirm Password</label>
    <input required type="password" id="confirmPassword" name="confirmPassword" class="form-control">
    <div class="error-message text-danger" id="confirmPassword-error"></div>
  </div>
  <button class="btn btn-primary">Submit</button>
</form>
Web API Cookbook
Joe Attardi