Last active
October 2, 2017 15:30
-
-
Save james-jlo-long/deeea69801585777be1f82a9f55e7827 to your computer and use it in GitHub Desktop.
A form validator that relies on HTML5 Form Validation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Validates the form. The validation relies on HTML5 Form Validation. | |
* | |
* @see https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Form_validation | |
* @alias validate | |
* @param {Element} form | |
* Form to validate. | |
* @param {Object} settings | |
* Settings for the form. | |
* @param {String} settings.highlightClass | |
* Class to be added to the form elements to show their validation state. | |
* @param {Object} settings.messages | |
* Validation messages. The key should be the input's name and the value | |
* should be an object of constraint validation API properties to the | |
* message. An example is shown below. | |
* | |
* @example <caption>Validating a form</caption> | |
* validate(document.querySelector("form"), { | |
* messages: { | |
* email: { | |
* valueMissing: "The e-mail address is essential" | |
* } | |
* } | |
* }); | |
*/ | |
var validate = (function () { | |
"use strict"; | |
const INPUT_SELECTOR = ( | |
"input:not([type=\"submit\"]):not([type=\"hidden\"])," | |
+ "select," | |
+ "textarea" | |
); | |
const CHECKBOX_SELECTOR = "[type=\"checkbox\"]"; | |
const DEFAULT_SETTINGS = { | |
highlightClass: "validate-field", | |
messages: {} | |
}; | |
/** | |
* Creates an array of elements that match the given selector. The selector | |
* can be limited so it only searches within a given context element.# | |
* | |
* @private | |
* @param {String} selector | |
* CSS selector representing the elements to find. | |
* @param {Element} [context = document] | |
* Optional context for the CSS selector. If ommitted, document is | |
* assumed. | |
* @return {Array.<Element>} | |
* Array of any/all matching elements. | |
*/ | |
function domArray(selector, context) { | |
return Array.from((context || document).querySelectorAll(selector)); | |
} | |
/** | |
* Adds the given highlight class to the element but only if the element | |
* matches the INPUT_SELECTOR CSS selector (and doesn't already have the | |
* class). | |
* | |
* @private | |
* @param {Element} element | |
* Element to which the class should be added. | |
* @param {String} highlightClass | |
* Class to add to the element. | |
*/ | |
function addHighlightClass(element, highlightClass) { | |
if (element.matches(INPUT_SELECTOR)) { | |
element.classList.add(highlightClass); | |
} | |
}; | |
/** | |
* Helper function for getting all inputs that match the given input's name. | |
* | |
* @private | |
* @see domArray | |
* @param {Element} input | |
* Input element whose name should be used to find all related | |
* elements. | |
* @param {Element} form | |
* Form in which to find the elements. | |
* @return {Array.<Element>} | |
* All elements that have the given element's name. | |
*/ | |
function getMatchingName(input, form) { | |
return domArray(`[name="${input.name}"]`, form); | |
} | |
/** | |
* Returns whether or not the input is required. | |
* | |
* @private | |
* @param {Element} input | |
* Input whose required state should be returned. | |
* @return {Boolean} | |
* true if the input is required, false otherwise. | |
*/ | |
function isRequired(input) { | |
return input.required; | |
} | |
/** | |
* Returns whether or not the input is checked. | |
* | |
* @private | |
* @param {Element} input | |
* Input whose checked state should be returned. | |
* @return {Boolean} | |
* true if the input is checked, false otherwise. | |
*/ | |
function isChecked(input) { | |
return input.checked; | |
} | |
/** | |
* Sets the validation message based on the given input's validation state | |
* and available messages. If no matching message is found, the default | |
* validation message is returned. | |
* | |
* @private | |
* @param {Element} input | |
* Input which should possibly get a custom validation message set. | |
* @param {Object} allMessages | |
* All validation messages. | |
*/ | |
function setValidationMessage(input, allMessages) { | |
var validity = input.validity; | |
var messages = allMessages[input.name]; | |
var isMsgSet = false; | |
if (messages) { | |
Object.keys(messages).some(function (test) { | |
if (validity[test]) { | |
input.setCustomValidity(messages[test]); | |
isMsgSet = true; | |
} | |
return isMsgSet; | |
}); | |
if (!isMsgSet) { | |
input.setCustomValidity(""); | |
} | |
} | |
} | |
return function (form, settings) { | |
var config = Object.assign({}, DEFAULT_SETTINGS, settings || {}); | |
var allMessages = config.messages; | |
var highlightClass = config.highlightClass; | |
// Create an opt-out. | |
if (!form.novalidate && !form.dataset.novalidate) { | |
// Flag the form so we don't re-bind existing functionality. | |
form.dataset.novalidate = true; | |
form.addEventListener("focusout", function (e) { | |
addHighlightClass(e.target, highlightClass); | |
}); | |
form.addEventListener("invalid", function (e) { | |
domArray(INPUT_SELECTOR, form).forEach(function (input) { | |
addHighlightClass(input, highlightClass); | |
if (input.matches(CHECKBOX_SELECTOR)) { | |
getMatchingName(input, form).forEach(function (check) { | |
check.required = input.required; | |
}); | |
} | |
if (allMessages) { | |
setValidationMessage(input, allMessages); | |
} | |
}); | |
}, true); | |
form.addEventListener("change", function (e) { | |
var input = e.target; | |
var others; | |
if (input.matches("[type=\"checkbox\"]")) { | |
others = getMatchingName(input, form); | |
if (others.filter(isRequired).length) { | |
if (input.checked) { | |
others.forEach(function (other) { | |
other.required = (other === input); | |
}); | |
} else if (!others.filter(isChecked).length) { | |
others.forEach(function (other) { | |
other.required = true; | |
}); | |
} | |
} | |
} | |
}); | |
if (allMessages) { | |
form.addEventListener("input", function (e) { | |
setValidationMessage(e.target, allMessages); | |
}); | |
} | |
} | |
}; | |
}()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment