/* * 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).addclass('hide-form-message').fadeout(250, function() { // remove prompt once invisible $(this).closest('.formerror').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.off(options.validationeventtrigger, "["+options.validateattribute+"*=validate]:not([type=checkbox]):not([type=radio]):not(.datepicker)", methods._onfieldevent); form.off("click", "["+options.validateattribute+"*=validate][type=checkbox],["+options.validateattribute+"*=validate][type=radio]", methods._onfieldevent); form.off(options.validationeventtrigger,"["+options.validateattribute+"*=validate][class*=datepicker]", methods._onfieldevent); // unbind form.submit form.off("submit", methods._onsubmitevent); 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).off("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(useroptions) { var element = $(this); var valid = null; var options; 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'); if(useroptions) options = methods._saveoptions(element, useroptions); else 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(); } return !valid; } 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 if (!options) options = methods._saveoptions(form, options); 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'); // no option, take default one if (!options) options = methods._saveoptions(form, options); var fadeduration = (options && options.fadeduration) ? options.fadeduration : 0.3; var closingtag; if(form.is("form") || form.hasclass("validationenginecontainer")) { closingtag = "parentform"+methods._getclassname($(form).attr("id")); } else { closingtag = methods._getclassname($(form).attr("id")) +"formerror"; } $('.'+closingtag).fadeto(fadeduration, 0, function() { $(this).closest('.formerror').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, 0, function() { $(this).closest('.formerror').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'); // no option, take default one if (!options) options = methods._saveoptions(form, options); options.eventtrigger = "field"; if (options.notempty == true){ if(field.val().length > 0){ // validate the current field window.settimeout(function() { methods._validatefield(field, options); }, (event.data) ? event.data.delay : 0); } }else{ // 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'); //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 { //check if we need to adjust what element to show the prompt on //and and such scroll to instead if(field.data('jqv-prompt-at') instanceof jquery ){ field = field.data('jqv-prompt-at'); } else if(field.data('jqv-prompt-at')) { field = $(field.data('jqv-prompt-at')); } 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).filter(":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) { if (options.onfailure) { options.onfailure(data, transport); } else { 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(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; } if(options.showprompts) methods._showprompt(errorfield, msg, "", false, options, true); } } } options.onajaxformcomplete(!errorinform, form, json, options); } else options.onajaxformcomplete(true, form, json, options); } }); }, /** * validates field, shows prompts accordingly * * @param {jqobject} * field * @param {array[string]} * field's validation rules * @param {map} * user options * @return false if field is valid (it is inversed for *fields*, it return false on validate and true on errors.) */ _validatefield: function(field, options, skipajaxvalidation) { if (!field.attr("id")) { field.attr("id", "form-validation-field-" + $.validationengine.fieldidcounter); ++$.validationengine.fieldidcounter; } if(field.hasclass(options.ignorefieldswithclass)) return false; if (!options.validatenonvisiblefields && (field.is(":hidden") && !options.prettyselect || field.parent().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 prompttype = ""; var required = false; var limiterrors = false; options.iserror = false; options.showarrow = options.showarrow ==true; // if the programmer wants to limit the amount of error messages per field, if (options.maxerrorsperfield > 0) { limiterrors = true; } var form = $(field.closest("form, .validationenginecontainer")); // fix for adding spaces in the rules for (var i = 0; i < rules.length; i++) { rules[i] = rules[i].tostring().replace(" ", "");//.tostring to worked on ie8 // remove any parsing errors if (rules[i] === '') { delete rules[i]; } } for (var i = 0, field_errors = 0; i < rules.length; i++) { // if we are limiting errors, and have hit the max, break if (limiterrors && field_errors >= options.maxerrorsperfield) { // if we haven't hit a required yet, check to see if there is one in the validation rules for this // field and that it's index is greater or equal to our current index if (!required) { var have_required = $.inarray('required', rules); required = (have_required != -1 && have_required >= i); } break; } var errormsg = undefined; switch (rules[i]) { case "required": required = true; errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._required); break; case "custom": errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._custom); 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; } errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._grouprequired); if(errormsg) required = true; options.showarrow = false; break; case "ajax": // ajax defaults to returning it's loading message errormsg = methods._ajax(field, rules, i, options); if (errormsg) { prompttype = "load"; } break; case "minsize": errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._minsize); break; case "maxsize": errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._maxsize); break; case "min": errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._min); break; case "max": errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._max); break; case "past": errormsg = methods._geterrormessage(form, field,rules[i], rules, i, options, methods._past); break; case "future": errormsg = methods._geterrormessage(form, field,rules[i], rules, i, options, methods._future); break; case "daterange": var classgroup = "["+options.validateattribute+"*=" + rules[i + 1] + "]"; options.firstofgroup = form.find(classgroup).eq(0); options.secondofgroup = form.find(classgroup).eq(1); //if one entry out of the pair has value then proceed to run through validation if (options.firstofgroup[0].value || options.secondofgroup[0].value) { errormsg = methods._geterrormessage(form, field,rules[i], rules, i, options, methods._daterange); } if (errormsg) required = true; options.showarrow = false; break; case "datetimerange": var classgroup = "["+options.validateattribute+"*=" + rules[i + 1] + "]"; options.firstofgroup = form.find(classgroup).eq(0); options.secondofgroup = form.find(classgroup).eq(1); //if one entry out of the pair has value then proceed to run through validation if (options.firstofgroup[0].value || options.secondofgroup[0].value) { errormsg = methods._geterrormessage(form, field,rules[i], rules, i, options, methods._datetimerange); } if (errormsg) required = true; options.showarrow = false; break; case "maxcheckbox": field = $(form.find("input[name='" + fieldname + "']")); errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._maxcheckbox); break; case "mincheckbox": field = $(form.find("input[name='" + fieldname + "']")); errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._mincheckbox); break; case "equals": errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._equals); break; case "funccall": errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._funccall); break; case "creditcard": errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._creditcard); break; case "condrequired": errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._condrequired); if (errormsg !== undefined) { required = true; } break; case "funccallrequired": errormsg = methods._geterrormessage(form, field, rules[i], rules, i, options, methods._funccallrequired); if (errormsg !== undefined) { required = true; } break; default: } var end_validation = false; // if we were passed back an message object, check what the status was to determine what to do if (typeof errormsg == "object") { switch (errormsg.status) { case "_break": end_validation = true; break; // if we have an error message, set errormsg to the error message case "_error": errormsg = errormsg.message; break; // if we want to throw an error, but not show a prompt, return early with true case "_error_no_prompt": return true; break; // anything else we continue on default: break; } } //funccallrequired, first in rules, and has error, skip anything else if( i==0 && str.indexof('funccallrequired')==0 && errormsg !== undefined ){ if(prompttext != '') { prompttext += "
"; } prompttext += errormsg; options.iserror=true; field_errors++; end_validation=true; } // if it has been specified that validation should end now, break if (end_validation) { break; } // if we have a string, that means that we have an error, so add it to the error message. if (typeof errormsg == 'string') { if(prompttext != '') { prompttext += "
"; } prompttext += errormsg; options.iserror = true; field_errors++; } } // if the rules required is not added, an empty field is not validated //the 3rd condition is added so that even empty password fields should be equal //otherwise if one is filled and another left empty, the "equal" condition would fail //which does not make any sense if(!required && !(field.val()) && field.val().length < 1 && $.inarray('equals', rules) < 0) 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"); var positiontype=field.data("promptposition") || options.promptposition; if ((fieldtype == "radio" || fieldtype == "checkbox") && form.find("input[name='" + fieldname + "']").length > 1) { if(positiontype === 'inline') { field = $(form.find("input[name='" + fieldname + "'][type!=hidden]:last")); } else { field = $(form.find("input[name='" + fieldname + "'][type!=hidden]:first")); } options.showarrow = options.showarrowonradioandcheckbox; } if(field.is(":hidden") && options.prettyselect) { field = form.find("#" + options.useprefix + methods._jqselector(field.attr('id')) + options.usesuffix); } if (options.iserror && options.showprompts){ methods._showprompt(field, prompttext, prompttype, 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); } methods._handlestatuscssclasses(field, options); /* run callback function for each field */ if (options.iserror && options.onfieldfailure) options.onfieldfailure(field); if (!options.iserror && options.onfieldsuccess) options.onfieldsuccess(field); return options.iserror; }, /** * handling css classes of fields indicating result of validation * * @param {jqobject} * field * @param {array[string]} * field's validation rules * @private */ _handlestatuscssclasses: function(field, options) { /* remove all classes */ if(options.addsuccesscssclasstofield) field.removeclass(options.addsuccesscssclasstofield); if(options.addfailurecssclasstofield) field.removeclass(options.addfailurecssclasstofield); /* add classes */ if (options.addsuccesscssclasstofield && !options.iserror) field.addclass(options.addsuccesscssclasstofield); if (options.addfailurecssclasstofield && options.iserror) field.addclass(options.addfailurecssclasstofield); }, /******************** * _geterrormessage * * @param form * @param field * @param rule * @param rules * @param i * @param options * @param originalvalidationmethod * @return {*} * @private */ _geterrormessage:function (form, field, rule, rules, i, options, originalvalidationmethod) { // if we are using the custon validation type, build the index for the rule. // otherwise if we are doing a function call, make the call and return the object // that is passed back. var rule_index = jquery.inarray(rule, rules); if (rule === "custom" || rule === "funccall" || rule === "funccallrequired") { var custom_validation_type = rules[rule_index + 1]; rule = rule + "[" + custom_validation_type + "]"; // delete the rule from the rules array so that it doesn't try to call the // same rule over again delete(rules[rule_index]); } // change the rule to the composite rule, if it was different from the original var alteredrule = rule; var element_classes = (field.attr("data-validation-engine")) ? field.attr("data-validation-engine") : field.attr("class"); var element_classes_array = element_classes.split(" "); // call the original validation method. if we are dealing with dates or checkboxes, also pass the form var errormsg; if (rule == "future" || rule == "past" || rule == "maxcheckbox" || rule == "mincheckbox") { errormsg = originalvalidationmethod(form, field, rules, i, options); } else { errormsg = originalvalidationmethod(field, rules, i, options); } // if the original validation method returned an error and we have a custom error message, // return the custom message instead. otherwise return the original error message. if (errormsg != undefined) { var custom_message = methods._getcustomerrormessage($(field), element_classes_array, alteredrule, options); if (custom_message) errormsg = custom_message; } return errormsg; }, _getcustomerrormessage:function (field, classes, rule, options) { var custom_message = false; var validityprop = /^custom\[.*\]$/.test(rule) ? methods._validityprop["custom"] : methods._validityprop[rule]; // if there is a validityprop for this rule, check to see if the field has an attribute for it if (validityprop != undefined) { custom_message = field.attr("data-errormessage-"+validityprop); // if there was an error message for it, return the message if (custom_message != undefined) return custom_message; } custom_message = field.attr("data-errormessage"); // if there is an inline custom error message, return it if (custom_message != undefined) return custom_message; var id = '#' + field.attr("id"); // if we have custom messages for the element's id, get the message for the rule from the id. // otherwise, if we have custom messages for the element's classes, use the first class message we find instead. if (typeof options.custom_error_messages[id] != "undefined" && typeof options.custom_error_messages[id][rule] != "undefined" ) { custom_message = options.custom_error_messages[id][rule]['message']; } else if (classes.length > 0) { for (var i = 0; i < classes.length && classes.length > 0; i++) { var element_class = "." + classes[i]; if (typeof options.custom_error_messages[element_class] != "undefined" && typeof options.custom_error_messages[element_class][rule] != "undefined") { custom_message = options.custom_error_messages[element_class][rule]['message']; break; } } } if (!custom_message && typeof options.custom_error_messages[rule] != "undefined" && typeof options.custom_error_messages[rule]['message'] != "undefined"){ custom_message = options.custom_error_messages[rule]['message']; } return custom_message; }, _validityprop: { "required": "value-missing", "custom": "custom-error", "grouprequired": "value-missing", "ajax": "custom-error", "minsize": "range-underflow", "maxsize": "range-overflow", "min": "range-underflow", "max": "range-overflow", "past": "type-mismatch", "future": "type-mismatch", "daterange": "type-mismatch", "datetimerange": "type-mismatch", "maxcheckbox": "range-overflow", "mincheckbox": "range-underflow", "equals": "pattern-mismatch", "funccall": "custom-error", "funccallrequired": "custom-error", "creditcard": "pattern-mismatch", "condrequired": "value-missing" }, /** * required validation * * @param {jqobject} field * @param {array[string]} rules * @param {int} i rules index * @param {map} * user options * @param {bool} condrequired flag when method is used for internal purpose in condrequired check * @return an error string if validation failed */ _required: function(field, rules, i, options, condrequired) { switch (field.prop("type")) { case "radio": case "checkbox": // new validation style to only check dependent field if (condrequired) { if (!field.prop('checked')) { return options.allrules[rules[i]].alerttextcheckboxmultiple; } break; } // old validation style var form = field.closest("form, .validationenginecontainer"); var name = field.attr("name"); if (form.find("input[name='" + name + "']:checked").length == 0) { if (form.find("input[name='" + name + "']:visible").length == 1) return options.allrules[rules[i]].alerttextcheckboxe; else return options.allrules[rules[i]].alerttextcheckboxmultiple; } break; case "text": case "password": case "textarea": case "file": case "select-one": case "select-multiple": default: var field_val = $.trim( field.val() ); var dv_placeholder = $.trim( field.attr("data-validation-placeholder") ); var placeholder = $.trim( field.attr("placeholder") ); if ( ( !field_val ) || ( dv_placeholder && field_val == dv_placeholder ) || ( placeholder && field_val == placeholder ) ) { 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 = "["+options.validateattribute+"*=" +rules[i + 1] +"]"; var isvalid = false; // check all fields from the group field.closest("form, .validationenginecontainer").find(classgroup).each(function(){ if(!methods._required($(this), rules, i, options)){ isvalid = true; return false; } }); if(!isvalid) { return options.allrules[rules[i]].alerttext; } }, /** * validate rules * * @param {jqobject} field * @param {array[string]} rules * @param {int} i rules index * @param {map} * user options * @return an error string if validation failed */ _custom: function(field, rules, i, options) { var customrule = rules[i + 1]; var rule = options.allrules[customrule]; var fn; if(!rule) { alert("jqv:custom rule not found - "+customrule); return; } if(rule["regex"]) { 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; } else if(rule["func"]) { fn = rule["func"]; if (typeof(fn) !== "function") { alert("jqv:custom parameter 'function' is no function - "+customrule); return; } if (!fn(field, rules, i, options)) return options.allrules[customrule].alerttext; } else { alert("jqv:custom type not allowed "+customrule); return; } }, /** * 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; if(functionname.indexof('.') >-1) { var namespaces = functionname.split('.'); var scope = window; while(namespaces.length) { scope = scope[namespaces.shift()]; } fn = scope; } else fn = window[functionname] || options.customfunctions[functionname]; if (typeof(fn) == 'function') return fn(field, rules, i, options); }, _funccallrequired: function(field, rules, i, options) { return methods._funccall(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(form, field, rules, i, options) { var p=rules[i + 1]; var fieldalt = $(form.find("*[name='" + p.replace(/^#+/, '') + "']")); var pdate; if (p.tolowercase() == "now") { pdate = new date(); } else if (undefined != fieldalt.val()) { if (fieldalt.is(":disabled")) return; pdate = methods._parsedate(fieldalt.val()); } else { pdate = 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(form, field, rules, i, options) { var p=rules[i + 1]; var fieldalt = $(form.find("*[name='" + p.replace(/^#+/, '') + "']")); var pdate; if (p.tolowercase() == "now") { pdate = new date(); } else if (undefined != fieldalt.val()) { if (fieldalt.is(":disabled")) return; pdate = methods._parsedate(fieldalt.val()); } else { pdate = 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]))$/); return dateregex.test(value); }, /** * 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})$/); return datetimeregex.test(value); }, //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 (field, rules, i, options) { //are not both populated if ((!options.firstofgroup[0].value && options.secondofgroup[0].value) || (options.firstofgroup[0].value && !options.secondofgroup[0].value)) { return options.allrules[rules[i]].alerttext + options.allrules[rules[i]].alerttext2; } //are not both dates if (!methods._isdate(options.firstofgroup[0].value) || !methods._isdate(options.secondofgroup[0].value)) { return options.allrules[rules[i]].alerttext + options.allrules[rules[i]].alerttext2; } //are both dates but range is off if (!methods._datecompare(options.firstofgroup[0].value, options.secondofgroup[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 (field, rules, i, options) { //are not both populated if ((!options.firstofgroup[0].value && options.secondofgroup[0].value) || (options.firstofgroup[0].value && !options.secondofgroup[0].value)) { return options.allrules[rules[i]].alerttext + options.allrules[rules[i]].alerttext2; } //are not both dates if (!methods._isdatetime(options.firstofgroup[0].value) || !methods._isdatetime(options.secondofgroup[0].value)) { return options.allrules[rules[i]].alerttext + options.allrules[rules[i]].alerttext2; } //are both dates but range is off if (!methods._datecompare(options.firstofgroup[0].value, options.secondofgroup[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(form, field, rules, i, options) { var nbcheck = rules[i + 1]; var groupname = field.attr("name"); var groupsize = form.find("input[name='" + groupname + "']:checked").length; 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(form, field, rules, i, options) { var nbcheck = rules[i + 1]; var groupname = field.attr("name"); var groupsize = form.find("input[name='" + groupname + "']:checked").length; if (groupsize < nbcheck) { options.showarrow = false; return options.allrules.mincheckbox.alerttext + " " + nbcheck + " " + options.allrules.mincheckbox.alerttext2; } }, /** * checks that it is a valid credit card number according to the * luhn checksum algorithm. * * @param {jqobject} field * @param {array[string]} rules * @param {int} i rules index * @param {map} * user options * @return an error string if validation failed */ _creditcard: function(field, rules, i, options) { //spaces and dashes may be valid characters, but must be stripped to calculate the checksum. var valid = false, cardnumber = field.val().replace(/ +/g, '').replace(/-+/g, ''); var numdigits = cardnumber.length; if (numdigits >= 14 && numdigits <= 16 && parseint(cardnumber) > 0) { var sum = 0, i = numdigits - 1, pos = 1, digit, luhn = new string(); do { digit = parseint(cardnumber.charat(i)); luhn += (pos++ % 2 == 0) ? digit * 2 : digit; } while (--i >= 0) for (i = 0; i < luhn.length; i++) { sum += parseint(luhn.charat(i)); } valid = sum % 10 == 0; } if (!valid) return options.allrules.creditcard.alerttext; }, /** * 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; var data = { "fieldid" : field.attr("id"), "fieldvalue" : field.val() }; if (typeof extradata === "object") { $.extend(data, extradata); } else if (typeof extradata === "string") { var tempdata = extradata.split("&"); for(var i = 0; i < tempdata.length; i++) { var values = tempdata[i].split("="); if (values[0] && values[0]) { data[values[0]] = values[1]; } } } 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, .validationenginecontainer").find(id).val(); var keyvalue = id.replace('#', '') + '=' + escape(inputvalue); data[id.replace('#', '')] = inputvalue; } } } // if a field change event triggered this we want to clear the cache for this id if (options.eventtrigger == "field") { delete(options.ajaxvalidcache[field.attr("id")]); } // if there is an error or if the the field is already validated, do not re-execute ajax if (!options.iserror && !methods._checkajaxfieldstatus(field.attr("id"), options)) { $.ajax({ type: options.ajaxformvalidationmethod, url: rule.url, cache: false, datatype: "json", data: data, field: field, rule: rule, methods: methods, options: options, beforesend: function() {}, error: function(data, transport) { if (options.onfailure) { options.onfailure(data, transport); } else { 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]); var errorfield = $("#"+ errorfieldid).eq(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; if (options.showprompts) methods._showprompt(errorfield, msg, "", true, options); } else { 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; if (options.showprompts) { // see if we should display a green prompt if (msg) methods._showprompt(errorfield, msg, "pass", true, options); else methods._closeprompt(errorfield); } // if a submit form triggered this, we want to re-submit the form if (options.eventtrigger == "submit") field.closest("form").submit(); } } errorfield.trigger("jqv.field.result", [errorfield, options.iserror, msg]); } }); return rule.alerttextload; } }, /** * 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("/"); if(dateparts==d) { dateparts = d.split("."); return new date(dateparts[2], (dateparts[1] - 1), dateparts[0]); } 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) { //check if we need to adjust what element to show the prompt on if(field.data('jqv-prompt-at') instanceof jquery ){ field = field.data('jqv-prompt-at'); } else if(field.data('jqv-prompt-at')) { field = $(field.data('jqv-prompt-at')); } 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; // check that there is indded text if($.trim(prompttext)){ 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 prompt.addclass("parentform"+methods._getclassname(field.closest('form, .validationenginecontainer').attr("id"))); prompt.addclass("formerror"); switch (type) { case "pass": prompt.addclass("greenpopup"); break; case "load": prompt.addclass("blackpopup"); break; default: /* it has error */ //alert("unknown popup type:"+type); } if (ajaxed) prompt.addclass("ajaxed"); // create the prompt content var promptcontent = $('
').addclass("formerrorcontent").html(prompttext).appendto(prompt); // determine position type var positiontype=field.data("promptposition") || options.promptposition; // 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"); //prompt positioning adjustment support. usage: positiontype:xshift,yshift (for ex.: bottomleft:+20 or bottomleft:-20,+10) if (typeof(positiontype)=='string') { var pos=positiontype.indexof(":"); if(pos!=-1) positiontype=positiontype.substring(0,pos); } switch (positiontype) { case "bottomleft": case "bottomright": prompt.find(".formerrorcontent").before(arrow); arrow.addclass("formerrorarrowbottom").html('
'); break; case "topleft": case "topright": arrow.html('
'); prompt.append(arrow); break; } } // add custom prompt class if (options.addpromptclass) prompt.addclass(options.addpromptclass); // add custom prompt class defined in element var requiredoverride = field.attr('data-required-class'); if(requiredoverride !== undefined) { prompt.addclass(requiredoverride); } else { if(options.prettyselect) { if($('#' + field.attr('id')).next().is('select')) { var prettyoverrideclass = $('#' + field.attr('id').substr(options.useprefix.length).substring(options.usesuffix.length)).attr('data-required-class'); if(prettyoverrideclass !== undefined) { prompt.addclass(prettyoverrideclass); } } } } prompt.css({ "opacity": 0 }); if(positiontype === 'inline') { prompt.addclass("inline"); if(typeof field.attr('data-prompt-target') !== 'undefined' && $('#'+field.attr('data-prompt-target')).length > 0) { prompt.appendto($('#'+field.attr('data-prompt-target'))); } else { field.after(prompt); } } else { field.before(prompt); } var pos = methods._calculateposition(field, prompt, options); // support rtl layouts by @yasser_lotfy ( yasser lotfy ) if ($('body').hasclass('rtl')) { prompt.css({ // 'position': positiontype === 'inline' ? 'relative' : 'absolute', // "top": pos.callertopposition, // "left": "initial", // "right": pos.callerleftposition, // "margintop": pos.margintopsize, "opacity": 0 }).data("callerfield", field); } else { prompt.css({ // 'position': positiontype === 'inline' ? 'relative' : 'absolute', // "top": pos.callertopposition, // "left": pos.callerleftposition, // "right": "initial", // "margintop": pos.margintopsize, "opacity": 0 }).data("callerfield", field); } if (options.autohideprompt) { settimeout(function(){ prompt.animate({ "opacity": 0 },function(){ prompt.closest('.formerror').remove(); }); }, options.autohidedelay); } return prompt.animate({ "opacity": 1 }); }, /** * 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, noanimation) { if (prompt) { if (typeof type !== "undefined") { 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); $(prompt).removeclass("run-animation"); //prompt.addclass("run-animation"); settimeout(function(){ $(prompt).addclass("run-animation"); },100) var pos = methods._calculateposition(field, prompt, options); // support rtl layouts by @yasser_lotfy ( yasser lotfy ) if ($('body').hasclass('rtl')) { var css = {"top": pos.callertopposition, "left": "initial", "right": pos.callerleftposition, "margintop": pos.margintopsize, "opacity": 1}; } else { var css = {"top": pos.callertopposition, "left": pos.callerleftposition, "right": "initial", "margintop": pos.margintopsize, "opacity": 1}; } prompt.css({ "opacity": 0, // "display": "block" }); if (noanimation) prompt.css(css); else prompt.animate(css); } }, /** * 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.closest('.formerror').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 formid = $(field).closest('form, .validationenginecontainer').attr('id'); var classname = methods._getclassname(field.attr("id")) + "formerror"; var match = $("." + methods._escapeexpression(classname) + '.parentform' + methods._getclassname(formid))[0]; if (match) return $(match); }, /** * returns the escapade classname * * @param {selector} * classname */ _escapeexpression: function (selector) { return selector.replace(/([#;&,\.\+\*\~':"\!\^$\[\]\(\)=>\|])/g, "\\$1"); }, /** * returns true if we are in a rtled document * * @param {jqobject} field */ isrtl: function(field) { var $document = $(document); var $body = $('body'); var rtl = (field && field.hasclass('rtl')) || (field && (field.attr('dir') || '').tolowercase()==='rtl') || $document.hasclass('rtl') || ($document.attr('dir') || '').tolowercase()==='rtl' || $body.hasclass('rtl') || ($body.attr('dir') || '').tolowercase()==='rtl'; return boolean(rtl); }, /** * 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 fieldleft = field.position().left; var fieldtop = field.position().top; var fieldheight = field.height(); var promptheight = promptelmt.height(); // is the form contained in an overflown container? prompttopposition = promptleftposition = 0; // compensation for the arrow margintopsize = -promptheight; //prompt positioning adjustment support //now you can adjust prompt position //usage: positiontype:xshift,yshift //for example: // bottomleft:+20 means bottomleft position shifted by 20 pixels right horizontally // topright:20, -15 means topright position shifted by 20 pixels to right and 15 pixels to top //you can use +pixels, - pixels. if no sign is provided than + is default. var positiontype=field.data("promptposition") || options.promptposition; var shift1=""; var shift2=""; var shiftx=0; var shifty=0; if (typeof(positiontype)=='string') { //do we have any position adjustments ? if (positiontype.indexof(":")!=-1) { shift1=positiontype.substring(positiontype.indexof(":")+1); positiontype=positiontype.substring(0,positiontype.indexof(":")); //if any advanced positioning will be needed (percents or something else) - parser should be added here //for now we use simple parseint() //do we have second parameter? if (shift1.indexof(",") !=-1) { shift2=shift1.substring(shift1.indexof(",") +1); shift1=shift1.substring(0,shift1.indexof(",")); shifty=parseint(shift2); if (isnan(shifty)) shifty=0; }; shiftx=parseint(shift1); if (isnan(shift1)) shift1=0; }; }; switch (positiontype) { default: case "topright": promptleftposition += fieldleft + fieldwidth - 27; prompttopposition += fieldtop; break; case "topleft": prompttopposition += fieldtop; promptleftposition += fieldleft; break; case "centerright": prompttopposition = fieldtop+4; margintopsize = 0; promptleftposition= fieldleft + field.outerwidth(true)+5; break; case "centerleft": promptleftposition = fieldleft - (promptelmt.width() + 2); prompttopposition = fieldtop+4; margintopsize = 0; break; case "bottomleft": prompttopposition = fieldtop + field.height() + 5; margintopsize = 0; promptleftposition = fieldleft; break; case "bottomright": promptleftposition = fieldleft + fieldwidth - 27; prompttopposition = fieldtop + field.height() + 5; margintopsize = 0; break; case "inline": promptleftposition = 0; prompttopposition = 0; margintopsize = 0; }; //apply adjusments if any promptleftposition += shiftx; prompttopposition += shifty; 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"); // --- internals do not touch or overload --- // validation rules and i18 $.validationengine.defaults.allrules = allrules; var useroptions = $.extend(true,{},$.validationengine.defaults,options); form.data('jqv', useroptions); return useroptions; }, /** * removes forbidden characters from class name * @param {string} classname */ _getclassname: function(classname) { if(classname) return classname.replace(/:/g, "_").replace(/\./g, "_"); }, /** * escape special character for jquery selector * http://totaldev.com/content/escaping-characters-get-valid-jquery-id * @param {string} selector */ _jqselector: function(str){ return str.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1'); }, /** * conditionally required field * * @param {jqobject} field * @param {array[string]} rules * @param {int} i rules index * @param {map} * user options * @return an error string if validation failed */ _condrequired: function(field, rules, i, options) { var idx, dependingfield; for(idx = (i + 1); idx < rules.length; idx++) { dependingfield = jquery("#" + rules[idx]).first(); /* use _required for determining wether dependingfield has a value. * there is logic there for handling all field types, and default value; so we won't replicate that here * indicate this special use by setting the last parameter to true so we only validate the dependingfield on chackboxes and radio buttons (#462) */ if (dependingfield.length && methods._required(dependingfield, ["required"], 0, options, true) == undefined) { /* we now know any of the depending fields has a value, * so we can validate this field as per normal required code */ return methods._required(field, ["required"], 0, options); } } }, _submitbuttonclick: function(event) { var button = $(this); var form = button.closest('form, .validationenginecontainer'); form.data("jqv_submitbutton", button.attr("id")); } }; /** * 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 form; // 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 != "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= {fieldidcounter: 0,defaults:{ // name of the event triggering field validation validationeventtrigger: "blur", // automatically scroll viewport to the first error scroll: true, // focus on the first input focusfirstfield:true, // show prompts, set to false to disable prompts showprompts: true, // should we attempt to validate non-visible input fields contained in the form? (useful in cases of tabbed containers, e.g. jquery-ui tabs) validatenonvisiblefields: false, // ignore the validation for fields with this specific class (useful in cases of tabbed containers and hidden fields we don't want to validate) ignorefieldswithclass: 'ignoreme', // opening box position, possible locations are: topleft, // topright, bottomleft, centerright, bottomright, inline // inline gets inserted after the validated field or into an element specified in data-prompt-target 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, // the url to send the submit ajax validation (default to action) ajaxformvalidationurl: false, // http method used for ajax validation ajaxformvalidationmethod: 'get', // ajax form validation callback method: boolean oncomplete(form, status, errors, options) // retuns false if the form.submit event needs to be canceled. 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 you have a form fields too close and the errors messages are on top of other disturbing viewing messages donotshowallerrosonsubmit: false, // object where you store custom messages to override the default error messages custom_error_messages:{}, // true if you want to validate the input fields on blur event binded: true, // set to true if you want to validate the input fields on blur only if the field it's not empty notempty: false, // set to true, when the prompt arrow needs to be displayed showarrow: true, // set to false, determines if the prompt arrow should be displayed when validating // checkboxes and radio buttons showarrowonradioandcheckbox: false, // did one of the validation fail ? kept global to stop further ajax validations iserror: false, // limit how many displayed errors a field can have maxerrorsperfield: 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: {}, // auto update prompt position after window resize autopositionupdate: false, invalidfields: [], onfieldsuccess: false, onfieldfailure: false, onsuccess: false, onfailure: false, validateattribute: "class", addsuccesscssclasstofield: "", addfailurecssclasstofield: "", // auto-hide prompt autohideprompt: false, // delay before auto-hide autohidedelay: 10000, // fade out duration while hiding the validations fadeduration: 300, // use prettify select library prettyselect: false, // add css class on prompt addpromptclass : "", // custom id uses prefix useprefix: "", // custom id uses suffix usesuffix: "", // only show one message per error prompt showonemessage: false }}; $(function(){$.validationengine.defaults.promptposition = methods.isrtl()?'topleft':"topright"}); })(jquery);