Here's my "old school" hack...
Below a utility function that allows the use of "asynchronous" validations with "jquery.validate.js" library. This function creates a delay between user keystrokes otherwise the validation function "validFunc" will be called "all time" which is not very performative in some circumstances and especially problematic for functions that perform validations on "serverside"/"backend" (ajax calls basically). In this way the "validFunc" validation function is only called when the user stops typing for a certain period of time which also allows a "realtime" validation ("onkeyup": true on jqv settings) as it occurs while the user is typing.
IMPORTANT: Validations involving the use of the "jqvAsyncValid" function should always be the last ones to avoid conflicts with the others due to the asynchrony.
{
[...]
"rules": {
"field_name": {
"required": true,
"maxlength": 12,
"minlength": 4,
// NOTE: Validation involving the use of the "jqvAsyncValid" function. By Questor
"my_custom_ajax_validation": true
},
[...]
}
ANSWER'S CODE:
// NOTE: Adds the custom validation "my_custom_ajax_validation". By Questor
$.validator.addMethod("my_custom_ajax_validation", function (value, element) {
return jqvAsyncValid(element, "my_custom_ajax_validation", myValidationFunc, this);
}, "My error message!");
// NOTE: My validation function. By Questor
function myValidationFunc(domElement) {
if (someFuncWithAjaxCall(domElement.value) == "ALL_RIGHT!") {
return true;
} else {
return false;
}
}
// NOTE: Global "json" variable that stores the "status" ("isValid") and cycle control
// ("toCtrl") of asynchronously validated elements using the "jqvAsyncValid" function.
// By Questor
var jqvAsyncVState = {};
// NOTE: A utility function that allows the use of asynchronous validations with
// "jquery.validate.js". This function creates a delay between one user keystroke and
// another otherwise the validation function "validFunc" will be called "all time"
// which is not very performative in some circumstances and especially problematic
// for functions that perform validations on the serverside/backend (ajax calls basically).
// In this way the "validFunc" validation function is only called when the user stops
// typing for a certain period of time, which also allows a "realtime" validation
// as it occurs while the user is typing. By Questor
// [Ref .: https://jqueryvalidation.org/ ]
//. domElement - DOM element informed by jqv in the "addMethod" for the anonymous
// function;
//. asyncRuleNm - Validation name added via "addMethod";
//. validFunc - Function that will do the validation. Must have the signature
// "funcName(domElement)" returning "true" for valid and "false" for not;
//. jQValidInst - Instance of the current jqv within "addMethod". It is usually
// denoted by "this";
//. toInMsecs - "Timeout" in "milliseconds". If not informed the default will be
// 1500 milliseconds. Be careful not to use a very short timeout especially in
// "ajax" calls so as not to overload the serverside/backend.
// Eg.: `return jqvAsyncValid(element, "my_custom_ajax_validation", myValidationFunc, this);`.
function jqvAsyncValid(domElement, asyncRuleNm, validFunc, jQValidInst, toInMsecs) {
if (typeof toInMsecs === "undefined" || toInMsecs === "") {
toInMsecs = 1500;
}
var domEKey = jQValidInst.currentForm.id + domElement.name;
// NOTE: The validation messages need to be "displayed" and "hidden" manually
// as they are displayed asynchronously. By Questor
function errMsgHandler() {
if (jqvAsyncVState[domEKey]["isValid"]) {
// NOTE: If the current error message displayed on the element was that
// set in the rule added via "addMethod" then it should be removed since
// the element is valid. By Questor
// [Ref.: https://stackoverflow.com/a/11652922/3223785 ,
// https://stackoverflow.com/a/11952571/3223785 ]
if (jQValidInst.errorMap[domElement.name] == $.validator.messages[asyncRuleNm]) {
var iMsgNow = {};
iMsgNow[domElement.name] = "";
jQValidInst.showErrors(iMsgNow);
}
} else {
var iMsgNow = {};
// NOTE: If the element is invalid, get the message set by "addMethod"
// for current rule in "$.validator.messages" and show it. By Questor
iMsgNow[domElement.name] = $.validator.messages[asyncRuleNm];
jQValidInst.showErrors(iMsgNow);
}
}
if (!jqvAsyncVState.hasOwnProperty(domEKey)) {
// NOTE: Set the global json variable "jqvAsyncVState" the control attributes
// for the element to be asynchronously validated if it has not already been
// set. The key "domEKey" is formed by the "id" of the "form" that contains
// the element and the element's "name". By Questor
jqvAsyncVState[domEKey] = {
"toCtrl": null,
"isValid": undefined
};
}
var useOnKeyup = true;
// NOTE: The "onblur" event is required for "first validation" that only occurs
// in a "blur" event - this is inherent to jqv - and for situations where the
// user types very fast and triggers "tab" and the event "onkeyup" can not deal
// with it. By Questor
domElement.onblur = function (e) {
jqvAsyncVState[domEKey]["isValid"] = validFunc(domElement);
errMsgHandler();
useOnKeyup = false;
}
if (useOnKeyup) {
// NOTE: The strategy with the event "onkeyup" below was created to create
// a "delay" between a "keystroke" and another one otherwise the validation
// function "validFunc" will be called "all time" which is not very performative
// in some circumstances and especially problematic for functions that perform
// serverside/backend validations (ajax calls basically). In this way the
// "validFunc" validation function is only called when the user stops typing
// for a certain period of time ("toInMsecs"). By Questor
domElement.onkeyup = function (e) {
// NOTE: Clear the "toCtrl" if it has already been set. This will
// prevent the previous task from executing if it has been less than
// "toInMsecs". By Questor
clearTimeout(jqvAsyncVState[domEKey]["toCtrl"]);
// NOTE: Make a new "toCtrl" set to go off in "toInMsecs" ms. By Questor
jqvAsyncVState[domEKey]["toCtrl"] = setTimeout(function () {
jqvAsyncVState[domEKey]["isValid"] = validFunc(domElement);
errMsgHandler();
}, toInMsecs);
};
}
return jqvAsyncVState[domEKey]["isValid"];
}