All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.resources.validatorjs.validator.js Maven / Gradle / Ivy

There is a newer version: 1.0.2-release
Show newest version
/*! validator-web 1.0.0
 * (c) 2016-2017 Youqian Yue , Apache Licensed
 * https://github.com/devefx/validator-web
 */
;(function(factory) {
	if ('function' === typeof define && (define.amd || define.cmd)) {
        // Register as an anonymous module.
        define([], function(){
            return factory;
        });
    } else {
        factory(jQuery);
    }
}(function($) {
	$.extend($.fn, {
		validate: function(options) {
			// if nothing is selected, return nothing; can't chain anyway
			if (!this.length) {
				if (options && options.debug && window.console) {
					console.warn("Nothing selected, can't validate, returning nothing.");
				}
				return;
			}

			// check if a validator for this form was already created
			var validator = $.data(this[0], "validator");
			if (validator) {
				return validator;
			}

			// Add novalidate tag if HTML5.
			this.attr("novalidate", "novalidate");

			validator = new $.validator(options, this[0]);
			$.data(this[0], "validator", validator);

			if (validator.settings.onsubmit) {

				this.on("click.validate", ":submit", function(event) {
					if (validator.settings.submitHandler) {
						validator.submitButton = event.target;
					}

					// allow suppressing validation by adding a cancel class to the submit button
					if ($(this).hasClass("cancel")) {
						validator.cancelSubmit = true;
					}
					
					// allow suppressing validation by adding the html5 formnovalidate attribute to the submit button
					if ($(this).attr("formnovalidate") !== undefined) {
						validator.cancelSubmit = true;
					}
				});

				// validate the form on submit
				this.on("submit.validate", function(event) {
					if (validator.settings.debug) {
						// prevent form submit to be able to see console output
						event.preventDefault();
					}
					function handle() {
						var hidden, result;
						if (validator.settings.submitHandler) {
							if (validator.submitButton) {
								// insert a hidden input as a replacement for the missing submit button
								hidden = $("")
									.attr("name", validator.submitButton.name)
									.val($(validator.submitButton).val())
									.appendTo(validator.currentForm);
							}
							result = validator.settings.submitHandler.call(validator, validator.currentForm, event);
							if (validator.submitButton) {
								// and clean up afterwards; thanks to no-block-scope, hidden can be referenced
								hidden.remove();
							}
							if (result !== undefined) {
								return result;
							}
							return false;
						}
						// jquery ajaxform plugin
						var ajaxsubmit = validator.settings.ajaxsubmit;
						if (ajaxsubmit) {
							var options = $(ajaxsubmit);
							options.data = $.extend(options.data || {}, {
								_validator_ajaxsubmit: 'true'
							});
							options.success = function (res) {
								if (res && res.status == 101) {
									for (var name in res.contents) {
										validator.invalid[name] = true;
										validator.showErrors(res.contents);
									}
									return;
								}
								ajaxsubmit.success && ajaxsubmit.success(res);
							};
							$(validator.currentForm).ajaxSubmit(options);
							return false;
						}
						return true;
					}

					// prevent submit for invalid forms or custom submit handlers
					if (validator.cancelSubmit) {
						validator.cancelSubmit = false;
						return handle();
					}
					if (validator.form()) {
						if (validator.pendingRequest) {
							validator.formSubmitted = true;
							return false;
						}
						return handle();
					} else {
						validator.focusInvalid();
						return false;
					}
				});

			}

			return validator;
		},

		valid: function () {
			var valid, validator, errorList;

			if ($(this[0]).is("form")) {
				valid = this.validate().form();
			} else {
				errorList = [];
				validator = $(this[0].form).validate();
				this.each(function () {
					validator.element(this);
					errorList = errorList.concat(validator.errorList);
				});
				validator.errorList = errorList;
				valid = validator.valid();
			}
			return valid;
		}

	});

	$(function () {
		$.each($("form[valid]"), function (n, form) {
			var params = $(form).attr("valid");
			$(form).validate(params ? eval("(" + params + ")") : {});
		});
	});
}));

$.validationContext = function() {
	this.constraints = [];
	this.failFast = false;
	this.throwException = false;
	this.setFailFast = function (failFast) {
		this.failFast = failFast;
	};
	this.setThrowException = function (throwException) {
		this.throwException = throwException;
	};
	this.constraint = function () {
		if (arguments.length < 3) {
			throw new Error("invalid arguments");
		}
		var args = $.makeArray(arguments);
		var descriptor = {
			name: args[0],
			message: args[1],
			constraintValidator: args[2],
			groups: (arguments.length > 3 ? args.slice(3) : ["Default"])
		};
		this.constraints.push(descriptor);
	};
};

$.parseURL = function (url) {
    url = url.replace(/\s/g, "");
    var a = document.createElement("a");
    a.href = url;
    if (url.indexOf(a.protocol) != 0) {
        throw new Error("no protocol: " + url);
    }
    return {
        source: url,
        protocol: a.protocol.replace(":", ""),
        host: a.hostname,
        port: a.port || -1,
        query: a.search.substring(1),
        params: (function () {
            var ret = {},
                seg = a.search.replace(/^\?/, '').split('&'),
                len = seg.length, i = 0, s;
            for (; i < len; i++) {
                if (!seg[i]) {
                    continue;
                }
                s = seg[i].split('=');
                ret[s[0]] = s[1];
            }
            return ret;
        })(),
        file: (a.pathname.match(/\/([^\/?#]+)$/i) || [, ''])[1],
        hash: a.hash.replace('#', ''),
        path: a.pathname.replace(/^([^\/])/, '/$1'),
        relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [, ''])[1],
        segments: a.pathname.replace(/^\//, '').split('/')
    };
};

$.validator = function(options, form) {
	this.settings = $.extend(true, {}, $.validator.defaults, options);
	this.currentForm = form;
	this.init();
};

$.validator.format = function(source, params) {
	if (arguments.length === 1) {
		return function(params) {
			var args = $.makeArray(arguments);
			args.unshift(source);
			return $.validator.format.apply(this, args);
		};
	}

	var valueExpression = {
		variables: [],
		setVariable: function (name, value) {
			this.variables.push(name + "=params."+ name);
		},
		getValue: function (expression) {
			eval("var " + this.variables.join(",") + ";");
			return eval(expression);
		}
	};

	$.each(params, function(k, v) {
		valueExpression.setVariable(k, v);
		source = source.replace(new RegExp("\\{" + k + "\\}", "g"), function() {
			return v;
		});
	});

	var patt = new RegExp("\\$\\{([^{}]+)\\}", "g");
	var result, str = source;
	while ((result = patt.exec(source))) {
		var expression = result[1],
			resolvedExpression = valueExpression.getValue(expression);
		str = str.replace(result[0], resolvedExpression);
	}
	return str;
};

$.extend($.validator, {
	
	defaults: {
		directly: false,
		failFast: false,
		throwException: false,
		groups: ["Default"],
		errorClass: "error",
		validClass: "valid",
		errorElement: "label",
		focusCleanup: false,
		focusInvalid: true,
		errorContainer: $([]),
		errorLabelContainer: $([]),
		onsubmit: true,
		ajaxsubmit: {},
		ignore: ":hidden",

		onfocusin: function(element) {
			this.lastActive = element;

			// Hide error label and remove error class on focus if enabled
			if (this.settings.focusCleanup) {
				if (this.settings.unhighlight) {
					this.settings.unhighlight.call(this, element, this.settings.errorClass, "");
				}
				this.hideThese(this.errorsFor(element));
			}
		},

		onfocusout: function(element) {
			if (!this.checkable(element) && (this.settings.directly || element.name in this.submitted || $(element).val().length)) {
				this.element(element);
			}
		},

		onkeyup: function(element, event) {
			// Avoid revalidate the field when pressing one of the following keys
			// Shift       => 16
			// Ctrl        => 17
			// Alt         => 18
			// Caps lock   => 20
			// End         => 35
			// Home        => 36
			// Left arrow  => 37
			// Up arrow    => 38
			// Right arrow => 39
			// Down arrow  => 40
			// Insert      => 45
			// Num lock    => 144
			// AltGr key   => 225
			var excludedKeys = [
				16, 17, 18, 20, 35, 36, 37,
				38, 39, 40, 45, 144, 225
			];

			if (event.which === 9 && this.elementValue(element) === "" || $.inArray(event.keyCode, excludedKeys) !== -1) {
				return;
			} else if (element.name in this.submitted || element === this.lastElement) {
				this.element(element);
			}
		},

		onclick: function(element) {
			// click on selects, radiobuttons and checkboxes
			if (element.name in this.submitted) {
				this.element(element);

			// or option elements, check parent select in that case
			} else if (element.parentNode.name in this.submitted) {
				this.element(element.parentNode);
			}
		},

		onchange: function (element) {
			if (element.type == "file") {
				$(element).removeData("savedValue").removeData("previousValue");
				this.element(element);
			}
		},

		highlight: function(element, errorClass, validClass) {
			if (element.type === "radio") {
				this.findByName(element.name).addClass(errorClass).removeClass(validClass);
			} else {
				$(element).addClass(errorClass).removeClass(validClass);
			}
		},

		unhighlight: function(element, errorClass, validClass) {
			if (element.type === "radio") {
				this.findByName(element.name).removeClass(errorClass).addClass(validClass);
			} else {
				$(element).removeClass(errorClass).addClass(validClass);
			}
		}
	},
	
	setDefaults: function(settings) {
		$.extend($.validator.defaults, settings);
	},
	
	prototype: {
		init: function() {
			this.labelContainer = $(this.settings.errorLabelContainer);
			this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm);
			this.containers = $(this.settings.errorContainer).add(this.settings.errorLabelContainer);
			this.submitted = {};
			this.pendingRequest = 0;
			this.pending = {};
			this.invalid = {};
			this.reset();
			this.uncheckedErrorList = [];
			this.context = new $.validationContext();
			this.initContext();
			this.checkElements();

			function delegate(event) {
				var validator = $.data(this.form, "validator"),
					eventType = "on" + event.type.replace(/^validate/, ""),
					settings = validator.settings;
				if (settings[eventType] && !$(this).is(settings.ignore)) {
					settings[eventType].call(validator, this, event);
				}
			}

			$(this.currentForm)
				.on("focusin.validate focusout.validate keyup.validate",
					":text, [type='password'], select, textarea, [type='number'], [type='search'], " +
					"[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], " +
					"[type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], " +
					"[type='radio'], [type='checkbox']", delegate)
				// Support: Chrome, oldIE
				// "select" is provided as event.target when clicking a option
				.on("click.validate", "select, option, [type='radio'], [type='checkbox']", delegate)
				.on("change.validate", "[type=file]", delegate);

			if (this.settings.invalidHandler) {
				$(this.currentForm).on("invalid-form.validate", this.settings.invalidHandler);
			}
		},

		initContext: function () {
			this.context.failFast = this.settings.failFast;
			this.context.throwException = this.settings.throwException;
			var use = this.settings.use;
			if (use) {
				var validation = $.validator.validations[use];
				if (validation) {
					validation(this.context);
				} else {
					throw new Error("not found validation '" + use + "', please check if there is any.");
				}
			} else {
				var results = $.map($.validator.validations, function(v, k) {
					return {id: k, validation: v};
				});

				if (results.length) {
					if (results.length > 1) {
						throw new Error("the validation is not the only, please specify the name of the validation.");
					}
					var result = results[0];
					this.settings.use = result.id;
					result.validation(this.context);
				} else {
					throw new Error("cannot find any validation.");
				}
			}
		},

		checkElements: function () {
			var validator = this;
			$.each(this.context.constraints, function (n, i) {
				var elements = validator.findByName(i.name);
				if (elements.length == 0 && (!i.name || !i.constraintValidator.isValid(undefined, validator))) {
					validator.uncheckedErrorList.push({
						element: elements[0],
						message: i.message,
						validator: i.constraintValidator
					});
					if (window.console) {
						console.error("Form elements is missing, the name is '%s'.", i.name);
					}
				}
			});
		},
		
		hasConstraint: function (element) {
			var element = this.validationTargetFor(this.clean(element));
			return $.map(this.context.constraints, function(n, i) {
				return n.name == element.name || undefined;
			}).length > 0;
		},

		groupsMatch: function (descriptor) {
			if (this.settings.groups.length == 0) {
				return $.inArray("Default", descriptor.groups) !== -1;
			}
			return $.map(this.settings.groups, function (n, i) {
				return $.inArray(n, descriptor.groups) !== -1 || undefined;
			}).length > 0;
		},

		findConstraints: function (element) {
			return $.map(this.context.constraints, function (n, i) {
				return (element.name == n.name && n) || undefined;
			});
		},

		form: function () {
			this.checkForm();
			$.extend(this.submitted, this.errorMap);
			this.invalid = $.extend({}, this.errorMap);
			if (!this.valid()) {
				$(this.currentForm).triggerHandler("invalid-form", [this]);
			}
			this.showErrors();
			return this.valid();
		},

		checkForm: function () {
			this.prepareForm();
			for(var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++) {
				if (this.check(elements[i]) === false && this.context.failFast) {
					break;
				}
			}
			return this.valid();
		},

		element: function (element) {
			var cleanElement = this.clean(element),
				checkElement = this.validationTargetFor(cleanElement),
				result = true;

			this.lastElement = checkElement;

			if (checkElement === undefined) {
				delete this.invalid[cleanElement.name];
			} else {
				this.prepareElement(checkElement);
				this.currentElements = $(checkElement);

				result = this.check(checkElement) !== false;
				if (result) {
					delete this.invalid[checkElement.name];
				} else {
					this.invalid[checkElement.name] = true;
				}
			}
			// Add aria-invalid status for screen readers
			$(element).attr("aria-invalid", !result);

			if (!this.numberOfInvalids()) {
				// Hide error containers on last error
				this.toHide = this.toHide.add(this.containers);
			}
			this.showErrors();
			return result;
		},

		showErrors: function (errors) {
			if (errors) {
				// add items to error list and map
				$.extend(this.errorMap, errors);
				this.errorList = [];
				for (var name in errors) {
					this.errorList.push({
						message: errors[name],
						element: this.findByName(name)[0]
					});
				}
				// remove items from success list
				this.successList = $.grep(this.successList, function(element) {
					return !(element.name in errors);
				});
			}
			if (this.settings.showErrors) {
				this.settings.showErrors.call(this, this.errorMap, this.errorList);
			} else {
				this.defaultShowErrors();
			}
		},

		resetForm: function() {
			if ($.fn.resetForm) {
				$(this.currentForm).resetForm();
			}
			this.submitted = {};
			this.lastElement = null;
			this.prepareForm();
			this.hideErrors();
			var i, elements = this.elements()
				.removeData("previousValue")
				.removeAttr("aria-invalid");

			if (this.settings.unhighlight) {
				for (i = 0; elements[i]; i++) {
					this.settings.unhighlight.call(this, elements[i],
						this.settings.errorClass, "");
				}
			} else {
				elements.removeClass(this.settings.errorClass);
			}
		},

		numberOfInvalids: function () {
			return this.objectLength(this.invalid);
		},

		objectLength: function(obj) {
			var count = 0, i;
			for (i in obj) {
				count++;
			}
			return count;
		},

		hideErrors: function() {
			this.hideThese(this.toHide);
		},

		hideThese: function(elements) {
			elements.not(this.containers).text("");
			this.addWrapper(elements).hide();
		},

		valid: function() {
			return this.errorList.length === 0 && this.uncheckedErrorList.length == 0;
		},

		focusInvalid: function() {
			if (this.settings.focusInvalid) {
				try {
					$(this.findLastActive() || this.errorList.length && this.errorList[0].element || [])
					.filter(":visible")
					.focus()
					// manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find
					.trigger("focusin");
				} catch (e) {
					// ignore IE throwing errors when focusing hidden elements
				}
			}
		},

		findLastActive: function() {
			var lastActive = this.lastActive;
			return lastActive && $.grep(this.errorList, function(n) {
				return n.element.name === lastActive.name;
			}).length === 1 && lastActive;
		},

		elements: function() {
			var validator = this,
				elesCache = {};
			
			return $(this.currentForm)
			.find("input, select, textarea")
			.not(":submit, :reset, :image, :disabled")
			.not(this.settings.ignore)
			.filter(function () {
				if (!this.name && validator.settings.debug && window.console) {
					console.error("%o has no name assigned", this);
				}

				if (this.name in elesCache || !validator.hasConstraint($(this))) {
					return false;
				}

				elesCache[this.name] = true;
				return true;
			});
		},

		clean: function (selector) {
			return $(selector)[0];
		},

		errorElements: function () {
			var errorClass = this.settings.errorClass.split(" ").join(".");
			return $(this.settings.errorElement + "." + errorClass, this.errorContext);
		},

		reset: function () {
			this.successList = [];
			this.errorList = [];
			this.errorMap = {};
			this.toShow = $([]);
			this.toHide = $([]);
			this.currentElements = $([]);
		},

		prepareForm: function () {
			this.reset();
			this.toHide = this.errorElements().add(this.containers);
		},

		prepareElement: function (element) {
			this.reset();
			this.toHide = this.errorsFor(element);
		},

		elementValue: function (element) {
			var val,
				$element = $(element),
				type = element.type;
			
			if (type === "radio" || type === "checkbox") {
				return this.findByName(element.name).filter(":checked").val();
			} else if (type === "number" && typeof element.validity !== "undefined") {
				return element.validity.badInput ? false : $element.val();
			} else if (type === "file") {
				var file = $element[0].files[0];
				if (this.imageable(file)) {
					var savedValue = $.data(element, "savedValue");
					return savedValue || function (callback) {
						var reader = new FileReader();
						reader.readAsDataURL(file);
						reader.onload = function() {
							var img = new Image();
							img.src = reader.result;
							img.onload = function () {
								file.width = img.width;
								file.height = img.height;
								$.data(element, "savedValue", file);
								if (callback) {
									callback(file);
								}
							};
						};
					};
				}
				return file;
			}

			val = $element.val();
			if (typeof val === "string") {
				return val.replace(/\r/g, "");
			}
			return val;
		},

		check: function (element) {
			element = this.validationTargetFor(this.clean(element));

			var constraints = this.findConstraints(element),
				value = this.elementValue(element),
				self = this, result, constraint, constraintValidator;
			
			if (typeof value == "function") {
				this.startRequest(element);
				value(function (val) {
					self.stopRequest(element, self.element(element));
				});
				return;
			}

			for (var i in constraints) {
				constraint = constraints[i],
					constraintValidator = constraint.constraintValidator;
				try {
					result = !this.groupsMatch(constraint) || constraintValidator.isValid(value, this, element, constraint);
					
					if (result === "pending") {
						this.toHide = this.toHide.not(this.errorsFor(element));
						return;
					}

					if (!result) {
						this.formatAndAdd(element, constraint);
						return false;
					}
				} catch (e) {
					if (this.settings.debug && window.console) {
						console.log("Exception occurred when checking element " + element.id + ", check the '" + constraintValidator.constructor.name + "' constraint.", e);
					}
					if (e instanceof TypeError) {
						e.message += ".  Exception occurred when checking element " + element.id + ", check the '" + constraintValidator.constructor.name + "' constraint.";
					}
					if (this.context.throwException) {
						throw e;
					}
					return false;
				}
			}
			if (this.hasConstraint(element)) {
				this.successList.push(element);
			}
			return true;
		},

		getMessage: function (element, constraint) {
			var message = constraint.message,
				theregex = /\$?\{([^{}]+)\}/g;
			if (typeof message === "function") {
				message = message.call(this, constraint.constraintValidator, element);
			} else if (theregex.test(message)) {
				var parameters = $.extend({
					value: this.elementValue(element)
				}, constraint.constraintValidator);
				message = $.validator.format(message, parameters);
			}
			return message;
		},

		formatAndAdd: function (element, constraint) {
			var message = this.getMessage(element, constraint);

			this.errorList.push({
				message: message,
				element: element,
				validator: constraint.constraintValidator
			});
			this.errorMap[element.name] = message;
			this.submitted[element.name] = message;
		},

		addWrapper: function(toToggle) {
			if (this.settings.wrapper) {
				toToggle = toToggle.add(toToggle.parent(this.settings.wrapper));
			}
			return toToggle;
		},

		defaultShowErrors: function () {
			var i, elements, error;
			for (i = 0; this.errorList[i]; i++) {
				error = this.errorList[i];
				if (this.settings.highlight) {
					this.settings.highlight.call(this, error.element, this.settings.errorClass, this.settings.validClass);
				}
				this.showLabel(error.element, error.message);
			}
			if (this.errorList.length) {
				this.toShow = this.toShow.add(this.containers);
			}
			if (this.settings.success) {
				for (i = 0; this.successList[i]; i++) {
					this.showLabel(this.successList[i]);
				}
			}
			if (this.settings.unhighlight) {
				for (i = 0, elements = this.validElements(); elements[i]; i++) {
					this.settings.unhighlight.call(this, elements[i], this.settings.errorClass, this.settings.validClass);
				}
			}
			this.toHide = this.toHide.not(this.toShow);
			this.hideErrors();
			this.addWrapper(this.toShow).show();
		},

		validElements: function() {
			return this.currentElements.not(this.invalidElements());
		},

		invalidElements: function() {
			return $(this.errorList).map(function() {
				return this.element;
			});
		},

		showLabel: function (element, message) {
			var place, errorId,
				error = this.errorsFor(element),
				elementID = this.idOrName(element),
				describedby = $(element).data("aria-describedby");

			if (error.length) {
				// refresh error/success class
				error.removeClass(this.settings.validClass).addClass(this.settings.errorClass);
				// replace message on existing label
				error.html(message);
			} else {
				// create error element
				error = $("<" + this.settings.errorElement + ">")
					.attr("id", elementID + "-error")
					.addClass(this.settings.errorClass)
					.html(message || "");
				
				// Maintain reference to the element to be placed into the DOM
				place = error;
				if (this.settings.wrapper) {
					// make sure the element is visible, even in IE
					// actually showing the wrapped element is handled elsewhere
					place = error.hide().show().wrap("<" + this.settings.wrapper + "/>").parent();
				}
				if (this.labelContainer.length) {
					this.labelContainer.append(place);
				} else if (this.settings.errorPlacement) {
					this.settings.errorPlacement(place, $(element));
				} else {
					place.insertAfter(element);
				}

				// Link error back to the element
				if (error.is("label")) {
					// If the error is a label, then associate using 'for'
					error.attr("for", elementID);
				} else if (error.parents("label[for='" + elementID + "']").length === 0) {
					// If the element is not a child of an associated label, then it's necessary
					// to explicitly apply aria-describedby

					errorID = error.attr("id").replace(/(:|\.|\[|\]|\$)/g, "\\$1");
					// Respect existing non-error aria-describedby
					if (!describedBy) {
						describedBy = errorID;
					} else if (!describedBy.match(new RegExp("\\b" + errorID + "\\b"))) {
						// Add to end of list if not already present
						describedBy += " " + errorID;
					}
					$(element).attr("aria-describedby", describedBy);
				}
			}
			if (message && this.settings.error) {
				if (typeof this.settings.error === "string") {
					error.addClass(this.settings.error);
				} else {
					this.settings.error(error, element);
				}
			}
			if (!message && this.settings.success) {
				error.text("");
				if (typeof this.settings.success === "string") {
					error.addClass(this.settings.success);
				} else {
					this.settings.success(error, element);
				}
			}
			this.toShow = this.toShow.add(error);
		},

		errorsFor: function (element) {
			var name = this.idOrName(element),
				describer = $(element).attr("aria-describedby"),
				selector = "label[for='" + name + "'], label[for='" + name + "'] *";
			
			// aria-describedby should directly reference the error element
			if (describer) {
				selector = selector + ", #" + describer.replace(/\s+/g, ", #");
			}
			return this
				.errorElements()
				.filter(selector);
		},

		idOrName: function (element) {
			return this.checkable(element) ? element.name : element.id || element.name;
		},

		validationTargetFor: function (element) {

			// If radio/checkbox, validate first element in group instead
			if (this.checkable(element)) {
				element = this.findByName(element.name);
			}

			// Always apply ignore filter
			return $(element).not(this.settings.ignore)[0];
		},

		checkable: function(element) {
			return (/radio|checkbox/i).test(element.type);
		},

		imageable: function (file) {
			return file && (/image\/(jpeg|png|gif)/).test(file.type);
		},

		findByName: function(name) {
			return $(this.currentForm).find("[name='" + name + "']");
		},

		startRequest: function(element) {
			if (!this.pending[element.name]) {
				this.pendingRequest++;
				this.pending[element.name] = true;
			}
		},

		stopRequest: function(element, valid) {
			this.pendingRequest--;
			// sometimes synchronization fails, make sure pendingRequest is never < 0
			if (this.pendingRequest < 0) {
				this.pendingRequest = 0;
			}
			delete this.pending[element.name];
			if (valid && this.pendingRequest === 0 && this.formSubmitted && this.form()) {
				$(this.currentForm).submit();
				this.formSubmitted = false;
			} else if (!valid && this.pendingRequest === 0 && this.formSubmitted) {
				$(this.currentForm).triggerHandler("invalid-form", [this]);
				this.formSubmitted = false;
			}
		},

		previousValue: function (element, url) {
			var previous = $.data(element, "previousValue") || $.data(element, "previousValue", {});
			if (!previous[url]) {
				previous[url] = {
					old: null,
					valid: false
				}
			}
			return previous[url];
		},

		// cleans up all forms and elements, removes validator-specific events
		destroy: function() {
			this.resetForm();

			$(this.currentForm)
				.off(".validate")
				.removeData("validator");
		}
	},

	validations: {},

});


/** extensions */
function SimpleDateFormat(pattern) {
    this.pattern = pattern;
    this.regex = new RegExp("^" + pattern.replace("yyyy", "\\d{4}").replace("MM", "(0\\d|1[12])").replace("dd", "([0-2]\\d|3[0-1])")
            .replace("HH", "([0-1]\\d|2[0-3])").replace("hh", "(0\\d|1[0-2])").replace("mm", "[0-5]\\d").replace("ss", "[0-5]\\d") + "$");
    this.position = {
        year: pattern.indexOf("yyyy"), month: pattern.indexOf("MM"), day: pattern.indexOf("dd"),
        hour: pattern.toLowerCase().indexOf("hh"), minute: pattern.indexOf("mm"), second: pattern.indexOf("ss")
    };
    this.parse = function (source) {
        if (!this.regex.test(source))
            throw new Error("Unparseable date: \"" + source + "\"");
        var time = {
            year: source.substr(this.position.year, 4),
            month: source.substr(this.position.month, 2),
            day: source.substr(this.position.day, 2)
        };
        if (this.position.hour != -1)
            time.hour = source.substr(this.position.hour, 2);
        if (this.position.minute != -1)
            time.minute = source.substr(this.position.minute, 2);
        if (this.position.second != -1)
            time.second = source.substr(this.position.second, 2);
        var day31 = "01,03,05,07,08,10,12";
        if (time.day == 31 && day31.indexOf(time.month) == -1)
            throw new Error("Unparseable date: \"" + source + "\"");
        if (time.month == 2 && time.day == 29 && !(time.year % 4 == 0 && time.year % 100 != 0)
            && !(time.year % 100 == 0 && time.year % 400 == 0)) {
            throw new Error("Unparseable date: \"" + source + "\"");
        }
        var date = new Date();
        date.setFullYear(time.year, time.month - 1, time.day);
        if (time.hour != undefined) date.setHours(time.hour);
        if (time.minute != undefined) date.setMinutes(time.minute);
        if (time.second != undefined) date.setSeconds(time.second);
        return date;
    };
    this.format = function (date) {
        function fmt(v, n) {
            for (var i = n - (v + "").length; i > 0; i--) {
                v = "0" + v;
            }
            return v;
        }
        var h24 = date.getHours();
        return this.pattern.replace("yyyy", fmt(date.getFullYear(), 4)).replace("MM", fmt(date.getMonth() + 1, 2))
            .replace("dd", fmt(date.getDate(), 2)).replace("HH", fmt(h24, 2)).replace("hh", fmt((h24 - 1) % 12 + 1, 2))
            .replace("mm", fmt(date.getMinutes(), 2)).replace("ss", fmt(date.getSeconds(), 2));
    };
}

Number.prototype.scale = function () {
    var numStr = this.toString();
    var scale = 0;
    var index = numStr.indexOf(".");
    if (index != -1) {
        scale = numStr.length - index - 1;
    } else {
        var matcher = /0*$/.exec(numStr);
        if (matcher.length != 0) {
            scale -= matcher[0].length;
        }
    }
    return scale;
};

Number.prototype.precision = function () {
    var numStr = this.toString();
    var precision = numStr.length;
    if (numStr.indexOf(".") != -1) {
        precision -= 1;
    } else {
        var matcher = /0*$/.exec(numStr);
        if (matcher.length != 0) {
            precision -= matcher[0].length;
        }
    }
    return precision;
};

/** constraints */
$.validator.constraints = {

	AssertFalse: function () {
		this.isValid = function (value) {
			return !value || value != "true";
		};
	},

	AssertTrue: function () {
		this.isValid = function (value) {
			return !value || value == "true"; 
		};
	},

	DecimalMax: function (maxValue, inclusive) {
		this.maxValue = maxValue;
		this.inclusive = inclusive || inclusive == undefined;
		this.isValid = function (value) {
			if (!value) {
				return true;
			}
			value = Number(value);
			if (isNaN(value)) {
				return false;
			}
			return this.inclusive ? value <= this.maxValue : value < this.maxValue;
		};
	},

	DecimalMin: function (minValue, inclusive) {
		this.minValue = minValue;
		this.inclusive = inclusive || inclusive == undefined;
		this.isValid = function (value) {
			if (!value) {
				return true;
			}
			value = Number(value);
			if (isNaN(value)) {
				return false;
			}
			return this.inclusive ? value >= this.minValue : value > this.minValue;
		};
	},

	Digits: function (maxIntegerLength, maxFractionLength) {
		this.maxIntegerLength = maxIntegerLength;
		this.maxFractionLength = maxFractionLength;
		this.isValid = function (value) {
			if (!value) {
				return true;
			}
			var bigNum = Number(value);
			var integerPartLength = bigNum.precision() - bigNum.scale();
			var fractionPartLength = bigNum.scale() < 0 ? 0 : bigNum.scale();
			return (this.maxIntegerLength >= integerPartLength && this.maxFractionLength >= fractionPartLength);
		};
	},
	
	Email: function () {
		this.isValid = function (value) {
			if (!value || value.length == 0) {
				return true;
			}
			var splitPosition = value.lastIndexOf("@");
			if (splitPosition < 0) {
				return false;
			}
			var name = value.substring(0, splitPosition);
			var domain = value.substring(splitPosition + 1);
			var valid = false;
			if (domain == "163.com" || domain == "126.com" || domain == "yeah.net") {
				valid = valid || /^[a-z][a-z0-9_]{5,17}$/i.test(name);
			} else if (domain == "qq.com" || domain == "foxmail.com") {
				valid = valid || (domain == "qq.com" && /^[1-9][0-9]{4,10}$/.test(name));
				valid = valid || (/^[a-z][a-z0-9._-]{2,17}$/i.test(name) && !/([._-]){2,}/.test(name));
			} else if (domain == "sina.com" || domain == "sina.cn") {
				valid = valid || /^[a-z0-9][a-z0-9_]{2,14}[a-z0-9]$/.test(name);
			} else if (domain == "sohu.com") {
				valid = valid || /^[a-z][a-zA-Z0-9_]{3,15}$/.test(name);
			} else if (domain == "gmail.com") {
				valid = valid || (/^[a-z0-9][a-z0-9.]{4,28}[a-z0-9]$/i.test(name) && !/\.{2,}/.test(name) &&
					(name.length < 8 || /[a-z]/.test(name)));
			} else if (domain == "outlook.com" || domain == "hotmail.com") {
				valid = valid || (/^[a-z][a-z0-9._-]{0,63}$/i.test(name) && !/\.{2,}/.test(name));
			} else if (domain == "yahoo.com" || domain == "yahoo.com.cn" || domain == "yahoo.cn") {
				valid = valid || (/^[a-z][a-z0-9._]{2,30}[a-z0-9]$/i.test(name) && !/_{2,}/.test(name) && name.match(/\./g).length < 2);
			} else {
				valid = valid || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(name);
			}
			return valid;
		};
	},

	Future: function (pattern) {
		this.pattern = pattern || "yyyy-MM-dd HH:mm:ss";
		var sdf = new SimpleDateFormat(this.pattern);
		this.isValid = function (value) {
			if (!value) {
				return true;
			}
			try {
				var date = sdf.parse(value);
				return date.getTime() > new Date().getTime();
			} catch (e) {
				return false;
			}
		};
	},

	Past: function (pattern) {
		this.pattern = pattern || "yyyy-MM-dd HH:mm:ss";
		var sdf = new SimpleDateFormat(this.pattern);
		this.isValid = function (value) {
			if (!value) {
				return true;
			}
			try {
				var date = this.sdf.parse(value);
				return date.getTime() < new Date().getTime();
			} catch (e) {
				return false;
			}
		};
	},

	Length: function (min, max) {
		this.min = min;
		this.max = max;
		this.isValid = function (value) {
			if (!value) {
				return true;
			}
			var length = value.length;
			return length >= this.min && length <= this.max;
		};
	},

	Max: function (maxValue) {
		this.maxValue = maxValue;
		this.isValid = function (value) {
			if (!value) {
				return true;
			}
			value = Number(value);
			if (isNaN(value)) {
				return false;
			}
			return value <= this.maxValue;
		};
	},

	Min: function (minValue) {
		this.minValue = minValue;
		this.isValid = function (value) {
			if (!value) {
				return true;
			}
			value = Number(value);
			if (isNaN(value)) {
				return false;
			}
			return value >= this.minValue;
		};
	},
	
	Mobile: function () {
		this.isValid = function (value) {
			if (!value) {
				return true;
			}
			return /^(0|86|17951)?(13[0-9]|15[012356789]|17[3678]|18[0-9]|14[57])[0-9]{8}$/.test(value);
		};
	},

	NotBlank: function () {
		this.isValid = function (value) {
			if (!value) {
				return false;
			}
			return $.trim(value).length > 0;
		};
	},

	NotEmpty: function () {
		this.isValid = function (value) {
			if (value == null) {
				return false;
			}
			return value.length > 0;
		};
	},

	NotNull: function () {
		this.isValid = function (value) {
			return !!value;
		};
	},

	Null: function() {
		this.isValid = function (value) {
			return !value;
		};
	},

	Regex: function (regexp, flags) {
		this.regexp = regexp;
		this.flags = flags || 0;
		var attributes = "";
		if (this.flags & 0x02) {
			attributes += "i";
		}
		if (this.flags & 0x08) {
			attributes += "m";
		}
		// FIXME Not supported: DOTALL UNICODE_CASE CANON_EQ UNIX_LINES LITERAL UNICODE_CHARACTER_CLASS COMMENTS
		var regex = new RegExp(this.regexp, attributes);
		this.isValid = function (value) {
			if (!value) {
				return true;
			}
			return regex.test(value);
		};
	},

	Size: function (min, max) {
		this.min = min;
		this.max = max;
		this.isValid = function (value, validator, element, constraint) {
			var elements = validator.findByName(constraint.name);
			var length = elements.length;
        	return length >= this.min && length <= this.max;
		};
	},

	URL: function (protocol, host, port) {
		this.protocol = protocol;
		this.host = host;
		this.port = port || -1;
		this.isValid = function (value) {
			if (!value || value.length == 0) {
				return true;
			}
			try {
				var url = $.parseURL(value);
			} catch (e) {
				return false;
			}
			if (this.protocol && this.protocol.length > 0 && url.protocol != this.protocol) {
				return false;
			}
			if (this.host && this.host.length > 0 && url.host != this.host) {
				return false;
			}
			if (this.port != -1 && url.port != this.port) {
				return false;
			}
			return true;
		};
	},

	EqualTo: function (name, ignoreCase) {
		this.name = name;
		this.ignoreCase = ignoreCase || false;
		this.isValid = function (value, validator) {
			var element = validator.findByName(this.name);
			var diffValue = validator.elementValue(element);
			return ignoreCase ? value.toLowerCase() == diffValue.toLowerCase()
					: value == diffValue;
		};
	},
	
	Remote: function (url, params) {
		this.isValid = function (value, validator, element, constraint) {
			if (arguments.length < 3) 
				return false;

			var previous = validator.previousValue(element, url),
				data;
			
			if (previous.old === value) {
				return previous.valid;
			}
			previous.old = value;
			validator.startRequest(element);
			data = {};
			data["value"] = value;
			$.ajax({
				mode: "abort",
				port: "validate" + element.name,
				url: url,
				type: "post",
				dataType: "json",
				data: data,
				context: validator.currentForm,
				success: function(response) {
					var valid = response === true || response === "true",
						errors, message, submitted;
					
					previous.valid = valid;
					if (valid) {
						validator.element(element);
					} else {
						errors = {};
						errors[element.name] = validator.getMessage(element, constraint);
						validator.invalid[element.name] = true;
						validator.showErrors(errors);
					}
					validator.stopRequest(element, valid);
				}
			});
			return "pending";
		};
		var self = this;
		params && $.each(params, function (n, i) {
			self[n] = i;
		});
	},

	Options: function (validators) {
		this.subValidators = validators;
		this.isValid = function (value, validator, element) {
			for (i in this.subValidators) {
				var va = this.subValidators[i];
				var valid = va && va.isValid(value, validator, element);
				if (valid == "pending" || valid === true) {
					return valid;
				}
			}
			return false;
		};
	},

	Separator: function (validator, separator, ignoreLastBlank) {
		this.validator = validator;
		this.separator = separator;
		this.ignoreLastBlank = ignoreLastBlank || true;
		this.isValid = function (value, validator, element) {
			if (!value) {
				return true;
			}
			if (this.ignoreLastBlank) {
				var pos = value.lastIndexOf(this.separator);
				var lastString = value.substring(pos + 1);
				if (/\s+/.test(lastString)) {
					value = value.substring(0, pos);
				}
			}
			var subTexts = value.split(this.separator);
			for (var subText in subTexts) {
				if (!this.validator.isValid(subText, validator, element)) {
					return false;
				}
			}
			return true;
		};
	},

	MultipartSize: function (min, max) {
		this.min = min;
		this.max = max;
		this.isValid = function (file) {
			if (!file) {
				return true;
			}
			size = file.size;
			return size >= this.min && size <= this.max;
		};
	},

	ImageSize: function (minWidth, maxWidth, minHeight, maxHeight) {
		this.minWidth = minWidth;
		this.maxWidth = maxWidth;
		this.minHeight = minHeight;
		this.maxHeight = maxHeight;
		this.isValid = function (image) {
			if (!image) {
				return true;
			}
			var width = image.width;
			var height = image.height;
			return width >= this.minWidth && width <= this.maxWidth &&
				height >= this.minHeight && height <= this.maxHeight;
		};
	},

	ImageRatio: function (ratio) {
		this.ratio = ratio;
		this.isValid = function (image) {
			if (!image) {
				return true;
			}
			var width = image.width;
			var height = image.height;
			return this.ratio == (width / height);
		};
	}
};




© 2015 - 2024 Weber Informatics LLC | Privacy Policy