1(function ($) { 2 3 var createdElements = []; 4 5 var defaults = { 6 options: { 7 prependExistingHelpBlock: false, 8 sniffHtml: true, // sniff for 'required', 'maxlength', etc 9 preventSubmit: true, // stop the form submit event from firing if validation fails 10 submitError: false, // function called if there is an error when trying to submit 11 submitSuccess: false, // function called just before a successful submit event is sent to the server 12 semanticallyStrict: false, // set to true to tidy up generated HTML output 13 removeSuccess : true, 14 bindEvents: [], 15 autoAdd: { 16 helpBlocks: true 17 }, 18 filter: function () { 19 // return $(this).is(":visible"); // only validate elements you can see 20 return true; // validate everything 21 } 22 }, 23 methods: { 24 init: function (options) { 25 26 // Get a clean copy of the defaults for extending 27 var settings = $.extend(true, {}, defaults); 28 // Set up the options based on the input 29 settings.options = $.extend(true, settings.options, options); 30 31 var $siblingElements = this; 32 33 var uniqueForms = $.unique( 34 $siblingElements.map(function () { 35 return $(this).parents("form")[0]; 36 }).toArray() 37 ); 38 39 $(uniqueForms).bind("submit.validationSubmit", function (e) { 40 var $form = $(this); 41 var warningsFound = 0; 42 // Get all inputs 43 var $allInputs = $form.find("input,textarea,select").not("[type=submit],[type=image]").filter(settings.options.filter); 44 var $allControlGroups = $form.find(".form-group"); 45 46 // Only trigger validation on the ones that actually _have_ validation 47 var $inputsWithValidators = $allInputs.filter(function () { 48 return $(this).triggerHandler("getValidatorCount.validation") > 0; 49 }); 50 $inputsWithValidators.trigger("submit.validation"); 51 52 // But all of them are out-of-focus now, because we're submitting. 53 $allInputs.trigger("validationLostFocus.validation"); 54 55 // Okay, now check each controlgroup for errors (or warnings) 56 $allControlGroups.each(function (i, el) { 57 var $controlGroup = $(el); 58 if ($controlGroup.hasClass("issue") || $controlGroup.hasClass("error")) { 59 $controlGroup.removeClass("issue").addClass("error"); 60 warningsFound++; 61 } 62 }); 63 64 if (warningsFound) { 65 // If we found any warnings, maybe we should prevent the submit 66 // event, and trigger 'submitError' (if they're set up) 67 if (settings.options.preventSubmit) { 68 e.preventDefault(); 69 e.stopImmediatePropagation(); 70 } 71 $form.addClass("error"); 72 if ($.isFunction(settings.options.submitError)) { 73 settings.options.submitError($form, e, $inputsWithValidators.jqBootstrapValidation("collectErrors", true)); 74 } 75 } else { 76 // Woo! No errors! We can pass the submit event to submitSuccess 77 // (if it has been set up) 78 $form.removeClass("error"); 79 if ($.isFunction(settings.options.submitSuccess)) { 80 settings.options.submitSuccess($form, e); 81 } 82 } 83 }); 84 85 return this.each(function () { 86 87 // Get references to everything we're interested in 88 var $this = $(this), 89 $controlGroup = $this.parents(".form-group").first(), 90 $helpBlock = $controlGroup.find(".help-block").first(), 91 $form = $this.parents("form").first(), 92 validatorNames = []; 93 94 // create message container if not exists 95 if (!$helpBlock.length && settings.options.autoAdd && settings.options.autoAdd.helpBlocks) { 96 $helpBlock = $('<div class="help-block" />'); 97 $controlGroup.find('.controls').append($helpBlock); 98 createdElements.push($helpBlock[0]); 99 } 100 101 // ============================================================= 102 // SNIFF HTML FOR VALIDATORS 103 // ============================================================= 104 105 // *snort sniff snuffle* 106 107 if (settings.options.sniffHtml) { 108 var message; 109 // --------------------------------------------------------- 110 // PATTERN 111 // --------------------------------------------------------- 112 if ($this.data("validationPatternPattern")) { 113 $this.attr("pattern", $this.data("validationPatternPattern")); 114 } 115 if ($this.attr("pattern") !== undefined) { 116 message = "Not in the expected format<!-- data-validation-pattern-message to override -->"; 117 if ($this.data("validationPatternMessage")) { 118 message = $this.data("validationPatternMessage"); 119 } 120 $this.data("validationPatternMessage", message); 121 $this.data("validationPatternRegex", $this.attr("pattern")); 122 } 123 // --------------------------------------------------------- 124 // MAX 125 // --------------------------------------------------------- 126 if ($this.attr("max") !== undefined || $this.attr("aria-valuemax") !== undefined) { 127 var max = ($this.attr("max") !== undefined ? $this.attr("max") : $this.attr("aria-valuemax")); 128 message = "Too high: Maximum of '" + max + "'<!-- data-validation-max-message to override -->"; 129 if ($this.data("validationMaxMessage")) { 130 message = $this.data("validationMaxMessage"); 131 } 132 $this.data("validationMaxMessage", message); 133 $this.data("validationMaxMax", max); 134 } 135 // --------------------------------------------------------- 136 // MIN 137 // --------------------------------------------------------- 138 if ($this.attr("min") !== undefined || $this.attr("aria-valuemin") !== undefined) { 139 var min = ($this.attr("min") !== undefined ? $this.attr("min") : $this.attr("aria-valuemin")); 140 message = "Too low: Minimum of '" + min + "'<!-- data-validation-min-message to override -->"; 141 if ($this.data("validationMinMessage")) { 142 message = $this.data("validationMinMessage"); 143 } 144 $this.data("validationMinMessage", message); 145 $this.data("validationMinMin", min); 146 } 147 // --------------------------------------------------------- 148 // MAXLENGTH 149 // --------------------------------------------------------- 150 if ($this.attr("maxlength") !== undefined) { 151 message = "Too long: Maximum of '" + $this.attr("maxlength") + "' characters<!-- data-validation-maxlength-message to override -->"; 152 if ($this.data("validationMaxlengthMessage")) { 153 message = $this.data("validationMaxlengthMessage"); 154 } 155 $this.data("validationMaxlengthMessage", message); 156 $this.data("validationMaxlengthMaxlength", $this.attr("maxlength")); 157 } 158 // --------------------------------------------------------- 159 // MINLENGTH 160 // --------------------------------------------------------- 161 if ($this.attr("minlength") !== undefined) { 162 message = "Too short: Minimum of '" + $this.attr("minlength") + "' characters<!-- data-validation-minlength-message to override -->"; 163 if ($this.data("validationMinlengthMessage")) { 164 message = $this.data("validationMinlengthMessage"); 165 } 166 $this.data("validationMinlengthMessage", message); 167 $this.data("validationMinlengthMinlength", $this.attr("minlength")); 168 } 169 // --------------------------------------------------------- 170 // REQUIRED 171 // --------------------------------------------------------- 172 if ($this.attr("required") !== undefined || $this.attr("aria-required") !== undefined) { 173 message = settings.builtInValidators.required.message; 174 if ($this.data("validationRequiredMessage")) { 175 message = $this.data("validationRequiredMessage"); 176 } 177 $this.data("validationRequiredMessage", message); 178 } 179 // --------------------------------------------------------- 180 // NUMBER 181 // --------------------------------------------------------- 182 if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "number") {
183 message = settings.validatorTypes.number.message; // TODO: fix this 184 if ($this.data("validationNumberMessage")) { 185 message = $this.data("validationNumberMessage"); 186 } 187 $this.data("validationNumberMessage", message); 188 189 var step = settings.validatorTypes.number.step; // TODO: and this 190 if ($this.data("validationNumberStep")) { 191 step = $this.data("validationNumberStep"); 192 } 193 $this.data("validationNumberStep", step); 194 195 var decimal = settings.validatorTypes.number.decimal; 196 if ($this.data("validationNumberDecimal")) { 197 decimal = $this.data("validationNumberDecimal"); 198 } 199 $this.data("validationNumberDecimal", decimal); 200 } 201 // --------------------------------------------------------- 202 // EMAIL 203 // --------------------------------------------------------- 204 if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "email") { 205 message = "Not a valid email address<!-- data-validation-email-message to override -->"; 206 if ($this.data("validationEmailMessage")) { 207 message = $this.data("validationEmailMessage"); 208 } 209 $this.data("validationEmailMessage", message); 210 } 211 // --------------------------------------------------------- 212 // MINCHECKED 213 // --------------------------------------------------------- 214 if ($this.attr("minchecked") !== undefined) { 215 message = "Not enough options checked; Minimum of '" + $this.attr("minchecked") + "' required<!-- data-validation-minchecked-message to override -->"; 216 if ($this.data("validationMincheckedMessage")) { 217 message = $this.data("validationMincheckedMessage"); 218 } 219 $this.data("validationMincheckedMessage", message); 220 $this.data("validationMincheckedMinchecked", $this.attr("minchecked")); 221 } 222 // --------------------------------------------------------- 223 // MAXCHECKED 224 // --------------------------------------------------------- 225 if ($this.attr("maxchecked") !== undefined) { 226 message = "Too many options checked; Maximum of '" + $this.attr("maxchecked") + "' required<!-- data-validation-maxchecked-message to override -->";
227 if ($this.data("validationMaxcheckedMessage")) { 228 message = $this.data("validationMaxcheckedMessage"); 229 } 230 $this.data("validationMaxcheckedMessage", message); 231 $this.data("validationMaxcheckedMaxchecked", $this.attr("maxchecked")); 232 } 233 } 234 235 // ============================================================= 236 // COLLECT VALIDATOR NAMES 237 // ============================================================= 238 239 // Get named validators 240 if ($this.data("validation") !== undefined) { 241 validatorNames = $this.data("validation").split(","); 242 } 243 244 // Get extra ones defined on the element's data attributes 245 $.each($this.data(), function (i, el) { 246 var parts = i.replace(/([A-Z])/g, ",$1").split(","); 247 if (parts[0] === "validation" && parts[1]) { 248 validatorNames.push(parts[1]); 249 } 250 }); 251 252 // ============================================================= 253 // NORMALISE VALIDATOR NAMES 254 // ============================================================= 255 256 var validatorNamesToInspect = validatorNames; 257 var newValidatorNamesToInspect = []; 258 259 var uppercaseEachValidatorName = function (i, el) { 260 validatorNames[i] = formatValidatorName(el); 261 }; 262 263 var inspectValidators = function (i, el) { 264 if ($this.data("validation" + el + "Shortcut") !== undefined) { 265 // Are these custom validators? 266 // Pull them out! 267 $.each($this.data("validation" + el + "Shortcut").split(","), function (i2, el2) { 268 newValidatorNamesToInspect.push(el2); 269 }); 270 } else if (settings.builtInValidators[el.toLowerCase()]) { 271 // Is this a recognised built-in? 272 // Pull it out! 273 var validator = settings.builtInValidators[el.toLowerCase()]; 274 if (validator.type.toLowerCase() === "shortcut") { 275 $.each(validator.shortcut.split(","), function (i, el) { 276 el = formatValidatorName(el); 277 newValidatorNamesToInspect.push(el); 278 validatorNames.push(el); 279 }); 280 } 281 } 282 }; 283 284 do // repeatedly expand 'shortcut' validators into their real validators 285 { 286 // Uppercase only the first letter of each name 287 $.each(validatorNames, uppercaseEachValidatorName); 288 289 // Remove duplicate validator names 290 validatorNames = $.unique(validatorNames); 291 292 // Pull out the new validator names from each shortcut 293 newValidatorNamesToInspect = []; 294 $.each(validatorNamesToInspect, inspectValidators); 295 296 validatorNamesToInspect = newValidatorNamesToInspect; 297 298 } while (validatorNamesToInspect.length > 0); 299 300 // ============================================================= 301 // SET UP VALIDATOR ARRAYS 302 // ============================================================= 303 304 /* We're gonna generate something like 305 * 306 * { 307 * "regex": [ 308 * { -- a validator object here --}, 309 * { -- a validator object here --} 310 * ], 311 * "required": [ 312 * { -- a validator object here --}, 313 * { -- a validator object here --} 314 * ] 315 * } 316 * 317 * with a few more entries. 318 * 319 * Because we only add a few validators to each field, most of the
320 * keys will be empty arrays with no validator objects in them, and 321 * thats fine. 322 */ 323 324 var validators = {}; 325 326 $.each(validatorNames, function (i, el) { 327 // Set up the 'override' message 328 var message = $this.data("validation" + el + "Message"); 329 var hasOverrideMessage = !!message; 330 var foundValidator = false; 331 if (!message) { 332 message = "'" + el + "' validation failed <!-- Add attribute 'data-validation-" + el.toLowerCase() + "-message' to input to change this message -->"; 333 } 334 335 $.each( 336 settings.validatorTypes, 337 function (validatorType, validatorTemplate) { 338 if (validators[validatorType] === undefined) { 339 validators[validatorType] = []; 340 } 341 if (!foundValidator && $this.data("validation" + el + formatValidatorName(validatorTemplate.name)) !== undefined) { 342 var initted = validatorTemplate.init($this, el); 343 if (hasOverrideMessage) { 344 initted.message = message; 345 } 346 347 validators[validatorType].push( 348 $.extend( 349 true, 350 { 351 name: formatValidatorName(validatorTemplate.name), 352 message: message 353 }, 354 initted 355 ) 356 ); 357 foundValidator = true; 358 } 359 } 360 ); 361 362 if (!foundValidator && settings.builtInValidators[el.toLowerCase()]) { 363 364 var validator = $.extend(true, {}, settings.builtInValidators[el.toLowerCase()]); 365 if (hasOverrideMessage) { 366 validator.message = message; 367 } 368 var validatorType = validator.type.toLowerCase(); 369 370 if (validatorType === "shortcut") { 371 foundValidator = true; 372 } else { 373 $.each( 374 settings.validatorTypes, 375 function (validatorTemplateType, validatorTemplate) { 376 if (validators[validatorTemplateType] === undefined) { 377 validators[validatorTemplateType] = []; 378 } 379 if (!foundValidator && validatorType === validatorTemplateType.toLowerCase()) { 380 $this.data( 381 "validation" + el + formatValidatorName(validatorTemplate.name), 382 validator[validatorTemplate.name.toLowerCase()] 383 ); 384 validators[validatorType].push( 385 $.extend( 386 validator, 387 validatorTemplate.init($this, el) 388 ) 389 ); 390 foundValidator = true; 391 } 392 } 393 ); 394 } 395 } 396 397 if (!foundValidator) { 398 $.error("Cannot find validation info for '" + el + "'"); 399 } 400 }); 401 402 // ============================================================= 403 // STORE FALLBACK VALUES 404 // ============================================================= 405 406 $helpBlock.data( 407 "original-contents", 408 ( 409 $helpBlock.data("original-contents") ? 410 $helpBlock.data("original-contents") : 411 $helpBlock.html() 412 ) 413 ); 414 415 $helpBlock.data( 416 "original-role", 417 ( 418 $helpBlock.data("original-role") ? 419 $helpBlock.data("original-role") : 420 $helpBlock.attr("role") 421 ) 422 ); 423 424 $controlGroup.data( 425 "original-classes", 426 ( 427 $controlGroup.data("original-clases") ? 428 $controlGroup.data("original-classes") : 429 $controlGroup.attr("class") 430 ) 431 ); 432 433 $this.data( 434 "original-aria-invalid", 435 ( 436 $this.data("original-aria-invalid") ? 437 $this.data("original-aria-invalid") : 438 $this.attr("aria-invalid") 439 ) 440 ); 441 442 // ============================================================= 443 // VALIDATION 444 // ============================================================= 445 446 $this.bind( 447 "validation.validation", 448 function (event, params) { 449 450 var value = getValue($this); 451 452 // Get a list of the errors to apply 453 var errorsFound = []; 454 455 $.each(validators, function (validatorType, validatorTypeArray) { 456 if ( 457 value || // has a truthy value 458 value.length || // not an empty string 459 ( // am including empty values 460 ( 461 params && 462 params.includeEmpty 463 ) || !!settings.val
463idatorTypes[validatorType].includeEmpty 464 ) || 465 ( // validator is blocking submit 466 !!settings.validatorTypes[validatorType].blockSubmit && 467 params && !!params.submitting 468 ) 469 ) { 470 $.each( 471 validatorTypeArray, 472 function (i, validator) { 473 if (settings.validatorTypes[validatorType].validate($this, value, validator)) { 474 errorsFound.push(validator.message); 475 } 476 } 477 ); 478 } 479 }); 480 481 return errorsFound; 482 } 483 ); 484 485 $this.bind( 486 "getValidators.validation", 487 function () { 488 return validators; 489 } 490 ); 491 492 var numValidators = 0; 493 494 $.each(validators, function (i, el) { 495 numValidators += el.length; 496 }); 497 498 $this.bind("getValidatorCount.validation", function () { 499 return numValidators; 500 }); 501 502 // ============================================================= 503 // WATCH FOR CHANGES 504 // ============================================================= 505 $this.bind( 506 "submit.validation", 507 function () { 508 return $this.triggerHandler("change.validation", {submitting: true}); 509 } 510 ); 511 $this.bind( 512 ( 513 settings.options.bindEvents.length > 0 ? 514 settings.options.bindEvents : 515 [ 516 "keyup", 517 "focus", 518 "blur", 519 "click", 520 "keydown", 521 "keypress", 522 "change" 523 ] 524 ).concat(["revalidate"]).join(".validation ") + ".validation", 525 function (e, params) { 526 527 var value = getValue($this); 528 529 var errorsFound = []; 530 531 if (params && !!params.submitting) { 532 $controlGroup.data("jqbvIsSubmitting", true); 533 } else if (e.type !== "revalidate") { 534 $controlGroup.data("jqbvIsSubmitting", false); 535 } 536 537 var formIsSubmitting = !!$controlGroup.data("jqbvIsSubmitting"); 538 539 $controlGroup.find("input,textarea,select").not('[type=submit]').each(function (i, el) { 540 var oldCount = errorsFound.length; 541 $.each($(el).triggerHandler("validation.validation", params) || [], function (j, message) { 542 errorsFound.push(message); 543 }); 544 if (errorsFound.length > oldCount) { 545 $(el).attr("aria-invalid", "true"); 546 } else { 547 var original = $this.data("original-aria-invalid"); 548 $(el).attr("aria-invalid", (original !== undefined ? original : false)); 549 } 550 }); 551 552 $form.find("input,select,textarea").not($this).not("[name=\"" + $this.attr("name") + "\"]").trigger("validationLostFocus.validation"); 553 554 errorsFound = $.unique(errorsFound.sort()); 555 556 // Were there any errors? 557 if (errorsFound.length) { 558 // Better flag it up as a warning.
559 $controlGroup.removeClass("validate error issue").addClass(formIsSubmitting ? "error" : "issue"); 560 561 // How many errors did we find? 562 if (settings.options.semanticallyStrict && errorsFound.length === 1) { 563 // Only one? Being strict? Just output it. 564 $helpBlock.html(errorsFound[0] + 565 ( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" )); 566 } else { 567 // Multiple? Being sloppy? Glue them together into an UL. 568 $helpBlock.html("<ul role=\"alert\"><li>" + errorsFound.join("</li><li>") + "</li></ul>" + 569 ( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" )); 570 } 571 } else { 572 $controlGroup.removeClass("issue error validate"); 573 if (value.length > 0) { 574 $controlGroup.addClass("validate"); 575 } 576 $helpBlock.html($helpBlock.data("original-contents")); 577 } 578 579 if (e.type === "blur") { 580 if( settings.options.removeSuccess ){ 581 // $controlGroup.removeClass("validate"); 582 } 583 } 584 } 585 ); 586 $this.bind("validationLostFocus.validation", function () { 587 if( settings.options.removeSuccess ){ 588 // $controlGroup.removeClass("validate"); 589 } 590 }); 591 }); 592 }, 593 destroy: function () { 594 595 return this.each( 596 function () { 597 598 var 599 $this = $(this), 600 $controlGroup = $this.parents(".form-group").first(), 601 $helpBlock = $controlGroup.find(".help-block").first(), 602 $form = $this.parents("form").first(); 603 604 // remove our events 605 $this.unbind('.validation'); // events are namespaced. 606 $form.unbind(".validationSubmit"); 607 // reset help text 608 $helpBlock.html($helpBlock.data("original-contents")); 609 // reset classes 610 $controlGroup.attr("class", $controlGroup.data("original-classes")); 611 // reset aria 612 $this.attr("aria-invalid", $this.data("original-aria-invalid")); 613 // reset role 614 $helpBlock.attr("role", $this.data("original-role")); 615 // remove all elements we created 616 if ($.inArray($helpBlock[0], createdElements) > -1) { 617 $helpBlock.remove(); 618 } 619 620 } 621 ); 622 623 }, 624 collectErrors: function (includeEmpty) { 625 626 var errorMessages = {}; 627 this.each(function (i, el) { 628 var $el = $(el); 629 var name = $el.attr("name"); 630 var errors = $el.triggerHandler("validation.validation", {includeEmpty: true}); 631 errorMessages[name] = $.extend(true, errors, errorMessages[name]); 632 }); 633 634 $.each(errorMessages, function (i, el) { 635 if (el.length === 0) { 636 delete errorMessages[i]; 637 } 638 }); 639 640 return errorMessages; 641 642 }, 643 hasErrors: function () { 644 645 var errorMessages = []; 646 647 this.find('input,select,textarea').add(this).each(function (i, el) { 648 errorMessages = errorMessages.concat( 649 $(el).triggerHandler("getValidators.validation") ? $(el).triggerHandler("validation.validation", {submitting: true}) : [] 650 ); 651 }); 652 653 return (errorMessages.length > 0); 654 }, 655 override: function (newDefaults) { 656 defaults = $.extend(true, defaults, newDefaults); 657 } 658 }, 659 validatorTypes: { 660 callback: { 661 name: "callback", 662 init: function ($this, name) { 663 var result = { 664 validatorName: name, 665 callback: $this.data("validation" + name + "Callback"),
666 lastValue: $this.val(), 667 lastValid: true, 668 lastFinished: true 669 }; 670 671 var message = "Not valid"; 672 if ($this.data("validation" + name + "Message")) { 673 message = $this.data("validation" + name + "Message"); 674 } 675 result.message = message; 676 677 return result; 678 }, 679 validate: function ($this, value, validator) { 680 if (validator.lastValue === value && validator.lastFinished) { 681 return !validator.lastValid; 682 } 683 684 if (validator.lastFinished === true) { 685 validator.lastValue = value; 686 validator.lastValid = true; 687 validator.lastFinished = false; 688 689 var rrjqbvValidator = validator; 690 var rrjqbvThis = $this; 691 executeFunctionByName( 692 validator.callback, 693 window, 694 $this, 695 value, 696 function (data) { 697 if (rrjqbvValidator.lastValue === data.value) { 698 rrjqbvValidator.lastValid = data.valid; 699 if (data.message) { 700 rrjqbvValidator.message = data.message; 701 } 702 rrjqbvValidator.lastFinished = true; 703 rrjqbvThis.data( 704 "validation" + rrjqbvValidator.validatorName + "Message", 705 rrjqbvValidator.message 706 ); 707 708 // Timeout is set to avoid problems with the events being considered 'already fired' 709 setTimeout(function () { 710 if (!$this.is(":focus") && $this.parents("form").first().data("jqbvIsSubmitting")) { 711 rrjqbvThis.trigger("blur.validation"); 712 } else { 713 rrjqbvThis.trigger("revalidate.validation"); 714 } 715 }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst 716 } 717 } 718 ); 719 } 720 721 return false; 722 723 } 724 }, 725 ajax: { 726 name: "ajax", 727 init: function ($this, name) { 728 return { 729 validatorName: name, 730 url: $this.data("validation" + name + "Ajax"), 731 lastValue: $this.val(), 732 lastValid: true, 733 lastFinished: true 734 }; 735 }, 736 validate: function ($this, value, validator) { 737 if ("" + validator.lastValue === "" + value && validator.lastFinished === true) { 738 return validator.lastValid === false; 739 } 740 741 if (validator.lastFinished === true) { 742 validator.lastValue = value; 743 validator.lastValid = true; 744 validator.lastFinished = false; 745 $.ajax({ 746 url: validator.url, 747 data: "value=" + encodeURIComponent(value) + "&field=" + $this.attr("name"), 748 dataType: "json", 749 success : function (data) { 750 if ("" + validator.lastValue === "" + data.value) { 751 validator.lastValid = !!(data.valid); 752 if (data.message) { 753 validator.message = data.message; 754 } 755 validator.lastFinished = true; 756 $this.data("validation" + validator.validatorName + "Message", validator.message); 757 // Timeout is set to avoid problems with the events being considered 'already fired' 758 setTimeout(function () { 759 $this.trigger("revalidate.validation"); 760 }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst 761 } 762 }, 763 failure: function () { 764 validator.lastValid = true; 765 validator.message = "ajax call failed"; 766 validator.lastFinished = true; 767 $this.data("validation" + validator.validatorName + "Message", validator.message); 768 // Timeout is set to avoid problems with the events being considered 'already fired' 769 setTimeout(function () { 770 $this.trigger("revalidate.validation"); 771 }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst 772 } 773 }); 774 } 775 776 return false;
777 778 } 779 }, 780 regex: { 781 name: "regex", 782 init: function ($this, name) { 783 var result = {}; 784 var regexString = $this.data("validation" + name + "Regex"); 785 result.regex = regexFromString(regexString); 786 if (regexString === undefined) { 787 $.error("Can't find regex for '" + name + "' validator on '" + $this.attr("name") + "'"); 788 } 789 790 var message = "Not in the expected format"; 791 if ($this.data("validation" + name + "Message")) { 792 message = $this.data("validation" + name + "Message"); 793 } 794 795 result.message = message; 796 797 result.originalName = name; 798 return result; 799 }, 800 validate: function ($this, value, validator) { 801 return (!validator.regex.test(value) && !validator.negative) || 802 (validator.regex.test(value) && validator.negative); 803 } 804 }, 805 email: { 806 name: "email", 807 init: function ($this, name) { 808 var result = {}; 809 result.regex = regexFromString('[a-zA-Z0-9.!#$%&\u2019*+/=?^_`{|}~-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}'); 810 811 var message = "Not a valid email address"; 812 if ($this.data("validation" + name + "Message")) { 813 message = $this.data("validation" + name + "Message"); 814 } 815 816 result.message = message; 817 818 result.originalName = name; 819 return result; 820 }, 821 validate: function ($this, value, validator) { 822 return (!validator.regex.test(value) && !validator.negative) || 823 (validator.regex.test(value) && validator.negative); 824 } 825 }, 826 required: { 827 name: "required", 828 init: function ($this, name) { 829 var message = "This is required"; 830 if ($this.data("validation" + name + "Message")) { 831 message = $this.data("validation" + name + "Message"); 832 } 833 834 return {message: message, includeEmpty: true}; 835 }, 836 validate: function ($this, value, validator) { 837 return !!( 838 (value.length === 0 && !validator.negative) || 839 (value.length > 0 && validator.negative) 840 ); 841 }, 842 blockSubmit: true 843 }, 844 match: { 845 name: "match", 846 init: function ($this, name) { 847 var elementName = $this.data("validation" + name + "Match"); 848 var $form = $this.parents("form").first(); 849 var $element = $form.find("[name=\"" + elementName + "\"]").first(); 850 $element.bind("validation.validation", function () { 851 $this.trigger("revalidate.validation", {submitting: true}); 852 }); 853 var result = {}; 854 result.element = $element; 855 856 if ($element.length === 0) { 857 $.error("Can't find field '" + elementName + "' to match '" + $this.attr("name") + "' against in '" + name + "' validator"); 858 } 859 860 var message = "Must match"; 861 var $label = null; 862 if (($label = $form.find("label[for=\"" + elementName + "\"]")).length) { 863 message += " '" + $label.text() + "'"; 864 } else if (($label = $element.parents(".form-group").first().find("label")).length) { 865 message += " '" + $label.first().text() + "'"; 866 } 867 868 if ($this.data("validation" + name + "Message")) { 869 message = $this.data("validation" + name + "Message"); 870 } 871 872 result.message = message; 873 874 return result; 875 }, 876 validate: function ($this, value, validator) { 877 return (value !== validator.element.val() && !validator.negative) || 878 (value === validator.element.val() && validator.negative); 879 }, 880 blockSubmit: true, 881 includeEmpty: true 882 }, 883 max: { 884 name: "max", 885 init: function ($this, name) { 886 var result = {}; 887 888 result.max = $this.data("validation" + name + "Max"); 889 890 result.message = "Too high: Maximum of '" + result.max + "'"; 891 if ($this.data("validation" + name + "Message")) { 892 result.message = $this.data("validation" + name + "Message"); 893 } 894 895 return result; 896 }, 897 validate: function ($this, value, validator) { 898 return (parseFloat(value, 10) > parseFloat(validator.max, 10) && !validator.negative) || 899 (parseFloat(value, 10) <= parseFloat(validator.max, 10) && validator.negative); 900 } 901 }, 902 min: { 903 name: "min", 904 init: function ($this, name) { 905 var result = {}; 906 907 result.min = $this.data("validation" + name + "Min"); 908 909 result.message = "Too low: Minimum of '" + result.min + "'"; 910 if ($this.data("validation" + name + "Message")) { 911 result.message = $this.data("validation" + name + "Message"); 912 } 913 914 return result; 915 }, 916 validate: function ($this, value, validator) { 917 return (parseFloat(value) < parseFloat(validator.min) && !validator.negative) || 918 (parseFloat(value) >= parseFloat(validator.min) && validator.negative); 919 } 920 }, 921 maxlength: { 922 name: "maxlength", 923 init: function ($this, name) { 924 var result = {}; 925 926 result.maxlength = $this.data("validation" + name + "Maxlength"); 927 928 result.message = "Too long: Maximum of '" + result.maxlength + "' characters"; 929 if ($this.data("validation" + name + "Message")) { 930 result.message = $this.data("validation" + name + "Message"); 931 } 932 933 return result; 934 }, 935 validate: function ($this, value, validator) { 936 return ((value.length > validator.maxlength) && !validator.negative) || 937 ((value.length <= validator.maxlength) && validator.negative); 938 } 939 }, 940 minlength: { 941 name: "minlength", 942 init: function ($this, name) { 943 var result = {}; 944 945 result.minlength = $this.data("validation" + name + "Minlength"); 946 947 result.message = "Too short: Minimum of '" + result.minlength + "' characters"; 948 if ($this.data("validation" + name + "Message")) { 949 result.message = $this.data("validation" + name + "Message"); 950 } 951 952 return result; 953 }, 954 validate: function ($this, value, validator) { 955 return ((value.length < validator.minlength) && !validator.negative) || 956 ((value.length >= validator.minlength) && validator.negative); 957 } 958 }, 959 maxchecked: { 960 name: "maxchecked", 961 init: function ($this, name) { 962 var result = {}; 963 964 var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]"); 965 elements.bind("change.validation click.validation", function () { 966 $this.trigger("revalidate.validation", {includeEmpty: true}); 967 }); 968 969 result.elements = elements; 970 result.maxchecked = $this.data("validation" + name + "Maxchecked"); 971
972 var message = "Too many: Max '" + result.maxchecked + "' checked"; 973 if ($this.data("validation" + name + "Message")) { 974 message = $this.data("validation" + name + "Message"); 975 } 976 result.message = message; 977 978 return result; 979 }, 980 validate: function ($this, value, validator) { 981 return (validator.elements.filter(":checked").length > validator.maxchecked && !validator.negative) || 982 (validator.elements.filter(":checked").length <= validator.maxchecked && validator.negative); 983 }, 984 blockSubmit: true 985 }, 986 minchecked: { 987 name: "minchecked", 988 init: function ($this, name) { 989 var result = {}; 990 991 var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]"); 992 elements.bind("change.validation click.validation", function () { 993 $this.trigger("revalidate.validation", {includeEmpty: true}); 994 }); 995 996 result.elements = elements; 997 result.minchecked = $this.data("validation" + name + "Minchecked"); 998 999 var message = "Too few: Min '" + result.minchecked + "' checked"; 1000 if ($this.data("validation" + name + "Message")) { 1001 message = $this.data("validation" + name + "Message"); 1002 } 1003 result.message = message; 1004 1005 return result; 1006 }, 1007 validate: function ($this, value, validator) { 1008 return (validator.elements.filter(":checked").length < validator.minchecked && !validator.negative) || 1009 (validator.elements.filter(":checked").length >= validator.minchecked && validator.negative); 1010 }, 1011 blockSubmit: true, 1012 includeEmpty: true 1013 }, 1014 number: { 1015 name: "number", 1016 init: function ($this, name) { 1017 var result = {}; 1018 result.step = 1; 1019 if ($this.attr("step")) { 1020 result.step = $this.attr("step"); 1021 } 1022 if ($this.data("validation" + name + "Step")) { 1023 result.step = $this.data("validation" + name + "Step"); 1024 } 1025 1026 result.decimal = "."; 1027 if ($this.data("validation" + name + "Decimal")) { 1028 result.decimal = $this.data("validation" + name + "Decimal"); 1029 } 1030 1031 result.thousands = ""; 1032 if ($this.data("validation" + name + "Thousands")) { 1033 result.thousands = $this.data("validation" + name + "Thousands"); 1034 } 1035 1036 result.regex = regexFromString("([+-]?\\d+(\\" + result.decimal + "\\d+)?)?"); 1037 1038 result.message = "Must be a number"; 1039 var dataMessage = $this.data("validation" + name + "Message"); 1040 if (dataMessage) { 1041 result.message = dataMessage; 1042 } 1043 1044 return result; 1045 }, 1046 validate: function ($this, value, validator) { 1047 var globalValue = value.replace(validator.decimal, ".").replace(validator.thousands, ""); 1048 var multipliedValue = parseFloat(globalValue); 1049 var multipliedStep = parseFloat(validator.step); 1050 while (multipliedStep % 1 !== 0) { 1051 /* thanks to @jkey #57 */ 1052 multipliedStep = parseFloat(multipliedStep.toPrecision(12)) * 10; 1053 multipliedValue = parseFloat(multipliedValue.toPrecision(12)) * 10; 1054 } 1055 var regexResult = validator.regex.test(value); 1056 var stepResult = parseFloat(multipliedValue) % parseFloat(multipliedStep) === 0; 1057 var typeResult = !isNaN(parseFloat(globalValue)) && isFinite(globalValue); 1058 var result = !(regexResult && stepResult && typeResult); 1059 return result; 1060 }, 1061 message: "Must be a number" 1062 } 1063 }, 1064 builtInValidators: { 1065 email: { 1066 name: "Email", 1067 type: "email" 1068 }, 1069 passwordagain: { 1070 name: "Passwordagain", 1071 type: "match", 1072 match: "password", 1073 message: "Does not match the given password<!-- data-validator-paswordagain-message to override -->" 1074 }, 1075 positive: { 1076 name: "Positive", 1077 type: "shortcut", 1078 shortcut: "number,positivenumber" 1079 }, 1080 negative: { 1081 name: "Negative", 1082 type: "shortcut", 1083 shortcut: "number,negativenumber" 1084 }, 1085 integer: { 1086 name: "Integer", 1087 type: "regex", 1088 regex: "[+-]?\\d+", 1089 message: "No decimal places allowed<!-- data-validator-integer-message to override -->" 1090 }, 1091 positivenumber: { 1092 name: "Positivenumber", 1093 type: "min", 1094 min: 0, 1095 message: "Must be a positive number<!-- data-validator-positivenumber-message to override -->" 1096 }, 1097 negativenumber: { 1098 name: "Negativenumber", 1099 type: "max", 1100 max: 0, 1101 message: "Must be a negative number<!-- data-validator-negativenumber-message to override -->" 1102 }, 1103 required: { 1104 name: "Required", 1105 type: "required", 1106 message: "This is required<!-- data-validator-required-message to override -->" 1107 }, 1108 checkone: { 1109 name: "Checkone", 1110 type: "minchecked", 1111 minchecked: 1, 1112 message: "Check at least one option<!-- data-validation-checkone-message to override -->" 1113 }, 1114 number: { 1115 name: "Number", 1116 type: "number",
1117 decimal: ".", 1118 step: "1" 1119 }, 1120 pattern: { 1121 name: "Pattern", 1122 type: "regex", 1123 message: "Not in expected format" 1124 } 1125 } 1126 }; 1127 1128 var formatValidatorName = function (name) { 1129 return name 1130 .toLowerCase() 1131 .replace( 1132 /(^|\s)([a-z])/g, 1133 function (m, p1, p2) { 1134 return p1 + p2.toUpperCase(); 1135 } 1136 ) 1137 ; 1138 }; 1139 1140 var getValue = function ($this) { 1141 // Extract the value we're talking about 1142 var value = null; 1143 var type = $this.attr("type"); 1144 if (type === "checkbox") { 1145 value = ($this.is(":checked") ? value : ""); 1146 var checkboxParent = $this.parents("form").first() || $this.parents(".form-group").first(); 1147 if (checkboxParent) { 1148 value = checkboxParent.find("input[name='" + $this.attr("name") + "']:checked").map(function (i, el) { 1149 return $(el).val(); 1150 }).toArray().join(","); 1151 } 1152 } 1153 else if (type === "radio") { 1154 value = ($('input[name="' + $this.attr("name") + '"]:checked').length > 0 ? $this.val() : ""); 1155 var radioParent = $this.parents("form").first() || $this.parents(".form-group").first(); 1156 if (radioParent) { 1157 value = radioParent.find("input[name='" + $this.attr("name") + "']:checked").map(function (i, el) { 1158 return $(el).val(); 1159 }).toArray().join(","); 1160 } 1161 } else if (type === "number") { 1162 if ($this[0].validity.valid) { 1163 value = $this.val(); 1164 } else { 1165 if ($this[0].validity.badInput || $this[0].validity.stepMismatch) { 1166 value = "NaN"; 1167 } else { 1168 value = ""; 1169 } 1170 } 1171 } else { 1172 value = $this.val(); 1173 } 1174 return value; 1175 }; 1176 1177 function regexFromString(inputstring) {
1178 return new RegExp("^" + inputstring + "$"); 1179 } 1180 1181 /** 1182 * Thanks to Jason Bunting / Alex Nazarov via StackOverflow.com 1183 * 1184 * http://stackoverflow.com/a/4351575 1185 **/ 1186 function executeFunctionByName(functionName, context /*, args */) { 1187 var args = Array.prototype.slice.call(arguments, 2); 1188 var namespaces = functionName.split("."); 1189 var func = namespaces.pop(); 1190 for (var i = 0; i < namespaces.length; i++) { 1191 context = context[namespaces[i]]; 1192 } 1193 return context[func].apply(context, args); 1194 } 1195 1196 $.fn.jqBootstrapValidation = function (method) { 1197 1198 if (defaults.methods[method]) { 1199 return defaults.methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 1200 } else if (typeof method === 'object' || !method) { 1201 return defaults.methods.init.apply(this, arguments); 1202 } else { 1203 $.error('Method ' + method + ' does not exist on jQuery.jqBootstrapValidation'); 1204 return null; 1205 } 1206 1207 }; 1208 1209 $.jqBootstrapValidation = function (options) { 1210 $(":input").not("[type=image],[type=submit]").jqBootstrapValidation.apply(this, arguments); 1211 }; 1212 1213})(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.