Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
368 lines (332 sloc)
13.1 KB
This file contains 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
/*! 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 | |
}; | |
})); |