jQuery validator and a custom rule that uses AJAX

85,100

Solution 1

You are doing an AJAX request, ergo: the validation is already finished working when your custom validator returns either true or false.

You will need to work with async. See also this post: How can I get jQuery to perform a synchronous, rather than asynchronous, Ajax request?

Something like:

function myValidator() {
   var isSuccess = false;

   $.ajax({ url: "", 
            data: {}, 
            async: false, 
            success: 
                function(msg) { isSuccess = msg === "true" ? true : false }
          });
    return isSuccess;
}

Warning:

As of jQuery 1.8, the use of async: false with jqXHR ($.Deferred) is deprecated; you must use the success/error/complete callback options instead of the corresponding methods of the jqXHR object such as jqXHR.done() or the deprecated jqXHR.success().

Solution 2

For anyone else who stumbles upon this, validate supports 'remote' method, which may not have existed in 2010:

https://jqueryvalidation.org/remote-method/

$("#myform").validate({
  rules: {
    email: {
      required: true,
      email: true,
      remote: {
        url: "check-email.php",
        type: "post",
        data: {
          username: function() {
            return $("#username").val();
          }
        }
      }
    }
  }
});

Solution 3

It took me forever to figure out how to get a jsonified string containing the value of an element in the page into the remote request- this is the result of a combination of many hours and trying many search results.

Key points:

  1. async:false has been deprecated,
  2. the function call right after remote: is the key for creating the data string with the element's value. Trying to access a current value from the form after data: was returning a blank value for the field with dataType set as json.

        $("#EmailAddress").rules("add", {
        required: true,
        remote: function () { // the function allows the creation of the data string 
                              // outside of the remote call itself, which would not 
                              // return a current value from the form.
            var emailData = "{'address':'" + 
                            $('#SignupForm :input[id$="EmailAddress"]').val() + 
                            "'}";
            var r = {
                url: "foobar.aspx/IsEmailAvailable",
                type: "post",
                dataType: "json",
                contentType: "application/json; charset=utf-8",
                cache: false,
                data: emailData,
                dataFilter: function(response) {
                    this.email_run = true; //fix for race condition with next button
                    return isAvailable(data); //return true or false
                }
            };
            return r;
        },
        messages: {
            remote: "* Email in use"
        }
    });
    

.ASPX page:

<input id="EmailAddress" required name="Email" type="email" placeholder="Email Address*" runat="server"/>

C# Code Behind:

[WebMethod]
    public static object IsEmailAvailable(string address){...}

Formatting the response object:

function isAvailable(data) {
    var response = JSON.parse(getJsonObject(data));
    return (response.isAvailable === "True") ? true : false;
};

//transform response string to a JavaScript Object()
//http://encosia.com/never-worry-about-asp-net-ajaxs-d-again/ 
function getJsonObject(data) {
    var msg = eval('(' + data + ')');
    if (msg.hasOwnProperty('d'))
        return msg.d;
    else
        return msg;
};

Solution 4

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"];
}
Share:
85,100
Admin
Author by

Admin

Updated on July 09, 2022

Comments

  • Admin
    Admin almost 2 years

    I read your reply regarding the jQuery validator where you outline a method to check a username against a value in a database.

    Ive tried implementing this method but no matter what is returned from the PHP file I always get the message that the username is already taken.

    Here is ths custom method...

    $.validator.addMethod("uniqueUserName", function(value, element) {
      $.ajax({
          type: "POST",
           url: "php/get_save_status.php",
          data: "checkUsername="+value,
          dataType:"html",
       success: function(msg)
       {
          // if the user exists, it returns a string "true"
          if(msg == "true")
             return false;  // already exists
          return true;      // username is free to use
       }
     })}, "Username is Already Taken");
    

    And here is the validate code...

    username: {
        required: true,
        uniqueUserName: true
    },
    

    Is there a specific way i am supposed to return the message from php.

    Thanks

    A

  • ndemoreau
    ndemoreau almost 11 years
    To make it work, I had to change msg==="true" to msg===true (Firefox). Thanks for the solution by the way!
  • Casey
    Casey over 10 years
    To anyone stumbling on this in the future, async has been deprecated. See Tim's answer below.
  • SourceVisor
    SourceVisor about 9 years
    with respect to your statement - 1. async:false has been deprecated,.. Yes, BUT it's still allowed provided you use the appropriate callback/handler such as ( complete: , success: , error: ) rather than a Promise method such as .then or .done... See the following reference here ==> bugs.jquery.com/ticket/11013#comment:40
  • Varadha31590
    Varadha31590 over 8 years
    This will freeze the page if server validation takes time.
  • Raghav
    Raghav about 8 years
    Is this making async request or sync request?
  • ekkis
    ekkis over 7 years
    @Raghav, this is an asynchronous validation
  • Jaswinder
    Jaswinder over 7 years
    could you please let me know how to delay this remote request?
  • wattry
    wattry over 5 years
    Based on the depreciation of async: false, I think that this should not be the correct answer