/*
* 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 = $('