forked from Coded/SIPRP
				
			
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							1567 lines
						
					
					
						
							52 KiB
						
					
					
				
			
		
		
	
	
							1567 lines
						
					
					
						
							52 KiB
						
					
					
				| /*
 | |
|  * 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
 | |
|     */
 | |
|     , 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();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|     * Add li error
 | |
|     *
 | |
|     * @method addError
 | |
|     * @param {Object} { minlength: "error message for minlength constraint" }
 | |
|     */
 | |
|     , 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 ] ) );
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|     * Remove all ul / li errors
 | |
|     *
 | |
|     * @method removeErrors
 | |
|     */
 | |
|     , removeErrors: function () {
 | |
|       this.options.animate ? $( this.ulError ).fadeOut( this.options.animateDuration, function () { $( this ).remove(); } ) : $( this.ulError ).remove();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|     * Remove ul errors and parsley error or success classes
 | |
|     *
 | |
|     * @method reset
 | |
|     */
 | |
|     , 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
 | |
|     */
 | |
|     , 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;
 | |
|       // 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;
 | |
|       }
 | |
| 
 | |
|       // 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 ) ) );
 | |
| 
 | |
|       // add liError if not shown. Do not add more than once custom errorMessage if exist
 | |
|       if ( !$( this.ulError + ' .' + liClass ).length ) {
 | |
|         liError[ liClass ] = message;
 | |
|         this.addError( liError );
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|     * Create ul error container
 | |
|     *
 | |
|     * @method manageErrorContainer
 | |
|     */
 | |
|     , 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 );
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   /**
 | |
|   * 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
 | |
|       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 ) {
 | |
|         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) {
 | |
|       if (!val || !val.hasOwnProperty('length')) return 0;
 | |
|       return 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 ? '[parsley-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 ) {
 | |
|       if ( $( elem ).is( this.options.excluded ) ) {
 | |
|         return false;
 | |
|       }
 | |
| 
 | |
|       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(parsleyItem != null)
 | |
|         {
 | |
|         	var item = this.items[ i ];
 | |
|         	
 | |
|             if(item.hash && parsleyItem.hash){// <!--- change date 19-11-2013
 | |
| 
 | |
|                 if ( item.hash === parsleyItem.hash ) {
 | |
|                   item.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 () {
 | |
|                 if(that.focusedField)// <--- custom change, 10-12-2013
 | |
|                     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 ( 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
 | |
| 
 | |
|     namespace: 'data-'                       // 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
 | |
|     , 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, ParsleyForm ) { 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 );
 |