Index: branches/1.0.x/inc/js/validate/jquery.validationEngine.js =================================================================== diff -u -N -r14600 -r15169 --- branches/1.0.x/inc/js/validate/jquery.validationEngine.js (.../jquery.validationEngine.js) (revision 14600) +++ branches/1.0.x/inc/js/validate/jquery.validationEngine.js (.../jquery.validationEngine.js) (revision 15169) @@ -1,1429 +1,1429 @@ -/* - * Inline Form Validation Engine 2.2, 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'); - - var validateAttribute = (form.find("[data-validation-engine*=validate]")) ? "data-validation-engine" : "class"; - - if (!options.binded) { - if (options.bindMethod == "bind"){ - - // bind fields - form.find("[class*=validate]").not("[type=checkbox]").not("[type=radio]").not(".datepicker").bind(options.validationEventTrigger, methods._onFieldEvent); - form.find("[class*=validate][type=checkbox],[class*=validate][type=radio]").bind("click", methods._onFieldEvent); - - form.find("[class*=validate][class*=datepicker]").bind(options.validationEventTrigger,{"delay": 300}, 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]").not(".datepicker").live(options.validationEventTrigger, methods._onFieldEvent); - form.find("[class*=validate][type=checkbox]").live("click", methods._onFieldEvent); - - form.find("[class*=validate][class*=datepicker]").live(options.validationEventTrigger,{"delay": 300}, methods._onFieldEvent); - - // bind form.submit - form.live("submit", methods._onSubmitEvent); - } - - options.binded = true; - } - return this; - }, - /** - * 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],[class*=validate][type=radio]").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); - }, - /** - * Redraw prompts position, useful when you change the DOM state when validating - */ - updatePromptsPosition: function() { - var form = this.closest('form'); - var options = form.data('jqv'); - // No option, take default one - form.find('[class*=validate]').not(':hidden').not(":disabled").each(function(){ - var field = $(this); - - var prompt = methods._getPrompt(field); - var promptText = $(prompt).find(".formErrorContent").html(); - - if(prompt) methods._updatePrompt(field, $(prompt), promptText, undefined, false, options); - }) - }, - /** - * 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() { - var closingtag; - if($(this).is("form")){ - closingtag = "parentForm"+$(this).attr('id'); - }else{ - 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(event) { - var field = $(this); - var form = field.closest('form'); - var options = form.data('jqv'); - // validate the current field - window.setTimeout(function() { - methods._validateField(field, options); - }, (event.data) ? event.data.delay : 0); - - }, - /** - * 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').not(":disabled").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 fixleft = 0; - var lst = $(".formError:not('.greenPopup')"); - - for (var i = 0; i < lst.length; i++) { - var d = $(lst[i]).offset().top; - if (d < destination){ - destination = d; - fixleft = $(lst[i]).offset().left; - } - } - - if (!options.isOverflown) - $("html:not(:animated),body:not(:animated)").animate({ - scrollTop: destination, - scrollLeft: fixleft - }, 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); - - $("html:not(:animated),body:not(:animated)").animate({ - scrollTop: overflowDIV.offset().top, - scrollLeft: fixleft - }, 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; - - for (var i = 0; i < rules.length; i++) { - - var errorMsg = undefined; - switch (rules[i]) { - - case "required": - required = true; - errorMsg = methods._required(field, rules, i, options); - break; - case "custom": - errorMsg = methods._customRegex(field, rules, i, options); - break; - case "groupRequired": - // Check is its the first of group, if not, reload validation with first field - // AND continue normal validation on present field - var classGroup = "[class*=" +rules[i + 1] +"]"; - var firstOfGroup = field.closest("form").find(classGroup).eq(0); - if(firstOfGroup[0] != field[0]){ - methods._validateField(firstOfGroup, options, skipAjaxValidation) - options.showArrow = true; - continue; - }; - errorMsg = methods._groupRequired(field, rules, i, options); - if(errorMsg) required = true; - options.showArrow = false; - 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 "dateRange": - var classGroup = "[class*=" + rules[i + 1] + "]"; - var firstOfGroup = field.closest("form").find(classGroup).eq(0); - var secondOfGroup = field.closest("form").find(classGroup).eq(1); - /* - if (firstOfGroup[0] != field[0]) { - methods._validateField(firstOfGroup, options, skipAjaxValidation) - options.showArrow = true; - continue; - }; - */ - //if one entry out of the pair has value then proceed to run through validation - if (firstOfGroup[0].value || secondOfGroup[0].value) { - errorMsg = methods._dateRange(firstOfGroup, secondOfGroup, rules, i, options); - } - if (errorMsg) required = true; - options.showArrow = false; - break; - - case "dateTimeRange": - var classGroup = "[class*=" + rules[i + 1] + "]"; - var firstOfGroup = field.closest("form").find(classGroup).eq(0); - var secondOfGroup = field.closest("form").find(classGroup).eq(1); - /* - if (firstOfGroup[0] != field[0]) { - methods._validateField(firstOfGroup, options, skipAjaxValidation) - options.showArrow = true; - continue; - }; - */ - //if one entry out of the pair has value then proceed to run through validation - if (firstOfGroup[0].value || secondOfGroup[0].value) { - errorMsg = methods._dateTimeRange(firstOfGroup, secondOfGroup, rules, i, options); - } - if (errorMsg) required = true; - options.showArrow = false; - 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 + "
"; - 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 (fieldType == "text" && $("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.trigger("jqv.field.result", [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 + 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 that 1 from the group field is required + * + * @param {jqObject} field + * @param {Array[String]} rules + * @param {int} i rules index + * @param {Map} + * user options + * @return an error string if validation failed + */ + _groupRequired: function(field, rules, i, options) { + var classGroup = "[class*=" +rules[i + 1] +"]"; + var isValid = false; + // Check all fields from the group + field.closest("form").find(classGroup).each(function(){ + if(!methods._required($(this), rules, i, options)){ + isValid = true; + return false; + } + }) + + if(!isValid) return options.allrules[rules[i]].alertText; + }, + /** + * 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.val())) + 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.val() != $("#" + equalsField).val()) + 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.val().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.val().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.val()); + + 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.val()); + + 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.val()); + + 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 future + * + * @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.val()); + + 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); + } + }, + /** + * Checks if valid date + * + * @param {string} date string + * @return a bool based on determination of valid date + */ + _isDate: function (value) { + var dateRegEx = new RegExp(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$|^(?:(?:(?:0?[13578]|1[02])(\/|-)31)|(?:(?:0?[1,3-9]|1[0-2])(\/|-)(?:29|30)))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(?:(?:0?[1-9]|1[0-2])(\/|-)(?:0?[1-9]|1\d|2[0-8]))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^(0?2(\/|-)29)(\/|-)(?:(?:0[48]00|[13579][26]00|[2468][048]00)|(?:\d\d)?(?:0[48]|[2468][048]|[13579][26]))$/); + if (dateRegEx.test(value)) { + return true; + } + return false; + }, + /** + * Checks if valid date time + * + * @param {string} date string + * @return a bool based on determination of valid date time + */ + _isDateTime: function (value){ + var dateTimeRegEx = new RegExp(/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])\s+(1[012]|0?[1-9]){1}:(0?[1-5]|[0-6][0-9]){1}:(0?[0-6]|[0-6][0-9]){1}\s+(am|pm|AM|PM){1}$|^(?:(?:(?:0?[13578]|1[02])(\/|-)31)|(?:(?:0?[1,3-9]|1[0-2])(\/|-)(?:29|30)))(\/|-)(?:[1-9]\d\d\d|\d[1-9]\d\d|\d\d[1-9]\d|\d\d\d[1-9])$|^((1[012]|0?[1-9]){1}\/(0?[1-9]|[12][0-9]|3[01]){1}\/\d{2,4}\s+(1[012]|0?[1-9]){1}:(0?[1-5]|[0-6][0-9]){1}:(0?[0-6]|[0-6][0-9]){1}\s+(am|pm|AM|PM){1})$/); + if (dateTimeRegEx.test(value)) { + return true; + } + return false; + }, + //Checks if the start date is before the end date + //returns true if end is later than start + _dateCompare: function (start, end) { + return (new Date(start.toString()) < new Date(end.toString())); + }, + /** + * Checks date range + * + * @param {jqObject} first field name + * @param {jqObject} second field name + * @return an error string if validation failed + */ + _dateRange: function (first, second, rules, i, options) { + //are not both populated + if ((!first[0].value && second[0].value) || (first[0].value && !second[0].value)) { + return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2; + } + + //are not both dates + if (!methods._isDate(first[0].value) || !methods._isDate(second[0].value)) { + return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2; + } + + //are both dates but range is off + if (!methods._dateCompare(first[0].value, second[0].value)) { + return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2; + } + }, + + + /** + * Checks date time range + * + * @param {jqObject} first field name + * @param {jqObject} second field name + * @return an error string if validation failed + */ + _dateTimeRange: function (first, second, rules, i, options) { + //are not both populated + if ((!first[0].value && second[0].value) || (first[0].value && !second[0].value)) { + return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2; + } + //are not both dates + if (!methods._isDateTime(first[0].value) || !methods._isDateTime(second[0].value)) { + return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2; + } + //are both dates but range is off + if (!methods._dateCompare(first[0].value, second[0].value)) { + return options.allrules[rules[i]].alertText + options.allrules[rules[i]].alertText2; + } + }, + /** + * 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; + if (options.allrules.maxCheckbox.alertText2) return options.allrules.maxCheckbox.alertText + " " + nbCheck + " " + options.allrules.maxCheckbox.alertText2; + 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; + var extraDataDynamic = rule.extraDataDynamic; + + if (!extraData) + extraData = ""; + + if (extraDataDynamic) { + var tmpData = []; + var domIds = String(extraDataDynamic).split(","); + for (var i = 0; i < domIds.length; i++) { + var id = domIds[i]; + if ($(id).length) { + var inputValue = field.closest("form").find(id).val(); + var keyValue = id.replace('#', '') + '=' + escape(inputValue); + tmpData.push(keyValue); + } + } + extraDataDynamic = tmpData.join("&"); + } else { + extraDataDynamic = ""; + } + + if (!options.isError) { + $.ajax({ + type: "GET", + url: rule.url, + cache: false, + dataType: "json", + data: "fieldId=" + field.attr("id") + "&fieldValue=" + field.val() + "&extraData=" + extraData + "&" + extraDataDynamic, + 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]; + // read the optional msg from the server + var msg = json[2]; + if (!status) { + // Houston we got a problem - display an red prompt + options.ajaxValidCache[errorFieldId] = false; + options.isError = true; + + // resolve the msg prompt + if(msg) { + if (options.allrules[msg]) { + var txt = options.allrules[msg].alertText; + if (txt) + msg = txt; + } + } + else + msg = rule.alertText; + + methods._showPrompt(errorField, msg, "", true, options); + } else { + if (options.ajaxValidCache[errorFieldId] !== undefined) + options.ajaxValidCache[errorFieldId] = true; + + // resolves the msg prompt + if(msg) { + if (options.allrules[msg]) { + var txt = options.allrules[msg].alertTextOk; + if (txt) + msg = txt; + } + } + else + msg = rule.alertTextOk; + + // see if we should display a green prompt + if (msg) + methods._showPrompt(errorField, msg, "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 = $('
'); + 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 = $('
').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 = $('
').addClass("formErrorArrow"); + + switch (options.promptPosition) { + case "bottomLeft": + case "bottomRight": + prompt.find(".formErrorContent").before(arrow); + arrow.addClass("formErrorArrowBottom").html('
'); + break; + case "topLeft": + case "topRight": + arrow.html('
'); + 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, + "left": pos.callerleftPosition, + "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 = field.attr("id").replace(":","_") + "formError"; + var match = $("." + methods._escapeExpression(className))[0]; + if (match) + return $(match); + }, + /** + * Returns the escapade classname + * + * @param {selector} + * className + */ + _escapeExpression: function (selector) { + return selector.replace(/([#;&,\.\+\*\~':"\!\^$\[\]\(\)=>\|])/g, "\\$1"); + }, + /** + * 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 -2; + } + 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 + 23 + "px", + "callerleftPosition": promptleftPosition + 50 + "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"); + // --- Internals DO NOT TOUCH or OVERLOAD --- + // validation rules and i18 + $.validationEngine.defaults.allrules = allRules; + + var userOptions = $.extend({},$.validationEngine.defaults, 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'); + } + }; + // LEAK GLOBAL OPTIONS + $.validationEngine= {defaults:{ + + // 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: "", + + // 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: {} + + }} + +})(jQuery);