/* * Inline Form Validation Engine 2.5.4, 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($) { "use strict"; 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 ) { options = 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).parent('.formErrorOuter').remove(); $(this).remove(); }); }); } return this; }, /** * 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) { if(!$(this).is("form")) { alert("Sorry, jqv.attach() only applies to a form"); return this; } var form = this; var options; if(userOptions) options = methods._saveOptions(form, userOptions); else options = form.data('jqv'); options.validateAttribute = (form.find("[data-validation-engine*=validate]").length) ? "data-validation-engine" : "class"; if (options.binded) { // bind fields form.find("["+options.validateAttribute+"*=validate]").not("[type=checkbox]").not("[type=radio]").not(".datepicker").bind(options.validationEventTrigger, methods._onFieldEvent); form.find("["+options.validateAttribute+"*=validate][type=checkbox],["+options.validateAttribute+"*=validate][type=radio]").bind("click", methods._onFieldEvent); form.find("["+options.validateAttribute+"*=validate][class*=datepicker]").bind(options.validationEventTrigger,{"delay": 300}, methods._onFieldEvent); } if (options.autoPositionUpdate) { $(window).bind("resize", { "noAnimation": true, "formElem": form }, methods.updatePromptsPosition); } // bind form.submit form.bind("submit", methods._onSubmitEvent); return this; }, /** * Unregisters any bindings that may point to jQuery.validaitonEngine */ detach: function() { if(!$(this).is("form")) { alert("Sorry, jqv.detach() only applies to a form"); return this; } var form = this; var options = form.data('jqv'); // unbind fields form.find("["+options.validateAttribute+"*=validate]").not("[type=checkbox]").unbind(options.validationEventTrigger, methods._onFieldEvent); form.find("["+options.validateAttribute+"*=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("["+options.validateAttribute+"*=validate]").not("[type=checkbox]").die(options.validationEventTrigger, methods._onFieldEvent); form.find("["+options.validateAttribute+"*=validate][type=checkbox]").die("click", methods._onFieldEvent); // unbind form.submit form.die("submit", methods.onAjaxFormComplete); form.removeData('jqv'); if (options.autoPositionUpdate) $(window).unbind("resize", methods.updatePromptsPosition); return this; }, /** * Validates either a form or a list of 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() { if($(this).is("form")) return methods._validateFields(this); else { // field validation var form = $(this).closest('form'); var options = form.data('jqv'); var r = methods._validateField($(this), options); if (options.onSuccess && options.InvalidFields.length == 0) options.onSuccess(); else if (options.onFailure && options.InvalidFields.length > 0) options.onFailure(); return r; } }, /** * Redraw prompts position, useful when you change the DOM state when validating */ updatePromptsPosition: function(event) { if (event && this == window) { var form = event.data.formElem; var noAnimation = event.data.noAnimation; } else var form = $(this.closest('form')); var options = form.data('jqv'); // No option, take default one form.find('['+options.validateAttribute+'*=validate]').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, noAnimation); }); return 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); return this; }, /** * Closes form error prompts, CAN be invidual */ hide: function() { var form = $(this).closest('form'); if(form.length == 0) return this; var options = form.data('jqv'); var closingtag; if($(this).is("form")) { closingtag = "parentForm"+methods._getClassName($(this).attr("id")); } else { closingtag = methods._getClassName($(this).attr("id")) +"formError"; } $('.'+closingtag).fadeTo(options.fadeDuration, 0.3, function() { $(this).parent('.formErrorOuter').remove(); $(this).remove(); }); return this; }, /** * Closes all error prompts on the page */ hideAll: function() { var form = this; var options = form.data('jqv'); var duration = options ? options.fadeDuration:0.3; $('.formError').fadeTo(duration, 0.3, function() { $(this).parent('.formErrorOuter').remove(); $(this).remove(); }); return this; }, /** * 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); if (options.InvalidFields.length == 0 && options.onSuccess) { options.onSuccess(); } else if (options.InvalidFields.length > 0 && options.onFailure) { options.onFailure(); } }, (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, not necessary IF we will perform an ajax form validation) var r=methods._validateFields(form, options.ajaxFormValidation); if (r && options.ajaxFormValidation) { methods._validateFormWithAjax(form, options); // cancel form auto-submission - process with async call onAjaxFormComplete 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 var first_err=null; form.find('['+options.validateAttribute+'*=validate]').not(":disabled").each( function() { var field = $(this); var names = []; if ($.inArray(field.attr('name'), names) < 0) { errorFound |= methods._validateField(field, options, skipAjaxValidation); if (errorFound && first_err==null) if (field.is(":hidden") && options.prettySelect) first_err = field = form.find("#" + options.usePrefix + field.attr('id') + options.useSuffix); else first_err=field; if (options.doNotShowAllErrosOnSubmit) return false; names.push(field.attr('name')); } }); // second, check to see if all ajax calls completed ok // errorFound |= !methods._checkAjaxStatus(options); // third, check status and scroll the container accordingly form.trigger("jqv.form.result", [errorFound]); if (errorFound) { if (options.scroll) { var destination=first_err.offset().top; var fixleft = first_err.offset().left; //prompt positioning adjustment support. Usage: positionType:Xshift,Yshift (for ex.: bottomLeft:+20 or bottomLeft:-20,+10) var positionType=options.promptPosition; if (typeof(positionType)=='string' && positionType.indexOf(":")!=-1) positionType=positionType.substring(0,positionType.indexOf(":")); if (positionType!="bottomRight" && positionType!="bottomLeft") { var prompt_err= methods._getPrompt(first_err); destination=prompt_err.offset().top; } // 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; if (options.isOverflown) { var overflowDIV = $(options.overflownDIV); if(!overflowDIV.length) return false; 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, function(){ if(options.focusFirstField) first_err.focus(); }); } else { $("html:not(:animated),body:not(:animated)").animate({ scrollTop: destination, scrollLeft: fixleft }, 1100, function(){ if(options.focusFirstField) first_err.focus(); }); } } else if(options.focusFirstField) first_err.focus(); 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: options.ajaxFormValidationMethod, 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")) { field.attr("id", "form-validation-field-" + $.validationEngine.fieldIdCounter); ++$.validationEngine.fieldIdCounter; } if (field.is(":hidden") && !options.prettySelect || field.parents().is(":hidden")) return false; var rulesParsing = field.attr(options.validateAttribute); 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; var form = $(field.closest("form")); for (var i = 0; i < rules.length; i++) { // Fix for adding spaces in the rules rules[i] = rules[i].replace(" ", ""); var errorMsg = undefined; switch (rules[i]) { case "required": required = true; errorMsg = methods._required(field, rules, i, options); break; case "custom": errorMsg = methods._custom(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 = "["+options.validateAttribute+"*=" +rules[i + 1] +"]"; var firstOfGroup = 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(form, field, rules, i, options); break; case "future": errorMsg = methods._future(form, field, rules, i, options); break; case "dateRange": var classGroup = "["+options.validateAttribute+"*=" + rules[i + 1] + "]"; var firstOfGroup = form.find(classGroup).eq(0); var secondOfGroup = form.find(classGroup).eq(1); //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 = "["+options.validateAttribute+"*=" + rules[i + 1] + "]"; var firstOfGroup = form.find(classGroup).eq(0); var secondOfGroup = form.find(classGroup).eq(1); //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(form, field, rules, i, options); field = $(form.find("input[name='" + fieldName + "']")); break; case "minCheckbox": errorMsg = methods._minCheckbox(form, field, rules, i, options); field = $(form.find("input[name='" + fieldName + "']")); break; case "equals": errorMsg = methods._equals(field, rules, i, options); break; case "funcCall": errorMsg = methods._funcCall(field, rules, i, options); break; case "creditCard": errorMsg = methods._creditCard(field, rules, i, options); break; default: } if (errorMsg !== undefined) { promptText += errorMsg + "
"; options.isError = true; } } // If the rules required is not added, an empty field is not validated if(!required && field.val() == "" && !errorMsg) options.isError = false; // Hack for radio/checkbox group button, the validation go into the // first radio/checkbox of the group var fieldType = field.prop("type"); if ((fieldType == "radio" || fieldType == "checkbox") && form.find("input[name='" + fieldName + "']").size() > 1) { field = $(form.find("input[name='" + fieldName + "'][type!=hidden]:first")); options.showArrow = false; } if(field.is(":hidden") && options.prettySelect) { field = form.find("#" + options.usePrefix + field.attr('id') + options.useSuffix); } if (options.isError){ methods._showPrompt(field, promptText, "", false, options); }else{ if (!isAjaxValidator) methods._closePrompt(field); } if (!isAjaxValidator) { field.trigger("jqv.field.result", [field, options.isError, promptText]); } /* Record error */ var errindex = $.inArray(field[0], options.InvalidFields); if (errindex == -1) { if (options.isError) options.InvalidFields.push(field[0]); } else if (!options.isError) { options.InvalidFields.splice(errindex, 1); } 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.prop("type")) { case "text": case "password": case "textarea": case "file": default: if (! $.trim(field.val()) || field.val() == field.attr("data-validation-placeholder")) return options.allrules[rules[i]].alertText; break; case "radio": case "checkbox": var form = field.closest("form"); var name = field.attr("name"); if (form.find("input[name='" + name + "']:checked").size() == 0) { if (form.find("input[name='" + name + "']").size() == 1) return options.allrules[rules[i]].alertTextCheckboxe; else return options.allrules[rules[i]].alertTextCheckboxMultiple; } break; // required for