1/* 2 * Parsley.js allows you to verify your form inputs frontend side, without writing a line of javascript. Or so.. 3 * 4 * Author: Guillaume Potier - @guillaumepotier 5*/ 6 7!function ($) { 8 9 'use strict'; 10 11 /** 12 * Validator class stores all constraints functions and associated messages. 13 * Provides public interface to add, remove or modify them 14 * 15 * @class Validator 16 * @constructor 17 */ 18 var Validator = function ( options ) { 19 /** 20 * Error messages 21 * 22 * @property messages 23 * @type {Object} 24 */ 25 this.messages = { 26 defaultMessage: "This value seems to be invalid." 27 , type: { 28 email: "This value should be a valid email." 29 , url: "This value should be a valid url." 30 , urlstrict: "This value should be a valid url." 31 , number: "This value should be a valid number." 32 , digits: "This value should be digits." 33 , dateIso: "This value should be a valid date (YYYY-MM-DD)." 34 , alphanum: "This value should be alphanumeric." 35 , phone: "This value should be a valid phone number." 36 } 37 , notnull: "This value should not be null." 38 , notblank: "This value should not be blank." 39 , required: "This value is required." 40 , regexp: "This value seems to be invalid." 41 , min: "This value should be greater than or equal to %s." 42 , max: "This value should be lower than or equal to %s." 43 , range: "This value should be between %s and %s." 44 , minlength: "This value is too short. It should have %s characters or more." 45 , maxlength: "This value is too long. It should have %s characters or less." 46 , rangelength: "This value length is invalid. It should be between %s and %s characters long." 47 , mincheck: "You must select at least %s choices." 48 , maxcheck: "You must select %s choices or less." 49 , rangecheck: "You must select between %s and %s choices." 50 , equalto: "This value should be the same." 51 }, 52 53 this.init( options ); 54 }; 55 56 Validator.prototype = { 57 58 constructor: Validator 59 60 /** 61 * Validator list. Built-in validators functions 62 * 63 * @property validators 64 * @type {Object} 65 */ 66 , validators: { 67 notnull: function () { 68 return { 69 validate: function ( val ) { 70 return val.length > 0; 71 } 72 , priority: 2 73 } 74 } 75 , notblank: function () { 76 return { 77 validate: function ( val ) { 78 return 'string' === typeof val && '' !== val.replace( /^\s+/g, '' ).replace( /\s+$/g, '' ); 79 } 80 , priority: 2 81 } 82 } 83 , required: function () { 84 var that = this; 85 return { 86 validate: function ( val ) { 87 // for checkboxes and select multiples. Check there is at least one required value 88 if ( 'object' === typeof val ) { 89 for ( var i in val ) { 90 if ( that.required().validate( val[ i ] ) ) { 91 return true; 92 } 93 } 94 95 return false; 96 } 97 98 return that.notnull().validate( val ) && that.notblank().validate( val ); 99 } 100 , priority: 512 101 } 102 } 103 , type: function () { 104 return { 105 validate: function ( val, type ) { 106 var regExp; 107 108 switch ( type ) { 109 case 'number': 110 regExp = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/; 111 break; 112 case 'digits': 113 regExp = /^\d+$/; 114 break; 115 case 'alphanum': 116 regExp = /^\w+$/; 117 break; 118 case 'email': 119 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; 120 break; 121 case 'url': 122 val = new RegExp( '(https?|s?ftp|git)', 'i' ).test( val ) ? val : 'http://' + val; 123 /* falls through */ 124 case 'urlstrict': 125 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; 126 break; 127 case 'dateIso': 128 regExp = /^(\d{4})\D?(0[1-9]|1[0-2])\D?([12]\d|0[1-9]|3[01])$/; 129 break; 130 case 'phone': 131 regExp = /^((\+\d{1,3}(-| )?\(?\d\)?(-| )?\d{1,5})|(\(?\d{2,6}\)?))(-| )?(\d{3,4})(-| )?(\d{4})(( x| ext)\d{1,5}){0,1}$/; 132 break; 133 default: 134 return false;
135 } 136 137 // test regExp if not null 138 return '' !== val ? regExp.test( val ) : false; 139 } 140 , priority: 256 141 } 142 } 143 , regexp: function () { 144 return { 145 validate: function ( val, regExp, self ) { 146 return new RegExp( regExp, self.options.regexpFlag || '' ).test( val ); 147 } 148 , priority: 64 149 } 150 } 151 , minlength: function () { 152 return { 153 validate: function ( val, min ) { 154 return val.length >= min; 155 } 156 , priority: 32 157 } 158 } 159 , maxlength: function () { 160 return { 161 validate: function ( val, max ) { 162 return val.length <= max; 163 } 164 , priority: 32 165 } 166 } 167 , rangelength: function () { 168 var that = this; 169 return { 170 validate: function ( val, arrayRange ) { 171 return that.minlength().validate( val, arrayRange[ 0 ] ) && that.maxlength().validate( val, arrayRange[ 1 ] ); 172 } 173 , priority: 32 174 } 175 } 176 , min: function () { 177 return { 178 validate: function ( val, min ) { 179 return Number( val ) >= min; 180 } 181 , priority: 32 182 } 183 } 184 , max: function () { 185 return { 186 validate: function ( val, max ) { 187 return Number( val ) <= max; 188 } 189 , priority: 32 190 } 191 } 192 , range: function () { 193 var that = this; 194 return { 195 validate: function ( val, arrayRange ) { 196 return that.min().validate( val, arrayRange[ 0 ] ) && that.max().validate( val, arrayRange[ 1 ] ); 197 } 198 , priority: 32 199 } 200 } 201 , equalto: function () { 202 return { 203 validate: function ( val, elem, self ) { 204 self.options.validateIfUnchanged = true; 205 return val === $( elem ).val(); 206 } 207 , priority: 64 208 } 209 } 210 , remote: function () { 211 return { 212 validate: function ( val, url, self ) { 213 var result = null 214 , data = {} 215 , dataType = {}; 216 217 data[ self.$element.attr( 'name' ) ] = val; 218 219 if ( 'undefined' !== typeof self.options.remoteDatatype ) 220 dataType = { dataType: self.options.remoteDatatype }; 221 222 var manage = function ( isConstraintValid, message ) { 223 // remove error message if we got a server message, different from previous message 224 if ( 'undefined' !== typeof message && 'undefined' !== typeof self.Validator.messages.remote && message !== self.Validator.messages.remote ) { 225 $( self.UI.ulError + ' .remote' ).remove(); 226 } 227 228 if (false === isConstraintValid) { 229 self.options.listeners.onFieldError( self.element, self.constraints, self ); 230 } else if (true === isConstraintValid && false === self.options.listeners.onFieldSuccess( self.element, self.constraints, self )) { 231 // if onFieldSuccess returns (bool) false, consider that field is invalid 232 isConstraintValid = false; 233 } 234 235 self.updtConstraint( { name: 'remote', valid: isConstraintValid }, message ); 236 self.manageValidationResult(); 237 }; 238 239 // transform string response into object 240 var handleResponse = function ( response ) { 241 if ( 'object' === typeof response ) { 242 return response; 243 } 244 245 try { 246 response = $.parseJSON( response ); 247 } catch ( err ) {} 248 249 return response; 250 } 251 252 var manageErrorMessage = function ( response ) { 253 return 'object' === typeof response && null !== response ? ( 'undefined' !== typeof response.error ? response.error : ( 'undefined' !== typeof response.message ? response.message : null ) ) : null; 254 } 255 256 $.ajax( $.extend( {}, { 257 url: url 258 , data: data 259 , type: self.options.remoteMethod || 'GET' 260 , success: function ( response ) { 261 response = handleResponse( response ); 262 manage( 1 === response || true === response || ( 'object' === typeof response && null !== response && 'undefined' !== typeof response.success ), manageErrorMessage( response ) 263 ); 264 } 265 , error: function ( response ) { 266 response = handleResponse( response ); 267 manage( false, manageErrorMessage( response ) ); 268 } 269 }, dataType ) ); 270 271 return result; 272 } 273 , priority: 64 274 } 275 } 276 277 /** 278 * Aliases for checkboxes constraints 279 */ 280 , mincheck: function () { 281 var that = this; 282 return { 283 validate: function ( obj, val ) { return that.minlength().validate( obj, val ) } 284 , priority: 32 285 } 286 } 287 , maxcheck: function () { 288 var that = this; 289 return { 290 validate: function ( obj, val ) { return that.maxlength().validate( obj, val ) } 291 , priority: 32 292 } 293 } 294 , rangecheck: function () { 295 var that = this; 296 return { 297 validate: function ( obj, arrayRange ) { return that.rangelength().validate( obj, arrayRange ) } 298 , priority: 32 299 } 300 } 301 } 302 303 /* 304 * Register custom validators and messages 305 */ 306 , init: function ( options ) { 307 var customValidators = options.validators 308 , customMessages = options.messages 309 , key; 310 311 for ( key in customValidators ) { 312 this.addValidator(key, customValidators[ key ]); 313 } 314 315 for ( key in customMessages ) { 316 this.addMessage(key, customMessages[ key ]); 317 } 318 } 319 320 /** 321 * Replace %s placeholders by values 322 * 323 * @method formatMesssage 324 * @param {String} message Message key 325 * @param {Mixed} args Args passed by validators functions. Could be string, number or object 326 * @return {String} Formatted string 327 */ 328 , formatMesssage: function ( message, args ) { 329 330 if ( 'object' === typeof args ) { 331 for ( var i in args ) { 332 message = this.formatMesssage( message, args[ i ] ); 333 } 334 335 return message; 336 } 337 338 return 'string' === typeof message ? message.replace( new RegExp( '%s', 'i' ), args ) : ''; 339 } 340 341 /** 342 * Add / override a validator in validators list 343 * 344 * @method addValidator 345 * @param {String} name Validator name. 346 * @param {Function}
346 fn Validator. Must return { validator: fn(), priority: int } 347 */ 348 , addValidator: function ( name, fn ) { 349 if ('undefined' === typeof fn().validate) { 350 throw new Error( 'Validator `' + name + '` must have a validate method. See more here: http://parsleyjs.org/documentation.html#javascript-general' ); 351 } 352 353 // add default prioirty if not given. 354 if ('undefined' === typeof fn().priority) { 355 fn = { 356 validate: fn().validate 357 , priority: 32 358 }; 359 360 // Warn if possible 361 if (window.console && window.console.warn) { 362 window.console.warn( 'Validator `' + name + '` should have a priority. Default priority 32 given' ); 363 } 364 } 365 366 this.validators[ name ] = fn; 367 } 368 369 /** 370 * Add / override error message 371 * 372 * @method addMessage 373 * @param {String} name Message name. Will automatically be binded to validator with same name 374 * @param {String} message Message 375 */ 376 , addMessage: function ( key, message, type ) { 377 378 if ( 'undefined' !== typeof type && true === type ) { 379 this.messages.type[ key ] = message; 380 return; 381 } 382 383 // custom types messages are a bit tricky cuz' nested ;) 384 if ( 'type' === key ) { 385 for ( var i in message ) { 386 this.messages.type[ i ] = message[ i ]; 387 } 388 389 return; 390 } 391 392 this.messages[ key ] = message; 393 } 394 }; 395 396 var ParsleyUI = function ( ParsleyInstance ) { 397 this.init( ParsleyInstance ); 398 }; 399 400 ParsleyUI.prototype = { 401 402 constructor: ParsleyUI 403 404 , init: function ( ParsleyInstance ) { 405 this.ParsleyInstance = ParsleyInstance; 406 this.hash = ParsleyInstance.hash; 407 this.options = this.ParsleyInstance.options; 408 this.errorClassHandler = this.options.errors.classHandler( this.ParsleyInstance.element, this.ParsleyInstance.isRadioOrCheckbox ) || this.ParsleyInstance.$element; 409 this.ulErrorManagement(); 410 } 411 412 /** 413 * Manage ul error Container 414 * 415 * @private 416 * @method ulErrorManagement 417 */ 418 , ulErrorManagement: function () { 419 this.ulError = '#' + this.hash; 420 this.ulTemplate = $( this.options.errors.errorsWrapper ).attr( 'id', this.hash ).addClass( 'parsley-error-list' ); 421 } 422 423 /** 424 * Remove li / ul error 425 * 426 * @method removeError 427 * @param {String} constraintName Method Name 428 * @return ParsleyUI 429 */ 430 , removeError: function ( constraintName ) { 431 var liError = this.ulError + ' .' + constraintName 432 , that = this; 433 434 this.options.animate ? $( liError ).fadeOut( this.options.animateDuration, function () { 435 $( this ).remove(); 436 437 if ( that.ulError && $( that.ulError ).children().length === 0 ) { 438 that.removeErrors(); 439 } } ) : $( liError ).remove(); 440 441 return this; 442 } 443 444 /** 445 * Add li error 446 * 447 * @method addError 448 * @param {Object} { minlength: "error message for minlength constraint" } 449 * @return ParsleyUI 450 */ 451 , addError: function ( error ) { 452 for ( var constraint in error ) { 453 var liTemplate = $( this.options.errors.errorElem ).addClass( constraint ); 454 455 $( this.ulError ).append( this.options.animate ? $( liTemplate ).html( error[ constraint ] ).hide().fadeIn( this.options.animateDuration ) : $( liTemplate ).html( error[ constraint ] ) ); 456 } 457 458 return this; 459 } 460 461 /** 462 * Update existing error if text has changed 463 * 464 * @method updateError 465 * @param {Object} { minlength: "error message for minlength constraint" } 466 * @return ParsleyUI 467 */ 468 , updateError: function ( error ) { 469 for ( var constraint in error ) { 470 if ( error[ constraint ] !== $( this.ulError + " > li." + constraint ).html() ) { 471 this.removeError( constraint ).addError( error ); 472 } 473 } 474 475 return this; 476 } 477 478 /** 479 * Remove all ul / li errors 480 * 481 * @method removeErrors 482 * @return ParsleyUI 483 */ 484 , removeErrors: function () { 485 this.options.animate ? $( this.ulError ).fadeOut( this.options.animateDuration, function () { $( this ).remove(); } ) : $( this.ulError ).remove(); 486 487 return this; 488 } 489 490 /** 491 * Remove ul errors and parsley error or success classes 492 * 493 * @method reset 494 * @return ParsleyUI 495 */ 496 , reset: function () { 497 this.ParsleyInstance.valid = null; 498 this.removeErrors(); 499 this.ParsleyInstance.validatedOnce = false;
500 this.errorClassHandler.removeClass( this.options.successClass ).removeClass( this.options.errorClass ); 501 502 for ( var constraint in this.constraints ) { 503 this.constraints[ constraint ].valid = null; 504 } 505 506 return this; 507 } 508 509 /** 510 * Add li / ul errors messages 511 * 512 * @method manageError 513 * @param {Object} constraint 514 * @return ParsleyUI 515 */ 516 , manageError: function ( constraint ) { 517 // display ulError container if it has been removed previously (or never shown) 518 if ( !$( this.ulError ).length ) { 519 this.manageErrorContainer(); 520 } 521 522 // TODO: refacto properly 523 // if required constraint but field is not null, do not display 524 if ( 'required' === constraint.name && null !== this.ParsleyInstance.getVal() && this.ParsleyInstance.getVal().length > 0 ) { 525 return this; 526 527 // if empty required field and non required constraint fails, do not display 528 } else if ( this.ParsleyInstance.isRequired && 'required' !== constraint.name && ( null === this.ParsleyInstance.getVal() || 0 === this.ParsleyInstance.getVal().length ) ) { 529 this.removeError( constraint.name ); 530 531 return this; 532 } 533 534 // TODO: refacto error name w/ proper & readable function 535 var constraintName = constraint.name 536 , liClass = false !== this.options.errorMessage ? 'custom-error-message' : constraintName 537 , liError = {} 538 , message = false !== this.options.errorMessage ? this.options.errorMessage : ( constraint.name === 'type' ? 539 this.ParsleyInstance.Validator.messages[ constraintName ][ constraint.requirements ] : ( 'undefined' === typeof this.ParsleyInstance.Validator.messages[ constraintName ] ? 540 this.ParsleyInstance.Validator.messages.defaultMessage : this.ParsleyInstance.Validator.formatMesssage( this.ParsleyInstance.Validator.messages[ constraintName ], constraint.requirements ) ) ); 541 542 liError[ liClass ] = message; 543 544 // add liError if not shown. update if already exist 545 !$( this.ulError + ' .' + liClass ).length ? this.addError( liError ) : this.updateError( liError ); 546 547 return this; 548 } 549 550 /** 551 * Create ul error container 552 * 553 * @method manageErrorContainer 554 * @return ParsleyUI 555 */ 556 , manageErrorContainer: function () { 557 var errorContainer = this.options.errorContainer || this.options.errors.container( this.ParsleyInstance.element, this.ParsleyInstance.isRadioOrCheckbox ) 558 , ulTemplate = this.options.animate ? this.ulTemplate.css('display', '') : this.ulTemplate; 559 560 if ( 'undefined' !== typeof errorContainer ) { 561 $( errorContainer ).append( ulTemplate ); 562 return; 563 } 564 565 !this.ParsleyInstance.isRadioOrCheckbox ? this.ParsleyInstance.$element.after( ulTemplate ) : this.ParsleyInstance.$element.parent().after( ulTemplate ); 566 567 return this; 568 } 569 }; 570 571 /** 572 * ParsleyField class manage each form field inside a validated Parsley form. 573 * Returns if field valid or not depending on its value and constraints 574 * Manage field error display and behavior, event triggers and more 575 * 576 * @class ParsleyField 577 * @constructor 578 */ 579 var ParsleyField = function ( element, options, type ) { 580 this.options = options; 581 582 // if type is ParsleyFieldMultiple, just return this. used for clone 583 if ( type === 'ParsleyFieldMultiple' ) { 584 return this; 585 } 586 587 this.init( element, type || 'ParsleyField' ); 588 }; 589 590 ParsleyField.prototype = { 591 592 constructor: ParsleyField 593 594 /** 595 * Set some properties, bind constraint validators and validation events 596 * 597 * @method init 598 * @param {Object} element 599 * @param {Object} options 600 */ 601 , init: function ( element, type ) { 602 this.type = type; 603 this.valid = true; 604 this.element = element; 605 this.validatedOnce = false; 606 this.$element = $( element ); 607 this.val = this.$element.val(); 608 this.Validator = new Validator( this.options ); 609 this.isRequired = false; 610 this.constraints = {}; 611 612 // overriden by ParsleyItemMultiple if radio or checkbox input 613 if ( 'undefined' === typeof this.isRadioOrCheckbox ) { 614 this.isRadioOrCheckbox = false;
615 this.hash = this.generateHash(); 616 } 617 618 // error ul dom management done only once at init 619 this.UI = new ParsleyUI( this ); 620 621 // bind some html5 properties 622 if ( this.options.useHtml5Constraints ) { 623 this.bindHtml5Constraints(); 624 } 625 626 // bind validators to field 627 this.addConstraints(); 628 629 // bind parsley events if validators have been registered 630 if ( this.hasConstraints() ) { 631 this.bindValidationEvents(); 632 } 633 } 634 635 , setParent: function ( elem ) { 636 this.$parent = $( elem ); 637 } 638 639 , getParent: function () { 640 return this.$parent; 641 } 642 643 /** 644 * Bind some extra html5 types / validators 645 * 646 * @private 647 * @method bindHtml5Constraints 648 */ 649 , bindHtml5Constraints: function () { 650 // add html5 required support + class required support 651 if ( this.$element.hasClass( 'required' ) || this.$element.attr( 'required' ) ) { 652 this.options.required = true; 653 } 654 655 // add html5 supported types & options 656 var type = this.$element.attr( 'type' ); 657 if ( 'undefined' !== typeof type && new RegExp( type, 'i' ).test( 'email url number range tel' ) ) { 658 this.options.type = 'tel' === type ? 'phone' : type; 659 660 // number and range types could have min and/or max values 661 if ( new RegExp( this.options.type, 'i' ).test( 'number range' ) ) { 662 this.options.type = 'number'; 663 664 // double condition to support jQuery and Zepto.. :( 665 if ( 'undefined' !== typeof this.$element.attr( 'min' ) && this.$element.attr( 'min' ).length ) { 666 this.options.min = this.$element.attr( 'min' ); 667 } 668 669 if ( 'undefined' !== typeof this.$element.attr( 'max' ) && this.$element.attr( 'max' ).length ) { 670 this.options.max = this.$element.attr( 'max' ); 671 } 672 } 673 } 674 675 if ( 'string' === typeof this.$element.attr( 'pattern' ) && this.$element.attr( 'pattern' ).length ) { 676 this.options.regexp = this.$element.attr( 'pattern' ); 677 } 678 679 } 680 681 /** 682 * Attach field validators functions passed through data-api 683 * 684 * @private 685 * @method addConstraints 686 */ 687 , addConstraints: function () { 688 for ( var constraint in this.options ) { 689 var addConstraint = {}; 690 addConstraint[ constraint ] = this.options[ constraint ]; 691 this.addConstraint( addConstraint, true, false ); 692 } 693 } 694 695 /** 696 * Dynamically add a new constraint to a field 697 * 698 * @method addConstraint 699 * @param {Object} constraint { name: requirements } 700 */ 701 , addConstraint: function ( constraint, doNotUpdateValidationEvents, sort ) { 702 for ( var name in constraint ) { 703 name = name.toLowerCase(); 704 705 if ( 'function' === typeof this.Validator.validators[ name ] ) { 706 this.constraints[ name ] = { 707 name: name 708 , requirements: constraint[ name ] 709 , valid: null 710 } 711 712 if ( name === 'required' ) { 713 this.isRequired = true; 714 } 715 716 this.addCustomConstraintMessage( name ); 717 } 718 } 719 720 // force field validation next check and reset validation events 721 if ( 'undefined' === typeof doNotUpdateValidationEvents ) { 722 this.bindValidationEvents(); 723 } 724 } 725 726 /** 727 * Dynamically update an existing constraint to a field. 728 * Simple API: { name: requirements } 729 * 730 * @method updtConstraint 731 * @param {Object} constraint 732 */ 733 , updateConstraint: function ( constraint, message ) { 734 for ( var name in constraint ) { 735 this.updtConstraint( { name: name, requirements: constraint[ name ], valid: null }, message ); 736 } 737 } 738 739 /** 740 * Dynamically update an existing constraint to a field. 741 * Complex API: { name: name, requirements: requirements, valid: boolean } 742 * 743 * @method updtConstraint 744 * @param {Object} constraint 745 */ 746 , updtConstraint: function ( constraint, message ) { 747 this.constraints[ constraint.name ] = $.extend( true, this.constraints[ c
747onstraint.name ], constraint ); 748 749 if ( 'string' === typeof message ) { 750 if ( constraint.name === 'type' ) 751 this.Validator.messages[ constraint.name ][ constraint.requirements ] = message ; 752 else 753 this.Validator.messages[ constraint.name ] = message ; 754 } 755 756 // force field validation next check and reset validation events 757 this.bindValidationEvents(); 758 } 759 760 /** 761 * Dynamically remove an existing constraint to a field. 762 * 763 * @method removeConstraint 764 * @param {String} constraintName 765 */ 766 , removeConstraint: function ( constraintName ) { 767 var constraintName = constraintName.toLowerCase(); 768 769 delete this.constraints[ constraintName ]; 770 771 if ( constraintName === 'required' ) { 772 this.isRequired = false; 773 } 774 775 // if there are no more constraint, reset errors and validation state 776 if ( !this.hasConstraints() ) { 777 this.UI.reset(); 778 return; 779 } 780 781 this.bindValidationEvents(); 782 } 783 784 /** 785 * Add custom constraint message, passed through data-API 786 * 787 * @private 788 * @method addCustomConstraintMessage 789 * @param constraint 790 */ 791 , addCustomConstraintMessage: function ( constraint ) { 792 // custom message type data-type-email-message -> typeEmailMessage | data-minlength-error => minlengthMessage 793 var customMessage = constraint 794 + ( 'type' === constraint && 'undefined' !== typeof this.options[ constraint ] ? this.options[ constraint ].charAt( 0 ).toUpperCase() + this.options[ constraint ].substr( 1 ) : '' ) 795 + 'Message'; 796 797 if ( 'undefined' !== typeof this.options[ customMessage ] ) { 798 this.Validator.addMessage( 'type' === constraint ? this.options[ constraint ] : constraint, this.options[ customMessage ], 'type' === constraint ); 799 } 800 } 801 802 /** 803 * Bind validation events on a field 804 * 805 * @private 806 * @method bindValidationEvents 807 */ 808 , bindValidationEvents: function () { 809 // this field has validation events, that means it has to be validated 810 this.valid = null; 811 this.$element.addClass( 'parsley-validated' ); 812 813 // remove eventually already binded events 814 this.$element.off( '.' + this.type ); 815 816 // force add 'change' event if async remote validator here to have result before form submitting 817 if ( this.options.remote && !new RegExp( 'change', 'i' ).test( this.options.trigger ) ) { 818 this.options.trigger = !this.options.trigger ? 'change' : ' change'; 819 } 820 821 // always bind keyup event, for better UX when a field is invalid 822 var triggers = ( !this.options.trigger ? '' : this.options.trigger ) 823 + ( new RegExp( 'key', 'i' ).test( this.options.trigger ) ? '' : ' keyup' ); 824 825 // always bind change event, for better UX when a select is invalid 826 if ( this.$element.is( 'select' ) ) { 827 triggers += new RegExp( 'change', 'i' ).test( triggers ) ? '' : ' change'; 828 } 829 830 // trim triggers to bind them correctly with .on() 831 triggers = triggers.replace( /^\s+/g , '' ).replace( /\s+$/g , '' ); 832 833 this.$element.on( ( triggers + ' ' ).split( ' ' ).join( '.' + this.type + ' ' ), false, $.proxy( this.eventValidation, this ) ); 834 } 835 836 /** 837 * Hash management. Used for ul error 838 * 839 * @method generateHash 840 * @returns {String} 5 letters unique hash 841 */ 842 , generateHash: function () { 843 return 'parsley-' + ( Math.random() + '' ).substring( 2 ); 844 } 845 846 /** 847 * Public getHash accessor 848 * 849 * @method getHash 850 * @returns {String} hash 851 */ 852 , getHash: function () { 853 return this.hash; 854 } 855 856 /** 857 * Returns field val needed for validation 858 * Special treatment for radio & checkboxes 859 * 860 * @method getVal 861 * @returns {String} val 862 */ 863 , getVal: function () { 864 if ('undefined' !== typeof this.$element.domApi( this.options.namespace )[ 'value' ]) { 865 return this.$element.domApi( this.options.namespace )[ 'value' ]; 866 } 867 868 return this.$element.val(); 869 } 870 871 /** 872 * Called when validation is triggered by an event 873 * Do nothing if val.length < this.options.validationMinlength 874 * 875 * @method eventValidation 876 * @param {Object} event jQuery event 877 */ 878 , eventValidation: function ( event ) { 879 var val = this.getVal(); 880 881 // do nothing on keypress event if not explicitely passed as data-trigger and if field has not already been validated once 882 if ( event.type === 'keyup' && !/keyup/i.test( this.options.trigger ) && !this.validatedOnce ) { 883 return true; 884 } 885 886 // do nothing on change event if not explicitely passed as data-trigger and if field has not already been validated once 887 if ( event.type === 'change' && !/change/i.test( this.options.trigger ) && !this.validatedOnce ) { 888 return true; 889 } 890 891 // start validation process only if field has enough chars and validation never started 892 if ( !this.isRadioOrCheckbox && this.getLength(val) < this.options.validationMinlength && !this.validatedOnce ) { 893 return true; 894 } 895 896 this.validate(); 897 } 898 899 /** 900 * Get the length of a given value 901 * 902 * @method getLength 903 * @return {int} The length of the value 904 */ 905 , getLength: function ( val ) { 906 return !val || !val.hasOwnProperty( 'length' ) ? 0 : val.length; 907 } 908 909 /** 910 * Return if field verify its constraints 911 * 912 * @method isValid 913 * @return {Boolean} Is field valid or not 914 */ 915 , isValid: function () { 916 return this.validate( false ); 917 } 918 919 /** 920 * Return if field has constraints 921 * 922 * @method hasConstraints 923 * @return {Boolean} Is field has constraints or not 924 */ 925 , hasConstraints: function () { 926 for ( var constraint in this.constraints ) { 927 return true; 928 } 929 930 return false;
931 } 932 933 /** 934 * Validate a field & display errors 935 * 936 * @method validate 937 * @param {Boolean} errorBubbling set to false if you just want valid boolean without error bubbling next to fields 938 * @return {Boolean} Is field valid or not 939 */ 940 , validate: function ( errorBubbling ) { 941 var val = this.getVal() 942 , valid = null; 943 944 // do not even bother trying validating a field w/o constraints 945 if ( !this.hasConstraints() ) { 946 return null; 947 } 948 949 // do not validate excluded fields 950 if ( this.$element.is( this.options.excluded ) ) { 951 return null; 952 } 953 954 // reset Parsley validation if onFieldValidate returns true, or if field is empty and not required 955 if ( this.options.listeners.onFieldValidate( this.element, this ) || ( '' === val && !this.isRequired ) ) { 956 this.UI.reset(); 957 return null; 958 } 959 960 // do not validate a field already validated and unchanged ! 961 if ( !this.needsValidation( val ) ) { 962 return this.valid; 963 } 964 965 valid = this.applyValidators(); 966 967 if ( 'undefined' !== typeof errorBubbling ? errorBubbling : this.options.showErrors ) { 968 this.manageValidationResult(); 969 } 970 971 return valid; 972 } 973 974 /** 975 * Check if value has changed since previous validation 976 * 977 * @method needsValidation 978 * @param value 979 * @return {Boolean} 980 */ 981 , needsValidation: function ( val ) { 982 if ( !this.options.validateIfUnchanged && this.valid !== null && this.val === val && this.validatedOnce ) { 983 return false; 984 } 985 986 this.val = val; 987 return this.validatedOnce = true; 988 } 989 990 /** 991 * Loop through every fields validators 992 * Adds errors after unvalid fields 993 * 994 * @method applyValidators 995 * @return {Mixed} {Boolean} If field valid or not, null if not validated 996 */ 997 , applyValidators: function () { 998 var valid = null; 999 1000 for ( var constraint in this.constraints ) { 1001 var result = this.Validator.validators[ this.constraints[ constraint ].name ]().validate( this.val, this.constraints[ constraint ].requirements, this ); 1002 1003 if ( false === result ) { 1004 valid = false; 1005 this.constraints[ constraint ].valid = valid; 1006 } else if ( true === result ) { 1007 this.constraints[ constraint ].valid = true; 1008 valid = false !== valid; 1009 } 1010 } 1011 1012 // listeners' ballet 1013 if (false === valid) { 1014 this.options.listeners.onFieldError( this.element, this.constraints, this ); 1015 } else if (true === valid && false === this.options.listeners.onFieldSuccess( this.element, this.constraints, this )) { 1016 // if onFieldSuccess returns (bool) false, consider that field si invalid 1017 valid = false; 1018 } 1019 1020 return valid; 1021 } 1022 1023 /** 1024 * Fired when all validators have be executed 1025 * Returns true or false if field is valid or not 1026 * Display errors messages below failed fields 1027 * Adds parsley-success or parsley-error class on fields 1028 * 1029 * @method manageValidationResult 1030 * @return {Boolean} Is field valid or not 1031 */ 1032 , manageValidationResult: function () { 1033 var valid = null 1034 , errors = []; 1035 1036 for ( var constraint in this.constraints ) { 1037 if ( false === this.constraints[ constraint ].valid ) { 1038 errors.push( this.constraints[ constraint ]); 1039 valid = false; 1040 } else if ( true === this.constraints[ constraint ].valid ) { 1041 this.UI.removeError( this.constraints[ constraint ].name ); 1042 valid = false !== valid; 1043 } 1044 } 1045 1046 this.valid = valid; 1047 1048 if ( true === this.valid ) { 1049 this.UI.removeErrors(); 1050 this.UI.errorClassHandler.removeClass( this.options.errorClass ).addClass( this.options.successClass ); 1051 1052 return true; 1053 } else if ( false === this.valid ) { 1054 if ( true === this.options.priorityEnabled ) { 1055 var maxPriority = 0, constraint, priority, error, errorArr = []; 1056 for ( var i = 0; i < errors.length; i++ ) { 1057 error = this.Validator.validators[ errors[ i ].name ](); 1058 priority = error.priority; 1059 errorArr.push(errors[ i ]); 1060 1061 if ( priority > maxPriority ) { 1062 constraint = errors[ i ]; 1063 maxPriority = priority; 1064 } 1065 } 1066 for ( var i = 0; i < errorArr.length; i++ ) { 1067 if ( constraint === errorArr[ i ] ) { 1068 this.UI.manageError( constraint ); 1069 } else { 1070 this.UI.removeError( errorArr[ i ].name ); 1071 } 1072 } 1073 } else { 1074 for ( var i = 0; i < errors.length; i++ ) 1075 this.UI.manageError( errors[ i ] ); 1076 } 1077 1078 this.UI.errorClassHandler.removeClass( this.options.successClass ).addClass( this.options.errorClass ); 1079 return false;
1080 } 1081 1082 // remove li error, and ul error if no more li inside 1083 if ( this.UI.ulError && $( this.ulError ).children().length === 0 ) { 1084 this.UI.removeErrors(); 1085 } 1086 1087 return valid; 1088 } 1089 1090 /** 1091 * Add custom listeners 1092 * 1093 * @param {Object} { listener: function () {} }, eg { onFormValidate: function ( valid, event, focus ) { ... } } 1094 */ 1095 , addListener: function ( object ) { 1096 for ( var listener in object ) { 1097 this.options.listeners[ listener ] = object[ listener ]; 1098 } 1099 } 1100 1101 /** 1102 * Destroy parsley field instance 1103 * 1104 * @private 1105 * @method destroy 1106 */ 1107 , destroy: function () { 1108 this.$element.removeClass( 'parsley-validated' ); 1109 this.UI.reset(); 1110 this.$element.off( '.' + this.type ).removeData( this.type ); 1111 } 1112 }; 1113 1114 /** 1115 * ParsleyFieldMultiple override ParsleyField for checkbox and radio inputs 1116 * Pseudo-heritance to manage divergent behavior from ParsleyItem in dedicated methods 1117 * 1118 * @class ParsleyFieldMultiple 1119 * @constructor 1120 */ 1121 var ParsleyFieldMultiple = function ( element, options, type ) { 1122 this.initMultiple( element, options ); 1123 this.inherit( element, options ); 1124 this.Validator = new Validator( options ); 1125 1126 // call ParsleyField constructor 1127 this.init( element, type || 'ParsleyFieldMultiple' ); 1128 }; 1129 1130 ParsleyFieldMultiple.prototype = { 1131 1132 constructor: ParsleyFieldMultiple 1133 1134 /** 1135 * Set some specific properties, call some extra methods to manage radio / checkbox 1136 * 1137 * @method init 1138 * @param {Object} element 1139 * @param {Object} options 1140 */ 1141 , initMultiple: function ( element, options ) { 1142 this.element = element; 1143 this.$element = $( element ); 1144 this.group = options.group || false; 1145 this.hash = this.getName(); 1146 this.siblings = this.group ? '[' + options.namespace + 'group="' + this.group + '"]' : 'input[name="' + this.$element.attr( 'name' ) + '"]'; 1147 this.isRadioOrCheckbox = true; 1148 this.isRadio = this.$element.is( 'input[type=radio]' ); 1149 this.isCheckbox = this.$element.is( 'input[type=checkbox]' ); 1150 this.errorClassHandler = options.errors.classHandler( element, this.isRadioOrCheckbox ) || this.$element.parent(); 1151 } 1152 1153 /** 1154 * Set specific constraints messages, do pseudo-heritance 1155 * 1156 * @private 1157 * @method inherit 1158 * @param {Object} element 1159 * @param {Object} options 1160 */ 1161 , inherit: function ( element, options ) { 1162 var clone = new ParsleyField( element, options, 'ParsleyFieldMultiple' ); 1163 1164 for ( var property in clone ) { 1165 if ( 'undefined' === typeof this[ property ] ) { 1166 this[ property ] = clone [ property ]; 1167 } 1168 } 1169 } 1170 1171 /** 1172 * Set specific constraints messages, do pseudo-heritance 1173 * 1174 * @method getName 1175 * @returns {String} radio / checkbox hash is cleaned 'name' or data-group property 1176 */ 1177 , getName: function () { 1178 if ( this.group ) { 1179 return 'parsley-' + this.group; 1180 } 1181 1182 if ( 'undefined' === typeof this.$element.attr( 'name' ) ) { 1183 throw "A radio / checkbox input must have a parsley-group attribute or a name to be Parsley validated !"; 1184 } 1185 1186 return 'parsley-' + this.$element.attr( 'name' ).replace( /(:|\.|\[|\]|\$)/g, '' ); 1187 } 1188 1189 /** 1190 * Special treatment for radio & checkboxes 1191 * Returns checked radio or checkboxes values 1192 * 1193 * @method getVal 1194 * @returns {String} val 1195 */ 1196 , getVal: function () { 1197 if ( this.isRadio ) { 1198 return $( this.siblings + ':checked' ).val() || ''; 1199 } 1200 1201 if ( this.isCheckbox ) { 1202 var values = []; 1203 1204 $( this.siblings + ':checked' ).each( function () { 1205 values.push( $( this ).val() ); 1206 } ); 1207 1208 return values; 1209 } 1210 } 1211 1212 /** 1213 * Bind validation events on a field 1214 * 1215 * @private 1216 * @method bindValidationEvents 1217 */ 1218 , bindValidationEvents: function () { 1219 // this field has validation events, that means it has to be validated 1220 this.valid = null; 1221 this.$element.addClass( 'parsley-validated' ); 1222 1223 // remove eventually already binded events 1224 this.$element.off( '.' + this.type ); 1225 1226 // always bind keyup event, for better UX when a field is invali
1226d 1227 var self = this 1228 , triggers = ( !this.options.trigger ? '' : this.options.trigger ) 1229 + ( new RegExp( 'change', 'i' ).test( this.options.trigger ) ? '' : ' change' ); 1230 1231 // trim triggers to bind them correctly with .on() 1232 triggers = triggers.replace( /^\s+/g , '' ).replace( /\s+$/g ,'' ); 1233 1234 // bind trigger event on every siblings 1235 $( this.siblings ).each(function () { 1236 $( this ).on( triggers.split( ' ' ).join( '.' + self.type + ' ' ) , false, $.proxy( self.eventValidation, self ) ); 1237 } ) 1238 } 1239 }; 1240 1241 /** 1242 * ParsleyForm class manage Parsley validated form. 1243 * Manage its fields and global validation 1244 * 1245 * @class ParsleyForm 1246 * @constructor 1247 */ 1248 var ParsleyForm = function ( element, options, type ) { 1249 this.init( element, options, type || 'parsleyForm' ); 1250 }; 1251 1252 ParsleyForm.prototype = { 1253 1254 constructor: ParsleyForm 1255 1256 /* init data, bind jQuery on() actions */ 1257 , init: function ( element, options, type ) { 1258 this.type = type; 1259 this.items = []; 1260 this.$element = $( element ); 1261 this.options = options; 1262 var self = this; 1263 1264 this.$element.find( options.inputs ).each( function () { 1265 self.addItem( this ); 1266 }); 1267 1268 this.$element.on( 'submit.' + this.type , false, $.proxy( this.validate, this ) ); 1269 } 1270 1271 /** 1272 * Add custom listeners 1273 * 1274 * @param {Object} { listener: function () {} }, eg { onFormValidate: function ( valid, event, focus ) { ... } } 1275 */ 1276 , addListener: function ( object ) { 1277 for ( var listener in object ) { 1278 if ( new RegExp( 'Field' ).test( listener ) ) { 1279 for ( var item = 0; item < this.items.length; item++ ) { 1280 this.items[ item ].addListener( object ); 1281 } 1282 } else { 1283 this.options.listeners[ listener ] = object[ listener ]; 1284 } 1285 } 1286 } 1287 1288 /** 1289 * Adds a new parsleyItem child to ParsleyForm 1290 * 1291 * @method addItem 1292 * @param elem 1293 */ 1294 , addItem: function ( elem ) { 1295 var ParsleyField = $( elem ).parsley( this.options ); 1296 ParsleyField.setParent( this ); 1297 1298 this.items.push( ParsleyField ); 1299 } 1300 1301 /** 1302 * Removes a parsleyItem child from ParsleyForm 1303 * 1304 * @method removeItem 1305 * @param elem 1306 * @return {Boolean} 1307 */ 1308 , removeItem: function ( elem ) { 1309 var parsleyItem = $( elem ).parsley(); 1310 1311 // identify & remove item if same Parsley hash 1312 for ( var i = 0; i < this.items.length; i++ ) { 1313 if ( this.items[ i ].hash === parsleyItem.hash ) { 1314 this.items[ i ].destroy(); 1315 this.items.splice( i, 1 ); 1316 return true; 1317 } 1318 } 1319 1320 return false; 1321 } 1322 1323 /** 1324 * Process each form field validation 1325 * Display errors, call custom onFormValidate() function 1326 * 1327 * @method validate 1328 * @param {Object} event jQuery Event 1329 * @return {Boolean} Is form valid or not 1330 */ 1331 , validate: function ( event ) { 1332 var valid = true; 1333 this.focusedField = false; 1334 1335 for ( var item = 0; item < this.items.length; item++ ) { 1336 if ( 'undefined' !== typeof this.items[ item ] && false === this.items[ item ].validate() ) { 1337 valid = false; 1338 1339 if ( !this.focusedField && 'first' === this.options.focus || 'last' === this.options.focus ) { 1340 this.focusedField = this.items[ item ].$element; 1341 } 1342 } 1343 } 1344 1345 // form is invalid, focus an error field depending on focus policy 1346 if ( this.focusedField && !valid ) { 1347 // Scroll smoothly 1348 if ( this.options.scrollDuration > 0 ) { 1349 var that = this, 1350 top = this.focusedField.offset().top - $( window ).height() / 2; // Center the window on the field 1351 1352 $( 'html, body' ).animate( { 1353 scrollTop: top 1354 }, 1355 this.options.scrollDuration, 1356 function () { 1357 that.focusedField.focus(); 1358 } 1359 ); 1360 // Just focus on the field and let the browser do the rest 1361 } else { 1362 this.focusedField.focus(); 1363 } 1364 } 1365 1366 // if onFormValidate returns (bool) false, form won't be submitted, even if valid 1367 var onFormValidate = this.options.listeners.onFormValidate( valid, event, this ); 1368 if ('undefined' !== typeof onFormValidate) { 1369 return onFormValidate; 1370 } 1371 1372 return valid; 1373 } 1374 1375 , isValid: function () { 1376 for ( var item = 0; item < this.items.length; item++ ) { 1377 if ( false === this.items[ item ].isValid() ) { 1378 return false;
1379 } 1380 } 1381 1382 return true; 1383 } 1384 1385 /** 1386 * Remove all errors ul under invalid fields 1387 * 1388 * @method removeErrors 1389 */ 1390 , removeErrors: function () { 1391 for ( var item = 0; item < this.items.length; item++ ) { 1392 this.items[ item ].parsley( 'reset' ); 1393 } 1394 } 1395 1396 /** 1397 * destroy Parsley binded on the form and its fields 1398 * 1399 * @method destroy 1400 */ 1401 , destroy: function () { 1402 for ( var item = 0; item < this.items.length; item++ ) { 1403 this.items[ item ].destroy(); 1404 } 1405 1406 this.$element.off( '.' + this.type ).removeData( this.type ); 1407 } 1408 1409 /** 1410 * reset Parsley binded on the form and its fields 1411 * 1412 * @method reset 1413 */ 1414 , reset: function () { 1415 for ( var item = 0; item < this.items.length; item++ ) { 1416 this.items[ item ].UI.reset(); 1417 } 1418 } 1419 }; 1420 1421 /** 1422 * Parsley plugin definition 1423 * Provides an interface to access public Validator, ParsleyForm and ParsleyField functions 1424 * 1425 * @class Parsley 1426 * @constructor 1427 * @param {Mixed} Options. {Object} to configure Parsley or {String} method name to call a public class method 1428 * @param {Function} Callback function 1429 * @return {Mixed} public class method return 1430 */ 1431 $.fn.parsley = function ( option, fn ) { 1432 var namespace = { namespace: $( this ).data( 'parsleyNamespace' ) ? $( this ).data( 'parsleyNamespace' ) : ( 'undefined' !== typeof option && 'undefined' !== typeof option.namespace ? option.namespace : $.fn.parsley.defaults.namespace ) } 1433 , options = $.extend( true, {}, $.fn.parsley.defaults, 'undefined' !== typeof window.ParsleyConfig ? window.ParsleyConfig : {}, option, this.domApi( namespace.namespace ) ) 1434 , newInstance = null 1435 , args = Array.prototype.slice.call(arguments, 1); 1436 1437 function bind ( self, type ) { 1438 var parsleyInstance = $( self ).data( type ); 1439 1440 // if data never binded or we want to clone a build (for radio & checkboxes), bind it right now! 1441 if ( !parsleyInstance ) { 1442 switch ( type ) { 1443 case 'parsleyForm': 1444 parsleyInstance = new ParsleyForm( self, options, 'parsleyForm' ); 1445 break; 1446 case 'parsleyField': 1447 parsleyInstance = new ParsleyField( self, options, 'parsleyField' ); 1448 break; 1449 case 'parsleyFieldMultiple': 1450 parsleyInstance = new ParsleyFieldMultiple( self, options, 'parsleyFieldMultiple' ); 1451 break; 1452 default: 1453 return; 1454 } 1455 1456 $( self ).data( type, parsleyInstance ); 1457 } 1458 1459 // here is our parsley public function accessor 1460 if ( 'string' === typeof option && 'function' === typeof parsleyInstance[ option ] ) { 1461 var response = parsleyInstance[ option ].apply( parsleyInstance, args ); 1462 1463 return 'undefined' !== typeof response ? response : $( self ); 1464 } 1465 1466 return parsleyInstance; 1467 } 1468 1469 // if a form elem is given, bind all its input children 1470 if ( $( this ).is( 'form' ) || 'undefined' !== typeof $( this ).domApi( namespace.namespace )[ 'bind' ] ) { 1471 newInstance = bind ( $( this ), 'parsleyForm' ); 1472 1473 // if it is a Parsley supported single element, bind it too, except inputs type hidden 1474 // add here a return instance, cuz' we could call public methods on single elems with data[ option ]() above 1475 } else if ( $( this ).is( options.inputs ) ) { 1476 newInstance = bind( $( this ), !$( this ).is( 'input[type=radio], input[type=checkbox]' ) ? 'parsleyField' : 'parsleyFieldMultiple' ); 1477 } 1478 1479 return 'function' === typeof fn ? fn() : newInstance; 1480 }; 1481 1482 /* PARSLEY auto-binding 1483 * =================================================== */ 1484 $( window ).on( 'load', function () { 1485 $( '[parsley-validate], [data-parsley-validate]' ).each( function () { 1486 $( this ).parsley(); 1487 } ); 1488 } ); 1489 1490 /* PARSLEY DOM API 1491 * =================================================== */ 1492 $.fn.domApi = function ( namespace ) { 1493 var attribute, 1494 obj = {} 1495 , regex = new RegExp("^" + namespace, 'i'); 1496 1497 if ( 'undefined' === typeof this[ 0 ] ) { 1498 return {}; 1499 } 1500 1501 for ( var i in this[ 0 ].attributes ) { 1502 attribute = this[ 0 ].attributes[ i ]; 1503 1504 if ( 'undefined' !== typeof attribute && null !== attribute && attribute.specified && regex.test( attribute.name ) ) { 1505 obj[ camelize( attribute.name.replace( namespace, '' ) ) ] = deserializeValue( attribute.value ); 1506 } 1507 } 1508 1509 return obj; 1510 }; 1511 1512 // Zepto deserializeValue function 1513 // "true" => true 1514 // "false" => false 1515 // "null" => null 1516 // "42" => 42 1517 // "42.5" => 42.5 1518 // JSON =>
1518 parse if valid 1519 // String => self 1520 var deserializeValue = function( value ) { 1521 var num 1522 try { 1523 return value ? 1524 value == "true" || 1525 ( value == "false" ? false : 1526 value == "null" ? null : 1527 !isNaN( num = Number( value ) ) ? num : 1528 /^[\[\{]/.test( value ) ? $.parseJSON( value ) : 1529 value ) 1530 : value; 1531 } catch ( e ) { 1532 return value; 1533 } 1534 }; 1535 1536 // Zepto camelize function 1537 var camelize = function ( str ) { 1538 return str.replace( /-+(.)?/g, function ( match, chr ) { 1539 return chr ? chr.toUpperCase() : ''; 1540 } ) 1541 }; 1542 1543 // Zepto dasherize function 1544 var dasherize = function ( str ) { 1545 return str.replace( /::/g, '/' ) 1546 .replace( /([A-Z]+)([A-Z][a-z])/g, '$1_$2' ) 1547 .replace( /([a-z\d])([A-Z])/g, '$1_$2' ) 1548 .replace( /_/g, '-' ) 1549 .toLowerCase() 1550 }; 1551 1552 /** 1553 * Parsley plugin configuration 1554 * 1555 * @property $.fn.parsley.defaults 1556 * @type {Object} 1557 */ 1558 $.fn.parsley.defaults = { 1559 // basic data-api overridable properties here.. 1560 namespace: 'parsley-' // DOM-API, default 'parsley-'. W3C valid would be 'data-parsley-' but quite ugly 1561 , inputs: 'input, textarea, select' // Default supported inputs. 1562 , excluded: 'input[type=hidden], input[type=file], :disabled' // Do not validate input[type=hidden] & :disabled.
1563 , priorityEnabled: true // Will display only one error at the time depending on validators priorities 1564 , trigger: false // $.Event() that will trigger validation. eg: keyup, change.. 1565 , animate: true // fade in / fade out error messages 1566 , animateDuration: 300 // fadein/fadout ms time 1567 , scrollDuration: 500 // Duration in ms time of the window scroll when focusing on invalid field (0 = no scroll) 1568 , focus: 'first' // 'fist'|'last'|'none' which error field would have focus first on form validation 1569 , validationMinlength: 3 // If trigger validation specified, only if value.length > validationMinlength 1570 , successClass: 'parsley-success' // Class name on each valid input 1571 , errorClass: 'parsley-error' // Class name on each invalid input 1572 , errorMessage: false // Customize an unique error message showed if one constraint fails 1573 , validators: {} // Add your custom validators functions 1574 , showErrors: true // Set to false if you don't want Parsley to display error messages 1575 , useHtml5Constraints: true // Set to false if you don't want Parsley to use html5 constraints 1576 , messages: {} // Add your own error messages here 1577 1578 //some quite advanced configuration here.. 1579 , validateIfUnchanged: false // false: validate once by field value change 1580 , errors: { 1581 classHandler: function ( elem, isRadioOrCheckbox ) {} // specify where parsley error-success classes are set 1582 , container: function ( elem, isRadioOrCheckbox ) {} // specify an elem where errors will be **apened** 1583 , errorsWrapper: '<ul></ul>' // do not set an id for this elem, it would have an auto-generated id 1584 , errorElem: '<li></li>' // each field constraint fail in an li 1585 } 1586 , listeners: { 1587 onFieldValidate: function ( elem, ParsleyField ) { return false; } // Executed on validation. Return true to ignore field validation 1588 , onFormValidate: function ( isFormValid, event, ParsleyForm ) {} // Executed once on form validation. Return (bool) false to block submit, even if valid 1589 , onFieldError: function ( elem, constraints, ParsleyField ) {} // Executed when a field is detected as invalid 1590 , onFieldSuccess: function ( elem, constraints, ParsleyField ) {} // Executed when a field passes validation 1591 } 1592 }; 1593 1594// This plugin works with jQuery or Zepto (with data extension built for Zepto.) 1595} ( window.jQuery || window.Zepto );
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.