/* * Inline Form Validation Engine 2.0 Beta, 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') === undefined || 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(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); // validate each field (- skip field ajax validation, no necessary since we will perform an ajax form validation) var r=methods._validateFields(form, true); var options = form.data('jqv'); // validate the form using AJAX 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 === false) { 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, skipAjaxFieldValidation) { var options = form.data('jqv'); // this variable is set to true if an error is found var errorFound = false; // first, evaluate status of non ajax fields form.find('[class*=validate]').not(':hidden').each( function() { var field = $(this); // fields being valiated though ajax are marked with 'ajaxed', // skip them if (!field.hasClass("ajaxed")) errorFound |= methods._validateField(field, options, skipAjaxFieldValidation); }); // second, check to see if all ajax calls completed ok errorFound |= !methods._checkAjaxStatus(options); // thrird, check status and scroll the container accordingly 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(); $.ajax({ type: "GET", url: form.attr("action"), 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 (value[1] === true) { if (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); } } 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); } } } 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, skipAjaxFieldValidation) { 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 === null) 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": if(skipAjaxFieldValidation===false) { // ajax has its own prompts handling technique 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 + "
"; options.isError = true; } } // If the rules required is not added, an empty field is not validated if(!required && !optional ){ 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 (!isAjaxValidator) { if (options.isError) methods._showPrompt(field, promptText, "", false, options); else methods._closePrompt(field); } 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