/*

* 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 ( val ) {
      return val.length > 0;
    }
    , notblank: function ( val ) {
      return 'string' === typeof val && '' !== val.replace( /^\s+/g, '' ).replace( /\s+$/g, '' );
    }
    // Works on all inputs. val is object for checkboxes
    , required: 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 ( this.required( val[ i ] ) ) {
            return true;
          }
        }
        return false;
      }
      return this.notnull( val ) && this.notblank( val );
    }
    , type: 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;
    }
    , regexp: function ( val, regExp, self ) {
      return new RegExp( regExp, self.options.regexpFlag || '' ).test( val );
    }
    , minlength: function ( val, min ) {
      return val.length >= min;
    }
    , maxlength: function ( val, max ) {
      return val.length <= max;
    }
    , rangelength: function ( val, arrayRange ) {
      return this.minlength( val, arrayRange[ 0 ] ) && this.maxlength( val, arrayRange[ 1 ] );
    }
    , min: function ( val, min ) {
      return Number( val ) >= min;
    }
    , max: function ( val, max ) {
      return Number( val ) <= max;
    }
    , range: function ( val, arrayRange ) {
      return val >= arrayRange[ 0 ] && val <= arrayRange[ 1 ];
    }
    , equalto: function ( val, elem, self ) {
      self.options.validateIfUnchanged = true;
      return val === $( elem ).val();
    }
    , remote: 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.ulError + ' .remote' ).remove();
        }
        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;
    }
    /**
    * Aliases for checkboxes constraints
    */
    , mincheck: function ( obj, val ) {
      return this.minlength( obj, val );
    }
    , maxcheck: function ( obj, val ) {
      return this.maxlength( obj, val);
    }
    , rangecheck: function ( obj, arrayRange ) {
      return this.rangelength( obj, arrayRange );
    }
  }
  /*
  * Register custom validators and messages
  */
  , init: function ( options ) {
    var customValidators = options.validators
      , customMessages = options.messages;
    var 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. Will automatically bindable through data-name=''
  * @param {Function} fn Validator function. Must return {Boolean}
  */
  , addValidator: function ( name, fn ) {
    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;
  }
};
/**
* 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;
  this.Validator = new Validator( 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.isRequired = false;
    this.constraints = {};
    // overriden by ParsleyItemMultiple if radio or checkbox input
    if ( 'undefined' === typeof this.isRadioOrCheckbox ) {
      this.isRadioOrCheckbox = false;
      this.hash = this.generateHash();
      this.errorClassHandler = this.options.errors.classHandler( element, this.isRadioOrCheckbox ) || this.$element;
    }
    // error ul dom management done only once at init
    this.ulErrorManagement();
    // 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.prop( 'required' ) ) {
      this.options.required = true;
    }
    // add html5 supported types & options
    if ( 'undefined' !== typeof this.$element.attr( 'type' ) && new RegExp( this.$element.attr( 'type' ), 'i' ).test( 'email url number range' ) ) {
      this.options.type = this.$element.attr( '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 );
    }
  }
  /**
  * Dynamically add a new constraint to a field
  *
  * @method addConstraint
  * @param {Object} constraint { name: requirements }
  */
  , addConstraint: function ( constraint, doNotUpdateValidationEvents ) {
      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, destroy parsley instance for this field
    if ( !this.hasConstraints() ) {
      // in a form context, remove item from parent
      if ( 'ParsleyForm' === typeof this.getParent() ) {
        this.getParent().removeItem( this.$element );
        return;
      }
      this.destroy();
      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';
    }
    // alaways 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' );
    // alaways 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 () {
    return this.$element.data('value') || 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;
    }
    // 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.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 ]( 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;
    for ( var constraint in this.constraints ) {
      if ( false === this.constraints[ constraint ].valid ) {
        this.manageError( this.constraints[ constraint ] );
        valid = false;
      } else if ( true === this.constraints[ constraint ].valid ) {
        this.removeError( this.constraints[ constraint ].name );
        valid = false !== valid;
      }
    }
    this.valid = valid;
    if ( true === this.valid ) {
      this.removeErrors();
      this.errorClassHandler.removeClass( this.options.errorClass ).addClass( this.options.successClass );
      return true;
    } else if ( false === this.valid ) {
      this.errorClassHandler.removeClass( this.options.successClass ).addClass( this.options.errorClass );
      return false;
    }
    // remove li error, and ul error if no more li inside
    if ( this.ulError && $( this.ulError ).children().length === 0 ) {
      this.removeErrors();
    }
    return valid;
  }
  /**
  * 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.valid = null;
    this.removeErrors();
    this.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.getVal() && this.getVal().length > 0 ) {
      return;
    // if empty required field and non required constraint fails, do not display
    } else if ( this.isRequired && 'required' !== constraint.name && ( null === this.getVal() || 0 === this.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.Validator.messages[ constraintName ][ constraint.requirements ] : ( 'undefined' === typeof this.Validator.messages[ constraintName ] ?
            this.Validator.messages.defaultMessage : this.Validator.formatMesssage( this.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.element, this.isRadioOrCheckbox )
      , ulTemplate = this.options.animate ? this.ulTemplate.show() : this.ulTemplate;
    if ( 'undefined' !== typeof errorContainer ) {
      $( errorContainer ).append( ulTemplate );
      return;
    }
    !this.isRadioOrCheckbox ? this.$element.after( ulTemplate ) : this.$element.parent().after( ulTemplate );
  }
  /**
  * Add custom listeners
  *
  * @param {Object} { listener: function () {} }, eg { onFormSubmit: 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.reset().$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 ? '[data-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 data-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 );
    // alaways 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 { onFormSubmit: 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 ( 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 onFormSubmit() 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 onFormSubmit returns (bool) false, form won't be submitted, even if valid
    var onFormSubmit = this.options.listeners.onFormSubmit( valid, event, this );
    if ('undefined' !== typeof onFormSubmit) {
      return onFormSubmit;
    }
    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 ].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 options = $.extend( true, {}, $.fn.parsley.defaults, 'undefined' !== typeof window.ParsleyConfig ? window.ParsleyConfig : {}, option, this.data() )
    , newInstance = null;
  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 ]( fn );
      return 'undefined' !== typeof response ? response : $( self );
    }
    return parsleyInstance;
  }
  // if a form elem is given, bind all its input children
  if ( $( this ).is( 'form' ) || true === $( this ).data( '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 ) && !$( this ).is( options.excluded ) ) {
    newInstance = bind( $( this ), !$( this ).is( 'input[type=radio], input[type=checkbox]' ) ? 'parsleyField' : 'parsleyFieldMultiple' );
  }
  return 'function' === typeof fn ? fn() : newInstance;
};
$.fn.parsley.Constructor = ParsleyForm;
/**
* Parsley plugin configuration
*
* @property $.fn.parsley.defaults
* @type {Object}
*/
$.fn.parsley.defaults = {
  // basic data-api overridable properties here..
  inputs: 'input, textarea, select'           // Default supported inputs.
  , excluded: 'input[type=hidden], input[type=file], :disabled' // Do not validate input[type=hidden] & :disabled.
  , 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
    , onFormSubmit: 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
  }
};
/* PARSLEY auto-bind DATA-API + Global config retrieving
* =================================================== */
$( window ).on( 'load', function () {
  $( '[data-validate="parsley"]' ).each( function () {
    $( this ).parsley();
  } );
} );

// This plugin works with jQuery or Zepto (with data extension built for Zepto.) }(window.jQuery || window.Zepto);