﻿/*
* Inline Form Validation Engine 2.1, jQuery plugin
*
* Copyright(c) 2010, Cedric Dugas
* http://www.position-absolute.com
*
* 2.0 Rewrite by Olivier Refalo
* http://www.crionics.com
*
* Form validation engine allowing custom regex rules to be added.
* Licensed under the MIT License
*/
(function ($) {

	var methods = {

		/**
		* Kind of the constructor, called before any action
		* @param {Map} user options
		*/
		init: function (options) {
			var form = this;
			if (!form.data('jqv') || form.data('jqv') == null) {
				methods._saveOptions(form, options);

				// bind all formError elements to close on click
				$(".formError").live("click", function () {
					$(this).fadeOut(150, function () {

						// remove prompt once invisible
						$(this).remove();
					});
				});
			}
		},
		/**
		* Attachs jQuery.validationEngine to form.submit and field.blur events
		* Takes an optional params: a list of options
		* ie. jQuery("#formID1").validationEngine('attach', {promptPosition : "centerRight"});
		*/
		attach: function (userOptions) {
			var form = this;
			var options;

			if (userOptions)
				options = methods._saveOptions(form, userOptions);
			else
				options = form.data('jqv');

			if (!options.binded) {
				if (options.bindMethod == "bind") {
					// bind fields
					form.find("[class*=validate]:not([type=checkbox])").bind(options.validationEventTrigger, methods._onFieldEvent);
					form.find("[class*=validate][type=checkbox]").bind("click", methods._onFieldEvent);

					// bind form.submit
					form.bind("submit", methods._onSubmitEvent);
				} else if (options.bindMethod == "live") {
					// bind fields with LIVE (for persistant state)
					form.find("[class*=validate]:not([type=checkbox])").live(options.validationEventTrigger, methods._onFieldEvent);
					form.find("[class*=validate][type=checkbox]").live("click", methods._onFieldEvent);

					// bind form.submit
					form.live("submit", methods._onSubmitEvent);
				}

				options.binded = true;
			}

		},
		/**
		* Unregisters any bindings that may point to jQuery.validaitonEngine
		*/
		detach: function () {
			var form = this;
			var options = form.data('jqv');
			if (options.binded) {

				// unbind fields
				form.find("[class*=validate]").not("[type=checkbox]").unbind(options.validationEventTrigger, methods._onFieldEvent);
				form.find("[class*=validate][type=checkbox]").unbind("click", methods._onFieldEvent);
				// unbind form.submit
				form.unbind("submit", methods.onAjaxFormComplete);


				// unbind live fields (kill)
				form.find("[class*=validate]").not("[type=checkbox]").die(options.validationEventTrigger, methods._onFieldEvent);
				form.find("[class*=validate][type=checkbox]").die("click", methods._onFieldEvent);
				// unbind form.submit
				form.die("submit", methods.onAjaxFormComplete);

				form.removeData('jqv');
			}
		},
		/**
		* Validates the form fields, shows prompts accordingly.
		* Note: There is no ajax form validation with this method, only field ajax validation are evaluated
		*
		* @return true if the form validates, false if it fails
		*/
		validate: function () {
			return methods._validateFields(this);
		},
		/**
		* Validates one field, shows prompt accordingly.
		* Note: There is no ajax form validation with this method, only field ajax validation are evaluated
		*
		* @return true if the form validates, false if it fails
		*/
		validateField: function (el) {
			var options = $(this).data('jqv');
			return methods._validateField($(el), options);
		},
		/**
		* Validates the form fields, shows prompts accordingly.
		* Note: this methods performs fields and form ajax validations(if setup)
		*
		* @return true if the form validates, false if it fails, undefined if ajax is used for form validation
		*/
		validateform: function () {
			return methods._onSubmitEvent.call(this);
		},
		/**
		* Displays a prompt on a element.
		* Note that the element needs an id!
		*
		* @param {String} promptText html text to display type
		* @param {String} type the type of bubble: 'pass' (green), 'load' (black) anything else (red)
		* @param {String} possible values topLeft, topRight, bottomLeft, centerRight, bottomRight
		*/
		showPrompt: function (promptText, type, promptPosition, showArrow) {

			var form = this.closest('form');
			var options = form.data('jqv');
			// No option, take default one
			if (!options) options = methods._saveOptions(this, options);
			if (promptPosition)
				options.promptPosition = promptPosition;
			options.showArrow = showArrow == true;

			methods._showPrompt(this, promptText, type, false, options);
		},
		/**
		* Closes all error prompts on the page
		*/
		hidePrompt: function () {
			var promptClass = "." + methods._getClassName($(this).attr("id")) + "formError"
			$(promptClass).fadeTo("fast", 0.3, function () {
				$(this).remove();
			});
		},
		/**
		* Closes form error prompts, CAN be invidual
		*/
		hide: function () {
			if ($(this).is("form")) {
				var closingtag = "parentForm" + $(this).attr('id');
			} else {

				var closingtag = $(this).attr('id') + "formError"
			}
			$('.' + closingtag).fadeTo("fast", 0.3, function () {
				$(this).remove();
			});
		},
		/**
		* Closes all error prompts on the page
		*/
		hideAll: function () {
			$('.formError').fadeTo("fast", 0.3, function () {
				$(this).remove();
			});
		},
		/**
		* Typically called when user exists a field using tab or a mouse click, triggers a field
		* validation
		*/
		_onFieldEvent: function () {
			var field = $(this);
			var form = field.closest('form');
			var options = form.data('jqv');
			// validate the current field
			methods._validateField(field, options);
		},
		/**
		* Called when the form is submited, shows prompts accordingly
		*
		* @param {jqObject}
		*            form
		* @return false if form submission needs to be cancelled
		*/
		_onSubmitEvent: function () {
			var form = $(this);
			var options = form.data('jqv');

			// validate each field (- skip field ajax validation, no necessary since we will perform an ajax form validation)
			var r = methods._validateFields(form, true);

			if (r && options.ajaxFormValidation) {
				methods._validateFormWithAjax(form, options);
				return false;
			}

			if (options.onValidationComplete) {
				options.onValidationComplete(form, r);
				return false;
			}
			return r;
		},

		/**
		* Return true if the ajax field validations passed so far
		* @param {Object} options
		* @return true, is all ajax validation passed so far (remember ajax is async)
		*/
		_checkAjaxStatus: function (options) {
			var status = true;
			$.each(options.ajaxValidCache, function (key, value) {
				if (!value) {
					status = false;
					// break the each
					return false;
				}
			});
			return status;
		},
		/**
		* Validates form fields, shows prompts accordingly
		*
		* @param {jqObject}
		*            form
		* @param {skipAjaxFieldValidation}
		*            boolean - when set to true, ajax field validation is skipped, typically used when the submit button is clicked
		*
		* @return true if form is valid, false if not, undefined if ajax form validation is done
		*/
		_validateFields: function (form, skipAjaxValidation) {
			var options = form.data('jqv');

			// this variable is set to true if an error is found
			var errorFound = false;

			// Trigger hook, start validation
			form.trigger("jqv.form.validating")
			// first, evaluate status of non ajax fields
			form.find('[class*=validate]').not(':hidden').each(function () {
				var field = $(this);
				errorFound |= methods._validateField(field, options, skipAjaxValidation);
			});
			// second, check to see if all ajax calls completed ok
			// errorFound |= !methods._checkAjaxStatus(options);

			// thrird, check status and scroll the container accordingly
			form.trigger("jqv.form.result", [errorFound])

			if (errorFound) {

				if (options.scroll) {

					// get the position of the first error, there should be at least one, no need to check this
					//var destination = form.find(".formError:not('.greenPopup'):first").offset().top;

					// look for the visually top prompt
					var destination = Number.MAX_VALUE;

					var lst = $(".formError:not('.greenPopup')");
					for (var i = 0; i < lst.length; i++) {
						var d = $(lst[i]).offset().top;
						if (d < destination)
							destination = d;
					}

					if (!options.isOverflown)
						$("html:not(:animated),body:not(:animated)").animate({
							scrollTop: destination
						}, 1100);
					else {
						var overflowDIV = $(options.overflownDIV);
						var scrollContainerScroll = overflowDIV.scrollTop();
						var scrollContainerPos = -parseInt(overflowDIV.offset().top);

						destination += scrollContainerScroll + scrollContainerPos - 5;
						var scrollContainer = $(options.overflownDIV + ":not(:animated)");

						scrollContainer.animate({
							scrollTop: destination
						}, 1100);
					}
				}
				return false;
			}
			return true;
		},
		/**
		* This method is called to perform an ajax form validation.
		* During this process all the (field, value) pairs are sent to the server which returns a list of invalid fields or true
		*
		* @param {jqObject} form
		* @param {Map} options
		*/
		_validateFormWithAjax: function (form, options) {

			var data = form.serialize();
			var url = (options.ajaxFormValidationURL) ? options.ajaxFormValidationURL : form.attr("action");
			$.ajax({
				type: "GET",
				url: url,
				cache: false,
				dataType: "json",
				data: data,
				form: form,
				methods: methods,
				options: options,
				beforeSend: function () {
					return options.onBeforeAjaxFormValidation(form, options);
				},
				error: function (data, transport) {
					methods._ajaxError(data, transport);
				},
				success: function (json) {

					if (json !== true) {

						// getting to this case doesn't necessary means that the form is invalid
						// the server may return green or closing prompt actions
						// this flag helps figuring it out
						var errorInForm = false;
						for (var i = 0; i < json.length; i++) {
							var value = json[i];

							var errorFieldId = value[0];
							var errorField = $($("#" + errorFieldId)[0]);

							// make sure we found the element
							if (errorField.length == 1) {

								// promptText or selector
								var msg = value[2];
								// if the field is valid
								if (value[1] == true) {

									if (msg == "" || !msg) {
										// if for some reason, status==true and error="", just close the prompt
										methods._closePrompt(errorField);
									} else {
										// the field is valid, but we are displaying a green prompt
										if (options.allrules[msg]) {
											var txt = options.allrules[msg].alertTextOk;
											if (txt)
												msg = txt;
										}
										methods._showPrompt(errorField, msg, "pass", false, options, true);
									}

								} else {
									// the field is invalid, show the red error prompt
									errorInForm |= true;
									if (options.allrules[msg]) {
										var txt = options.allrules[msg].alertText;
										if (txt)
											msg = txt;
									}
									methods._showPrompt(errorField, msg, "", false, options, true);
								}
							}
						}
						options.onAjaxFormComplete(!errorInForm, form, json, options);
					} else
						options.onAjaxFormComplete(true, form, "", options);
				}
			});

		},
		/**
		* Validates field, shows prompts accordingly
		*
		* @param {jqObject}
		*            field
		* @param {Array[String]}
		*            field's validation rules
		* @param {Map}
		*            user options
		* @return true if field is valid
		*/
		_validateField: function (field, options, skipAjaxValidation) {
			if (!field.attr("id"))
				$.error("jQueryValidate: an ID attribute is required for this field: " + field.attr("name") + " class:" +
                field.attr("class"));

			var rulesParsing = field.attr('class');
			var getRules = /validate\[(.*)\]/.exec(rulesParsing);
			if (!getRules)
				return false;
			var str = getRules[1];
			var rules = str.split(/\[|,|\]/);

			// true if we ran the ajax validation, tells the logic to stop messing with prompts
			var isAjaxValidator = false;
			var fieldName = field.attr("name");
			var promptText = "";
			var required = false;
			options.isError = false;
			options.showArrow = true;
			optional = false;

			for (var i = 0; i < rules.length; i++) {

				var errorMsg = undefined;
				switch (rules[i]) {

					case "optional":
						optional = true;
						break;
					case "required":
						required = true;
						errorMsg = methods._required(field, rules, i, options);
						break;
					case "custom":
						errorMsg = methods._customRegex(field, rules, i, options);
						break;
					case "ajax":
						// ajax has its own prompts handling technique
						if (!skipAjaxValidation) {
							methods._ajax(field, rules, i, options);
							isAjaxValidator = true;
						}
						break;
					case "minSize":
						errorMsg = methods._minSize(field, rules, i, options);
						break;
					case "maxSize":
						errorMsg = methods._maxSize(field, rules, i, options);
						break;
					case "min":
						errorMsg = methods._min(field, rules, i, options);
						break;
					case "max":
						errorMsg = methods._max(field, rules, i, options);
						break;
					case "past":
						errorMsg = methods._past(field, rules, i, options);
						break;
					case "future":
						errorMsg = methods._future(field, rules, i, options);
						break;
					case "maxCheckbox":
						errorMsg = methods._maxCheckbox(field, rules, i, options);
						field = $($("input[name='" + fieldName + "']"));
						break;
					case "minCheckbox":
						errorMsg = methods._minCheckbox(field, rules, i, options);
						field = $($("input[name='" + fieldName + "']"));
						break;
					case "equals":
						errorMsg = methods._equals(field, rules, i, options);
						break;
					case "funcCall":
						errorMsg = methods._funcCall(field, rules, i, options);
						break;

					default:
						//$.error("jQueryValidator rule not found"+rules[i]);
				}
				if (errorMsg !== undefined) {
					promptText += errorMsg + "<br/>";
					options.isError = true;

				}

			}
			// If the rules required is not added, an empty field is not validated
			if (!required) {
				if (field.val() == "") options.isError = false;
			}
			// Hack for radio/checkbox group button, the validation go into the
			// first radio/checkbox of the group
			var fieldType = field.attr("type");

			if ((fieldType == "radio" || fieldType == "checkbox") && $("input[name='" + fieldName + "']").size() > 1) {
				field = $($("input[name='" + fieldName + "'][type!=hidden]:first"));
				options.showArrow = false;
			}

			if (options.isError) {

				methods._showPrompt(field, promptText, "", false, options);
			} else {
				if (!isAjaxValidator) methods._closePrompt(field);
			}
			field.closest('form').trigger("jqv.field.error", [field, options.isError, promptText])
			return options.isError;
		},
		/**
		* Required validation
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return an error string if validation failed
		*/
		_required: function (field, rules, i, options) {
			switch (field.attr("type")) {
				case "text":
				case "password":
				case "textarea":
				case "file":
				default:
					if (!field.val())
						return options.allrules[rules[i]].alertText;
					break;
				case "radio":
				case "checkbox":
					var name = field.attr("name");
					if ($("input[name='" + name + "']:checked").size() == 0) {

						if ($("input[name='" + name + "']").size() == 1)
							return options.allrules[rules[i]].alertTextCheckboxe;
						else
							return options.allrules[rules[i]].alertTextCheckboxMultiple;
					}
					break;
				// required for <select> 
				case "select-one":
					// added by paul@kinetek.net for select boxes, Thank you
					if (!field.val())
						return options.allrules[rules[i]].alertText;
					break;
				case "select-multiple":
					// added by paul@kinetek.net for select boxes, Thank you
					if (!field.find("option:selected").val())
						return options.allrules[rules[i]].alertText;
					break;
			}
		},
		/**
		* Validate Regex rules
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return an error string if validation failed
		*/
		_customRegex: function (field, rules, i, options) {
			var customRule = rules[i + 1];
			var rule = options.allrules[customRule];
			if (!rule) {
				alert("jqv:custom rule not found " + customRule);
				return;
			}

			var ex = rule.regex;
			if (!ex) {
				alert("jqv:custom regex not found " + customRule);
				return;
			}
			var pattern = new RegExp(ex);

			if (!pattern.test(field.attr('value')))
				return options.allrules[customRule].alertText;
		},
		/**
		* Validate custom function outside of the engine scope
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return an error string if validation failed
		*/
		_funcCall: function (field, rules, i, options) {
			var functionName = rules[i + 1];
			var fn = window[functionName];
			if (typeof (fn) == 'function')
				return fn(field, rules, i, options);

		},
		/**
		* Field match
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return an error string if validation failed
		*/
		_equals: function (field, rules, i, options) {
			var equalsField = rules[i + 1];

			if (field.attr('value') != $("#" + equalsField).attr('value'))
				return options.allrules.equals.alertText;
		},
		/**
		* Check the maximum size (in characters)
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return an error string if validation failed
		*/
		_maxSize: function (field, rules, i, options) {
			var max = rules[i + 1];
			var len = field.attr('value').length;

			if (len > max) {
				var rule = options.allrules.maxSize;
				return rule.alertText + max + rule.alertText2;
			}
		},
		/**
		* Check the minimum size (in characters)
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return an error string if validation failed
		*/
		_minSize: function (field, rules, i, options) {
			var min = rules[i + 1];
			var len = field.attr('value').length;

			if (len < min) {
				var rule = options.allrules.minSize;
				return rule.alertText + min + rule.alertText2;
			}
		},
		/**
		* Check number minimum value
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return an error string if validation failed
		*/
		_min: function (field, rules, i, options) {
			var min = parseFloat(rules[i + 1]);
			var len = parseFloat(field.attr('value'));

			if (len < min) {
				var rule = options.allrules.min;
				if (rule.alertText2) return rule.alertText + min + rule.alertText2;
				return rule.alertText + min;
			}
		},
		/**
		* Check number maximum value
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return an error string if validation failed
		*/
		_max: function (field, rules, i, options) {
			var max = parseFloat(rules[i + 1]);
			var len = parseFloat(field.attr('value'));

			if (len > max) {
				var rule = options.allrules.max;
				if (rule.alertText2) return rule.alertText + max + rule.alertText2;
				//orefalo: to review, also do the translations
				return rule.alertText + max;
			}
		},
		/**
		* Checks date is in the past
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return an error string if validation failed
		*/
		_past: function (field, rules, i, options) {

			var p = rules[i + 1];
			var pdate = (p.toLowerCase() == "now") ? new Date() : methods._parseDate(p);
			var vdate = methods._parseDate(field.attr('value'));

			if (vdate > pdate) {
				var rule = options.allrules.past;
				if (rule.alertText2) return rule.alertText + methods._dateToString(pdate) + rule.alertText2;
				return rule.alertText + methods._dateToString(pdate);
			}
		},
		/**
		* Checks date is in the past
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return an error string if validation failed
		*/
		_future: function (field, rules, i, options) {

			var p = rules[i + 1];
			var pdate = (p.toLowerCase() == "now") ? new Date() : methods._parseDate(p);
			var vdate = methods._parseDate(field.attr('value'));

			if (vdate < pdate) {
				var rule = options.allrules.future;
				if (rule.alertText2) return rule.alertText + methods._dateToString(pdate) + rule.alertText2;
				return rule.alertText + methods._dateToString(pdate);
			}
		},
		/**
		* Max number of checkbox selected
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return an error string if validation failed
		*/
		_maxCheckbox: function (field, rules, i, options) {

			var nbCheck = rules[i + 1];
			var groupname = field.attr("name");
			var groupSize = $("input[name='" + groupname + "']:checked").size();
			if (groupSize > nbCheck) {
				options.showArrow = false;
				return options.allrules.maxCheckbox.alertText;
			}
		},
		/**
		* Min number of checkbox selected
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return an error string if validation failed
		*/
		_minCheckbox: function (field, rules, i, options) {

			var nbCheck = rules[i + 1];
			var groupname = field.attr("name");
			var groupSize = $("input[name='" + groupname + "']:checked").size();
			if (groupSize < nbCheck) {
				options.showArrow = false;
				return options.allrules.minCheckbox.alertText + " " + nbCheck + " " +
                options.allrules.minCheckbox.alertText2;
			}
		},
		/**
		* Ajax field validation
		*
		* @param {jqObject} field
		* @param {Array[String]} rules
		* @param {int} i rules index
		* @param {Map}
		*            user options
		* @return nothing! the ajax validator handles the prompts itself
		*/
		_ajax: function (field, rules, i, options) {


			var errorSelector = rules[i + 1];
			var rule = options.allrules[errorSelector];
			var extraData = rule.extraData;

			if (!extraData)
				extraData = "";

			if (!options.isError) {
				$.ajax({
					type: "GET",
					url: rule.url,
					cache: false,
					dataType: "json",
					data: "fieldId=" + field.attr("id") + "&fieldValue=" + field.attr("value") + "&extraData=" + extraData,
					field: field,
					rule: rule,
					methods: methods,
					options: options,
					beforeSend: function () {
						// build the loading prompt
						var loadingText = rule.alertTextLoad;
						if (loadingText)
							methods._showPrompt(field, loadingText, "load", true, options);
					},
					error: function (data, transport) {
						methods._ajaxError(data, transport);
					},
					success: function (json) {

						// asynchronously called on success, data is the json answer from the server
						var errorFieldId = json[0];
						var errorField = $($("#" + errorFieldId)[0]);
						// make sure we found the element
						if (errorField.length == 1) {
							var status = json[1];
							if (!status) {
								// Houston we got a problem
								options.ajaxValidCache[errorFieldId] = false;
								options.isError = true;
								var promptText = rule.alertText;
								methods._showPrompt(errorField, promptText, "", true, options);
							} else {
								if (options.ajaxValidCache[errorFieldId] !== undefined)
									options.ajaxValidCache[errorFieldId] = true;

								// see if we should display a green prompt
								var alertTextOk = rule.alertTextOk;
								if (alertTextOk)
									methods._showPrompt(errorField, alertTextOk, "pass", true, options);
								else
									methods._closePrompt(errorField);
							}
						}
					}
				});
			}
		},
		/**
		* Common method to handle ajax errors
		*
		* @param {Object} data
		* @param {Object} transport
		*/
		_ajaxError: function (data, transport) {
			if (data.status == 0 && transport == null)
				alert("The page is not served from a server! ajax call failed");
			else if (typeof console != "undefined")
				console.log("Ajax error: " + data.status + " " + transport);
		},
		/**
		* date -> string
		*
		* @param {Object} date
		*/
		_dateToString: function (date) {

			return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
		},
		/**
		* Parses an ISO date
		* @param {String} d
		*/
		_parseDate: function (d) {

			var dateParts = d.split("-");
			if (dateParts !== d)
				dateParts = d.split("/");
			return new Date(dateParts[0], (dateParts[1] - 1), dateParts[2]);
		},
		/**
		* Builds or updates a prompt with the given information
		*
		* @param {jqObject} field
		* @param {String} promptText html text to display type
		* @param {String} type the type of bubble: 'pass' (green), 'load' (black) anything else (red)
		* @param {boolean} ajaxed - use to mark fields than being validated with ajax
		* @param {Map} options user options
		*/
		_showPrompt: function (field, promptText, type, ajaxed, options, ajaxform) {
			var prompt = methods._getPrompt(field);
			// The ajax submit errors are not see has an error in the form,
			// When the form errors are returned, the engine see 2 bubbles, but those are ebing closed by the engine at the same time
			// Because no error was found befor submitting
			if (ajaxform) prompt = false;
			if (prompt)
				methods._updatePrompt(field, prompt, promptText, type, ajaxed, options);
			else
				methods._buildPrompt(field, promptText, type, ajaxed, options);
		},
		/**
		* Builds and shades a prompt for the given field.
		*
		* @param {jqObject} field
		* @param {String} promptText html text to display type
		* @param {String} type the type of bubble: 'pass' (green), 'load' (black) anything else (red)
		* @param {boolean} ajaxed - use to mark fields than being validated with ajax
		* @param {Map} options user options
		*/
		_buildPrompt: function (field, promptText, type, ajaxed, options) {

			// create the prompt
			var prompt = $('<div>');
			prompt.addClass(methods._getClassName(field.attr("id")) + "formError");
			// add a class name to identify the parent form of the prompt
			if (field.is(":input")) prompt.addClass("parentForm" + methods._getClassName(field.parents('form').attr("id")));
			prompt.addClass("formError");

			switch (type) {
				case "pass":
					prompt.addClass("greenPopup");
					break;
				case "load":
					prompt.addClass("blackPopup");
			}
			if (ajaxed)
				prompt.addClass("ajaxed");

			// create the prompt content
			var promptContent = $('<div>').addClass("formErrorContent").html(promptText).appendTo(prompt);
			// create the css arrow pointing at the field
			// note that there is no triangle on max-checkbox and radio
			if (options.showArrow) {
				var arrow = $('<div>').addClass("formErrorArrow");

				switch (options.promptPosition) {
					case "bottomLeft":
					case "bottomRight":
						prompt.find(".formErrorContent").before(arrow);
						arrow.addClass("formErrorArrowBottom").html('<div class="line1"><!-- --></div><div class="line2"><!-- --></div><div class="line3"><!-- --></div><div class="line4"><!-- --></div><div class="line5"><!-- --></div><div class="line6"><!-- --></div><div class="line7"><!-- --></div><div class="line8"><!-- --></div><div class="line9"><!-- --></div><div class="line10"><!-- --></div>');
						break;
					case "topLeft":
					case "topRight":
						arrow.html('<div class="line10"><!-- --></div><div class="line9"><!-- --></div><div class="line8"><!-- --></div><div class="line7"><!-- --></div><div class="line6"><!-- --></div><div class="line5"><!-- --></div><div class="line4"><!-- --></div><div class="line3"><!-- --></div><div class="line2"><!-- --></div><div class="line1"><!-- --></div>');
						prompt.append(arrow);
						break;
				}
			}

			//Cedric: Needed if a container is in position:relative
			// insert prompt in the form or in the overflown container?
			if (options.isOverflown)
				field.before(prompt);
			else
				$("body").append(prompt);

			var pos = methods._calculatePosition(field, prompt, options);
			prompt.css({
				"top": pos.callerTopPosition,
				"left": pos.callerleftPosition,
				"marginTop": pos.marginTopSize,
				"opacity": 0
			});

			return prompt.animate({
				"opacity": 0.87
			});

		},
		/**
		* Updates the prompt text field - the field for which the prompt
		* @param {jqObject} field
		* @param {String} promptText html text to display type
		* @param {String} type the type of bubble: 'pass' (green), 'load' (black) anything else (red)
		* @param {boolean} ajaxed - use to mark fields than being validated with ajax
		* @param {Map} options user options
		*/
		_updatePrompt: function (field, prompt, promptText, type, ajaxed, options) {

			if (prompt) {
				if (type == "pass")
					prompt.addClass("greenPopup");
				else
					prompt.removeClass("greenPopup");

				if (type == "load")
					prompt.addClass("blackPopup");
				else
					prompt.removeClass("blackPopup");

				if (ajaxed)
					prompt.addClass("ajaxed");
				else
					prompt.removeClass("ajaxed");

				prompt.find(".formErrorContent").html(promptText);

				var pos = methods._calculatePosition(field, prompt, options);
				prompt.animate({
					"top": pos.callerTopPosition,
					"marginTop": pos.marginTopSize
				});
			}
		},
		/**
		* Closes the prompt associated with the given field
		*
		* @param {jqObject}
		*            field
		*/
		_closePrompt: function (field) {

			var prompt = methods._getPrompt(field);
			if (prompt)
				prompt.fadeTo("fast", 0, function () {
					prompt.remove();
				});
		},
		closePrompt: function (field) {
			return methods._closePrompt(field);
		},
		/**
		* Returns the error prompt matching the field if any
		*
		* @param {jqObject}
		*            field
		* @return undefined or the error prompt (jqObject)
		*/
		_getPrompt: function (field) {

			var className = "." + methods._getClassName(field.attr("id")) + "formError";
			var match = $(className)[0];
			if (match)
				return $(match);
		},
		/**
		* Calculates prompt position
		*
		* @param {jqObject}
		*            field
		* @param {jqObject}
		*            the prompt
		* @param {Map}
		*            options
		* @return positions
		*/
		_calculatePosition: function (field, promptElmt, options) {

			var promptTopPosition, promptleftPosition, marginTopSize;
			var fieldWidth = field.width();
			var promptHeight = promptElmt.height();

			var overflow = options.isOverflown;
			if (overflow) {
				// is the form contained in an overflown container?
				promptTopPosition = promptleftPosition = 0;
				// compensation for the arrow
				marginTopSize = -promptHeight;
			} else {
				var offset = field.offset();
				promptTopPosition = offset.top;
				promptleftPosition = offset.left;
				marginTopSize = 0;
			}

			switch (options.promptPosition) {

				default:
				case "topRight":
					if (overflow)
					// Is the form contained in an overflown container?
						promptleftPosition += fieldWidth - 30;
					else {
						promptleftPosition += fieldWidth - 30;
						promptTopPosition += -promptHeight;
					}
					break;
				case "topLeft":
					promptTopPosition += -promptHeight - 10;
					break;
				case "centerRight":
					promptleftPosition += fieldWidth + 13;
					break;
				case "bottomLeft":
					promptTopPosition = promptTopPosition + field.height() + 15;
					break;
				case "bottomRight":
					promptleftPosition += fieldWidth - 30;
					promptTopPosition += field.height() + 5;
			}

			return {
				"callerTopPosition": promptTopPosition + "px",
				"callerleftPosition": promptleftPosition + "px",
				"marginTopSize": marginTopSize + "px"
			};
		},
		/**
		* Saves the user options and variables in the form.data
		*
		* @param {jqObject}
		*            form - the form where the user option should be saved
		* @param {Map}
		*            options - the user options
		* @return the user options (extended from the defaults)
		*/
		_saveOptions: function (form, options) {

			// is there a language localisation ?
			if ($.validationEngineLanguage)
				var allRules = $.validationEngineLanguage.allRules;
			else
				$.error("jQuery.validationEngine rules are not loaded, plz add localization files to the page");

			var userOptions = $.extend({

				// Name of the event triggering field validation
				validationEventTrigger: "blur",
				// Automatically scroll viewport to the first error
				scroll: true,
				// Opening box position, possible locations are: topLeft,
				// topRight, bottomLeft, centerRight, bottomRight
				promptPosition: "topRight",
				bindMethod: "bind",
				// internal, automatically set to true when it parse a _ajax rule
				inlineAjax: false,
				// if set to true, the form data is sent asynchronously via ajax to the form.action url (get)
				ajaxFormValidation: false,
				// Ajax form validation callback method: boolean onComplete(form, status, errors, options)
				// retuns false if the form.submit event needs to be canceled.
				ajaxFormValidationURL: false,
				// The url to send the submit ajax validation (default to action)
				onAjaxFormComplete: $.noop,
				// called right before the ajax call, may return false to cancel
				onBeforeAjaxFormValidation: $.noop,
				// Stops form from submitting and execute function assiciated with it
				onValidationComplete: false,

				// Used when the form is displayed within a scrolling DIV
				isOverflown: false,
				overflownDIV: "",

				// --- Internals DO NOT TOUCH or OVERLOAD ---
				// validation rules and i18
				allrules: allRules,
				// true when form and fields are binded
				binded: false,
				// set to true, when the prompt arrow needs to be displayed
				showArrow: true,
				// did one of the validation fail ? kept global to stop further ajax validations
				isError: false,
				// Caches field validation status, typically only bad status are created.
				// the array is used during ajax form validation to detect issues early and prevent an expensive submit
				ajaxValidCache: {}

			}, options);

			form.data('jqv', userOptions);
			return userOptions;
		},

		/**
		* Removes forbidden characters from class name
		* @param {String} className
		*/
		_getClassName: function (className) {
			return className.replace(":", "_").replace(".", "_");
		}
	};

	/**
	* Plugin entry point.
	* You may pass an action as a parameter or a list of options.
	* if none, the init and attach methods are being called.
	* Remember: if you pass options, the attached method is NOT called automatically
	*
	* @param {String}
	*            method (optional) action
	*/
	$.fn.validationEngine = function (method) {

		var form = $(this);
		if (!form[0]) return false;  // stop here if the form does not exist

		if (typeof (method) == 'string' && method.charAt(0) != '_' && methods[method]) {

			// make sure init is called once
			if (method != "showPrompt" && method != "hidePrompt" && method != "hide" && method != "hideAll")
				methods.init.apply(form);

			return methods[method].apply(form, Array.prototype.slice.call(arguments, 1));
		} else if (typeof method == 'object' || !method) {
			// default constructor with or without arguments
			methods.init.apply(form, arguments);
			return methods.attach.apply(form);
		} else {
			$.error('Method ' + method + ' does not exist in jQuery.validationEngine');
		}
	};
})(jQuery);


