123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594 |
- /*
- Validator v3.3.1
- (c) Yair Even Or
- https://github.com/yairEO/validator
- */
- ;(function(root, factory){
- var define = define || {};
- if( typeof define === 'function' && define.amd )
- define([], factory);
- else if( typeof exports === 'object' && typeof module === 'object' )
- module.exports = factory();
- else if(typeof exports === 'object')
- exports["FormValidator"] = factory();
- else
- root.FormValidator = factory();
- }(this, function(){
- function FormValidator( settings, formElm ){
- this.data = {}; // holds the form fields' data
- this.DOM = {
- scope : formElm
- };
- this.settings = this.extend({}, this.defaults, settings || {});
- this.texts = this.extend({}, this.texts, this.settings.texts || {});
- this.settings.events && this.events();
- }
- FormValidator.prototype = {
- // Validation error texts
- texts : {
- invalid : 'inupt is not as expected',
- short : 'input is too short',
- long : 'input is too long',
- checked : 'must be checked',
- empty : 'please put something here',
- select : 'Please select an option',
- number_min : 'too low',
- number_max : 'too high',
- url : 'invalid URL',
- number : 'not a number',
- email : 'email address is invalid',
- email_repeat : 'emails do not match',
- date : 'invalid date',
- time : 'invalid time',
- password_repeat : 'passwords do not match',
- no_match : 'no match',
- complete : 'input is not complete'
- },
- // default settings
- defaults : {
- alerts : true,
- events : false,
- regex : {
- url : /^(https?:\/\/)?([\w\d\-_]+\.+[A-Za-z]{2,})+\/?/,
- phone : /^\+?([0-9]|[-|' '])+$/i,
- numeric : /^[0-9]+$/i,
- alphanumeric : /^[a-zA-Z0-9]+$/i,
- email : {
- illegalChars : /[\(\)\<\>\,\;\:\\\/\"\[\]]/,
- filter : /^.+@.+\..{2,6}$/ // exmaple email "steve@s-i.photo"
- }
- },
- classes : {
- item : 'field',
- alert : 'alert',
- bad : 'bad'
- }
- },
- // Tests (per type)
- // each test return "true" when passes and a string of error text otherwise
- tests : {
- sameAsPlaceholder : function( field, data ){
- if( field.getAttribute('placeholder') )
- return data.value != field.getAttribute('placeholder') || this.texts.empty;
- else
- return true;
- },
- hasValue : function( value ){
- return value ? true : this.texts.empty;
- },
- // 'linked' is a special test case for inputs which their values should be equal to each other (ex. confirm email or retype password)
- linked : function(a, b, type){
- if( b != a ){
- // choose a specific message or a general one
- return this.texts[type + '_repeat'] || this.texts.no_match;
- }
- return true;
- },
- email : function(field, data){
- if ( !this.settings.regex.email.filter.test( data.value ) || data.value.match( this.settings.regex.email.illegalChars ) ){
- return this.texts.email;
- }
- return true;
- },
- // a "skip" will skip some of the tests (needed for keydown validation)
- text : function(field, data){
- var that = this;
- // make sure there are at least X number of words, each at least 2 chars long.
- // for example 'john F kenedy' should be at least 2 words and will pass validation
- if( data.validateWords ){
- var words = data.value.split(' ');
- // iterate on all the words
- var wordsLength = function(len){
- for( var w = words.length; w--; )
- if( words[w].length < len )
- return that.texts.short;
- return true;
- };
- if( words.length < data.validateWords || !wordsLength(2) )
- return this.texts.complete;
- return true;
- }
- if( data.lengthRange && data.value.length < data.lengthRange[0] ){
- return this.texts.short;
- }
- // check if there is max length & field length is greater than the allowed
- if( data.lengthRange && data.lengthRange[1] && data.value.length > data.lengthRange[1] ){
- return this.texts.long;
- }
- // check if the field's value should obey any length limits, and if so, make sure the length of the value is as specified
- if( data.lengthLimit && data.lengthLimit.length ){
- while( data.lengthLimit.length ){
- if( data.lengthLimit.pop() == data.value.length ){
- return true;
- }
- }
- return this.texts.complete;
- }
- if( data.pattern ){
- var regex, jsRegex;
- switch( data.pattern ){
- case 'alphanumeric' :
- regex = this.settings.regex.alphanumeric
- break;
- case 'numeric' :
- regex = this.settings.regex.numeric
- break;
- case 'phone' :
- regex = this.settings.regex.phone
- break;
- default :
- regex = data.pattern;
- }
- try{
- jsRegex = new RegExp(regex).test(data.value);
- if( data.value && !jsRegex ){
- return this.texts.invalid;
- }
- }
- catch(err){
- console.warn(err, field, 'regex is invalid');
- return this.texts.invalid;
- }
- }
- return true;
- },
- number : function( field, data ){
- var a = data.value;
- // if not not a number
- if( isNaN(parseFloat(a)) && !isFinite(a) ){
- return this.texts.number;
- }
- // not enough numbers
- else if( data.lengthRange && a.length < data.lengthRange[0] ){
- return this.texts.short;
- }
- // check if there is max length & field length is greater than the allowed
- else if( data.lengthRange && data.lengthRange[1] && a.length > data.lengthRange[1] ){
- return this.texts.long;
- }
- else if( data.minmax[0] && (a|0) < data.minmax[0] ){
- return this.texts.number_min;
- }
- else if( data.minmax[1] && (a|0) > data.minmax[1] ){
- return this.texts.number_max;
- }
- return true;
- },
- // Date is validated in European format (day,month,year)
- date : function( field, data ){
- var day, A = data.value.split(/[-./]/g), i;
- // if there is native HTML5 support:
- if( field.valueAsNumber )
- return true;
- for( i = A.length; i--; ){
- if( isNaN(parseFloat( data.value )) && !isFinite(data.value) )
- return this.texts.date;
- }
- try{
- day = new Date(A[2], A[1]-1, A[0]);
- if( day.getMonth()+1 == A[1] && day.getDate() == A[0] )
- return true;
- return this.texts.date;
- }
- catch(er){
- return this.texts.date;
- }
- },
- time : function( field, data ){
- var pattern = /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/;
- if( pattern.test(data.value) )
- return true;
- else
- return this.texts.time;
- },
- url : function( field, data ){
- // minimalistic URL validation
- if( !this.settings.regex.url.test(data.value) )
- return this.texts.url;
- return true;
- },
- hidden : function( field, data ){
- if( data.lengthRange && data.value.length < data.lengthRange[0] )
- return this.texts.short;
- if( data.pattern ){
- if( data.pattern == 'alphanumeric' && !this.settings.regex.alphanumeric.test(data.value) )
- return this.texts.invalid;
- }
- return true;
- },
- select : function( field, data ){
- return data.value ? true : this.texts.select;
- },
- checkbox : function( field, data ){
- if( field.checked ) return true;
- return this.texts.checked;
- }
- },
- /**
- * bind events on form elements
- * @param {Array/String} types [description]
- * @param {Object} formElm [optional - form element, if one is not already defined on the instance]
- * @return {[type]} [description]
- */
- events : function( types, formElm ){
- var that = this;
- types = types || this.settings.events;
- formElm = formElm || this.DOM.scope;
- if( !formElm || !types ) return;
- if( types instanceof Array )
- types.forEach(bindEventByType);
- else if( typeof types == 'string' )
- bindEventByType(types)
- function bindEventByType( type ){
- formElm.addEventListener(type, function(e){
- that.checkField(e.target)
- }, true);
- }
- },
- /**
- * Marks an field as invalid
- * @param {DOM Object} field
- * @param {String} text
- * @return {String} - useless string (should be the DOM node added for warning)
- */
- mark : function( field, text ){
- if( !text || !field )
- return false;
- var that = this;
- // check if not already marked as 'bad' and add the 'alert' object.
- // if already is marked as 'bad', then make sure the text is set again because i might change depending on validation
- var item = this.closest(field, '.' + this.settings.classes.item),
- alert = item.querySelector('.'+this.settings.classes.alert),
- warning;
- if( this.settings.alerts ){
- if( alert )
- alert.innerHTML = text;
- else{
- warning = '<div class="'+ this.settings.classes.alert +'">' + text + '</div>';
- item.insertAdjacentHTML('beforeend', warning);
- }
- }
- item.classList.remove(this.settings.classes.bad);
- // a delay so the "alert" could be transitioned via CSS
- setTimeout(function(){
- item.classList.add( that.settings.classes.bad );
- });
- return warning;
- },
- /* un-marks invalid fields
- */
- unmark : function( field ){
- var warning;
- if( !field ){
- console.warn('no "field" argument, null or DOM object not found');
- return false;
- }
- var fieldWrap = this.closest(field, '.' + this.settings.classes.item);
- if( fieldWrap ){
- warning = fieldWrap.querySelector('.'+ this.settings.classes.alert);
- fieldWrap.classList.remove(this.settings.classes.bad);
- }
- if( warning )
- warning.parentNode.removeChild(warning);
- },
- /**
- * removes unmarks all fields
- * @return {[type]} [description]
- */
- reset : function( formElm ){
- var fieldsToCheck,
- that = this;
- formElm = formElm || this.DOM.scope;
- fieldsToCheck = this.filterFormElements( formElm.elements );
- fieldsToCheck.forEach(function(elm){
- that.unmark(elm);
- });
- },
- /**
- * Normalize types if needed & return the results of the test (per field)
- * @param {String} type [form field type]
- * @param {*} value
- * @return {Boolean} - validation test result
- */
- testByType : function( field, data ){
- data = this.extend({}, data); // clone the data
- var type = data.type;
- if( type == 'tel' )
- data.pattern = data.pattern || 'phone';
- if( !type || type == 'password' || type == 'tel' || type == 'search' || type == 'file' )
- type = 'text';
- return this.tests[type] ? this.tests[type].call(this, field, data) : true;
- },
- prepareFieldData : function( field ){
- var nodeName = field.nodeName.toLowerCase(),
- id = Math.random().toString(36).substr(2,9);
- field["_validatorId"] = id;
- this.data[id] = {};
- this.data[id].value = field.value.replace(/^\s+|\s+$/g, ""); // cache the value of the field and trim it
- this.data[id].valid = true; // initialize validity of field
- this.data[id].type = field.getAttribute('type'); // every field starts as 'valid=true' until proven otherwise
- this.data[id].pattern = field.getAttribute('pattern');
- // Special treatment
- if( nodeName === "select" )
- this.data[id].type = "select";
- else if( nodeName === "textarea" )
- this.data[id].type = "text";
- /* Gather Custom data attributes for specific validation:
- */
- this.data[id].validateWords = field.getAttribute('data-validate-words') || 0;
- this.data[id].lengthRange = field.getAttribute('data-validate-length-range') ? (field.getAttribute('data-validate-length-range')+'').split(',') : [1];
- this.data[id].lengthLimit = field.getAttribute('data-validate-length') ? (field.getAttribute('data-validate-length')+'').split(',') : false;
- this.data[id].minmax = field.getAttribute('data-validate-minmax') ? (field.getAttribute('data-validate-minmax')+'').split(',') : false; // for type 'number', defines the minimum and/or maximum for the value as a number.
- this.data[id].validateLinked = field.getAttribute('data-validate-linked');
- return this.data[id];
- },
- /**
- * Find the closeset element, by selector
- * @param {Object} el [DOM node]
- * @param {String} selector [CSS-valid selector]
- * @return {Object} [Found element or null if not found]
- */
- closest : function(el, selector){
- var matchesFn;
- // find vendor prefix
- ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some(function(fn){
- if( typeof document.body[fn] == 'function' ){
- matchesFn = fn;
- return true;
- }
- return false;
- })
- var parent;
- // traverse parents
- while (el) {
- parent = el.parentElement;
- if (parent && parent[matchesFn](selector)) {
- return parent;
- }
- el = parent;
- }
- return null;
- },
- /**
- * MDN polyfill for Object.assign
- */
- extend : function( target, varArgs ){
- if( !target )
- throw new TypeError('Cannot convert undefined or null to object');
- var to = Object(target),
- nextKey, nextSource, index;
- for( index = 1; index < arguments.length; index++ ){
- nextSource = arguments[index];
- if( nextSource != null ) // Skip over if undefined or null
- for( nextKey in nextSource )
- // Avoid bugs when hasOwnProperty is shadowed
- if( Object.prototype.hasOwnProperty.call(nextSource, nextKey) )
- to[nextKey] = nextSource[nextKey];
- }
- return to;
- },
- /* Checks a single form field by it's type and specific (custom) attributes
- * {DOM Object} - the field to be checked
- * {Boolean} silent - don't mark a field and only return if it passed the validation or not
- */
- checkField : function( field, silent ){
- // skip testing fields whom their type is not HIDDEN but they are HIDDEN via CSS.
- if( field.type !='hidden' && !field.clientWidth )
- return { valid:true, error:"" }
- field = this.filterFormElements( [field] )[0];
- // if field did not pass filtering or is simply not passed
- if( !field )
- return { valid:true, error:"" }
- // this.unmark( field );
- var linkedTo,
- testResult,
- optional = field.className.indexOf('optional') != -1,
- data = this.prepareFieldData( field ),
- form = this.closest(field, 'form'); // if the field is part of a form, then cache it
- // check if field has any value
- /* Validate the field's value is different than the placeholder attribute (and attribute exists)
- * this is needed when fixing the placeholders for older browsers which does not support them.
- */
- // first, check if the field even has any value
- testResult = this.tests.hasValue.call(this, data.value);
- // if the field has value, check if that value is same as placeholder
- if( testResult === true )
- testResult = this.tests.sameAsPlaceholder.call(this, field, data );
- data.valid = optional || testResult === true;
- if( optional && !data.value ){
- return { valid:true, error:"" }
- }
- if( testResult !== true )
- data.valid = false;
- // validate by type of field. use 'attr()' is proffered to get the actual value and not what the browsers sees for unsupported types.
- if( data.valid ){
- testResult = this.testByType(field, data);
- data.valid = testResult === true ? true : false;
- }
- // if this field is linked to another field (their values should be the same)
- if( data.valid && data.validateLinked ){
- if( data['validateLinked'].indexOf('#') == 0 )
- linkedTo = document.body.querySelector(data['validateLinked'])
- else if( form.length )
- linkedTo = form.querySelector('[name=' + data['validateLinked'] + ']');
- else
- linkedTo = document.body.querySelector('[name=' + data['validateLinked'] + ']');
- testResult = this.tests.linked.call(this, field.value, linkedTo.value, data.type );
- data.valid = testResult === true ? true : false;
- }
- if( !silent )
- this[data.valid ? "unmark" : "mark"]( field, testResult ); // mark / unmark the field
- return {
- valid : data.valid,
- error : data.valid === true ? "" : testResult
- };
- },
- /**
- * Only allow certain form elements which are actual inputs to be validated
- * @param {HTMLCollection} form fields Array [description]
- * @return {Array} [description]
- */
- filterFormElements : function( fields ){
- var i,
- fieldsToCheck = [];
- for( i = fields.length; i--; ) {
- var isAllowedElement = fields[i].nodeName.match(/input|textarea|select/gi),
- isRequiredAttirb = fields[i].hasAttribute('required'),
- isDisabled = fields[i].hasAttribute('disabled'),
- isOptional = fields[i].className.indexOf('optional') != -1;
- if( isAllowedElement && (isRequiredAttirb || isOptional) && !isDisabled )
- fieldsToCheck.push(fields[i]);
- }
- return fieldsToCheck;
- },
- checkAll : function( formElm ){
- if( !formElm ){
- console.warn('element not found');
- return false;
- }
- var that = this,
- result = {
- valid : true, // overall form validation flag
- fields : [] // array of objects (per form field)
- },
- fieldsToCheck = this.filterFormElements( formElm.elements );
- // get all the input/textareas/select fields which are required or optional (meaning, they need validation only if they were filled)
- fieldsToCheck.forEach(function(elm, i){
- var fieldData = that.checkField(elm);
- // use an AND operation, so if any of the fields returns 'false' then the submitted result will be also FALSE
- result.valid = !!(result.valid * fieldData.valid);
- result.fields.push({
- field : elm,
- error : fieldData.error,
- valid : !!fieldData.valid
- })
- });
- return result;
- }
- }
- return FormValidator;
- }));
|