Search code examples
javascriptvalidationhtml-selecthtml-input

Validating a form when either a "select" OR "input" is required


I have a page containing multiple forms, all different, and when one is submitted I use the function below to gather all the inputs from that form with the class "required" and check for empty values:

function validateForm(form) {
  var inputs = form.getElementsByTagName('input');
  var selects = form.getElementsByTagName('select');
  var errors = 0;

  for (var i = 0; i < inputs.length; i++) {
    if(inputs[i].classList.contains('required')) {    
      if(inputs[i].value === "") {
        inputs[i].classList.add("warning");
        errors++;
      } else {
        inputs[i].classList.remove("warning");
      }
    }
  }
  if(errors) {
    return false;
  } else {
    return true;
  }
}

If it finds an empty value, it adds the class "warning" which just gives the input a red border, then returns false so the form doesn't get submitted.

Here's where I'm running into trouble: Some forms contain a <select> and a text input, ONE of which must be filled in, but not both, as well as various other text inputs. I'm trying to figure out how to modify the above function to handle this.

Let's say the form is for adding a new product. The select is dynamically populated with existing product "categories" and the text input is for if the user wants to create a new category. Here's a simplified version of the form:

    <form method = "post" onsubmit = "return validateForm(this)">
      <div class = "form-group">
        <label>Product Name</label>
        <input class = "form-control required" type = "text" name = "product" />
      </div>
      <div class = "form-group">
        <select class = "form-control required" id = "category" name = "category[]">
          <option value = "">Select Existing Category</option>
          <option value = "Shirts">Shirts</option>
          <option value = "Shoes">Shoes</option>
          <option value = "Pants">Pants</option>
        </select>
      </div>
      <div class = "form-group">
        <label>Create New Category</label>
        <input class = "form-control required" type = "text" name = "category[]" />
      </div>
      <div class = "form-group">
        <input class = "btn btn-primary" type = "submit" value = "Submit" />
      </div>
    </form>

Since I'm using a for loop to go through the inputs - the select and the input are not going to have the same index, so I can't do something like this:

  if((selects[i].value === "" && inputs[i].value === "") || (selects[i].value !== "" && inputs[i].value !== "")) {
    // add the warning class to both
   }

I feel the answer lies somewhere in using the name attribute, i.e. compare selects.name and inputs.name, but how do I get around the differing index in the loop? And also, it should only make this comparison when the select is encountered anyway. It doesn't necessarily exist, depending on the form.

Basically, I need to modify my function to do this:

I. Gather all inputs and selects (if any - some forms will not) from a submitted form

II. Make sure none of the inputs with the "required" class are blank (unless there's a corresponding select, in which case see III below)

III. If there's a select, find the text input with the same "name" (not a requirement to have the same name, but I assume this is the right way to do it). One of them, but not both, must have a value. If both are blank, or both have a value, they should get the "warning" class;

Any help anyone can offer will be greatly appreciated!


Solution

  • Here's a function that do exactly what you want and can handle any form you want, as long as they have the same HTML structure.

    Notes:

    • I recommend avoiding inline event listeners as much as you can, in the snippet below I used addEventListener method to attach submit event to all the forms in the document, you can change this to just some specific forms if you want.
    • Instead of only adding a border to the required elements, I suggest you also add some text to tell what the problem is.

    // getting all forms in the page you can also get specific forms based on their class-name
    var forms = document.getElementsByTagName('form'),
      l = forms.length,
      i = 0;
    // adding submit submit event listener to the referenced forms
    for(; i < l; i++) {
      forms[i].addEventListener('submit', validateForm);
    }
    
    
    function validateForm(e) {
      var els = this.querySelectorAll('input.required'),
        len = els.length,
        err = false,
        c = 0,
        inpName = '';
      // checking if the form has a select, if so, allow only the select or the input to be filled
      var isSelect = this.getElementsByTagName('select');
      if(isSelect[0] !== undefined && isSelect[0] !== null) {
        var inp = isSelect[0].parentNode.nextElementSibling.querySelector('input.required');
        inpName = inp.name;
        if((isSelect[0].value == '' && inp.value.trim().length === 0) || (isSelect[0].value != '' && inp.value.trim().length > 0)) {
          err = true;
          isSelect[0].classList.add("warning");
          inp.classList.add("warning");
        } else {
          isSelect[0].classList.remove("warning");
          inp.classList.remove("warning");
        }
      }
    
      // iterate through the rest of the inputs and check for empty one, thus trimming them before checking
      for(; c < len; c++) {
        if(els[c].name !== inpName) {
          if(els[c].value.trim() == '') {
            err = true;
            els[c].classList.add("warning");
          } else {
            els[c].classList.remove("warning");
          }
        }
      }
      // based on the error variable, either submit the form or cancel submission
      (!err) ? this.submit():e.preventDefault();
    }
    .warning {
      border: 2px solid red;
    }
    <form method="post">
        <div class="form-group">
            <label>Product Name</label>
            <input class="form-control required" type="text" name="product" />
        </div>
        <div class="form-group">
            <select class="form-control required" id="category" name="category[]">
                <option value="">Select Existing Category</option>
                <option value="Shirts">Shirts</option>
                <option value="Shoes">Shoes</option>
                <option value="Pants">Pants</option>
            </select>
        </div>
        <div class="form-group">
            <label>Create New Category</label>
            <input class="form-control required" type="text" name="category[]" />
        </div>
        <div class="form-group">
            <input class="btn btn-primary" type="submit" value="Submit" />
        </div>
    </form>

    Hope I pushed you further.

    You may get a message saying: "The custom error module does not recognize this error." when you successfully submit the form from the snippet above, that due to StackOverflow's restrictions as they don't allow/server side code (StackOverflow doesn't let the form to be submitted).