2 * Copyright 2016-2017 ZTE Corporation.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 /* jqBootstrapValidation
17 * A plugin for automating validation on Twitter Bootstrap formatted forms.
21 * License: MIT <http://opensource.org/licenses/mit-license.php> - see LICENSE file
23 * http://ReactiveRaven.github.com/jqBootstrapValidation/
28 var createdElements = [];
32 prependExistingHelpBlock: false,
33 sniffHtml: true, // sniff for 'required', 'maxlength', etc
34 preventSubmit: true, // stop the form submit event from firing if validation fails
35 submitError: false, // function called if there is an error when trying to submit
36 submitSuccess: false, // function called just before a successful submit event is sent to the server
37 semanticallyStrict: false, // set to true to tidy up generated HTML output
42 // return $(this).is(":visible"); // only validate elements you can see
43 return true; // validate everything
47 init: function (options) {
49 var settings = $.extend(true, {}, defaults);
51 settings.options = $.extend(true, settings.options, options);
53 var $siblingElements = this;
55 var uniqueForms = $.unique(
56 $siblingElements.map(function () {
57 return $(this).parents("form")[0];
61 $(uniqueForms).bind("submit", function (e) {
63 var warningsFound = 0;
64 var $inputs = $form.find("input,textarea,select").not("[type=submit],[type=image]").filter(settings.options.filter);
65 $inputs.trigger("submit.validation").trigger("validationLostFocus.validation");
67 $inputs.each(function (i, el) {
69 $controlGroup = $this.parents(".control-group").first();
71 $controlGroup.hasClass("warning")
73 $controlGroup.removeClass("warning").addClass("error");
78 $inputs.trigger("validationLostFocus.validation");
81 if (settings.options.preventSubmit) {
84 $form.addClass("error");
85 if ($.isFunction(settings.options.submitError)) {
86 settings.options.submitError($form, e, $inputs.jqBootstrapValidation("collectErrors", true));
89 $form.removeClass("error");
90 if ($.isFunction(settings.options.submitSuccess)) {
91 settings.options.submitSuccess($form, e);
96 return this.each(function () {
98 // Get references to everything we're interested in
100 $controlGroup = $this.parents(".control-group").first(),
101 $helpBlock = $controlGroup.find(".help-block").first(),
102 $form = $this.parents("form").first(),
105 // create message container if not exists
106 if (!$helpBlock.length && settings.options.autoAdd && settings.options.autoAdd.helpBlocks) {
107 $helpBlock = $('<div class="help-block" />');
108 $controlGroup.find('.controls').append($helpBlock);
109 createdElements.push($helpBlock[0]);
112 // =============================================================
113 // SNIFF HTML FOR VALIDATORS
114 // =============================================================
116 // *snort sniff snuffle*
118 if (settings.options.sniffHtml) {
120 // ---------------------------------------------------------
122 // ---------------------------------------------------------
123 if ($this.attr("pattern") !== undefined) {
124 message = "Not in the expected format<!-- data-validation-pattern-message to override -->";
125 if ($this.data("validationPatternMessage")) {
126 message = $this.data("validationPatternMessage");
128 $this.data("validationPatternMessage", message);
129 $this.data("validationPatternRegex", $this.attr("pattern"));
131 // ---------------------------------------------------------
133 // ---------------------------------------------------------
134 if ($this.attr("max") !== undefined || $this.attr("aria-valuemax") !== undefined) {
135 var max = ($this.attr("max") !== undefined ? $this.attr("max") : $this.attr("aria-valuemax"));
136 message = "Too high: Maximum of '" + max + "'<!-- data-validation-max-message to override -->";
137 if ($this.data("validationMaxMessage")) {
138 message = $this.data("validationMaxMessage");
140 $this.data("validationMaxMessage", message);
141 $this.data("validationMaxMax", max);
143 // ---------------------------------------------------------
145 // ---------------------------------------------------------
146 if ($this.attr("min") !== undefined || $this.attr("aria-valuemin") !== undefined) {
147 var min = ($this.attr("min") !== undefined ? $this.attr("min") : $this.attr("aria-valuemin"));
148 message = "Too low: Minimum of '" + min + "'<!-- data-validation-min-message to override -->";
149 if ($this.data("validationMinMessage")) {
150 message = $this.data("validationMinMessage");
152 $this.data("validationMinMessage", message);
153 $this.data("validationMinMin", min);
155 // ---------------------------------------------------------
157 // ---------------------------------------------------------
158 if ($this.attr("maxlength") !== undefined) {
159 message = "Too long: Maximum of '" + $this.attr("maxlength") + "' characters<!-- data-validation-maxlength-message to override -->";
160 if ($this.data("validationMaxlengthMessage")) {
161 message = $this.data("validationMaxlengthMessage");
163 $this.data("validationMaxlengthMessage", message);
164 $this.data("validationMaxlengthMaxlength", $this.attr("maxlength"));
166 // ---------------------------------------------------------
168 // ---------------------------------------------------------
169 if ($this.attr("minlength") !== undefined) {
170 message = "Too short: Minimum of '" + $this.attr("minlength") + "' characters<!-- data-validation-minlength-message to override -->";
171 if ($this.data("validationMinlengthMessage")) {
172 message = $this.data("validationMinlengthMessage");
174 $this.data("validationMinlengthMessage", message);
175 $this.data("validationMinlengthMinlength", $this.attr("minlength"));
177 // ---------------------------------------------------------
179 // ---------------------------------------------------------
180 if ($this.attr("required") !== undefined || $this.attr("aria-required") !== undefined) {
181 message = settings.builtInValidators.required.message;
182 if ($this.data("validationRequiredMessage")) {
183 message = $this.data("validationRequiredMessage");
185 $this.data("validationRequiredMessage", message);
187 // ---------------------------------------------------------
189 // ---------------------------------------------------------
190 if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "number") {
191 message = settings.builtInValidators.number.message;
192 if ($this.data("validationNumberMessage")) {
193 message = $this.data("validationNumberMessage");
195 $this.data("validationNumberMessage", message);
197 // ---------------------------------------------------------
199 // ---------------------------------------------------------
200 if ($this.attr("type") !== undefined && $this.attr("type").toLowerCase() === "email") {
201 message = "Not a valid email address<!-- data-validator-validemail-message to override -->";
202 if ($this.data("validationValidemailMessage")) {
203 message = $this.data("validationValidemailMessage");
204 } else if ($this.data("validationEmailMessage")) {
205 message = $this.data("validationEmailMessage");
207 $this.data("validationValidemailMessage", message);
209 // ---------------------------------------------------------
211 // ---------------------------------------------------------
212 if ($this.attr("minchecked") !== undefined) {
213 message = "Not enough options checked; Minimum of '" + $this.attr("minchecked") + "' required<!-- data-validation-minchecked-message to override -->";
214 if ($this.data("validationMincheckedMessage")) {
215 message = $this.data("validationMincheckedMessage");
217 $this.data("validationMincheckedMessage", message);
218 $this.data("validationMincheckedMinchecked", $this.attr("minchecked"));
220 // ---------------------------------------------------------
222 // ---------------------------------------------------------
223 if ($this.attr("maxchecked") !== undefined) {
224 message = "Too many options checked; Maximum of '" + $this.attr("maxchecked") + "' required<!-- data-validation-maxchecked-message to override -->";
225 if ($this.data("validationMaxcheckedMessage")) {
226 message = $this.data("validationMaxcheckedMessage");
228 $this.data("validationMaxcheckedMessage", message);
229 $this.data("validationMaxcheckedMaxchecked", $this.attr("maxchecked"));
233 // =============================================================
234 // COLLECT VALIDATOR NAMES
235 // =============================================================
237 // Get named validators
238 if ($this.data("validation") !== undefined) {
239 validatorNames = $this.data("validation").split(",");
242 // Get extra ones defined on the element's data attributes
243 $.each($this.data(), function (i, el) {
244 var parts = i.replace(/([A-Z])/g, ",$1").split(",");
245 if (parts[0] === "validation" && parts[1]) {
246 validatorNames.push(parts[1]);
250 // =============================================================
251 // NORMALISE VALIDATOR NAMES
252 // =============================================================
254 var validatorNamesToInspect = validatorNames;
255 var newValidatorNamesToInspect = [];
257 do // repeatedly expand 'shortcut' validators into their real validators
259 // Uppercase only the first letter of each name
260 $.each(validatorNames, function (i, el) {
261 validatorNames[i] = formatValidatorName(el);
264 // Remove duplicate validator names
265 validatorNames = $.unique(validatorNames);
267 // Pull out the new validator names from each shortcut
268 newValidatorNamesToInspect = [];
269 $.each(validatorNamesToInspect, function (i, el) {
270 if ($this.data("validation" + el + "Shortcut") !== undefined) {
271 // Are these custom validators?
273 $.each($this.data("validation" + el + "Shortcut").split(","), function (i2, el2) {
274 newValidatorNamesToInspect.push(el2);
276 } else if (settings.builtInValidators[el.toLowerCase()]) {
277 // Is this a recognised built-in?
279 var validator = settings.builtInValidators[el.toLowerCase()];
280 if (validator.type.toLowerCase() === "shortcut") {
281 $.each(validator.shortcut.split(","), function (i, el) {
282 el = formatValidatorName(el);
283 newValidatorNamesToInspect.push(el);
284 validatorNames.push(el);
290 validatorNamesToInspect = newValidatorNamesToInspect;
292 } while (validatorNamesToInspect.length > 0)
294 // =============================================================
295 // SET UP VALIDATOR ARRAYS
296 // =============================================================
300 $.each(validatorNames, function (i, el) {
301 // Set up the 'override' message
302 var message = $this.data("validation" + el + "Message");
303 var hasOverrideMessage = (message !== undefined);
304 var foundValidator = false;
309 : "'" + el + "' validation failed <!-- Add attribute 'data-validation-" + el.toLowerCase() + "-message' to input to change this message -->"
314 settings.validatorTypes,
315 function (validatorType, validatorTemplate) {
316 if (validators[validatorType] === undefined) {
317 validators[validatorType] = [];
319 if (!foundValidator && $this.data("validation" + el + formatValidatorName(validatorTemplate.name)) !== undefined) {
320 validators[validatorType].push(
324 name: formatValidatorName(validatorTemplate.name),
327 validatorTemplate.init($this, el)
330 foundValidator = true;
335 if (!foundValidator && settings.builtInValidators[el.toLowerCase()]) {
337 var validator = $.extend(true, {}, settings.builtInValidators[el.toLowerCase()]);
338 if (hasOverrideMessage) {
339 validator.message = message;
341 var validatorType = validator.type.toLowerCase();
343 if (validatorType === "shortcut") {
344 foundValidator = true;
347 settings.validatorTypes,
348 function (validatorTemplateType, validatorTemplate) {
349 if (validators[validatorTemplateType] === undefined) {
350 validators[validatorTemplateType] = [];
352 if (!foundValidator && validatorType === validatorTemplateType.toLowerCase()) {
353 $this.data("validation" + el + formatValidatorName(validatorTemplate.name), validator[validatorTemplate.name.toLowerCase()]);
354 validators[validatorType].push(
357 validatorTemplate.init($this, el)
360 foundValidator = true;
367 if (!foundValidator) {
368 $.error("Cannot find validation info for '" + el + "'");
372 // =============================================================
373 // STORE FALLBACK VALUES
374 // =============================================================
379 $helpBlock.data("original-contents")
380 ? $helpBlock.data("original-contents")
388 $helpBlock.data("original-role")
389 ? $helpBlock.data("original-role")
390 : $helpBlock.attr("role")
397 $controlGroup.data("original-clases")
398 ? $controlGroup.data("original-classes")
399 : $controlGroup.attr("class")
404 "original-aria-invalid",
406 $this.data("original-aria-invalid")
407 ? $this.data("original-aria-invalid")
408 : $this.attr("aria-invalid")
412 // =============================================================
414 // =============================================================
417 "validation.validation",
418 function (event, params) {
420 var value = getValue($this);
422 // Get a list of the errors to apply
423 var errorsFound = [];
425 $.each(validators, function (validatorType, validatorTypeArray) {
426 if (value || value.length || (params && params.includeEmpty) || (!!settings.validatorTypes[validatorType].blockSubmit && params && !!params.submitting)) {
427 $.each(validatorTypeArray, function (i, validator) {
428 if (settings.validatorTypes[validatorType].validate($this, value, validator)) {
429 errorsFound.push(validator.message);
440 "getValidators.validation",
446 // =============================================================
448 // =============================================================
452 return $this.triggerHandler("change.validation", {submitting: true});
464 ].join(".validation ") + ".validation",
465 function (e, params) {
467 var value = getValue($this);
469 var errorsFound = [];
471 $controlGroup.find("input,textarea,select").each(function (i, el) {
472 var oldCount = errorsFound.length;
473 $.each($(el).triggerHandler("validation.validation", params), function (j, message) {
474 errorsFound.push(message);
476 if (errorsFound.length > oldCount) {
477 $(el).attr("aria-invalid", "true");
479 var original = $this.data("original-aria-invalid");
480 $(el).attr("aria-invalid", (original !== undefined ? original : false));
484 $form.find("input,select,textarea").not($this).not("[name=\"" + $this.attr("name") + "\"]").trigger("validationLostFocus.validation");
486 errorsFound = $.unique(errorsFound.sort());
488 // Were there any errors?
489 if (errorsFound.length) {
490 // Better flag it up as a warning.
491 $controlGroup.removeClass("success error").addClass("warning");
493 // How many errors did we find?
494 if (settings.options.semanticallyStrict && errorsFound.length === 1) {
495 // Only one? Being strict? Just output it.
496 $helpBlock.html(errorsFound[0] +
497 ( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" ));
499 // Multiple? Being sloppy? Glue them together into an UL.
500 $helpBlock.html("<ul role=\"alert\"><li>" + errorsFound.join("</li><li>") + "</li></ul>" +
501 ( settings.options.prependExistingHelpBlock ? $helpBlock.data("original-contents") : "" ));
504 $controlGroup.removeClass("warning error success");
505 if (value.length > 0) {
506 $controlGroup.addClass("success");
508 $helpBlock.html($helpBlock.data("original-contents"));
511 if (e.type === "blur") {
512 $controlGroup.removeClass("success");
516 $this.bind("validationLostFocus.validation", function () {
517 $controlGroup.removeClass("success");
521 destroy: function () {
528 $controlGroup = $this.parents(".control-group").first(),
529 $helpBlock = $controlGroup.find(".help-block").first();
532 $this.unbind('.validation'); // events are namespaced.
534 $helpBlock.html($helpBlock.data("original-contents"));
536 $controlGroup.attr("class", $controlGroup.data("original-classes"));
538 $this.attr("aria-invalid", $this.data("original-aria-invalid"));
540 $helpBlock.attr("role", $this.data("original-role"));
541 // remove all elements we created
542 if (createdElements.indexOf($helpBlock[0]) > -1) {
550 collectErrors: function (includeEmpty) {
552 var errorMessages = {};
553 this.each(function (i, el) {
555 var name = $el.attr("name");
556 var errors = $el.triggerHandler("validation.validation", {includeEmpty: true});
557 errorMessages[name] = $.extend(true, errors, errorMessages[name]);
560 $.each(errorMessages, function (i, el) {
561 if (el.length === 0) {
562 delete errorMessages[i];
566 return errorMessages;
569 hasErrors: function () {
571 var errorMessages = [];
573 this.each(function (i, el) {
574 errorMessages = errorMessages.concat(
575 $(el).triggerHandler("getValidators.validation") ? $(el).triggerHandler("validation.validation", {submitting: true}) : []
579 return (errorMessages.length > 0);
581 override: function (newDefaults) {
582 defaults = $.extend(true, defaults, newDefaults);
588 init: function ($this, name) {
591 callback: $this.data("validation" + name + "Callback"),
592 lastValue: $this.val(),
597 validate: function ($this, value, validator) {
598 if (validator.lastValue === value && validator.lastFinished) {
599 return !validator.lastValid;
602 if (validator.lastFinished === true) {
603 validator.lastValue = value;
604 validator.lastValid = true;
605 validator.lastFinished = false;
607 var rrjqbvValidator = validator;
608 var rrjqbvThis = $this;
609 executeFunctionByName(
615 if (rrjqbvValidator.lastValue === data.value) {
616 rrjqbvValidator.lastValid = data.valid;
618 rrjqbvValidator.message = data.message;
620 rrjqbvValidator.lastFinished = true;
621 rrjqbvThis.data("validation" + rrjqbvValidator.validatorName + "Message", rrjqbvValidator.message);
622 // Timeout is set to avoid problems with the events being considered 'already fired'
623 setTimeout(function () {
624 rrjqbvThis.trigger("change.validation");
625 }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
637 init: function ($this, name) {
640 url: $this.data("validation" + name + "Ajax"),
641 lastValue: $this.val(),
646 validate: function ($this, value, validator) {
647 if ("" + validator.lastValue === "" + value && validator.lastFinished === true) {
648 return validator.lastValid === false;
651 if (validator.lastFinished === true) {
652 validator.lastValue = value;
653 validator.lastValid = true;
654 validator.lastFinished = false;
657 data: "value=" + value + "&field=" + $this.attr("name"),
659 success: function (data) {
660 if ("" + validator.lastValue === "" + data.value) {
661 validator.lastValid = !!(data.valid);
663 validator.message = data.message;
665 validator.lastFinished = true;
666 $this.data("validation" + validator.validatorName + "Message", validator.message);
667 // Timeout is set to avoid problems with the events being considered 'already fired'
668 setTimeout(function () {
669 $this.trigger("change.validation");
670 }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
673 failure: function () {
674 validator.lastValid = true;
675 validator.message = "ajax call failed";
676 validator.lastFinished = true;
677 $this.data("validation" + validator.validatorName + "Message", validator.message);
678 // Timeout is set to avoid problems with the events being considered 'already fired'
679 setTimeout(function () {
680 $this.trigger("change.validation");
681 }, 1); // doesn't need a long timeout, just long enough for the event bubble to burst
692 init: function ($this, name) {
693 return {regex: regexFromString($this.data("validation" + name + "Regex"))};
695 validate: function ($this, value, validator) {
696 return (!validator.regex.test(value) && !validator.negative)
697 || (validator.regex.test(value) && validator.negative);
702 init: function ($this, name) {
705 validate: function ($this, value, validator) {
706 return !!(value.length === 0 && !validator.negative)
707 || !!(value.length > 0 && validator.negative);
713 init: function ($this, name) {
714 var element = $this.parents("form").first().find("[name=\"" + $this.data("validation" + name + "Match") + "\"]").first();
715 element.bind("validation.validation", function () {
716 $this.trigger("change.validation", {submitting: true});
718 return {"element": element};
720 validate: function ($this, value, validator) {
721 return (value !== validator.element.val() && !validator.negative)
722 || (value === validator.element.val() && validator.negative);
728 init: function ($this, name) {
729 return {max: $this.data("validation" + name + "Max")};
731 validate: function ($this, value, validator) {
732 return (parseFloat(value, 10) > parseFloat(validator.max, 10) && !validator.negative)
733 || (parseFloat(value, 10) <= parseFloat(validator.max, 10) && validator.negative);
738 init: function ($this, name) {
739 return {min: $this.data("validation" + name + "Min")};
741 validate: function ($this, value, validator) {
742 return (parseFloat(value) < parseFloat(validator.min) && !validator.negative)
743 || (parseFloat(value) >= parseFloat(validator.min) && validator.negative);
748 init: function ($this, name) {
749 return {maxlength: $this.data("validation" + name + "Maxlength")};
751 validate: function ($this, value, validator) {
752 return ((value.length > validator.maxlength) && !validator.negative)
753 || ((value.length <= validator.maxlength) && validator.negative);
758 init: function ($this, name) {
759 return {minlength: $this.data("validation" + name + "Minlength")};
761 validate: function ($this, value, validator) {
762 return ((value.length < validator.minlength) && !validator.negative)
763 || ((value.length >= validator.minlength) && validator.negative);
768 init: function ($this, name) {
769 var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]");
770 elements.bind("click.validation", function () {
771 $this.trigger("change.validation", {includeEmpty: true});
773 return {maxchecked: $this.data("validation" + name + "Maxchecked"), elements: elements};
775 validate: function ($this, value, validator) {
776 return (validator.elements.filter(":checked").length > validator.maxchecked && !validator.negative)
777 || (validator.elements.filter(":checked").length <= validator.maxchecked && validator.negative);
783 init: function ($this, name) {
784 var elements = $this.parents("form").first().find("[name=\"" + $this.attr("name") + "\"]");
785 elements.bind("click.validation", function () {
786 $this.trigger("change.validation", {includeEmpty: true});
788 return {minchecked: $this.data("validation" + name + "Minchecked"), elements: elements};
790 validate: function ($this, value, validator) {
791 return (validator.elements.filter(":checked").length < validator.minchecked && !validator.negative)
792 || (validator.elements.filter(":checked").length >= validator.minchecked && validator.negative);
801 shortcut: "validemail"
806 regex: "[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\.[A-Za-z]{2,4}",
807 message: "Not a valid email address<!-- data-validator-validemail-message to override -->"
810 name: "Passwordagain",
813 message: "Does not match the given password<!-- data-validator-paswordagain-message to override -->"
818 shortcut: "number,positivenumber"
823 shortcut: "number,negativenumber"
828 regex: "([+-]?\\\d+(\\\.\\\d*)?([eE][+-]?[0-9]+)?)?",
829 message: "Must be a number<!-- data-validator-number-message to override -->"
835 message: "No decimal places allowed<!-- data-validator-integer-message to override -->"
838 name: "Positivenumber",
841 message: "Must be a positive number<!-- data-validator-positivenumber-message to override -->"
844 name: "Negativenumber",
847 message: "Must be a negative number<!-- data-validator-negativenumber-message to override -->"
852 message: "This is required<!-- data-validator-required-message to override -->"
858 message: "Check at least one option<!-- data-validation-checkone-message to override -->"
863 var formatValidatorName = function (name) {
868 function (m, p1, p2) {
869 return p1 + p2.toUpperCase();
875 var getValue = function ($this) {
876 // Extract the value we're talking about
877 var value = $this.val();
878 var type = $this.attr("type");
879 if (type === "checkbox") {
880 value = ($this.is(":checked") ? value : "");
882 if (type === "radio") {
883 value = ($('input[name="' + $this.attr("name") + '"]:checked').length > 0 ? value : "");
888 function regexFromString(inputstring) {
889 return new RegExp("^" + inputstring + "$");
893 * Thanks to Jason Bunting via StackOverflow.com
895 * http://stackoverflow.com/questions/359788/how-to-execute-a-javascript-function-when-i-have-its-name-as-a-string#answer-359910
896 * Short link: http://tinyurl.com/executeFunctionByName
898 function executeFunctionByName(functionName, context /*, args*/) {
899 var args = Array.prototype.slice.call(arguments).splice(2);
900 var namespaces = functionName.split(".");
901 var func = namespaces.pop();
902 for (var i = 0; i < namespaces.length; i++) {
903 context = context[namespaces[i]];
905 return context[func].apply(this, args);
908 $.fn.jqBootstrapValidation = function (method) {
910 if (defaults.methods[method]) {
911 return defaults.methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
912 } else if (typeof method === 'object' || !method) {
913 return defaults.methods.init.apply(this, arguments);
915 $.error('Method ' + method + ' does not exist on jQuery.jqBootstrapValidation');
921 $.jqBootstrapValidation = function (options) {
922 $(":input").not("[type=image],[type=submit]").jqBootstrapValidation.apply(this, arguments);