1/* 2 * jQuery.validity v1.3.0 3 * http://validity.thatscaptaintoyou.com/ 4 * https://github.com/whatgoodisaroad/validity 5 * 6 * Dual licensed under MIT and GPL 7 * 8 * Date: 2012-11-24 (Saturday, 24 November 2012) 9 */ 10(function($, undefined) { 11 12// Default settings: 13/////////////////////////////////////////////////////////////////////////////// 14 15var 16 defaults = { 17 // The default output mode is tooltip because it requires no 18 // dependencies: 19 outputMode:"tooltip", 20 21 // The this property is set to true, validity will scroll the browser 22 // viewport so that the first error is visible when validation fails: 23 scrollTo:false, 24 25 // If this setting is true, modal errors will disappear when they are 26 // clicked on: 27 modalErrorsClickable:true, 28 29 // If a field name cannot be otherwise inferred, this will be used: 30 defaultFieldName:"Ce champ", 31 32 // jQuery selector to filter down to validation-supported elements: 33 elementSupport:":text, :password, textarea, select, :radio, :checkbox, input[type='hidden']", 34 35 // Function to stringify argments for use when generating error 36 // messages. Primarily, it just generates pretty date strings: 37 argToString:function(val) { 38 return val.getDate ? [ 39 val.getMonth() + 1, 40 val.getDate(), 41 val.getFullYear() 42 ].join("/") : 43 val + ""; 44 }, 45 46 debugPrivates:false 47 }, 48 49 __private; 50 51// Static functions and properties: 52/////////////////////////////////////////////////////////////////////////////// 53 54$.validity = { 55 56 // Clone the defaults. They can be overridden with the setup function: 57 settings:$.extend(defaults, {}), 58 59 // Built-in library of format-checking tools for use with the match 60 // validator as well as the nonHtml validator: 61 patterns:{ 62 integer:/^\d+$/, 63 64 // Used to use Date.parse(), which was the cause of Issue 9, where the 65 // function would accept 09/80/2009 as parseable. The fix is to use a 66 // RegExp that will only accept American Middle-Endian form. See the 67 // Internationalization section in the documentation for how to cause
68 // it to support other date formats: 69 date:/^((0?\d)|(1[012]))[\/-]([012]?\d|30|31)[\/-]\d{1,4}$/, 70 71 email:/^((([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])))\.?$/i, 72 usd:/^\$?((\d{1,3}(,\d{3})*)|\d+)(\.(\d{2})?)?$/, 73 url:/^(https?|ftp):\/\/(((([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, 74 75 // Number should accept floats or integers, be they positive or 76 // negative. It should also support scientific-notation, written as a 77 // lower or capital 'E' followed by the radix. Number assumes base 10. 78 // Unlike the native parseFloat or parseInt functions, this should not 79 // accept trailing Latin characters. 80 number:/^[+-]?(\d+(\.\d*)?|\.\d+)([Ee]-?\d+)?$/, 81 82 zip:/^\d{5}(-\d{4})?$/, 83 phone:/^[2-9]\d{2}-\d{3}-\d{4}$/, 84 guid:/^(\{?([0-9a-fA-F]){8}-(([0-9a-fA-F]){4}-){3}([0-9a-fA-F]){12}\}?)$/, 85 time12:/^((0?\d)|(1[012])):[0-5]\d?\s?[aApP]\.?[mM]\.?$/, 86 time24:/^(20|21|22|23|[01]\d|\d)(([:][0-5]\d){1,2})$/, 87 88 nonHtml:/^[^<>]*$/ 89 }, 90 91 // Built-in set of default error messages (for use when a message isn't 92 // specified): 93 messages:{ 94 95 require:"#{field} est requis.", 96 97 // Format validators: 98 match:"#{field} is in an invalid format.", 99 integer:"#{field} must be a positive, whole number.", 100 date:"#{field} must be formatted as a date. (mm/dd/yyyy)", 101 email:"#{field} must be formatted as an email.", 102 usd:"#{field} must be formatted as a US Dollar amount.", 103 url:"#{field} must be formatted as a URL.", 104 number:"#{field} must be formatted as a number.", 105 zip:"#{field} must be formatted as a zipcode ##### or #####-####.", 106 phone:"#{field} must be formatted as a phone number ###-###-####.", 107 guid:"#{field} must be formatted as a guid like {3F2504E0-4F89-11D3-9A0C-0305E82C3301}.", 108 time24:"#{field} must be formatted as a 24 hour time: 23:00.", 109 time12:"#{field} must be formatted as a 12 hour time: 12:00 AM/PM", 110 111 // Value range messages: 112 lessThan:"#{field} must be less than #{max}.", 113 lessThanOrEqualTo:"#{field} must be less than or equal to #{max}.", 114 greaterThan:"#{field} must be greater than #{min}.", 115 greaterThanOrEqualTo:"#{field} must be greater than or equal to #{min}.", 116 range:"#{field} must be between #{min} and #{max}.", 117 118 // Value length messages: 119 tooLong:"#{field} cannot be longer than #{max} characters.", 120 tooShort:"#{field} cannot be shorter than #{min} characters.", 121 122 // Composition validators: 123 nonHtml:"#{field} cannot contain HTML characters.", 124 alphabet:"#{field} contains disallowed characters.", 125 126 minCharClass:"#{field} cannot have more than #{min} #{charClass} characters.",
127 maxCharClass:"#{field} cannot have less than #{min} #{charClass} characters.", 128 129 // Aggregate validator messages: 130 equal:"Values don't match.", 131 distinct:"A value was repeated.", 132 sum:"Values don't add to #{sum}.", 133 sumMax:"The sum of the values must be less than #{max}.", 134 sumMin:"The sum of the values must be greater than #{min}.", 135 136 // Radio validator messages: 137 radioChecked:"The selected value is not valid.", 138 139 generic:"Invalid." 140 }, 141 142 // Abstract output: 143 out:{ 144 start:function() { 145 this.defer("start"); 146 }, 147 end:function(results) { 148 this.defer("end", results); 149 }, 150 raise:function($obj, msg) { 151 this.defer("raise", $obj, msg); 152 }, 153 raiseAggregate:function($obj, msg) { 154 this.defer("raiseAggregate", $obj, msg); 155 }, 156 defer:function(name) { 157 var 158 v = $.validity, 159 o = v.outputs[v.settings.outputMode]; 160 161 o[name].apply( 162 o, 163 Array.prototype.slice.call(arguments, 1) 164 ); 165 } 166 }, 167 168 // Character classes can be used to determine the quantity of a given type 169 // of character in a string: 170 charClasses:{ 171 alphabetical:/\w/g, 172 numeric:/\d/g, 173 alphanumeric:/[A-Za-z0-9]/g, 174 symbol:/[^A-Za-z0-9]/g 175 }, 176 177 // Object to contain the output modes. The three built-in output modes are 178 // installed later on in this script. 179 outputs:{}, 180 181 // By default, private members are not exposed. 182 __private:undefined, 183 184 // Override the default settings with user-specified ones. 185 setup:function(options) { 186 this.settings = $.extend(this.settings, options); 187 188 if (this.settings.debugPrivates) { 189 this.__private = __private; 190 } 191 else { 192 this.__private = undefined; 193 } 194 }, 195 196 // Object to store information about ongoing validation. When validation 197 // starts, this will be set to a report object. When validators fail, they 198 // will inform this object. When validation is completed, this object will 199 // contain the information of whether it succeeded: 200 report:null, 201 202 // Determine whether validity is in the middle of validation: 203 isValidating:function() { 204 return !!this.report; 205 }, 206 207 // Function to prepare validity to start validating: 208 start:function() { 209 210 // The output mode should be notified that validation is starting. This 211 // usually means that the output mode will erase errors from the 212 // document in whatever way the mode needs to: 213 this.out.start(); 214 215 // Initialize the report object: 216 this.report = { errors:0, valid:true }; 217 }, 218 219 // Function called when validation is over to examine the results and 220 // clean-up: 221 end:function() { 222 223 // Null coalescence: fix for Issue 5: 224 var results = this.report || { errors: 0, valid: true }; 225 226 this.report = null; 227 228 // Notify the current output mode that validation is over: 229 this.out.end(results); 230 231 return results; 232 }, 233 234 // Remove validiation errors: 235 clear:function() { 236 this.start(); 237 this.end(); 238 } 239}; 240 241// jQuery instance methods: 242/////////////////////////////////////////////////////////////////////////////// 243 244$.fn.extend({ 245 246 // The validity function is how validation can be bound to forms. The user 247 // may pass in a validation function or, as a shortcut, pass in a string of 248 // a CSS selector that grabs all the inputs to require: 249 validity:function(arg) { 250 251 return this.each( 252 253 function() { 254 255 // Only operate on forms: 256 if (this.tagName.toLowerCase() == "form") { 257 var f = null; 258 259 // If the user entered a string to select the inputs to 260 // require, then make the validation logic ad hoc: 261 if (typeof (arg) == "string") { 262 f = function() { 263 $(arg).require(); 264 }; 265 } 266 267 // If the user entered a validation function then just call 268 // that at the appropriate time: 269 else if ($.isFunction(arg)) { 270 f = arg; 271 } 272 273 if (arg) { 274 $(this).bind( 275 "submit", 276 function() { 277 $.validity.start(); 278 f();
279 return $.validity.end().valid; 280 } 281 ); 282 } 283 } 284 } 285 ); 286 }, 287 288 // Validators: 289 /////////////////////////////////////////////////////////////////////////// 290 291 // Common validators: 292 /////////////////////////////////////////////////////////////////////////// 293 294 // Validate whether the field has a value. 295 require:function(msg) { 296 return validate( 297 this, 298 function(obj) { 299 return !!$(obj).val().length; 300 }, 301 msg || $.validity.messages.require 302 ); 303 }, 304 305 // Validate whether the field matches a regex. 306 match:function(rule, msg) { 307 308 // If a default message is to be used: 309 if (!msg) { 310 // First grab the generic one: 311 msg = $.validity.messages.match; 312 313 // If there's a more specific one, use that. 314 if (typeof (rule) === "string" && $.validity.messages[rule]) { 315 msg = $.validity.messages[rule]; 316 } 317 } 318 319 // If the rule is named, rather than specified: 320 if (typeof (rule) == "string") { 321 rule = $.validity.patterns[rule]; 322 } 323 324 return validate( 325 this, 326 327 // Some of the named rules can be functions, such as 'date'. If the 328 // discovered rule is a function use it as such. Otherwise, assume 329 // it's a RegExp. 330 $.isFunction(rule) ? 331 332 function(obj) { 333 return !obj.value.length || rule(obj.value); 334 } : 335 336 function(obj) { 337 // Fix for regexes where the global flag is set. Make sure 338 // to test from the start of the string. 339 if (rule.global) { 340 rule.lastIndex = 0; 341 } 342 343 return !obj.value.length || rule.test(obj.value); 344 }, 345 346 msg 347 ); 348 }, 349 350 range:function(min, max, msg) { 351 return validate( 352 this, 353 354 min.getTime && max.getTime ? 355 356 // If both arguments are dates then use them that way. 357 function(obj) { 358 var d = new Date(obj.value); 359 return d >= new Date(min) && d <= new Date(max); 360 } : 361 362 min.substring && max.substring && Big ? 363 364 // If both arguments are strings then parse them using the 365 // Arbitrary-Precision library. 366 function(obj) { 367 var n = new Big(obj.value); 368 return ( 369 n.greaterThanOrEqualTo(new Big(min)) && 370 n.lessThanOrEqualTo(new Big(max)) 371 ); 372 } : 373 374 // Otherwise treat them like floats. 375 function(obj) { 376 var f = parseFloat(obj.value); 377 return f >= min && f <= max; 378 }, 379 380 msg || format( 381 $.validity.messages.range, { 382 min:$.validity.settings.argToString(min), 383 max:$.validity.settings.argToString(max) 384 } 385 ) 386 ); 387 }, 388 389 greaterThan:function(min, msg) { 390 return validate( 391 this, 392 393 min.getTime ? 394 function(obj) { 395 return new Date(obj.value) > min; 396 } : 397 398 min.substring && Big ? 399 400 function(obj) { 401 return new Big(obj.value).greaterThan(new Big(min)); 402 } : 403 404 function(obj) { 405 return parseFloat(obj.value) > min; 406 }, 407 408 msg || format( 409 $.validity.messages.greaterThan, { 410 min:$.validity.settings.argToString(min) 411 } 412 ) 413 ); 414 }, 415 416 greaterThanOrEqualTo:function(min, msg) { 417 return validate( 418 this, 419 420 min.getTime ? 421 function(obj) { 422 return new Date(obj.value) >= min; 423 } : 424 425 min.substring && Big ? 426 427 function(obj) { 428 return new Big(obj.value).greaterThanOrEqualTo(new Big(min)); 429 } : 430 431 function(obj) { 432 return parseFloat(obj.value) >= min; 433 }, 434 435 msg || format( 436 $.validity.messages.greaterThanOrEqualTo, { 437 min:$.validity.settings.argToString(min) 438 } 439 ) 440 ); 441 }, 442 443 lessThan:function(max, msg) { 444 return validate( 445 this, 446 447 max.getTime ? 448 function(obj) { 449 return new Date(obj.value) < max; 450 } : 451 452 max.substring && Big ? 453 454 function(obj) { 455 return new Big(obj.value).lessThan(new Big(max)); 456 } : 457 458 function(obj) { 459 return parseFloat(obj.value) < max; 460 }, 461 462 msg || format( 463 $.validity.messages.lessThan, { 464 max:$.validity.settings.argToString(max) 465 } 466 ) 467 ); 468 }, 469 470 lessThanOrEqualTo:function(max, msg) { 471 return validate( 472 this, 473 474 max.getTime ? 475 function(obj) { 476 return new Date(obj.value) <= max; 477 } : 478 479 max.substring && Big ? 480 481 function(obj) { 482 return new Big(obj.value).lessThanOrEqualTo(new Big(max)); 483 } : 484 485 function(obj) { 486 return parseFloat(obj.value) <= max; 487 }, 488 489 msg || format( 490 $.validity.messages.lessThanOrEqualTo, { 491 max:$.validity.settings.argToString(max) 492 } 493 ) 494 ); 495 }, 496 497 maxLength:function(max, msg) { 498 return validate( 499 this, 500 function(obj) { 501 return obj.value.length <= max; 502 }, 503 msg || format( 504 $.validity.messages.tooLong, { 505 max:max 506 } 507 ) 508 ); 509 }, 510 511 minLength:function(min, msg) { 512 return validate( 513 this, 514 function(obj) { 515 return obj.value.length >= min; 516 }, 517 msg || format( 518 $.validity.messages.tooShort, { 519 min:min 520 } 521 ) 522 ); 523 }, 524
525 alphabet:function(alpha, msg) { 526 var chars = []; 527 528 return validate( 529 this, 530 function(obj) { 531 532 // For each character in the string, ensure that it's in the 533 // alphabet definition: 534 for (var idx = 0; idx < obj.value.length; ++idx) { 535 if (alpha.indexOf(obj.value.charAt(idx)) == -1) { 536 chars.push(obj.value.charAt(idx)); 537 return false; 538 } 539 } 540 541 return true; 542 }, 543 msg || format( 544 $.validity.messages.alphabet, { 545 chars:chars.join(", ") 546 } 547 ) 548 ); 549 }, 550 551 minCharClass:function(charClass, min, msg) { 552 if (typeof(charClass) == "string") { 553 charClass = charClass.toLowerCase(); 554 555 if ($.validity.charClasses[charClass]) { 556 charClass = $.validity.charClasses[charClass]; 557 } 558 } 559 560 return validate( 561 this, 562 function(obj) { 563 return (obj.value.match(charClass) || []).length >= min; 564 }, 565 msg || format( 566 $.validity.messages.minCharClass, { 567 min:min, 568 charClass:charClass 569 } 570 ) 571 ); 572 }, 573 574 maxCharClass:function(charClass, max, msg) { 575 if (typeof(charClass) == "string") { 576 charClass = charClass.toLowerCase(); 577 578 if ($.validity.charClasses[charClass]) { 579 charClass = $.validity.charClasses[charClass]; 580 } 581 } 582 583 return validate( 584 this, 585 function(obj) { 586 return (obj.value.match(charClass) || []).length <= max; 587 }, 588 msg || format(
589 $.validity.messages.maxCharClass, { 590 max:max, 591 charClass:charClass 592 } 593 ) 594 ); 595 }, 596 597 // Validate that the input does not contain potentially dangerous strings. 598 nonHtml:function(msg) { 599 return validate( 600 this, 601 602 function(obj) { 603 return $.validity.patterns.nonHtml.test(obj.value); 604 }, 605 606 msg || $.validity.messages.nonHtml 607 ); 608 }, 609 610 // Aggregate validators: 611 /////////////////////////////////////////////////////////////////////////// 612 613 // Validate that all matched elements bear the same values. Accepts a 614 // function to transform the values for testing. 615 equal:function(arg0, arg1) { 616 var 617 // If a reduced set is attached, use it. Also, remove unsupported 618 // elements. 619 $reduction = (this.reduction || this) 620 .filter($.validity.settings.elementSupport), 621 622 transform = function(val) { 623 return val; 624 }, 625 626 msg = $.validity.messages.equal; 627 628 if ($reduction.length) { 629 630 // Figure out what arguments were specified. 631 if ($.isFunction(arg0)) { 632 transform = arg0; 633 634 if (typeof (arg1) == "string") { 635 msg = arg1; 636 } 637 } 638 639 else if (typeof (arg0) == "string") { 640 msg = arg0; 641 } 642 643 var 644 map = $.map( 645 $reduction, 646 function(obj) { 647 return transform(obj.value); 648 } 649 ), 650 651 // Get the first value. 652 first = map[0], 653 valid = true; 654 655 // If any value is not equal to the first value, then they aren't 656 // all equal, and it's not valid. 657 for (var i in map) { 658 if (map[i] != first) { 659 valid = false; 660 } 661 } 662 663 if (!valid) { 664 raiseAggregateError($reduction, msg); 665 666 // The set reduces to zero valid elements. 667 this.reduction = $([]); 668 } 669 } 670 671 return this; 672 }, 673 674 // Validate that all matched elements bear distinct values. Accepts an 675 // optional function to transform the values for testing. 676 distinct:function(arg0, arg1) { 677 var 678 // If a reduced set is attached, use it. 679 // Also, remove unsupported elements. 680 $reduction = (this.reduction || this).filter($.validity.settings.elementSupport), 681 682 transform = function(val) { 683 return val; 684 }, 685 686 msg = $.validity.messages.distinct, 687 688 // An empty array to store already-examined values 689 subMap = [], 690 691 // An array with repeated values 692 repeatedVal = [], 693 694 valid = true; 695 696 if ($reduction.length) { 697 698 // Figure out what arguments were specified. 699 if ($.isFunction(arg0)) { 700 transform = arg0; 701 702 if (typeof (arg1) == "string") { 703 msg = arg1; 704 } 705 } 706 707 else if (typeof (arg0) == "string") { 708 msg = arg0; 709 } 710 711 // Map all the matched values into an array. 712 var map = $.map( 713 $reduction, 714 function(obj) { 715 return transform(obj.value); 716 } 717 ); 718 719 // For each transformed value: 720 for (var i1 = 0; i1 < map.length; ++i1) { 721 // Unless it's an empty string: 722 if (map[i1].length) { 723 // For each value we've already looked at: 724 for (var i2 = 0; i2 < subMap.length; ++i2) { 725 // If we've already seen the transformed value: 726 if (subMap[i2] == map[i1]) { 727 valid = false;
728 // Collect repeated values 729 repeatedVal.push(map[i1]); 730 } 731 } 732 733 // We looked at the value. 734 subMap.push(map[i1]); 735 } 736 } 737 738 if (!valid) { 739 // Remove duplicates of duplicates 740 repeatedVal = $.unique(repeatedVal); 741 742 // For all repeated values found... 743 for ( 744 var 745 i = 0, 746 repeatedLength = repeatedVal.length; 747 i < repeatedLength; 748 ++i) { 749 750 // raise the error - aggregate will use last repeated value 751 raiseAggregateError( 752 $reduction.filter( 753 "[value=\'" + repeatedVal[i] + "\']" 754 ), 755 msg 756 ); 757 } 758 759 // The set reduces to zero valid elements. 760 this.reduction = $([]); 761 } 762 } 763 764 return this; 765 }, 766 767 // Validate that the numeric sum of all values is equal to a given value. 768 sum:function(sum, msg) { 769 // If a reduced set is attached, use it. Also, remove unsupported 770 // elements. 771 var $reduction = (this.reduction || this).filter($.validity.settings.elementSupport); 772 773 if ($reduction.length && sum != numericSum($reduction)) { 774 raiseAggregateError( 775 $reduction, 776 msg || format( 777 $.validity.messages.sum, 778 { sum:sum } 779 ) 780 ); 781 782 // The set reduces to zero valid elements. 783 this.reduction = $([]); 784 } 785 786 return this; 787 }, 788 789 // Validates an inclusive upper-bound on the numeric sum of the values of 790 // all matched elements. 791 sumMax:function(max, msg) { 792 // If a reduced set is attached, use it. Also, remove unsupported 793 // elements. 794 var $reduction = (this.reduction || this).filter($.validity.settings.elementSupport); 795 796 if ($reduction.length && max < numericSum($reduction)) { 797 raiseAggregateError( 798 $reduction, 799 msg || format( 800 $.validity.messages.sumMax, 801 { max:max } 802 ) 803 ); 804 805 // The set reduces to zero valid elements. 806 this.reduction = $([]); 807 } 808 809 return this; 810 }, 811 812 // Validates an inclusive lower-bound on the numeric sum of the values of 813 // all matched elements. 814 sumMin:function(min, msg) { 815 // If a reduced set is attached, use it. Also, remove unsupported 816 // elements. 817 var $reduction = (this.reduction || this).filter($.validity.settings.elementSupport); 818 819 if ($reduction.length && min > numericSum($reduction)) { 820 raiseAggregateError( 821 $reduction, 822 msg || format( 823 $.validity.messages.sumMin, 824 { min:min } 825 ) 826 ); 827 828 // The set reduces to zero valid elements. 829 this.reduction = $([]); 830 } 831 832 return this; 833 }, 834 835 // Radio group validators: 836 /////////////////////////////////////////////////////////////////////////// 837 838 radioChecked:function(val, msg) { 839 // If a reduced set is attached, use it. Also, remove unsupported 840 // elements. 841 var $reduction = (this.reduction || this).filter($.validity.settings.elementSupport); 842 843 if ($reduction.is(":radio") && $reduction.find(":checked").val() != val) { 844 raiseAggregateError( 845 $reduction, 846 msg || $.validity.messages.radioChecked 847 ); 848 } 849 }, 850 851 radioNotChecked:function(val, msg) { 852 // If a reduced set is attached, use it. Also, remove unsupported 853 // elements. 854 var $reduction = (this.reduction || this).filter($.validity.settings.elementSupport); 855 856 if ($reduction.is(":radio") && $reduction.filter(":checked").val() == val) { 857 raiseAggregateError( 858 $reduction, 859 msg || $.validity.messages.radioChecked 860 ); 861 } 862 }, 863 864 // Checkbox validators:
865 /////////////////////////////////////////////////////////////////////////// 866 867 checkboxChecked:function(msg) { 868 // If a reduced set is attached, use it. Also, remove unsupported 869 // elements. 870 var $reduction = (this.reduction || this).filter($.validity.settings.elementSupport); 871 872 if ($reduction.is(":checkbox") && !$reduction.is(":checked")) { 873 raiseAggregateError( 874 $reduction, 875 msg || $.validity.messages.radioChecked 876 ); 877 } 878 }, 879 880 // Specialized validators: 881 /////////////////////////////////////////////////////////////////////////// 882 883 // If expression is a function, it will be called on each matched element. 884 // Otherwise, it is treated as a boolean, and the determines the validity 885 // of elements in an aggregate fashion. 886 assert:function(expression, msg) { 887 888 // If a reduced set is attached, use it. Do not reduce to supported 889 // elements. 890 var $reduction = this.reduction || this; 891 892 if ($reduction.length) { 893 894 // In the case that 'expression' is a function, use it as a 895 // regimen on each matched element individually: 896 if ($.isFunction(expression)) { 897 return validate( 898 this, 899 expression, 900 msg || $.validity.messages.generic 901 ); 902 } 903 904 // Otherwise map it to a boolean and consider this as an aggregate 905 // validation: 906 else if (!expression) { 907 908 raiseAggregateError( 909 $reduction, 910 msg || $.validity.messages.generic 911 ); 912 913 // The set reduces to zero valid elements. 914 this.reduction = $([]); 915 } 916 } 917 918 return this; 919 }, 920 921 fail:function(msg) { 922 return this.assert(false, msg); 923 } 924}); 925 926// Private utilities: 927/////////////////////////////////////////////////////////////////////////////// 928 929// Do non-aggregate validation. Subject each element in $obj to the regimen. 930// Raise the specified error on failures. This function is the heart of 931// validity: 932function validate($obj, regimen, message) { 933 934 var 935 // If a reduced set is attached, use it Also, remove any unsupported 936 // elements. 937 $reduction = ($obj.reduction || $obj).filter($.validity.settings.elementSupport), 938 939 // Array to store only elements that pass the regimen. 940 elements = []; 941 942 // For each in the reduction. 943 $reduction.each( 944 function() { 945 // If the element passes the regimen, include it in the reduction. 946 if (regimen(this)) { 947 elements.push(this); 948 } 949 950 // Else give the element an error message. 951 else { 952 raiseError( 953 this, 954 format(message, { 955 field:infer(this) 956 }) 957 ); 958 } 959 } 960 ); 961 962 // Attach a (potentially) reduced set of only elements that passed. 963 $obj.reduction = $(elements); 964 965 // Return the full set with attached reduction. 966 return $obj; 967} 968 969// Inform the report object that there was a failure. 970function addToReport() { 971 if ($.validity.isValidating()) { 972 $.validity.report.errors++; 973 $.validity.report.valid = false; 974 } 975} 976 977// Inform the report of a failure and display an error according to the idiom 978// of the current output mode. 979function raiseError(obj, msg) { 980 addToReport(); 981 982 $.validity.out.raise($(obj), msg); 983} 984 985// Inform the report of a failure and display an aggregate error according to 986// the idiom of the current output mode. 987function raiseAggregateError($obj, msg) { 988 addToReport(); 989 990 $.validity.out.raiseAggregate($obj, msg); 991} 992 993// Yield the sum of the values of all fields matched in obj that can be parsed. 994function numericSum(obj) { 995 var accumulator = 0; 996 obj.each( 997 function() { 998 var n = parseFloat(this.value); 999 1000 accumulator += isNaN(n) ? 0 : n; 1001 } 1002 ); 1003 return accumulator; 1004} 1005 1006// Using the associative array supplied as the 'obj' argument, replace tokens 1007// of the format #{<key>} in the 'str' argument with that key's value. 1008function format(str, obj) { 1009 for (var p in obj) { 1010 if (obj.hasOwnProperty(p)) { 1011 str = str.replace( 1012 new RegExp("#\\{" + p + "\\}", "g"), 1013 obj[p] 1014 ); 1015 } 1016 } 1017 return capitalize(str); 1018} 1019 1020// Infer the field name of the passed DOM element. If a title is specified, its 1021// value is returned. Otherwise, attempt to parse a field name out of the id 1022// attribute. If that doesn't work, return the default field name in the 1023// configuration. 1024function infer(field) { 1025 var 1026 $f = $(field), 1027 id = $f.prop("id"), 1028 ret = $.validity.settings.defaultFieldName; 1029 1030 // Check for title. 1031 if ($f.prop("title").length) { 1032 ret = $f.prop("title"); 1033 } 1034 1035 // Check for UpperCamelCase. 1036 else if (/^([A-Z0-9][a-z]*)+$/.test(id)) { 1037 ret = id.replace(/([A-Z0-9])[a-z]*/g, " $&"); 1038 } 1039 1040 // Check for lowercase_separated_by_underscores 1041 else if (/^[a-z0-9]+(_[a-z0-9]+)*$/.test(id)) { 1042 var arr = id.split("_"); 1043 1044 for (var i = 0; i < arr.length; ++i) { 1045 arr[i] = capitalize(arr[i]); 1046 } 1047 1048 ret = arr.join(" "); 1049 } 1050 1051 return $.trim(ret); 1052} 1053 1054// Capitalize the first character of the string argument. 1055function capitalize(sz) { 1056 return sz.substring ? 1057 sz.substring(0, 1).toUpperCase() + sz.substring(1, sz.length) : 1058 sz; 1059} 1060 1061__private = { 1062 validate:validate, 1063 addToReport:addToReport, 1064 raiseError:raiseError, 1065 raiseAggregateError:raiseAggregateError, 1066 numericSum:numericSum, 1067 format:format, 1068 infer:infer, 1069 capitalize:capitalize 1070}; 1071 1072})(jQuery); 1073// Output modes: 1074//////////////////////////////////////////////////////////////// 1075 1076// Each output mode gets its own closure, 1077// distinct from the validity closure. 1078 1079// Install the tooltip output. 1080(function($) { 1081 $.validity.outputs.tooltip = { 1082 tooltipClass:"validity-tooltip", 1083 1084 start:function() { 1085 $("." + $.validity.outputs.tooltip.tooltipClass) 1086 .remove(); 1087 }, 1088 1089 end:function(results) { 1090 // If not valid and scrollTo is enabled, scroll the page to the first error. 1091 if (!results.valid && $.validity.settings.scrollTo) { 1092 document.body.scrollTop = $("." + $.validity.outputs.tooltip.tooltipClass) 1093 .offset() 1094 .top; 1095 } 1096 }, 1097 1098 raise:function($obj, msg) { 1099 var pos = $obj.offset(); 1100 pos.left += $obj.width() + 18; 1101 pos.top += 8; 1102 1103 $( 1104 "<div class=\"validity-tooltip\">" + 1105 msg + 1106 "<div class=\"validity-tooltip-outer\">" + 1107 "<div class=\"validity-tooltip-inner\"></div>" + 1108 "</div>" + 1109 "</div>" 1110 ) 1111 .click(function() { 1112 $obj.focus(); 1113 $(this).fadeOut(); 1114 }) 1115 .css(pos) 1116 .hide() 1117 .appendTo("body") 1118 .fadeIn(); 1119 }, 1120 1121 raiseAggregate:function($obj, msg) { 1122 // Just raise the error on the last input. 1123 if ($obj.length) { 1124 this.raise($obj.filter(":last"), msg); 1125 } 1126 } 1127 }; 1128})(jQuery); 1129 1130 1131// Install the label output. 1132(function($) { 1133 function getIdentifier($obj) { 1134 return $obj.attr('id').length ? 1135 $obj.attr('id') : 1136 $obj.attr('name'); 1137 } 1138 1139 $.validity.outputs.label = { 1140 cssClass:"error", 1141 1142 start:function() { 1143 // Remove all the existing error labels. 1144 $("." + $.validity.outputs.label.cssClass) 1145 .remove(); 1146 }, 1147 1148 end:function(results) { 1149 // If not valid and scrollTo is enabled, scroll the page to the first error. 1150 if (!results.valid && $.validity.settings.scrollTo) { 1151 location.hash = $("." + $.validity.outputs.label.cssClass + ":eq(0)").attr('for'); 1152 } 1153 }, 1154 1155 raise:function($obj, msg) { 1156 var 1157 labelSelector = "." + $.validity.outputs.label.cssClass + "[for='" + getIdentifier($obj) + "']"; 1158 1159 // If an error label already exists for the bad input just update its text: 1160 if ($(labelSelector).length) { 1161 $(labelSelector).text(msg); 1162 } 1163 1164 // Otherwize create a new one and stick it after the input: 1165 else { 1166 $("<label/>") 1167 .attr("for", getIdentifier($obj)) 1168 .addClass($.validity.outputs.label.cssClass) 1169 .text(msg) 1170 1171 // In the case that the element does not have an id 1172 // then the for attribute in the label will not cause 1173 // clicking the label to focus the element. This line 1174 // will make that happen. 1175 .click(function() { 1176 if ($obj.length) { 1177 $obj[0].select(); 1178 } 1179 }) 1180 1181 .insertAfter($obj); 1182 } 1183 }, 1184 1185 raiseAggregate:function($obj, msg) { 1186 // Just raise the error on the last input. 1187 if ($obj.length) { 1188 this.raise($($obj.get($obj.length - 1)), msg); 1189 } 1190 } 1191 }; 1192})(jQuery); 1193 1194// Install the modal output. 1195(function($) { 1196 var 1197 // Class to apply to modal errors. 1198 errorClass = "validity-modal-msg", 1199 1200 // The selector for the element where modal errors will me injected semantically. 1201 container = "body"; 1202 1203 $.validity.outputs.modal = { 1204 start:function() { 1205 // Remove all the existing errors. 1206 $("." + errorClass).remove(); 1207 }, 1208 1209 end:function(results) { 1210 // If not valid and scrollTo is enabled, scroll the page to the first error. 1211 if (!results.valid && $.validity.settings.scrollTo) { 1212 location.hash = $("." + errorClass + ":eq(0)").attr('id'); 1213 } 1214 }, 1215 1216 raise:function($obj, msg) { 1217 if ($obj.length) { 1218 var 1219 off = $obj.offset(), 1220 obj = $obj.get(0), 1221 1222 // Design a style object based off of the input's location. 1223 errorStyle = { 1224 left:parseInt(off.left + $obj.width() + 4, 10) + "px", 1225 top:parseInt(off.top - 10, 10) + "px" 1226 }; 1227 1228 // Create one and position it next to the input. 1229 $("<div/>") 1230 .addClass(errorClass) 1231 .css(errorStyle) 1232 .text(msg) 1233 .click($.validity.settings.modalErrorsClickable ? 1234 function() { $(this).remove(); } : null 1235 ) 1236 .appendTo(container); 1237 } 1238 }, 1239 1240 raiseAggregate:function($obj, msg) { 1241 // Just raise the error on the last input. 1242 if ($obj.length) { 1243 this.raise($($obj.get($obj.length - 1)), msg); 1244 } 1245 } 1246 }; 1247})(jQuery); 1248 1249// Install the summary output 1250(function($) { 1251 var 1252 // Container contains the summary. This is the element that is shown or hidden. 1253 container = ".validity-summary-container", 1254
1255 // Erroneous refers to an input with an invalid value, 1256 // not the error message itself. 1257 erroneous = "validity-erroneous", 1258 1259 // Selector for erroneous inputs. 1260 errors = "." + erroneous, 1261 1262 // The wrapper for entries in the summary. 1263 wrapper = "<li/>", 1264 1265 // Buffer to contain all the error messages that build up during validation. 1266 // When validation ends, it'll be flushed into the summary. 1267 // This way, the summary doesn't flicker empty then fill up. 1268 buffer = []; 1269 1270 $.validity.outputs.summary = { 1271 start:function() { 1272 $(errors).removeClass(erroneous); 1273 buffer = []; 1274 }, 1275 1276 end:function(results) { 1277 // Hide the container and empty its summary. 1278 $(container) 1279 .hide() 1280 .find("ul") 1281 .html(''); 1282 1283 // If there are any errors at all: 1284 // (Otherwise the container shouldn't be shown): 1285 if (buffer.length) { 1286 // Use integer based iteration for solution to Issue 7. 1287 for (var i = 0; i < buffer.length; ++i) { 1288 $(wrapper) 1289 .text(buffer[i]) 1290 .appendTo(container + " ul"); 1291 } 1292 1293 $(container).show(); 1294 1295 // If scrollTo is enabled, scroll the page to the first error. 1296 if ($.validity.settings.scrollTo) { 1297 location.hash = $(errors + ":eq(0)").attr("id"); 1298 } 1299 } 1300 }, 1301 1302 raise:function($obj, msg) { 1303 buffer.push(msg); 1304 $obj.addClass(erroneous); 1305 }, 1306 1307 raiseAggregate:function($obj, msg) { 1308 this.raise($obj, msg); 1309 }, 1310 1311 container:function() { 1312 document.write( 1313 "<div class=\"validity-summary-container\">" + 1314 "The form didn't submit for the following reason(s):" + 1315 "<ul></ul>" + 1316 "</div>" 1317 ); 1318 } 1319 }; 1320})(jQuery);
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.