exhtml
2/9/2018 - 8:37 AM

Instant HTML5 form validation using JavaScript

article: https://www.sitepoint.com/instant-validation/ Demo: https://codepen.io/SitePoint/pen/XgvNNe

*As an alternative, we could use the 'oninput' event, which fires as soon as any value is typed or pasted into the field oninput doesn’t fire from programmatic input, which onchange does, and we might need that to handle things like auto-complete from third-party add-ons.

Example: simple comments form, with required and validated fields. *required fields also have aria-required (provide fallback-semantics for assistive technologies that don’t understand the new input types). The ARIA specification also defines an 'aria-invalid' attribute, and that’s what we’re going to use to indicate when a field is invalid (for which there is no equivalent attribute in HTML5). The aria-invalid attribute obviously provides accessible information, but it can be also used as a CSS hook to apply the red outline:


<style>
input[aria-invalid="true"], textarea[aria-invalid="true"] {
  border: 1px solid #f00;
  box-shadow: 0 0 4px 0 #f00;
}
</style>


<form action="#" method="post">
  <fieldset>

    <legend><strong>Add your comment</strong></legend>

    <p>
      <label for="author">Name <abbr title="Required">*</abbr></label>
      <input 
        aria-required="true"
        id="author"
        name="author"
        pattern="^([- \w\d\u00c0-\u024f]+)$"
        required="required"
        size="20"
        spellcheck="false"
        title="Your name (no special characters, diacritics are okay)"
        type="text"
        value="">
    </p>

    <p>
      <label for="email">Email <abbr title="Required">*</abbr></label>
      <input 
        aria-required="true"
        id="email"
        name="email"
        pattern="^(([-\w\d]+)(\.[-\w\d]+)*@([-\w\d]+)(\.[-\w\d]+)*(\.([a-zA-Z]{2,5}|[\d]{1,3})){1,2})$" 
        required="required"
        size="30"
        spellcheck="false"
        title="Your email address"
        type="email"
        value="">
    </p>

    <p>
      <label for="website">Website</label>
      <input
        id="website"
        name="website"
        pattern="^(http[s]?:\/\/)?([-\w\d]+)(\.[-\w\d]+)*(\.([a-zA-Z]{2,5}|[\d]{1,3})){1,2}(\/([-~%\.\(\)\w\d]*\/*)*(#[-\w\d]+)?)?$"
        size="30"
        spellcheck="false"
        title="Your website address"
        type="url"
        value="">
    </p>

    <p>
      <label for="text">Comment <abbr title="Required">*</abbr></label> 
      <textarea
        aria-required="true"
        cols="40"
        id="text"
        name="text"
        required="required"
        rows="10"
        spellcheck="true"
        title="Your comment"></textarea>
    </p>

  </fieldset>
  <fieldset>

    <button name="preview" type="submit">Preview</button>
    <button name="save" type="submit">Submit Comment</button>

  </fieldset>
</form>



<script>

// Basic addEvent function
function addEvent(node, type, callback) {
  if (node.addEventListener) {
    node.addEventListener(type, function(e) {
      callback(e, e.target);
    }, false);
  } else if (node.attachEvent) {
    node.attachEvent('on' + type, function(e) {
      callback(e, e.srcElement);
    });
  }
}

//determining whether a given field should be validated (simply tests that it’s neither disabled nor readonly, and that it has either a pattern or a required attribute)
function shouldBeValidated(field) {
  return (
    !(field.getAttribute("readonly") || field.readonly) &&
    !(field.getAttribute("disabled") || field.disabled) &&
    (field.getAttribute("pattern") || field.getAttribute("required"))
  );
}
//The first two conditions may seem verbose, but they are necessary, because an element’s disabled and readonly properties don’t necessarily reflect its attribute states. In Opera, for example, a field with the hard-coded attribute readonly="readonly" will still return undefined for its readonly property (*the dot property only matches states which are set through scripting).

//main validation function: tests the field and then performs the actual validation
function instantValidation(field) {
  if (shouldBeValidated(field)) {
    //A field is invalid if it’s required but doesn’t have a value, 
    //or it has a pattern and a value but the value doesn’t match the pattern.
    //'pattern' defines the string form of a regular-expression, so we pass it to the RegExp constructor 
    //and that will create a regex object we can test against the value.
    //but we do have to pre-test the value to make sure it isn’t empty,
    //so that the regular expression itself doesn’t have to account for empty strings.
    var invalid =
      (field.getAttribute("required") && !field.value) ||
      (field.getAttribute("pattern") &&
        field.value &&
        !new RegExp(field.getAttribute("pattern")).test(field.value));

    //then control its aria-invalid attribute to indicate that state:
    //adding it to an invalid field that doesn’t already have it, 
    //or removing it from a valid field that does. 
    if (!invalid && field.getAttribute("aria-invalid")) {
      field.removeAttribute("aria-invalid");
    } else if (invalid && !field.getAttribute("aria-invalid")) {
      field.setAttribute("aria-invalid", "true");
    }
  }
}

//to put this all into action, we need to bind the validation function to an onchange event
addEvent(document, "change", function(e, target) {
  instantValidation(target);
});

//for that to work, the onchange events must bubble (using a technique that’s usually known as event delegation), 
//in IE8 and earlier onchange events don’t bubble.
/*
//workaround: get the collections of input and textarea elements, iterate through them and bind the onchange event to each field individually:

var fields = [
  document.getElementsByTagName("input"),
  document.getElementsByTagName("textarea")
];
for (var a = fields.length, i = 0; i < a; i++) {
  for (var b = fields[i].length, j = 0; j < b; j++) {
    addEvent(fields[i][j], "change", function(e, target) {
      instantValidation(target);
    });
  }
}
*/

</script>