Backups Created:
/home/japatmex/public_html/wp-content/edit-wolf.php
Savvy
W
olf -
MANAGER
Edit File: parsley.min.js
/* * Parsley.js allows you to verify your form inputs frontend side, without writing a line of javascript. Or so.. * * Author: Guillaume Potier - @guillaumepotier */ !function ($) { 'use strict'; /** * Validator class stores all constraints functions and associated messages. * Provides public interface to add, remove or modify them * * @class Validator * @constructor */ var Validator = function ( options ) { /** * Error messages * * @property messages * @type {Object} */ this.messages = { defaultMessage: "This value seems to be invalid." , type: { email: "This value should be a valid email." , url: "This value should be a valid url." , urlstrict: "This value should be a valid url." , number: "This value should be a valid number." , digits: "This value should be digits." , dateIso: "This value should be a valid date (YYYY-MM-DD)." , alphanum: "This value should be alphanumeric." , phone: "This value should be a valid phone number." } , notnull: "This value should not be null." , notblank: "This value should not be blank." , required: "This value is required." , regexp: "This value seems to be invalid." , min: "This value should be greater than or equal to %s." , max: "This value should be lower than or equal to %s." , range: "This value should be between %s and %s." , minlength: "This value is too short. It should have %s characters or more." , maxlength: "This value is too long. It should have %s characters or less." , rangelength: "This value length is invalid. It should be between %s and %s characters long." , mincheck: "You must select at least %s choices." , maxcheck: "You must select %s choices or less." , rangecheck: "You must select between %s and %s choices." , equalto: "This value should be the same." }, this.init( options ); }; Validator.prototype = { constructor: Validator /** * Validator list. Built-in validators functions * * @property validators * @type {Object} */ , validators: { notnull: function () { return { validate: function ( val ) { return val.length > 0; } , priority: 2 } } , notblank: function () { return { validate: function ( val ) { return 'string' === typeof val && '' !== val.replace( /^\s+/g, '' ).replace( /\s+$/g, '' ); } , priority: 2 } } , required: function () { var that = this; return { validate: function ( val ) { // for checkboxes and select multiples. Check there is at least one required value if ( 'object' === typeof val ) { for ( var i in val ) { if ( that.required().validate( val[ i ] ) ) { return true; } } return false; } return that.notnull().validate( val ) && that.notblank().validate( val ); } , priority: 512 } } , type: function () { return { validate: function ( val, type ) { var regExp; switch ( type ) { case 'number': regExp = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/; break; case 'digits': regExp = /^\d+$/; break; case 'alphanum': regExp = /^\w+$/; break; case 'email': regExp = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))){2,6}$/i; break; case 'url': val = new RegExp( '(https?|s?ftp|git)', 'i' ).test( val ) ? val : 'http://' + val; /* falls through */ case 'urlstrict': regExp = /^(https?|s?ftp|git):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i; break; case 'dateIso': regExp = /^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$/; break; case 'phone': regExp = /^((\+\d{1,3}(-| )?\(?\d\)?(-| )?\d{1,5})|(\(?\d{2,6}\)?))(-| )?(\d{3,4})(-| )?(\d{4})(( x| ext)\d{1,5}){0,1}$/; break; default: return false; } // test regExp if not null return '' !== val ? regExp.test( val ) : false; } , priority: 256 } } , regexp: function () { return { validate: function ( val, regExp, self ) { return new RegExp( regExp, self.options.regexpFlag || '' ).test( val ); } , priority: 64 } } , minlength: function () { return { validate: function ( val, min ) { return val.length >= min; } , priority: 32 } } , maxlength: function () { return { validate: function ( val, max ) { return val.length <= max; } , priority: 32 } } , rangelength: function () { var that = this; return { validate: function ( val, arrayRange ) { return that.minlength().validate( val, arrayRange[ 0 ] ) && that.maxlength().validate( val, arrayRange[ 1 ] ); } , priority: 32 } } , min: function () { return { validate: function ( val, min ) { return Number( val ) >= min; } , priority: 32 } } , max: function () { return { validate: function ( val, max ) { return Number( val ) <= max; } , priority: 32 } } , range: function () { var that = this; return { validate: function ( val, arrayRange ) { return that.min().validate( val, arrayRange[ 0 ] ) && that.max().validate( val, arrayRange[ 1 ] ); } , priority: 32 } } , equalto: function () { return { validate: function ( val, elem, self ) { self.options.validateIfUnchanged = true; return val === $( elem ).val(); } , priority: 64 } } , remote: function () { return { validate: function ( val, url, self ) { var result = null , data = {} , dataType = {}; data[ self.$element.attr( 'name' ) ] = val; if ( 'undefined' !== typeof self.options.remoteDatatype ) dataType = { dataType: self.options.remoteDatatype }; var manage = function ( isConstraintValid, message ) { // remove error message if we got a server message, different from previous message if ( 'undefined' !== typeof message && 'undefined' !== typeof self.Validator.messages.remote && message !== self.Validator.messages.remote ) { $( self.UI.ulError + ' .remote' ).remove(); } if (false === isConstraintValid) { self.options.listeners.onFieldError( self.element, self.constraints, self ); } else if (true === isConstraintValid && false === self.options.listeners.onFieldSuccess( self.element, self.constraints, self )) { // if onFieldSuccess returns (bool) false, consider that field si invalid isConstraintValid = false; } self.updtConstraint( { name: 'remote', valid: isConstraintValid }, message ); self.manageValidationResult(); }; // transform string response into object var handleResponse = function ( response ) { if ( 'object' === typeof response ) { return response; } try { response = $.parseJSON( response ); } catch ( err ) {} return response; } var manageErrorMessage = function ( response ) { return 'object' === typeof response && null !== response ? ( 'undefined' !== typeof response.error ? response.error : ( 'undefined' !== typeof response.message ? response.message : null ) ) : null; } $.ajax( $.extend( {}, { url: url , data: data , type: self.options.remoteMethod || 'GET' , success: function ( response ) { response = handleResponse( response ); manage( 1 === response || true === response || ( 'object' === typeof response && null !== response && 'undefined' !== typeof response.success ), manageErrorMessage( response ) ); } , error: function ( response ) { response = handleResponse( response ); manage( false, manageErrorMessage( response ) ); } }, dataType ) ); return result; } , priority: 64 } } /** * Aliases for checkboxes constraints */ , mincheck: function () { var that = this; return { validate: function ( obj, val ) { return that.minlength().validate( obj, val ) } , priority: 32 } } , maxcheck: function () { var that = this; return { validate: function ( obj, val ) { return that.maxlength().validate( obj, val ) } , priority: 32 } } , rangecheck: function () { var that = this; return { validate: function ( obj, arrayRange ) { return that.rangelength().validate( obj, arrayRange ) } , priority: 32 } } } /* * Register custom validators and messages */ , init: function ( options ) { var customValidators = options.validators , customMessages = options.messages , key; for ( key in customValidators ) { this.addValidator(key, customValidators[ key ]); } for ( key in customMessages ) { this.addMessage(key, customMessages[ key ]); } } /** * Replace %s placeholders by values * * @method formatMesssage * @param {String} message Message key * @param {Mixed} args Args passed by validators functions. Could be string, number or object * @return {String} Formatted string */ , formatMesssage: function ( message, args ) { if ( 'object' === typeof args ) { for ( var i in args ) { message = this.formatMesssage( message, args[ i ] ); } return message; } return 'string' === typeof message ? message.replace( new RegExp( '%s', 'i' ), args ) : ''; } /** * Add / override a validator in validators list * * @method addValidator * @param {String} name Validator name. * @param {Function} fn Validator. Must return { validator: fn(), priority: int } */ , addValidator: function ( name, fn ) { if ('undefined' === typeof fn().validate) { throw new Error( 'Validator `' + name + '` must have a validate method. See more here: http://parsleyjs.org/documentation.html#javascript-general' ); } // add default prioirty if not given. if ('undefined' === typeof fn().priority) { fn = { validate: fn().validate , priority: 32 }; // Warn if possible if (window.console && window.console.warn) { window.console.warn( 'Validator `' + name + '` should have a priority. Default priority 32 given' ); } } this.validators[ name ] = fn; } /** * Add / override error message * * @method addMessage * @param {String} name Message name. Will automatically be binded to validator with same name * @param {String} message Message */ , addMessage: function ( key, message, type ) { if ( 'undefined' !== typeof type && true === type ) { this.messages.type[ key ] = message; return; } // custom types messages are a bit tricky cuz' nested ;) if ( 'type' === key ) { for ( var i in message ) { this.messages.type[ i ] = message[ i ]; } return; } this.messages[ key ] = message; } }; var ParsleyUI = function ( ParsleyInstance ) { this.init( ParsleyInstance ); }; ParsleyUI.prototype = { constructor: ParsleyUI , init: function ( ParsleyInstance ) { this.ParsleyInstance = ParsleyInstance; this.hash = ParsleyInstance.hash; this.options = this.ParsleyInstance.options; this.errorClassHandler = this.options.errors.classHandler( this.ParsleyInstance.element, this.ParsleyInstance.isRadioOrCheckbox ) || this.ParsleyInstance.$element; this.ulErrorManagement(); } /** * Manage ul error Container * * @private * @method ulErrorManagement */ , ulErrorManagement: function () { this.ulError = '#' + this.hash; this.ulTemplate = $( this.options.errors.errorsWrapper ).attr( 'id', this.hash ).addClass( 'parsley-error-list' ); } /** * Remove li / ul error * * @method removeError * @param {String} constraintName Method Name * @return ParsleyUI */ , removeError: function ( constraintName ) { var liError = this.ulError + ' .' + constraintName , that = this; this.options.animate ? $( liError ).fadeOut( this.options.animateDuration, function () { $( this ).remove(); if ( that.ulError && $( that.ulError ).children().length === 0 ) { that.removeErrors(); } } ) : $( liError ).remove(); return this; } /** * Add li error * * @method addError * @param {Object} { minlength: "error message for minlength constraint" } * @return ParsleyUI */ , addError: function ( error ) { for ( var constraint in error ) { var liTemplate = $( this.options.errors.errorElem ).addClass( constraint ); $( this.ulError ).append( this.options.animate ? $( liTemplate ).html( error[ constraint ] ).hide().fadeIn( this.options.animateDuration ) : $( liTemplate ).html( error[ constraint ] ) ); } return this; } /** * Update existing error if text has changed * * @method updateError * @param {Object} { minlength: "error message for minlength constraint" } * @return ParsleyUI */ , updateError: function ( error ) { for ( var constraint in error ) { if ( error[ constraint ] !== $( this.ulError + " > li." + constraint ).html() ) { this.removeError( constraint ).addError( error ); } } return this; } /** * Remove all ul / li errors * * @method removeErrors * @return ParsleyUI */ , removeErrors: function () { this.options.animate ? $( this.ulError ).fadeOut( this.options.animateDuration, function () { $( this ).remove(); } ) : $( this.ulError ).remove(); return this; } /** * Remove ul errors and parsley error or success classes * * @method reset * @return ParsleyUI */ , reset: function () { this.ParsleyInstance.valid = null; this.removeErrors(); this.ParsleyInstance.validatedOnce = false; this.errorClassHandler.removeClass( this.options.successClass ).removeClass( this.options.errorClass ); for ( var constraint in this.constraints ) { this.constraints[ constraint ].valid = null; } return this; } /** * Add li / ul errors messages * * @method manageError * @param {Object} constraint * @return ParsleyUI */ , manageError: function ( constraint ) { // display ulError container if it has been removed previously (or never shown) if ( !$( this.ulError ).length ) { this.manageErrorContainer(); } // TODO: refacto properly // if required constraint but field is not null, do not display if ( 'required' === constraint.name && null !== this.ParsleyInstance.getVal() && this.ParsleyInstance.getVal().length > 0 ) { return this; // if empty required field and non required constraint fails, do not display } else if ( this.ParsleyInstance.isRequired && 'required' !== constraint.name && ( null === this.ParsleyInstance.getVal() || 0 === this.ParsleyInstance.getVal().length ) ) { this.removeError( constraint.name ); return this; } // TODO: refacto error name w/ proper & readable function var constraintName = constraint.name , liClass = false !== this.options.errorMessage ? 'custom-error-message' : constraintName , liError = {} , message = false !== this.options.errorMessage ? this.options.errorMessage : ( constraint.name === 'type' ? this.ParsleyInstance.Validator.messages[ constraintName ][ constraint.requirements ] : ( 'undefined' === typeof this.ParsleyInstance.Validator.messages[ constraintName ] ? this.ParsleyInstance.Validator.messages.defaultMessage : this.ParsleyInstance.Validator.formatMesssage( this.ParsleyInstance.Validator.messages[ constraintName ], constraint.requirements ) ) ); liError[ liClass ] = message; // add liError if not shown. update if already exist !$( this.ulError + ' .' + liClass ).length ? this.addError( liError ) : this.updateError( liError ); return this; } /** * Create ul error container * * @method manageErrorContainer * @return ParsleyUI */ , manageErrorContainer: function () { var errorContainer = this.options.errorContainer || this.options.errors.container( this.ParsleyInstance.element, this.ParsleyInstance.isRadioOrCheckbox ) , ulTemplate = this.options.animate ? this.ulTemplate.css('display', '') : this.ulTemplate; if ( 'undefined' !== typeof errorContainer ) { $( errorContainer ).append( ulTemplate ); return; } !this.ParsleyInstance.isRadioOrCheckbox ? this.ParsleyInstance.$element.after( ulTemplate ) : this.ParsleyInstance.$element.parent().after( ulTemplate ); return this; } }; /** * ParsleyField class manage each form field inside a validated Parsley form. * Returns if field valid or not depending on its value and constraints * Manage field error display and behavior, event triggers and more * * @class ParsleyField * @constructor */ var ParsleyField = function ( element, options, type ) { this.options = options; // if type is ParsleyFieldMultiple, just return this. used for clone if ( type === 'ParsleyFieldMultiple' ) { return this; } this.init( element, type || 'ParsleyField' ); }; ParsleyField.prototype = { constructor: ParsleyField /** * Set some properties, bind constraint validators and validation events * * @method init * @param {Object} element * @param {Object} options */ , init: function ( element, type ) { this.type = type; this.valid = true; this.element = element; this.validatedOnce = false; this.$element = $( element ); this.val = this.$element.val(); this.Validator = new Validator( this.options ); this.isRequired = false; this.constraints = {}; // overriden by ParsleyItemMultiple if radio or checkbox input if ( 'undefined' === typeof this.isRadioOrCheckbox ) { this.isRadioOrCheckbox = false; this.hash = this.generateHash(); } // error ul dom management done only once at init this.UI = new ParsleyUI( this ); // bind some html5 properties if ( this.options.useHtml5Constraints ) { this.bindHtml5Constraints(); } // bind validators to field this.addConstraints(); // bind parsley events if validators have been registered if ( this.hasConstraints() ) { this.bindValidationEvents(); } } , setParent: function ( elem ) { this.$parent = $( elem ); } , getParent: function () { return this.$parent; } /** * Bind some extra html5 types / validators * * @private * @method bindHtml5Constraints */ , bindHtml5Constraints: function () { // add html5 required support + class required support if ( this.$element.hasClass( 'required' ) || this.$element.attr( 'required' ) ) { this.options.required = true; } // add html5 supported types & options var type = this.$element.attr( 'type' ); if ( 'undefined' !== typeof type && new RegExp( type, 'i' ).test( 'email url number range tel' ) ) { this.options.type = 'tel' === type ? 'phone' : type; // number and range types could have min and/or max values if ( new RegExp( this.options.type, 'i' ).test( 'number range' ) ) { this.options.type = 'number'; // double condition to support jQuery and Zepto.. :( if ( 'undefined' !== typeof this.$element.attr( 'min' ) && this.$element.attr( 'min' ).length ) { this.options.min = this.$element.attr( 'min' ); } if ( 'undefined' !== typeof this.$element.attr( 'max' ) && this.$element.attr( 'max' ).length ) { this.options.max = this.$element.attr( 'max' ); } } } if ( 'string' === typeof this.$element.attr( 'pattern' ) && this.$element.attr( 'pattern' ).length ) { this.options.regexp = this.$element.attr( 'pattern' ); } } /** * Attach field validators functions passed through data-api * * @private * @method addConstraints */ , addConstraints: function () { for ( var constraint in this.options ) { var addConstraint = {}; addConstraint[ constraint ] = this.options[ constraint ]; this.addConstraint( addConstraint, true, false ); } } /** * Dynamically add a new constraint to a field * * @method addConstraint * @param {Object} constraint { name: requirements } */ , addConstraint: function ( constraint, doNotUpdateValidationEvents, sort ) { for ( var name in constraint ) { name = name.toLowerCase(); if ( 'function' === typeof this.Validator.validators[ name ] ) { this.constraints[ name ] = { name: name , requirements: constraint[ name ] , valid: null } if ( name === 'required' ) { this.isRequired = true; } this.addCustomConstraintMessage( name ); } } // force field validation next check and reset validation events if ( 'undefined' === typeof doNotUpdateValidationEvents ) { this.bindValidationEvents(); } } /** * Dynamically update an existing constraint to a field. * Simple API: { name: requirements } * * @method updtConstraint * @param {Object} constraint */ , updateConstraint: function ( constraint, message ) { for ( var name in constraint ) { this.updtConstraint( { name: name, requirements: constraint[ name ], valid: null }, message ); } } /** * Dynamically update an existing constraint to a field. * Complex API: { name: name, requirements: requirements, valid: boolean } * * @method updtConstraint * @param {Object} constraint */ , updtConstraint: function ( constraint, message ) { this.constraints[ constraint.name ] = $.extend( true, this.constraints[ constraint.name ], constraint ); if ( 'string' === typeof message ) { if ( constraint.name === 'type' ) this.Validator.messages[ constraint.name ][ constraint.requirements ] = message ; else this.Validator.messages[ constraint.name ] = message ; } // force field validation next check and reset validation events this.bindValidationEvents(); } /** * Dynamically remove an existing constraint to a field. * * @method removeConstraint * @param {String} constraintName */ , removeConstraint: function ( constraintName ) { var constraintName = constraintName.toLowerCase(); delete this.constraints[ constraintName ]; if ( constraintName === 'required' ) { this.isRequired = false; } // if there are no more constraint, reset errors and validation state if ( !this.hasConstraints() ) { this.UI.reset(); return; } this.bindValidationEvents(); } /** * Add custom constraint message, passed through data-API * * @private * @method addCustomConstraintMessage * @param constraint */ , addCustomConstraintMessage: function ( constraint ) { // custom message type data-type-email-message -> typeEmailMessage | data-minlength-error => minlengthMessage var customMessage = constraint + ( 'type' === constraint && 'undefined' !== typeof this.options[ constraint ] ? this.options[ constraint ].charAt( 0 ).toUpperCase() + this.options[ constraint ].substr( 1 ) : '' ) + 'Message'; if ( 'undefined' !== typeof this.options[ customMessage ] ) { this.Validator.addMessage( 'type' === constraint ? this.options[ constraint ] : constraint, this.options[ customMessage ], 'type' === constraint ); } } /** * Bind validation events on a field * * @private * @method bindValidationEvents */ , bindValidationEvents: function () { // this field has validation events, that means it has to be validated this.valid = null; this.$element.addClass( 'parsley-validated' ); // remove eventually already binded events this.$element.off( '.' + this.type ); // force add 'change' event if async remote validator here to have result before form submitting if ( this.options.remote && !new RegExp( 'change', 'i' ).test( this.options.trigger ) ) { this.options.trigger = !this.options.trigger ? 'change' : ' change'; } // always bind keyup event, for better UX when a field is invalid var triggers = ( !this.options.trigger ? '' : this.options.trigger ) + ( new RegExp( 'key', 'i' ).test( this.options.trigger ) ? '' : ' keyup' ); // always bind change event, for better UX when a select is invalid if ( this.$element.is( 'select' ) ) { triggers += new RegExp( 'change', 'i' ).test( triggers ) ? '' : ' change'; } // trim triggers to bind them correctly with .on() triggers = triggers.replace( /^\s+/g , '' ).replace( /\s+$/g , '' ); this.$element.on( ( triggers + ' ' ).split( ' ' ).join( '.' + this.type + ' ' ), false, $.proxy( this.eventValidation, this ) ); } /** * Hash management. Used for ul error * * @method generateHash * @returns {String} 5 letters unique hash */ , generateHash: function () { return 'parsley-' + ( Math.random() + '' ).substring( 2 ); } /** * Public getHash accessor * * @method getHash * @returns {String} hash */ , getHash: function () { return this.hash; } /** * Returns field val needed for validation * Special treatment for radio & checkboxes * * @method getVal * @returns {String} val */ , getVal: function () { if ('undefined' !== typeof this.$element.domApi( this.options.namespace )[ 'value' ]) { return this.$element.domApi( this.options.namespace )[ 'value' ]; } return this.$element.val(); } /** * Called when validation is triggered by an event * Do nothing if val.length < this.options.validationMinlength * * @method eventValidation * @param {Object} event jQuery event */ , eventValidation: function ( event ) { var val = this.getVal(); // do nothing on keypress event if not explicitely passed as data-trigger and if field has not already been validated once if ( event.type === 'keyup' && !/keyup/i.test( this.options.trigger ) && !this.validatedOnce ) { return true; } // do nothing on change event if not explicitely passed as data-trigger and if field has not already been validated once if ( event.type === 'change' && !/change/i.test( this.options.trigger ) && !this.validatedOnce ) { return true; } // start validation process only if field has enough chars and validation never started if ( !this.isRadioOrCheckbox && this.getLength(val) < this.options.validationMinlength && !this.validatedOnce ) { return true; } this.validate(); } /** * Get the length of a given value * * @method getLength * @return {int} The length of the value */ , getLength: function ( val ) { return !val || !val.hasOwnProperty( 'length' ) ? 0 : val.length; } /** * Return if field verify its constraints * * @method isValid * @return {Boolean} Is field valid or not */ , isValid: function () { return this.validate( false ); } /** * Return if field has constraints * * @method hasConstraints * @return {Boolean} Is field has constraints or not */ , hasConstraints: function () { for ( var constraint in this.constraints ) { return true; } return false; } /** * Validate a field & display errors * * @method validate * @param {Boolean} errorBubbling set to false if you just want valid boolean without error bubbling next to fields * @return {Boolean} Is field valid or not */ , validate: function ( errorBubbling ) { var val = this.getVal() , valid = null; // do not even bother trying validating a field w/o constraints if ( !this.hasConstraints() ) { return null; } // do not validate excluded fields if ( this.$element.is( this.options.excluded ) ) { return null; } // reset Parsley validation if onFieldValidate returns true, or if field is empty and not required if ( this.options.listeners.onFieldValidate( this.element, this ) || ( '' === val && !this.isRequired ) ) { this.UI.reset(); return null; } // do not validate a field already validated and unchanged ! if ( !this.needsValidation( val ) ) { return this.valid; } valid = this.applyValidators(); if ( 'undefined' !== typeof errorBubbling ? errorBubbling : this.options.showErrors ) { this.manageValidationResult(); } return valid; } /** * Check if value has changed since previous validation * * @method needsValidation * @param value * @return {Boolean} */ , needsValidation: function ( val ) { if ( !this.options.validateIfUnchanged && this.valid !== null && this.val === val && this.validatedOnce ) { return false; } this.val = val; return this.validatedOnce = true; } /** * Loop through every fields validators * Adds errors after unvalid fields * * @method applyValidators * @return {Mixed} {Boolean} If field valid or not, null if not validated */ , applyValidators: function () { var valid = null; for ( var constraint in this.constraints ) { var result = this.Validator.validators[ this.constraints[ constraint ].name ]().validate( this.val, this.constraints[ constraint ].requirements, this ); if ( false === result ) { valid = false; this.constraints[ constraint ].valid = valid; } else if ( true === result ) { this.constraints[ constraint ].valid = true; valid = false !== valid; } } // listeners' ballet if (false === valid) { this.options.listeners.onFieldError( this.element, this.constraints, this ); } else if (true === valid && false === this.options.listeners.onFieldSuccess( this.element, this.constraints, this )) { // if onFieldSuccess returns (bool) false, consider that field si invalid valid = false; } return valid; } /** * Fired when all validators have be executed * Returns true or false if field is valid or not * Display errors messages below failed fields * Adds parsley-success or parsley-error class on fields * * @method manageValidationResult * @return {Boolean} Is field valid or not */ , manageValidationResult: function () { var valid = null , errors = []; for ( var constraint in this.constraints ) { if ( false === this.constraints[ constraint ].valid ) { errors.push( this.constraints[ constraint ]); valid = false; } else if ( true === this.constraints[ constraint ].valid ) { this.UI.removeError( this.constraints[ constraint ].name ); valid = false !== valid; } } this.valid = valid; if ( true === this.valid ) { this.UI.removeErrors(); this.UI.errorClassHandler.removeClass( this.options.errorClass ).addClass( this.options.successClass ); return true; } else if ( false === this.valid ) { if ( true === this.options.priorityEnabled ) { var maxPriority = 0, constraint, priority; for ( var i = 0; i < errors.length; i++ ) { priority = this.Validator.validators[ errors[ i ].name ]().priority; if ( priority > maxPriority ) { constraint = errors[ i ]; maxPriority = priority; } } this.UI.manageError( constraint ); } else { for ( var i = 0; i < errors.length; i++ ) this.UI.manageError( errors[ i ] ); } this.UI.errorClassHandler.removeClass( this.options.successClass ).addClass( this.options.errorClass ); return false; } // remove li error, and ul error if no more li inside if ( this.UI.ulError && $( this.ulError ).children().length === 0 ) { this.UI.removeErrors(); } return valid; } /** * Add custom listeners * * @param {Object} { listener: function () {} }, eg { onFormValidate: function ( valid, event, focus ) { ... } } */ , addListener: function ( object ) { for ( var listener in object ) { this.options.listeners[ listener ] = object[ listener ]; } } /** * Destroy parsley field instance * * @private * @method destroy */ , destroy: function () { this.$element.removeClass( 'parsley-validated' ); this.UI.reset(); this.$element.off( '.' + this.type ).removeData( this.type ); } }; /** * ParsleyFieldMultiple override ParsleyField for checkbox and radio inputs * Pseudo-heritance to manage divergent behavior from ParsleyItem in dedicated methods * * @class ParsleyFieldMultiple * @constructor */ var ParsleyFieldMultiple = function ( element, options, type ) { this.initMultiple( element, options ); this.inherit( element, options ); this.Validator = new Validator( options ); // call ParsleyField constructor this.init( element, type || 'ParsleyFieldMultiple' ); }; ParsleyFieldMultiple.prototype = { constructor: ParsleyFieldMultiple /** * Set some specific properties, call some extra methods to manage radio / checkbox * * @method init * @param {Object} element * @param {Object} options */ , initMultiple: function ( element, options ) { this.element = element; this.$element = $( element ); this.group = options.group || false; this.hash = this.getName(); this.siblings = this.group ? '[' + options.namespace + 'group="' + this.group + '"]' : 'input[name="' + this.$element.attr( 'name' ) + '"]'; this.isRadioOrCheckbox = true; this.isRadio = this.$element.is( 'input[type=radio]' ); this.isCheckbox = this.$element.is( 'input[type=checkbox]' ); this.errorClassHandler = options.errors.classHandler( element, this.isRadioOrCheckbox ) || this.$element.parent(); } /** * Set specific constraints messages, do pseudo-heritance * * @private * @method inherit * @param {Object} element * @param {Object} options */ , inherit: function ( element, options ) { var clone = new ParsleyField( element, options, 'ParsleyFieldMultiple' ); for ( var property in clone ) { if ( 'undefined' === typeof this[ property ] ) { this[ property ] = clone [ property ]; } } } /** * Set specific constraints messages, do pseudo-heritance * * @method getName * @returns {String} radio / checkbox hash is cleaned 'name' or data-group property */ , getName: function () { if ( this.group ) { return 'parsley-' + this.group; } if ( 'undefined' === typeof this.$element.attr( 'name' ) ) { throw "A radio / checkbox input must have a parsley-group attribute or a name to be Parsley validated !"; } return 'parsley-' + this.$element.attr( 'name' ).replace( /(:|\.|\[|\]|\$)/g, '' ); } /** * Special treatment for radio & checkboxes * Returns checked radio or checkboxes values * * @method getVal * @returns {String} val */ , getVal: function () { if ( this.isRadio ) { return $( this.siblings + ':checked' ).val() || ''; } if ( this.isCheckbox ) { var values = []; $( this.siblings + ':checked' ).each( function () { values.push( $( this ).val() ); } ); return values; } } /** * Bind validation events on a field * * @private * @method bindValidationEvents */ , bindValidationEvents: function () { // this field has validation events, that means it has to be validated this.valid = null; this.$element.addClass( 'parsley-validated' ); // remove eventually already binded events this.$element.off( '.' + this.type ); // always bind keyup event, for better UX when a field is invalid var self = this , triggers = ( !this.options.trigger ? '' : this.options.trigger ) + ( new RegExp( 'change', 'i' ).test( this.options.trigger ) ? '' : ' change' ); // trim triggers to bind them correctly with .on() triggers = triggers.replace( /^\s+/g , '' ).replace( /\s+$/g ,'' ); // bind trigger event on every siblings $( this.siblings ).each(function () { $( this ).on( triggers.split( ' ' ).join( '.' + self.type + ' ' ) , false, $.proxy( self.eventValidation, self ) ); } ) } }; /** * ParsleyForm class manage Parsley validated form. * Manage its fields and global validation * * @class ParsleyForm * @constructor */ var ParsleyForm = function ( element, options, type ) { this.init( element, options, type || 'parsleyForm' ); }; ParsleyForm.prototype = { constructor: ParsleyForm /* init data, bind jQuery on() actions */ , init: function ( element, options, type ) { this.type = type; this.items = []; this.$element = $( element ); this.options = options; var self = this; this.$element.find( options.inputs ).each( function () { self.addItem( this ); }); this.$element.on( 'submit.' + this.type , false, $.proxy( this.validate, this ) ); } /** * Add custom listeners * * @param {Object} { listener: function () {} }, eg { onFormValidate: function ( valid, event, focus ) { ... } } */ , addListener: function ( object ) { for ( var listener in object ) { if ( new RegExp( 'Field' ).test( listener ) ) { for ( var item = 0; item < this.items.length; item++ ) { this.items[ item ].addListener( object ); } } else { this.options.listeners[ listener ] = object[ listener ]; } } } /** * Adds a new parsleyItem child to ParsleyForm * * @method addItem * @param elem */ , addItem: function ( elem ) { var ParsleyField = $( elem ).parsley( this.options ); ParsleyField.setParent( this ); this.items.push( ParsleyField ); } /** * Removes a parsleyItem child from ParsleyForm * * @method removeItem * @param elem * @return {Boolean} */ , removeItem: function ( elem ) { var parsleyItem = $( elem ).parsley(); // identify & remove item if same Parsley hash for ( var i = 0; i < this.items.length; i++ ) { if ( this.items[ i ].hash === parsleyItem.hash ) { this.items[ i ].destroy(); this.items.splice( i, 1 ); return true; } } return false; } /** * Process each form field validation * Display errors, call custom onFormValidate() function * * @method validate * @param {Object} event jQuery Event * @return {Boolean} Is form valid or not */ , validate: function ( event ) { var valid = true; this.focusedField = false; for ( var item = 0; item < this.items.length; item++ ) { if ( 'undefined' !== typeof this.items[ item ] && false === this.items[ item ].validate() ) { valid = false; if ( !this.focusedField && 'first' === this.options.focus || 'last' === this.options.focus ) { this.focusedField = this.items[ item ].$element; } } } // form is invalid, focus an error field depending on focus policy if ( this.focusedField && !valid ) { // Scroll smoothly if ( this.options.scrollDuration > 0 ) { var that = this, top = this.focusedField.offset().top - $( window ).height() / 2; // Center the window on the field $( 'html, body' ).animate( { scrollTop: top }, this.options.scrollDuration, function () { that.focusedField.focus(); } ); // Just focus on the field and let the browser do the rest } else { this.focusedField.focus(); } } // if onFormValidate returns (bool) false, form won't be submitted, even if valid var onFormValidate = this.options.listeners.onFormValidate( valid, event, this ); if ('undefined' !== typeof onFormValidate) { return onFormValidate; } return valid; } , isValid: function () { for ( var item = 0; item < this.items.length; item++ ) { if ( false === this.items[ item ].isValid() ) { return false; } } return true; } /** * Remove all errors ul under invalid fields * * @method removeErrors */ , removeErrors: function () { for ( var item = 0; item < this.items.length; item++ ) { this.items[ item ].parsley( 'reset' ); } } /** * destroy Parsley binded on the form and its fields * * @method destroy */ , destroy: function () { for ( var item = 0; item < this.items.length; item++ ) { this.items[ item ].destroy(); } this.$element.off( '.' + this.type ).removeData( this.type ); } /** * reset Parsley binded on the form and its fields * * @method reset */ , reset: function () { for ( var item = 0; item < this.items.length; item++ ) { this.items[ item ].UI.reset(); } } }; /** * Parsley plugin definition * Provides an interface to access public Validator, ParsleyForm and ParsleyField functions * * @class Parsley * @constructor * @param {Mixed} Options. {Object} to configure Parsley or {String} method name to call a public class method * @param {Function} Callback function * @return {Mixed} public class method return */ $.fn.parsley = function ( option, fn ) { var namespace = { namespace: $( this ).data( 'parsleyNamespace' ) ? $( this ).data( 'parsleyNamespace' ) : ( 'undefined' !== typeof option && 'undefined' !== typeof option.namespace ? option.namespace : $.fn.parsley.defaults.namespace ) } , options = $.extend( true, {}, $.fn.parsley.defaults, 'undefined' !== typeof window.ParsleyConfig ? window.ParsleyConfig : {}, option, this.domApi( namespace.namespace ) ) , newInstance = null , args = Array.prototype.slice.call(arguments, 1); function bind ( self, type ) { var parsleyInstance = $( self ).data( type ); // if data never binded or we want to clone a build (for radio & checkboxes), bind it right now! if ( !parsleyInstance ) { switch ( type ) { case 'parsleyForm': parsleyInstance = new ParsleyForm( self, options, 'parsleyForm' ); break; case 'parsleyField': parsleyInstance = new ParsleyField( self, options, 'parsleyField' ); break; case 'parsleyFieldMultiple': parsleyInstance = new ParsleyFieldMultiple( self, options, 'parsleyFieldMultiple' ); break; default: return; } $( self ).data( type, parsleyInstance ); } // here is our parsley public function accessor if ( 'string' === typeof option && 'function' === typeof parsleyInstance[ option ] ) { var response = parsleyInstance[ option ].apply( parsleyInstance, args ); return 'undefined' !== typeof response ? response : $( self ); } return parsleyInstance; } // if a form elem is given, bind all its input children if ( $( this ).is( 'form' ) || 'undefined' !== typeof $( this ).domApi( namespace.namespace )[ 'bind' ] ) { newInstance = bind ( $( this ), 'parsleyForm' ); // if it is a Parsley supported single element, bind it too, except inputs type hidden // add here a return instance, cuz' we could call public methods on single elems with data[ option ]() above } else if ( $( this ).is( options.inputs ) ) { newInstance = bind( $( this ), !$( this ).is( 'input[type=radio], input[type=checkbox]' ) ? 'parsleyField' : 'parsleyFieldMultiple' ); } return 'function' === typeof fn ? fn() : newInstance; }; /* PARSLEY auto-binding * =================================================== */ $( window ).on( 'load', function () { $( '[parsley-validate], [data-parsley-validate]' ).each( function () { $( this ).parsley(); } ); } ); /* PARSLEY DOM API * =================================================== */ $.fn.domApi = function ( namespace ) { var attribute, obj = {} , regex = new RegExp("^" + namespace, 'i'); if ( 'undefined' === typeof this[ 0 ] ) { return {}; } for ( var i in this[ 0 ].attributes ) { attribute = this[ 0 ].attributes[ i ]; if ( 'undefined' !== typeof attribute && null !== attribute && attribute.specified && regex.test( attribute.name ) ) { obj[ camelize( attribute.name.replace( namespace, '' ) ) ] = deserializeValue( attribute.value ); } } return obj; }; // Zepto deserializeValue function // "true" => true // "false" => false // "null" => null // "42" => 42 // "42.5" => 42.5 // JSON => parse if valid // String => self var deserializeValue = function( value ) { var num try { return value ? value == "true" || ( value == "false" ? false : value == "null" ? null : !isNaN( num = Number( value ) ) ? num : /^[\[\{]/.test( value ) ? $.parseJSON( value ) : value ) : value; } catch ( e ) { return value; } }; // Zepto camelize function var camelize = function ( str ) { return str.replace( /-+(.)?/g, function ( match, chr ) { return chr ? chr.toUpperCase() : ''; } ) }; // Zepto dasherize function var dasherize = function ( str ) { return str.replace( /::/g, '/' ) .replace( /([A-Z]+)([A-Z][a-z])/g, '$1_$2' ) .replace( /([a-z\d])([A-Z])/g, '$1_$2' ) .replace( /_/g, '-' ) .toLowerCase() }; /** * Parsley plugin configuration * * @property $.fn.parsley.defaults * @type {Object} */ $.fn.parsley.defaults = { // basic data-api overridable properties here.. namespace: 'parsley-' // DOM-API, default 'parsley-'. W3C valid would be 'data-parsley-' but quite ugly , inputs: 'input, textarea, select' // Default supported inputs. , excluded: 'input[type=hidden], input[type=file], :disabled' // Do not validate input[type=hidden] & :disabled. , priorityEnabled: true // Will display only one error at the time depending on validators priorities , trigger: false // $.Event() that will trigger validation. eg: keyup, change.. , animate: true // fade in / fade out error messages , animateDuration: 300 // fadein/fadout ms time , scrollDuration: 500 // Duration in ms time of the window scroll when focusing on invalid field (0 = no scroll) , focus: 'first' // 'fist'|'last'|'none' which error field would have focus first on form validation , validationMinlength: 3 // If trigger validation specified, only if value.length > validationMinlength , successClass: 'parsley-success' // Class name on each valid input , errorClass: 'parsley-error' // Class name on each invalid input , errorMessage: false // Customize an unique error message showed if one constraint fails , validators: {} // Add your custom validators functions , showErrors: true // Set to false if you don't want Parsley to display error messages , useHtml5Constraints: true // Set to false if you don't want Parsley to use html5 constraints , messages: {} // Add your own error messages here //some quite advanced configuration here.. , validateIfUnchanged: false // false: validate once by field value change , errors: { classHandler: function ( elem, isRadioOrCheckbox ) {} // specify where parsley error-success classes are set , container: function ( elem, isRadioOrCheckbox ) {} // specify an elem where errors will be **apened** , errorsWrapper: '<ul></ul>' // do not set an id for this elem, it would have an auto-generated id , errorElem: '<li></li>' // each field constraint fail in an li } , listeners: { onFieldValidate: function ( elem, ParsleyField ) { return false; } // Executed on validation. Return true to ignore field validation , onFormValidate: function ( isFormValid, event, ParsleyForm ) {} // Executed once on form validation. Return (bool) false to block submit, even if valid , onFieldError: function ( elem, constraints, ParsleyField ) {} // Executed when a field is detected as invalid , onFieldSuccess: function ( elem, constraints, ParsleyField ) {} // Executed when a field passes validation } }; // This plugin works with jQuery or Zepto (with data extension built for Zepto.) } ( window.jQuery || window.Zepto );