/* * Inline Form Validation Engine 2.6.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($) { "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 $(document).on("click", ".formError", 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) { 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) { // delegate fields form.on(options.validationEventTrigger, "["+options.validateAttribute+"*=validate]:not([type=checkbox]):not([type=radio]):not(.datepicker)", methods._onFieldEvent); form.on("click", "["+options.validateAttribute+"*=validate][type=checkbox],["+options.validateAttribute+"*=validate][type=radio]", methods._onFieldEvent); form.on(options.validationEventTrigger,"["+options.validateAttribute+"*=validate][class*=datepicker]", {"delay": 300}, methods._onFieldEvent); } if (options.autoPositionUpdate) { $(window).bind("resize", { "noAnimation": true, "formElem": form }, methods.updatePromptsPosition); } form.on("click","a[data-validation-engine-skip], a[class*='validate-skip'], button[data-validation-engine-skip], button[class*='validate-skip'], input[data-validation-engine-skip], input[class*='validate-skip']", methods._submitButtonClick); form.removeData('jqv_submitButton'); // bind form.submit form.on("submit", methods._onSubmitEvent); return this; }, /** * Unregisters any bindings that may point to jQuery.validaitonEngine */ detach: function() { var form = this; var options = form.data('jqv'); // unbind fields form.find("["+options.validateAttribute+"*=validate]").not("[type=checkbox]").off(options.validationEventTrigger, methods._onFieldEvent); form.find("["+options.validateAttribute+"*=validate][type=checkbox],[class*=validate][type=radio]").off("click", methods._onFieldEvent); // unbind form.submit form.off("submit", methods.onAjaxFormComplete); // unbind form.submit form.off("submit", methods.onAjaxFormComplete); form.removeData('jqv'); form.off("click", "a[data-validation-engine-skip], a[class*='validate-skip'], button[data-validation-engine-skip], button[class*='validate-skip'], input[data-validation-engine-skip], input[class*='validate-skip']", methods._submitButtonClick); form.removeData('jqv_submitButton'); 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() { var element = $(this); var valid = null; if (element.is("form") || element.hasClass("validationEngineContainer")) { if (element.hasClass('validating')) { // form is already validating. // Should abort old validation and start new one. I don't know how to implement it. return false; } else { element.addClass('validating'); var options = element.data('jqv'); var valid = methods._validateFields(this); // If the form doesn't validate, clear the 'validating' class before the user has a chance to submit again setTimeout(function(){ element.removeClass('validating'); }, 100); if (valid && options.onSuccess) { options.onSuccess(); } else if (!valid && options.onFailure) { options.onFailure(); } } } else if (element.is('form') || element.hasClass('validationEngineContainer')) { element.removeClass('validating'); } else { // field validation var form = element.closest('form, .validationEngineContainer'), options = (form.data('jqv')) ? form.data('jqv') : $.validationEngine.defaults, valid = methods._validateField(element, options); if (valid && options.onFieldSuccess) options.onFieldSuccess(); else if (options.onFieldFailure && options.InvalidFields.length > 0) { options.onFieldFailure(); } } if(options.onValidationComplete) { // !! ensures that an undefined return is interpreted as return false but allows a onValidationComplete() to possibly return true and have form continue processing return !!options.onValidationComplete(form, valid); } return valid; }, /** * 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, .validationEngineContainer')); var options = form.data('jqv'); // No option, take default one form.find('['+options.validateAttribute+'*=validate]').not(":disabled").each(function(){ var field = $(this); if (options.prettySelect && field.is(":hidden")) field = form.find("#" + options.usePrefix + field.attr('id') + options.useSuffix); 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, .validationEngineContainer'); 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, .validationEngineContainer'); var options = form.data('jqv'); var fadeDuration = (options && options.fadeDuration) ? options.fadeDuration : 0.3; var closingtag; if($(this).is("form") || $(this).hasClass("validationEngineContainer")) { closingtag = "parentForm"+methods._getClassName($(this).attr("id")); } else { closingtag = methods._getClassName($(this).attr("id")) +"formError"; } $('.'+closingtag).fadeTo(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:300; $('.formError').fadeTo(duration, 300, 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, .validationEngineContainer'); var options = form.data('jqv'); options.eventTrigger = "field"; // validate the current field window.setTimeout(function() { methods._validateField(field, options); if (options.InvalidFields.length == 0 && options.onFieldSuccess) { options.onFieldSuccess(); } else if (options.InvalidFields.length > 0 && options.onFieldFailure) { options.onFieldFailure(); } }, (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'); //check if it is trigger from skipped button if (form.data("jqv_submitButton")){ var submitButton = $("#" + form.data("jqv_submitButton")); if (submitButton){ if (submitButton.length > 0){ if (submitButton.hasClass("validate-skip") || submitButton.attr("data-validation-engine-skip") == "true") return true; } } } options.eventTrigger = "submit"; // validate each field // (- skip field ajax validation, not necessary IF we will perform an ajax form validation) var r=methods._validateFields(form); if (r && options.ajaxFormValidation) { methods._validateFormWithAjax(form, options); // cancel form auto-submission - process with async call onAjaxFormComplete return false; } if(options.onValidationComplete) { // !! ensures that an undefined return is interpreted as return false but allows a onValidationComplete() to possibly return true and have form continue processing return !!options.onValidationComplete(form, r); } 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; }, /** * Return true if the ajax field is validated * @param {String} fieldid * @param {Object} options * @return true, if validation passed, false if false or doesn't exist */ _checkAjaxFieldStatus: function(fieldid, options) { return options.ajaxValidCache[fieldid] == true; }, /** * 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) { 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); if (errorFound && first_err==null) if (field.is(":hidden") && options.prettySelect) first_err = field = form.find("#" + options.usePrefix + methods._jqSelector(field.attr('id')) + options.useSuffix); else first_err=field; if (options.doNotShowAllErrosOnSubmit) return false; names.push(field.attr('name')); //if option set, stop checking validation rules after one error is found if(options.showOneMessage == true && errorFound){ return false; } } }); // 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); if (prompt_err) { destination=prompt_err.offset().top; } } // Offset the amount the page scrolls by an amount in px to accomodate fixed elements at top of page if (options.scrollOffset) { destination -= options.scrollOffset; } // 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, body").animate({ scrollTop: destination }, 1100, function(){ if(options.focusFirstField) first_err.focus(); }); $("html, body").animate({scrollLeft: fixleft},1100) } } 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 type = (options.ajaxFormValidationMethod) ? options.ajaxFormValidationMethod : "GET"; var url = (options.ajaxFormValidationURL) ? options.ajaxFormValidationURL : form.attr("action"); var dataType = (options.dataType) ? options.dataType : "json"; $.ajax({ type: type, url: url, cache: false, dataType: dataType, 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 ((dataType == "json") && (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; } if (options.showPrompts) methods._showPrompt(er