/** * @author Grey Suit Retail * @date August 29, 2013 * @plugin jQuery Slideshow * @url https://www.greysuitretail.com/ */ (function ($) { $.fn.GSRValidatorCheck = function( checkType, value ) { var patterns = { alnum: /[^A-Za-z0-9\ ]/, alnumhyphen: /[^A-Za-z0-9\-_]/, alpha: /[^A-Za-z]/, cc: /^(3[47]|4|5[1-5]|6011)/, csv: /[^-a-zA-Z0-9,\s]/, custom: /[^A-Za-z0-9\042\047\055\057\ _$.,!?()]/, date: /^[\d]{4}-[\d]{2}-[\d]{2}$/, email: /^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/, float: /[^0-9\.]/, img: /^[0-9A-Za-z_ \-]+(.[jJ][pP][gG]|.[jJ][pP][eE][gG]|.[gG][iI][fF]|.[pP][nN][gG])$/, ip: /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:[.](?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/, num: /[^0-9]/, phone: /[^0-9\- ()]/, url: /(([\w]+:)?\/\/)?(([\d\w]|%[a-fA-f\d]{2,2})+(:([\d\w]|%[a-fA-f\d]{2,2})+)?@)?([\d\w][-\d\w]{0,253}[\d\w]\.)+[\w]{2,4}(:[\d]+)?(\/([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)*(\?(&?([-+_~.\d\w]|%[a-fA-f\d]{2,2})=?)*)?(#([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)?/, zip: /[0-9]{5}/, postal:/^[A-Z][0-9][A-Z]\s?[0-9][A-Z][0-9]$/ // Canadian Postal Code }; switch ( checkType ) { case 'credit-card': // Get object value var value = $(this).val().replace( /\D/g, ''), valueLength = value.length, firstNumber = value[0], cardType = ''; // Make sure it's valid credit card type if ( null == patterns['cc'].exec( value ) ) return false; // Add the specific validation for the other types switch ( firstNumber ) { // Visa case '4': if ( 13 != valueLength && 16 != valueLength ) return false; // Set the type to select it in the credit card drop down cardType = 'visa'; break; // MasterCard case '5': if ( 16 != valueLength ) return false; // Set the type to select it in the credit card drop down cardType = 'master-card'; break; // Discover case '6': if ( 16 != valueLength ) return false; // Set the type to select it in the credit card drop down cardType = 'discover'; break; // American Express case '3': if ( 15 != valueLength ) return false; // Set the type to select it in the credit card drop down cardType = 'american-express'; break; } // Do the Mod 10 (Luhn Check), if valid, select the right credit card type if ( !luhnCheck( value ) ) return false; // Trigger selecting a card $(this).trigger( 'gsrv-selected-card', [cardType] ); break; case 'email' : return null != $(this).val().match( patterns.email ); break; case 'minlength' : return $(this).val().trim().length >= value; break; case 'empty' : if ( $(this).is(':radio') ) { return 0 == $('input[name="' + $(this).attr('name') + '"]:checked').length; } else { return 0 == $(this).val().trim().length; } break; case 'phone': return $(this).val().search( patterns.phone ) < 0; break; case 'zip': return $(this).val().match( patterns.zip ) || $(this).val().match( patterns.postal ); break; case 'full-name': return $(this).val().indexOf(' ') > -1 && $(this).val().indexOf(' ') !== $(this).val().length; break; } return true; }; // Object var GSRValidator = { // Init init: function( options, form ) { // setup variables var self = this; self.form = form; self.$form = $( form ); // Extend options self.options = $.extend( {}, $.fn.GSRValidator.options, options ); // get elements self.elements = self.$form.find('[data-validation]').filter(':input'); // Stop form submission self.$form.submit( function() { var is_small = $(window).width() < 768; var validation_result = self.validate( self.elements.filter(':visible') ); // scroll to error element if we are on a small window if ( is_small && $('body').data('validator-scroll-to') ) { $('html, body').animate({ scrollTop: ( $('body').data('validator-scroll-to').offset().top ) }, function() { $('body').data('validator-scroll-to', false); }); } return validation_result; }); // On leaving a field, let's validate it self.elements.bind( 'focusout gsr-validator-check', function(e) { var element = $(this); element.data( 'gsr-validator-success', self.validate( $($(this)) ) ); // Fade out in a few seconds if ( element.GSRValidatorCheck('empty') && 'gsr-validator-check' != e.type ) setTimeout( function() { // Only destroy it if it's not focused if ( !element.is(':focus') ) element.popover('destroy').parent().removeClass('has-error'); }, self.options.fadeOutSeconds * 1000 ); }); }, // Validate function validate: function( elements ) { // We plan on submitting var self = this, submit = true, result = false; elements.each( function() { var criteria = $(this).attr('data-validation').split(' '); result = self.checkCriteria( $(this), criteria ); if ( submit ) submit = result; }); return submit; }, // Check criteria checkCriteria: function( element, criteria ) { var self = this, errorMessage = false, errorMessages = []; self.element = element; self.element.val( $.trim( self.element.val() ) ); for ( var i in criteria ) { errorMessage = self.checkCriterium( criteria[i] ); if ( errorMessage ) errorMessages.push( errorMessage ); } // Check for error messages if ( 0 != errorMessages.length ) return self.showErrors( errorMessages ); // Remove any error message self.element.popover('destroy').parent().removeClass('has-error'); return true; }, // Check criterium checkCriterium: function( criterium ) { var self = this, message = false, criteriumArray = criterium.split('='); // Show what we're trying to do criterium = criteriumArray.shift(); value = criteriumArray.shift(); switch ( criterium ) { case 'credit-card': if ( !self.check('empty') && !self.check('credit-card') ) message = 'May only be a valid credit card number'; break; case 'email': if ( !self.check('empty') && !self.check('email') ) message = 'May only be a valid email address'; break; case 'minlength': if ( !self.check( 'minlength', value ) ) message = 'Must be at least ' + value + ' characters long'; break; case 'phone': if ( !self.check('empty') && !self.check('phone') ) message = 'May only be a valid phone number'; break; case 'required': if ( self.check('empty') ) message = ( $(self.element).is(':radio') ) ? 'Must select one' : 'This field is required'; break; case 'zip': if ( !self.check('empty') && !self.check('zip') ) message = 'May only be a valid zip code'; break; case 'postal': if ( !self.check('empty') && !self.check('postal') ) message = 'May only be a valid canadian postal code'; break; case 'full-name': if ( !self.check('empty') && !self.check('full-name') ) message = 'Your full name is required'; break; default:break; } return ( message ) ? self.getErrormessage( criterium, message ) : false; }, // Check error message check: function( checkType, value ) { var self = this; return $(self.element).GSRValidatorCheck( checkType, value ); }, // Get an error message getErrormessage: function( criterium, message ) { var self = this, elementError = self.element.attr('data-' + criterium + '-error'); return ( elementError ) ? elementError : message; }, // Show errors showErrors: function( messages ) { var self = this; var is_small = $(window).width() < 768; var placement = self.element.parents('form').data('popover-placement') || self.options.notificationPlacement; if ( is_small ) { placement = 'top'; } // Bootstrap popover self.element.popover('destroy').popover( { placement : placement , html : true , trigger : 'manual' , content : '' + messages.join('
') + '
' }).popover('show').parent().addClass('has-error'); if ( !$('body').data('validator-scroll-to') ) { $('body').data('validator-scroll-to', self.element); } // Static return to make the function 1-line-able (rather than having to add a secondary line: return false) return false; } }; $.fn.GSRValidator = function( options ) { return this.each(function() { var validator = Object.create( GSRValidator ); validator.init( options, this ); $.data( this, 'validator', GSRValidator ); }); }; // Default options $.fn.GSRValidator.options = { notificationPlacement : 'left' , fadeOutSeconds : 3 , patterns : { alnum: /[^A-Za-z0-9\ ]/, alnumhyphen: /[^A-Za-z0-9\-_]/, alpha: /[^A-Za-z]/, cc: /^(3[47]|4|5[1-5]|6011)/, csv: /[^-a-zA-Z0-9,\s]/, custom: /[^A-Za-z0-9\042\047\055\057\ _$.,!?()]/, date: /^[\d]{4}-[\d]{2}-[\d]{2}$/, email: /^([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$/, float: /[^0-9\.]/, img: /^[0-9A-Za-z_ \-]+(.[jJ][pP][gG]|.[jJ][pP][eE][gG]|.[gG][iI][fF]|.[pP][nN][gG])$/, ip: /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:[.](?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}$/, num: /[^0-9]/, phone: /[^0-9\- ()]/, url: /(([\w]+:)?\/\/)?(([\d\w]|%[a-fA-f\d]{2,2})+(:([\d\w]|%[a-fA-f\d]{2,2})+)?@)?([\d\w][-\d\w]{0,253}[\d\w]\.)+[\w]{2,4}(:[\d]+)?(\/([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)*(\?(&?([-+_~.\d\w]|%[a-fA-f\d]{2,2})=?)*)?(#([-+_~.\d\w]|%[a-fA-f\d]{2,2})*)?/, zip: /[^-0-9]/, postal:/^[A-Z][0-9][A-Z]\s?[0-9][A-Z][0-9]$/ // Canadian Postal Code } }; // Setup on elements $('.form-validate').GSRValidator(); }(jQuery)); /* Luhn algorithm number checker - (c) 2005-2009 - planzero.org * * This code has been released into the public domain, however please * * give credit to the original author where possible. */ function luhnCheck( number ) { // Strip any non-digits (useful for credit card numbers with spaces and hyphens) var ccnumber = number.replace( /\D/g, '' ); // Set the string length and parity var number_length = ccnumber.length; var parity = number_length % 2; // Loop through each digit and do the maths var total = 0; for ( var i = 0; i < number_length; i++ ) { var digit = number.charAt(i); // Multiply alternate digits by two if (i % 2 == parity) { digit = digit * 2; // If the sum is two digits, add them together (in effect) if ( digit > 9 ) digit = digit - 9; } // Total up the digits total = total + parseInt( digit, 10 ); } // If the total mod 10 equals 0, the number is valid return total % 10 == 0; }