Search code examples
javascripthtmlforms

How to validate only parts of a form?


I have a multistep form with two fieldsets, containing two required inputs each. I want to validate only the first fieldset when the user wants to continue to the second one.

However, with my current approach, the entire form is validated, although I call checkValidity() only on the input elements of the first fieldset. As a result, when progressing to the second fieldset, users are greeted with invalidated input fields and warning tooltips.

What am I doing wrong? How can I validate the fieldsets separately? I've found approaches for jQuery and JSP, but not for plain ES6.

Here is my code.

let fieldsetElements;

function validateFieldset(step) {
  const inputElements = document.querySelectorAll(`fieldset.active input`);
  return !Array.from(inputElements).find((element) => !element.checkValidity());
}

function goToStep(step) {
  Array.from(fieldsetElements)
    .filter((element) => element.classList.contains("active"))
    .forEach((element) => element.classList.remove("active"));
  fieldsetElements[step - 1].classList.add("active");
}

function continueToStep(nextStep) {
  const currentStep = nextStep - 1;
  const fieldsetValid = validateFieldset(currentStep);
  if (fieldsetValid) {
    goToStep(nextStep);
  }
}

document.addEventListener("DOMContentLoaded", () => {
  fieldsetElements = document.getElementsByTagName("fieldset");
  goToStep(1);
});
fieldset {
  display: none;
}
fieldset.active {
  display: block;
}
<form>
  <fieldset>
    <legend>Your name</legend>
    <p>
      <label for="prename">Prename (required)</label><br />
      <input type="text" id="prename" name="prename" required />
    </p>
    <p>
      <label for="surname">Surname (required)</label><br />
      <input type="text" id="surname" name="surname" required />
    </p>
    <p>
      <button onclick="continueToStep(2)">Continue</button>
    </p>
  </fieldset>
  <fieldset>
    <legend>Your contact information</legend>
    <p>
      <label for="email">Email (required)</label><br />
      <input type="email" id="email" name="email" required />
    </p>
    <p>
      <label for="phone">Phone</label><br />
      <input type="tel" id="phone" name="phone" />
    </p>
    <button onclick="goToStep(1)">Go back</button>
    <button type="submit">Submit</button>
  </fieldset>
</form>


Solution

  • The issue is that without a type attribute all button tags trigger form validation. Add type="button" to your buttons and it should work as you expect.

    let fieldsetElements;
    
    function validateFieldset(step) {
      const inputElements = document.querySelectorAll(`fieldset.active input`);
      return !Array.from(inputElements).find((element) => !element.checkValidity());
    }
    
    function goToStep(step) {
      Array.from(fieldsetElements)
        .filter((element) => element.classList.contains("active"))
        .forEach((element) => element.classList.remove("active"));
      fieldsetElements[step - 1].classList.add("active");
    }
    
    function continueToStep(nextStep) {
      const currentStep = nextStep - 1;
      const fieldsetValid = validateFieldset(currentStep);
      if (fieldsetValid) {
        goToStep(nextStep);
      }
    }
    
    document.addEventListener("DOMContentLoaded", () => {
      fieldsetElements = document.getElementsByTagName("fieldset");
      goToStep(1);
    });
    fieldset {
      display: none;
    }
    fieldset.active {
      display: block;
    }
    <form>
      <fieldset>
        <legend>Your name</legend>
        <p>
          <label for="prename">Prename (required)</label><br />
          <input type="text" id="prename" name="prename" required />
        </p>
        <p>
          <label for="surname">Surname (required)</label><br />
          <input type="text" id="surname" name="surname" required />
        </p>
        <p>
          <button type="button" onclick="continueToStep(2)">Continue</button>
        </p>
      </fieldset>
      <fieldset>
        <legend>Your contact information</legend>
        <p>
          <label for="email">Email (required)</label><br />
          <input type="email" id="email" name="email" required />
        </p>
        <p>
          <label for="phone">Phone</label><br />
          <input type="tel" id="phone" name="phone" />
        </p>
        <button  type="button" onclick="goToStep(1)">Go back</button>
        <button type="submit">Submit</button>
      </fieldset>
    </form>