/*! H5F * https://github.com/ryanseddon/H5F/ * Copyright (c) Ryan Seddon | Licensed MIT */ (function (root, factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(factory); } else if (typeof module === 'object' && module.exports) { // CommonJS module.exports = factory(); } else { // Browser globals root.H5F = factory(); } }(this, function () { var d = document, field = d.createElement("input"), emailPatt = /^[a-zA-Z0-9.!#$%&'*+-\/=?\^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/, urlPatt = /[a-z][\-\.+a-z]*:\/\//i, nodes = /^(input|select|textarea)$/i, isSubmit, bypassSubmit, usrPatt, curEvt, args, // Methods setup, validation, validity, checkField, bypassChecks, checkValidity, setCustomValidity, support, pattern, placeholder, range, required, valueMissing, listen, unlisten, preventActions, getTarget, addClass, removeClass, isHostMethod, isSiblingChecked; setup = function(form, settings) { var isCollection = !form.nodeType || false; var opts = { validClass : "valid", invalidClass : "error", requiredClass : "required", placeholderClass : "placeholder", onSubmit : Function.prototype, onInvalid : Function.prototype }; if(typeof settings === "object") { for (var i in opts) { if(typeof settings[i] === "undefined") { settings[i] = opts[i]; } } } args = settings || opts; if(isCollection) { for(var k=0,len=form.length;k<len;k++) { validation(form[k]); } } else { validation(form); } }; validation = function(form) { var f = form.elements, flen = f.length, isRequired, noValidate = !!(form.attributes["novalidate"]); listen(form,"invalid",checkField,true); listen(form,"blur",checkField,true); listen(form,"input",checkField,true); listen(form,"keyup",checkField,true); listen(form,"focus",checkField,true); listen(form,"change",checkField,true); listen(form,"click",bypassChecks,true); listen(form,"submit",function(e){ isSubmit = true; if(!bypassSubmit && !noValidate && !form.checkValidity()) { preventActions(e); return; } args.onSubmit.call(form, e); },false); if(!support()) { form.checkValidity = function() { return checkValidity(form); }; while(flen--) { isRequired = !!(f[flen].attributes["required"]); // Firefox includes fieldsets inside elements nodelist so we filter it out. if(f[flen].nodeName.toLowerCase() !== "fieldset") { validity(f[flen]); // Add validity object to field } } } }; validity = function(el) { var elem = el, missing = valueMissing(elem), attrs = { type: elem.getAttribute("type"), pattern: elem.getAttribute("pattern"), placeholder: elem.getAttribute("placeholder") }, isType = /^(email|url)$/i, evt = /^(input|keyup)$/i, fType = ((isType.test(attrs.type)) ? attrs.type : ((attrs.pattern) ? attrs.pattern : false)), patt = pattern(elem,fType), step = range(elem,"step"), min = range(elem,"min"), max = range(elem,"max"), customError = !( elem.validationMessage === "" || elem.validationMessage === undefined ); elem.checkValidity = function() { return checkValidity.call(this,elem); }; elem.setCustomValidity = function(msg) { setCustomValidity.call(elem,msg); }; elem.validity = { valueMissing: missing, patternMismatch: patt, rangeUnderflow: min, rangeOverflow: max, stepMismatch: step, customError: customError, valid: (!missing && !patt && !step && !min && !max && !customError) }; if(attrs.placeholder && !evt.test(curEvt)) { placeholder(elem); } }; checkField = function(e) { var el = getTarget(e) || e, // checkValidity method passes element not event events = /^(input|keyup|focusin|focus|change)$/i, ignoredTypes = /^(submit|image|button|reset)$/i, specialTypes = /^(checkbox|radio)$/i, checkForm = true; if(nodes.test(el.nodeName) && !(ignoredTypes.test(el.type) || ignoredTypes.test(el.nodeName))) { curEvt = e.type; if(!support()) { validity(el); } if(el.validity.valid && (el.value !== "" || specialTypes.test(el.type)) || (el.value !== el.getAttribute("placeholder") && el.validity.valid)) { removeClass(el,[args.invalidClass,args.requiredClass]); addClass(el,args.validClass); } else if(!events.test(curEvt)) { if(el.validity.valueMissing) { removeClass(el,[args.invalidClass,args.validClass]); addClass(el,args.requiredClass); } else if(!el.validity.valid) { removeClass(el,[args.validClass,args.requiredClass]); addClass(el,args.invalidClass); } } else if(el.validity.valueMissing) { removeClass(el,[args.requiredClass,args.invalidClass,args.validClass]); } if(curEvt === "input" && checkForm) { // If input is triggered remove the keyup event unlisten(el.form,"keyup",checkField,true); checkForm = false; } } }; checkValidity = function(el) { var f, ff, isDisabled, isRequired, hasPattern, invalid = false; if(el.nodeName.toLowerCase() === "form") { f = el.elements; for(var i = 0,len = f.length;i < len;i++) { ff = f[i]; isDisabled = !!(ff.attributes["disabled"]); isRequired = !!(ff.attributes["required"]); hasPattern = !!(ff.attributes["pattern"]); if(ff.nodeName.toLowerCase() !== "fieldset" && !isDisabled && (isRequired || hasPattern && isRequired)) { checkField(ff); if(!ff.validity.valid && !invalid) { if(isSubmit) { // If it's not a submit event the field shouldn't be focused ff.focus(); } invalid = true; args.onInvalid.call(el, ff); } } } return !invalid; } else { checkField(el); return el.validity.valid; } }; setCustomValidity = function(msg) { var el = this; el.validationMessage = msg; }; bypassChecks = function(e) { // handle formnovalidate attribute var el = getTarget(e); if(el.attributes["formnovalidate"] && el.type === "submit") { bypassSubmit = true; } }; support = function() { return (isHostMethod(field,"validity") && isHostMethod(field,"checkValidity")); }; // Create helper methods to emulate attributes in older browsers pattern = function(el, type) { if(type === "email") { return !emailPatt.test(el.value); } else if(type === "url") { return !urlPatt.test(el.value); } else if(!type) { return false; } else { var placeholder = el.getAttribute("placeholder"), val = el.value; usrPatt = new RegExp('^(?:' + type + ')$'); if(val === placeholder) { return false; } else if(val === "") { return false; } else { return !usrPatt.test(el.value); } } }; placeholder = function(el) { var attrs = { placeholder: el.getAttribute("placeholder") }, focus = /^(focus|focusin|submit)$/i, node = /^(input|textarea)$/i, ignoredType = /^password$/i, isNative = !!("placeholder" in field); if(!isNative && node.test(el.nodeName) && !ignoredType.test(el.type)) { if(el.value === "" && !focus.test(curEvt)) { el.value = attrs.placeholder; listen(el.form,'submit', function () { curEvt = 'submit'; placeholder(el); }, true); addClass(el,args.placeholderClass); } else if(el.value === attrs.placeholder && focus.test(curEvt)) { el.value = ""; removeClass(el,args.placeholderClass); } } }; range = function(el, type) { // Emulate min, max and step var min = parseInt(el.getAttribute("min"),10) || 0, max = parseInt(el.getAttribute("max"),10) || false, step = parseInt(el.getAttribute("step"),10) || 1, val = parseInt(el.value,10), mismatch = (val-min)%step; if(!valueMissing(el) && !isNaN(val)) { if(type === "step") { return (el.getAttribute("step")) ? (mismatch !== 0) : false; } else if(type === "min") { return (el.getAttribute("min")) ? (val < min) : false; } else if(type === "max") { return (el.getAttribute("max")) ? (val > max) : false; } } else if(el.getAttribute("type") === "number") { return true; } else { return false; } }; required = function(el) { var required = !!(el.attributes["required"]); return (required) ? valueMissing(el) : false; }; valueMissing = function(el) { var placeholder = el.getAttribute("placeholder"), specialTypes = /^(checkbox|radio)$/i, isRequired = !!(el.attributes["required"]); return !!(isRequired && (el.value === "" || el.value === placeholder || (specialTypes.test(el.type) && !isSiblingChecked(el)))); }; /* Util methods */ listen = function (node,type,fn,capture) { if(isHostMethod(window,"addEventListener")) { /* FF & Other Browsers */ node.addEventListener( type, fn, capture ); } else if(isHostMethod(window,"attachEvent") && typeof window.event !== "undefined") { /* Internet Explorer way */ if(type === "blur") { type = "focusout"; } else if(type === "focus") { type = "focusin"; } node.attachEvent( "on" + type, fn ); } }; unlisten = function (node,type,fn,capture) { if(isHostMethod(window,"removeEventListener")) { /* FF & Other Browsers */ node.removeEventListener( type, fn, capture ); } else if(isHostMethod(window,"detachEvent") && typeof window.event !== "undefined") { /* Internet Explorer way */ node.detachEvent( "on" + type, fn ); } }; preventActions = function (evt) { evt = evt || window.event; if(evt.stopPropagation && evt.preventDefault) { evt.stopPropagation(); evt.preventDefault(); } else { evt.cancelBubble = true; evt.returnValue = false; } }; getTarget = function (evt) { evt = evt || window.event; return evt.target || evt.srcElement; }; addClass = function (e,c) { var re; if (!e.className) { e.className = c; } else { re = new RegExp('(^|\\s)' + c + '(\\s|$)'); if (!re.test(e.className)) { e.className += ' ' + c; } } }; removeClass = function (e,c) { var re, m, arr = (typeof c === "object") ? c.length : 1, len = arr; if (e.className) { if (e.className === c) { e.className = ''; } else { while(arr--) { re = new RegExp('(^|\\s)' + ((len > 1) ? c[arr] : c) + '(\\s|$)'); m = e.className.match(re); if (m && m.length === 3) { e.className = e.className.replace(re, (m[1] && m[2])?' ':''); } } } } }; isHostMethod = function(o, m) { var t = typeof o[m], reFeaturedMethod = new RegExp('^function|object$', 'i'); return !!((reFeaturedMethod.test(t) && o[m]) || t === 'unknown'); }; /* Checking if one of the radio siblings is checked */ isSiblingChecked = function(el) { var siblings = document.getElementsByName(el.name); for(var i=0; i<siblings.length; i++){ if(siblings[i].checked){ return true; } } return false; }; // Since all methods are only used internally no need to expose globally return { setup: setup }; }));