/*! jqBootstrapValidation - v1.3.7 - 2013-05-07 * http://reactiveraven.github.com/jqBootstrapValidation * Copyright (c) 2013 David Godfrey; Licensed MIT */ (function ($) { var createdElements = [], defaults = { options: { prependExistingHelpBlock: false, sniffHtml: true, // sniff for 'required', 'maxlength', etc preventSubmit: true, // stop the form submit event from firing if validation fails submitError: true, // function called if there is an error when trying to submit submitSuccess: false, // function called just before a successful submit event is sent to the server scrollToError: false, // automatically scroll to first error on page after invalid submit attempt semanticallyStrict: false, // set to true to tidy up generated HTML output bindEvents: [], autoAdd: { helpBlocks: true }, filter: function () { // return $(this).is(":visible"); // only validate elements you can see return true; // validate everything } }, methods: { init: function (options) { // Get a clean copy of the defaults for extending var settings = $.extend(true, {}, defaults), $siblingElements = this, uniqueForms = $.unique( $siblingElements.map(function () { return $(this).parents("form")[0]; }).toArray() ); // Set up the options based on the input settings.options = $.extend(true, settings.options, options); $(uniqueForms).bind("submit.validationSubmit", function (e) { var $form = $(this), warningsFound = 0, $allInputs = $form.find("input,textarea,select").not("[type=submit],[type=image]").filter(settings.options.filter), $allControlGroups = $form.find(".form-group"), // Only trigger validation on the ones that actually _have_ validation $inputsWithValidators = $allInputs.filter(function () { return $(this).triggerHandler("getValidatorCount.validation") > 0; }); $inputsWithValidators.trigger("submit.validation"); // But all of them are out-of-focus now, because we're submitting. $allInputs.trigger("validationLostFocus.validation"); // Okay, now check each controlgroup for errors (or warnings) $allControlGroups.each(function (i, el) { var $controlGroup = $(el); if ($controlGroup.hasClass("has-warning") || $controlGroup.hasClass("has-error")) { $controlGroup.removeClass("has-warning").addClass("has-error"); warningsFound++; } }); if (warningsFound) { // If we found any warnings, maybe we should prevent the submit // event, and trigger 'submitError' (if they're set up) if (settings.options.preventSubmit) { e.preventDefault(); e.stopImmediatePropagation(); } // $form.addClass("has-error"); if ($.isFunction(settings.options.submitError)) { settings.options.submitError($form, e, $inputsWithValidators.jqBootstrapValidation("collectErrors", true)); } // scroll to first invalid control if (settings.options.scrollToError) { scrollToFirstError($form); } } else { // Woo! No errors! We can pass the submit event to submitSuccess // (if it has been set up) $form.removeClass("has-error"); if ($.isFunction(settings.options.submitSuccess)) { settings.options.submitSuccess($form, e); } } }); return this.each(function () { // Get references to everything we're interested in var $this = $(this), $controlGroup = $this.parents(".form-group").first(), $helpBlock = $controlGroup.find(".help-block").first(), $form = $this.parents("form").first(), validatorNames = []; // create message container if not exists if (!$helpBlock.length && settings.options.autoAdd && settings.options.autoAdd.helpBlocks) { $helpBlock = $('
'); $controlGroup.find('.controls').append($helpBlock); createdElements.push($helpBlock[0]); } // ============================================================= // SNIFF HTML FOR VALIDATORS // ============================================================= // *snort sniff snuffle* if (settings.options.sniffHtml) { var message; // --------------------------------------------------------- // PATTERN // --------------------------------------------------------- if ($this.data("validationPatternPattern")) { $this.attr("pattern", $this.data("validationPatternPattern")); } if ($this.attr("pattern") !== undefined) { message = "Not in the expected format"; if ($this.data("validationPatternMessage")) { message = $this.data("validationPatternMessage"); } $this.data("validationPatternMessage", message); $this.data("validationPatternRegex", $this.attr("pattern")); } // --------------------------------------------------------- // MAX // --------------------------------------------------------- if ($this.attr("max") !== undefined || $this.attr("aria-valuemax") !== undefined) { var max = ($this.attr("max") !== undefined ? $this.attr("max") : $this.attr("aria-valuemax")); message = "Too high: Maximum of '" + max + "'"; if ($this.data("validationMaxMessage")) { message = $this.data("validationMaxMessage"); } $this.data("validationMaxMessage", message); $this.data("validationMaxMax", max); } // --------------------------------------------------------- // MIN // --------------------------------------------------------- if ($this.attr("min") !== undefined || $this.attr("aria-valuemin") !== undefined) { var min = ($this.attr("min") !== undefined ? $this.attr("min") : $this.attr("aria-valuemin")); message = "Too low: Minimum of '" + min + "'"; if ($this.data("validationMinMessage")) { message = $this.data("validationMinMessage"); } $this.data("validationMinMessage", message); $this.data("validationMinMin", min); } // --------------------------------------------------------- // MAXLENGTH // --------------------------------------------------------- if ($this.attr("maxlength") !== undefined) { message = "Too long: Maximum of '" + $this.attr("maxlength") + "' characters"; if ($this.data("validationMaxlengthMessage")) { message = $this.data("validationMaxlengthMessage"); } $this.data("validationMaxlengthMessage", message); $this.data("validationMaxlengthMaxlength", $this.attr("maxlength")); } // --------------------------------------------------------- // MINLENGTH // --------------------------------------------------------- if ($this.attr("minlength") !== undefined) { message = "Too short: Minimum of '" + $this.attr("minlength") + "' characters"; if ($this.data("validationMinlengthMessage")) { message = $this.data("validationMinlengthMessage"); } $this.data("validationMinlengthMessage", message); $this.data("validationMinlengthMinlength", $this.attr("minlength")); } // --------------------------------------------------------- // REQUIRED // --------------------------------------------------------- if ($this.attr("required") !== undefined || $this.attr("aria-required") !== undefined) { message = settings.builtInValidators.required.message; if ($this.data("validationRequiredMessage")) { message = $this.data("validationRequiredMessage"); } $this.data("validationRequiredMessage", message); } // --------------------------------------------------------- // NUMBER // --------------------------------------------------------- if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "number") { message = settings.validatorTypes.number.message; // TODO: fix this if ($this.data("validationNumberMessage")) { message = $this.data("validationNumberMessage"); } $this.data("validationNumberMessage", message); var step = settings.validatorTypes.number.step; // TODO: and this if ($this.data("validationNumberStep")) { step = $this.data("validationNumberStep"); } $this.data("validationNumberStep", step); var decimal = settings.validatorTypes.number.decimal; if ($this.data("validationNumberDecimal")) { decimal = $this.data("validationNumberDecimal"); } $this.data("validationNumberDecimal", decimal); } // --------------------------------------------------------- // EMAIL // --------------------------------------------------------- if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "email") { message = "Not a valid email address"; if ($this.data("validationEmailMessage")) { message = $this.data("validationEmailMessage"); } $this.data("validationEmailMessage", message); } // --------------------------------------------------------- // MINCHECKED // --------------------------------------------------------- if ($this.attr("minchecked") !== undefined) { message = "Not enough options checked; Minimum of '" + $this.attr("minchecked") + "' required"; if ($this.data("validationMincheckedMessage")) { message = $this.data("validationMincheckedMessage"); } $this.data("validationMincheckedMessage", message); $this.data("validationMincheckedMinchecked", $this.attr("minchecked")); } // --------------------------------------------------------- // MAXCHECKED // --------------------------------------------------------- if ($this.attr("maxchecked") !== undefined) { message = "Too many options checked; Maximum of '" + $this.attr("maxchecked") + "' required"; if ($this.data("validationMaxcheckedMessage")) { message = $this.data("validationMaxcheckedMessage"); } $this.data("validationMaxcheckedMessage", message); $this.data("validationMaxcheckedMaxchecked", $this.attr("maxchecked")); } } // ============================================================= // COLLECT VALIDATOR NAMES // ============================================================= // Get named validators if ($this.data("validation") !== undefined) { validatorNames = $this.data("validation").split(","); } // Get extra ones defined on the element's data attributes $.each($this.data(), function (i, el) { var parts = i.replace(/([A-Z])/g, ",$1").split(","); if (parts[0] === "validation" && parts[1]) { validatorNames.push(parts[1]); } }); // ============================================================= // NORMALISE VALIDATOR NAMES // ============================================================= var validatorNamesToInspect = validatorNames; var newValidatorNamesToInspect = []; var uppercaseEachValidatorName = function (i, el) { validatorNames[i] = formatValidatorName(el); }; var inspectValidators = function (i, el) { if ($this.data("validation" + el + "Shortcut") !== undefined) { // Are these custom validators? // Pull them out! $.each($this.data("validation" + el + "Shortcut").split(","), function (i2, el2) { newValidatorNamesToInspect.push(el2); }); } else if (settings.builtInValidators[el.toLowerCase()]) { // Is this a recognised built-in? // Pull it out! var validator = settings.builtInValidators[el.toLowerCase()]; if (validator.type.toLowerCase() === "shortcut") { $.each(validator.shortcut.split(","), function (i, el) { el = formatValidatorName(el); newValidatorNamesToInspect.push(el); validatorNames.push(el); }); } } }; do // repeatedly expand 'shortcut' validators into their real validators { // Uppercase only the first letter of each name $.each(validatorNames, uppercaseEachValidatorName); // Remove duplicate validator names validatorNames = $.unique(validatorNames); // Pull out the new validator names from each shortcut newValidatorNamesToInspect = []; $.each(validatorNamesToInspect, inspectValidators); validatorNamesToInspect = newValidatorNamesToInspect; } while (validatorNamesToInspect.length > 0); // ============================================================= // SET UP VALIDATOR ARRAYS // ============================================================= /* We're gonna generate something like * * { * "regex": [ * { -- a validator object here --}, * { -- a validator object here --} * ], * "required": [ * { -- a validator object here --}, * { -- a validator object here --} * ] * } * * with a few more entries. * * Because we only add a few validators to each field, most of the * keys will be empty arrays with no validator objects in them, and * thats fine. */ var validators = {}; $.each(validatorNames, function (i, el) { // Set up the 'override' message var message = $this.data("validation" + el + "Message"); var hasOverrideMessage = !!message; var foundValidator = false; if (!message) { message = "'" + el + "' validation failed "; } $.each( settings.validatorTypes, function (validatorType, validatorTemplate) { if (validators[validatorType] === undefined) { validators[validatorType] = []; } if (!foundValidator && $this.data("validation" + el + formatValidatorName(validatorTemplate.name)) !== undefined) { var initted = validatorTemplate.init($this, el); if (hasOverrideMessage) { initted.message = message; } validators[validatorType].push( $.extend( true, { name: formatValidatorName(validatorTemplate.name), message: message }, initted)); foundValidator = true; } } ); if (!foundValidator && settings.builtInValidators[el.toLowerCase()]) { var validator = $.extend(true, {}, settings.builtInValidators[el.toLowerCase()]); if (hasOverrideMessage) { validator.message = message; } var validatorType = validator.type.toLowerCase(); if (validatorType === "shortcut") { foundValidator = true; } else { $.each( settings.validatorTypes, function (validatorTemplateType, validatorTemplate) { if (validators[validatorTemplateType] === undefined) { validators[validatorTemplateType] = []; } if (!foundValidator && validatorType === validatorTemplateType.toLowerCase()) { $this.data("validation" + el + formatValidatorName(validatorTemplate.name), validator[validatorTemplate.name.toLowerCase()]); validators[validatorType].push( $.extend( validator, validatorTemplate.init($this, el) ) ); foundValidator = true; } } ); } } if (!foundValidator) { $.error("Cannot find validation info for '" + el + "'"); } }); // ============================================================= // STORE FALLBACK VALUES // ============================================================= $helpBlock.data("original-contents", ( $helpBlock.data("original-contents") ? $helpBlock.data("original-contents") : $helpBlock.html())); $helpBlock.data("original-role", ( $helpBlock.data("original-role") ? $helpBlock.data("original-role") : $helpBlock.attr("role"))); $controlGroup.data("original-classes", ( $controlGroup.data("original-clases") ? $controlGroup.data("original-classes") : $controlGroup.attr("class"))); $this.data("original-aria-invalid", ( $this.data("original-aria-invalid") ? $this.data("original-aria-invalid") : $this.attr("aria-invalid"))); // ============================================================= // VALIDATION // ============================================================= $this.bind("validation.validation", function (event, params) { var value = getValue($this); // Get a list of the errors to apply var errorsFound = []; $.each(validators, function (validatorType, validatorTypeArray) { if ( value || // has a truthy value value.length || // not an empty string ( // am including empty values ( params && params.includeEmpty) || !!settings.validatorTypes[validatorType].includeEmpty) || ( // validator is blocking submit !! settings.validatorTypes[validatorType].blockSubmit && params && !!params.submitting)) { $.each( validatorTypeArray, function (i, validator) { if (settings.validatorTypes[validatorType].validate($this, value, validator)) { errorsFound.push(validator.message); } }); } }); return errorsFound; }); $this.bind("getValidators.validation", function () { return validators; }); var numValidators = 0; $.each(validators, function (i, el) { numValidators += el.length; }); $this.bind("getValidatorCount.validation", function () { return numValidators; }); // ============================================================= // WATCH FOR CHANGES // ============================================================= $this.bind("submit.validation", function () { return $this.triggerHandler("change.validation", { submitting: true }); }); $this.bind( ( settings.options.bindEvents.length > 0 ? settings.options.bindEvents : ["keyup", "focus", "blur", "click", "keydown", "keypress", "change"]).concat(["revalidate"]).join(".validation ") + ".validation", function (e, params) { var value = getValue($this); var errorsFound = []; if (params && !!params.submitting) { $controlGroup.data("jqbvIsSubmitting", true); } else if (e.type !== "revalidate") { $controlGroup.data("jqbvIsSubmitting", false); } var formIsSubmitting = !!$controlGroup.data("jqbvIsSubmitting"); $controlGroup.find("input,textarea,select").each(function (i, el) { var oldCount = errorsFound.length; $.each($(el).triggerHandler("validation.validation", params), function (j, message) { errorsFound.push(message); }); if (errorsFound.length > oldCount) { $(el).attr("aria-invalid", "true"); } else { var original = $this.data("original-aria-invalid"); $(el).attr("aria-invalid", (original !== undefined ? original : false)); } }); $form.find("input,select,textarea").not($this).not("[name=\"" + $this.attr("name") + "\"]").trigger("validationLostFocus.validation"); errorsFound = $.unique(errorsFound.sort()); // Were there any errors? if (errorsFound.length) { // Better flag it up as a warning. $controlGroup.removeClass("has-success has-error has-warning").addClass(formIsSubmitting ? "has-error" : "has-warning"); // How many errors did we find? if (settings.options.semanticallyStrict && errorsFound.length === 1) { // Only one? Being strict? Just output it. $helpBlock.html(errorsFound[0] + (settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "")); } else { // Multiple? Being sloppy? Glue them together into an UL. $helpBlock.html("