/* Validator v3.3.7 (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(){ // More information at: https://gist.github.com/dperini/729294 var urlRegex = new RegExp( "^" + "(?:(?:(?:https?|ftp):)?\\/\\/)" + "(?:\\S+(?::\\S*)?@)?" + "(?:" + "(?!(?:10|127)(?:\\.\\d{1,3}){3})" + "(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})" + "(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})" + "(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" + "(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" + "(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))" + "|" + "(?:" + "(?:" + "[a-z0-9\\u00a1-\\uffff]" + "[a-z0-9\\u00a1-\\uffff_-]{0,62}" + ")?" + "[a-z0-9\\u00a1-\\uffff]\\." + ")+" + "(?:[a-z\\u00a1-\\uffff]{2,}\\.?)" + ")" + "(?::\\d{2,5})?" + "(?:[/?#]\\S*)?" + "$", "i" ); 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 : 'input 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 : urlRegex, phone : /^\+?([0-9]|[-|' '])+$/i, numeric : /^[0-9]+$/i, alphanumeric : /^[a-zA-Z0-9]+$/i, time : /^([0-1][0-9]|2[0-3]):[0-5][0-9]$/, email : { illegalChars : /[\(\)\<\>\,\;\:\\\/\"\[\]]/, filter : /^.+@.+\..{2,24}$/ // 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){ var i, emails = data.value.trim().split(' ') if( !field.multiple && emails.length > 1 ) return this.texts.email for( var i = 0; i < emails.length; i++ ) if ( !this.settings.regex.email.filter.test( emails[i] ) || emails[i].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 = this.settings.regex[data.pattern]; if( !regex ) regex = data.pattern; try{ var 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 = this.settings.regex.time; if( pattern.test(data.value) ) return true; else return this.texts.time }, url : function( field, data ){ 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){ // a timeout is needed because on paste event the new value will not be available right away setTimeout(that.checkField.bind(that), 0, 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) || field, alert = item.querySelector('.'+this.settings.classes.alert), warning; if( this.settings.alerts ){ if( alert ) alert.innerHTML = text; else{ warning = '