27
$("#new_component_form").validate({
  errorClass: 'input-error',
  rules : {
    "comp_data[account_name]" : {
      required: true,
      remote: {
        url: "/validate",
        data: {
          provider: 'twitter'
        }
      }
    }
  },
  onsubmit: true,
  onfocusout: false,
  onkeyup: false,
  onclick: false
});



 $("#new_component_form").submit(function(){
    console.log($(this).valid());

This outputs true, even if the value is invalid. I see the validation eventually fail and show the error message but the form is still submitted.

oreoshake
  • 4,712
  • 1
  • 31
  • 38

9 Answers9

31

As of jQuery Validate 1.11.1 (and perhaps even older), the accepted answer does not work. In addition, there is no easy answer to this question, and the solution requires adding a custom validation method to jQuery Validation.

Actually, the easy answer may just be: Don't call valid() manually if all you want to do is submit the form. Just let the Validate plugin do it for you. Internally, it will wait for all asynchronous requests to complete before allowing the form to be submitted. This issue only arises when you are manually checking valid() or element().

However, there are plenty of reasons why you may need to do that. For instance, the page I am working on needs to check the validity of a field using a remote validator before enabling the rest of the form. I could just do it by hand instead of using jQuery Validation, but that's a duplication of effort.

So, why does setting async: false not work? If you set async to false, the request will be made synchronously, however, the plugin doesn't handle this correctly. The internal remote function always returns "pending" which will cause the valid() function to return true even if the request is already complete and received a false response! It doesn't check the value of the response or show the error until later.

The solution to making valid() and element() behave synchronously when using a synchronous callback is to add a custom validation method. I've tried this myself, and it seems to work fine. You can just copy the source code from the regular remote validation and modify it to handle synchronous ajax calls, and be synchronous by default.

The source code of of the remote function in v1.11.1 starts on line 1112 of jquery.validate.js:

remote: function( value, element, param ) {
    if ( this.optional(element) ) {
        return "dependency-mismatch";
    }

    var previous = this.previousValue(element);
    if (!this.settings.messages[element.name] ) {
        this.settings.messages[element.name] = {};
    }
    previous.originalMessage = this.settings.messages[element.name].remote;
    this.settings.messages[element.name].remote = previous.message;

    param = typeof param === "string" && {url:param} || param;

    if ( previous.old === value ) {
        return previous.valid;
    }

    previous.old = value;
    var validator = this;
    this.startRequest(element);
    var data = {};
    data[element.name] = value;
    $.ajax($.extend(true, {
        url: param,
        mode: "abort",
        port: "validate" + element.name,
        dataType: "json",
        data: data,
        success: function( response ) {
            validator.settings.messages[element.name].remote = previous.originalMessage;
            var valid = response === true || response === "true";
            if ( valid ) {
                var submitted = validator.formSubmitted;
                validator.prepareElement(element);
                validator.formSubmitted = submitted;
                validator.successList.push(element);
                delete validator.invalid[element.name];
                validator.showErrors();
            } else {
                var errors = {};
                var message = response || validator.defaultMessage( element, "remote" );
                errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message;
                validator.invalid[element.name] = true;
                validator.showErrors(errors);
            }
            previous.valid = valid;
            validator.stopRequest(element, valid);
        }
    }, param));
    return "pending";
}

Notice how it always returns "pending" even if the ajax call is complete.

To fix this issue, make the following modifications:

  1. Move the declaration of the valid variable outside of the ajax call and the success function in order to make a closure, and assign it a default value of "pending".
  2. Change the old declaration of the valid variable to an assignment.
  3. Return the valid variable instead of the constant "pending".

Here's the complete code for a plugin to the plugin. Just save this as a js file and include it in your page or template after the include for jQuery Validation:

//Created for jQuery Validation 1.11.1
$.validator.addMethod("synchronousRemote", function (value, element, param) {
    if (this.optional(element)) {
        return "dependency-mismatch";
    }

    var previous = this.previousValue(element);
    if (!this.settings.messages[element.name]) {
        this.settings.messages[element.name] = {};
    }
    previous.originalMessage = this.settings.messages[element.name].remote;
    this.settings.messages[element.name].remote = previous.message;

    param = typeof param === "string" && { url: param } || param;

    if (previous.old === value) {
        return previous.valid;
    }

    previous.old = value;
    var validator = this;
    this.startRequest(element);
    var data = {};
    data[element.name] = value;
    var valid = "pending";
    $.ajax($.extend(true, {
        url: param,
        async: false,
        mode: "abort",
        port: "validate" + element.name,
        dataType: "json",
        data: data,
        success: function (response) {
            validator.settings.messages[element.name].remote = previous.originalMessage;
            valid = response === true || response === "true";
            if (valid) {
                var submitted = validator.formSubmitted;
                validator.prepareElement(element);
                validator.formSubmitted = submitted;
                validator.successList.push(element);
                delete validator.invalid[element.name];
                validator.showErrors();
            } else {
                var errors = {};
                var message = response || validator.defaultMessage(element, "remote");
                errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message;
                validator.invalid[element.name] = true;
                validator.showErrors(errors);
            }
            previous.valid = valid;
            validator.stopRequest(element, valid);
        }
    }, param));
    return valid;
}, "Please fix this field.");

I've tested this with my own form and it works great. I can test my element for validity before enabling the rest of the form. However, you probably want to set onkeyup: false to prevent performing a synchronous callback on every key press. I also like to use onfocusout: false.

To use this, just replace "remote" in your validation settings with "synchronousRemote" everywhere you'd like to use this. For example:

$("#someForm").validate({
    rules: {
        someField: {
            required: true,
            synchronousRemote: {
                    url: "/SomePath/ValidateSomeField"
                    //notice that async: false need not be specified. It's the default.
            }
        }
    },
    messages: {
        someField: {
            required: "SomeField is required.",
            synchronousRemote: "SomeField does not exist."
        }
    },
    onkeyup: false,
    onfocusout: false
});
Glazed
  • 2,048
  • 18
  • 20
  • 2
    Do you think this is a bug in jQuery Validate? – lboullo0 Dec 30 '13 at 16:02
  • 2
    I don't think it's a bug, per se, but a design choice which has negative consequences. They certainly knew what they were doing when they made asynchronous remote validators work when the form is submitted. I don't know why they didn't give us the choice to specify `async: false` at times when we needed it, though. Maybe they didn't want to give developers a chance to ruin the UI by making it synchronous and leaving `onkeyup: true`. Imagine a 1 second delay on every keystroke. But there might be a way to abort the synchronous ajax request if another keypress is detected. I'm not sure. – Glazed Jan 18 '14 at 01:53
  • No idea why this isn't the accepted answer, thank you very much for this solution and especially for taking time to explain why. – 0plus1 Feb 07 '14 at 02:42
  • It's a 2.5 year old question, and I added my answer about 6 weeks ago. I think that's why. The accepted answer probably worked in jQuery from long ago. Plus the question's author may not have revisited this post since I answered it. – Glazed Feb 07 '14 at 16:19
  • async: false - this is solution – Ivan Apr 11 '14 at 08:32
  • @Ivan What version? Did they change the behavior of async: false to fix this issue? As of 1.11.1 that does not help, not when when you are manually checking the form with `valid()` or `element()`. – Glazed Apr 11 '14 at 14:59
  • Thank you very much! Can't understand why they did this change, it's pointless now to use the normal remote function. Do you know how I can change the error message according to the remote response (as it was before)? – rotsch Apr 24 '14 at 14:45
  • @ChoptimusPrime I tried to return custom error message from server, which works fine with **remote** but failed with **synchronousRemote**. The **synchronousRemote** always show the default error message. Do you know how to modify this function so I can show message sent from server? – mevernom Dec 04 '14 at 03:40
  • So your whole form freezes whenever you validate a field? That's not very user friendly... – Andrei Dvoynos Dec 04 '14 at 15:49
  • 2
    @mevernom I suggested an edit but I got rejected. Just add this line when the validation fails in the sucess callback: that.settings.messages[element.name].synchronousRemote = message; – ChoptimusPrime Dec 04 '14 at 18:55
  • Also, this answer has some copy pasta side effects from the remote validator where the remote validator messages are being set instead of the synchronousRemote, eg: previous.originalMessage = this.settings.messages[element.name].remote; – ChoptimusPrime Dec 04 '14 at 18:57
  • @ChoptimusPrime I never rejected an edit, nor do I recall the system asking me to review one. Am I the one who would need to approve it? Or did some mod reject it? – Glazed Dec 04 '14 at 19:18
  • @AndreiDvoynos The point of this plugin is to force the page to wait for validation to occur **after the user clicks the submit button**. Otherwise is doesn't wait and submits despite the errors. *That* would be non user friendly. – Glazed Dec 04 '14 at 19:20
  • @ChoptimusPrime Thank you, it works. Would you also reply your correction in [this question](http://stackoverflow.com/questions/27287284/let-synchonousremote-show-custom-error-message-from-server) so I can accept yours as the answer? – mevernom Dec 05 '14 at 01:12
  • @Glazed I'm not saying that the form shouldn't wait for validation, it should, but it should also be responsive, allowing the user to do whatever he feels like while it is validating - or at least showing a modal window with a loading gif and a cancel button. I never do sync ajax calls as a rule of thumb, having a frozen webpage feels awful. – Andrei Dvoynos Dec 05 '14 at 16:50
  • @Glazed What I used to solve this issue is to check as well for validator.pendingRequest. If it is > 0, something's not finished yet, and form should be invalid. – Andrei Dvoynos Dec 05 '14 at 16:52
  • @Glazed Not sure who approved/rejects to be honest. According to my account the following users rejected my edit: Krom Stern, Roman Goyenko, Cfreak. – ChoptimusPrime Dec 05 '14 at 19:30
  • 1
    @mevernom Wish I could, looks like it got flagged as a dup. I will say though that this isn't my favorite solution given the sync issues. I used it in a project with JTable for a very simple case to get around hacking apart the library. – ChoptimusPrime Dec 05 '14 at 19:42
  • @Alexander Get the full version, not the minified one. Then copy and paste the remote validator into your own plugin and fix the issue. I doubt you can just use what I have here. The code has probably changed too much since v1.11. – Glazed Feb 29 '16 at 20:09
  • @Glazed the error was not related to the method. However, finally the problem I have is still not resolved. If to be precise: i have several "bound" fields, the set of those field should be unique. After I change one field and the set gets unique - other fields keep being marked as erroneous... :( P.S. Remote method returns correct true / false value after each change. I've tried to loop via those fields and call valid() for them, still nothing...I think I even don't need to call valid() - the other fields ARE valid, because method calculates uniqueness for whole set. But i need to clear error – Alexander Feb 29 '16 at 20:28
9

Bumped into the same issue it seems that you have the set the remote call to synchronous - async: false

Otherwise the $("form").valid() will return true for the remote validation, please see below what I use

rules: {
    NameToValidate: {
        required: true,
        remote: function()
        {
            return {
            type: "POST",
            async: false,
            url: "www.mysite.com/JSONStuff",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            data: JSON.stringify( {
                Name: "UNIQUE NAME"
            } )
        }
    }
},
.....
Glazed
  • 2,048
  • 18
  • 20
Bogdan
  • 1,323
  • 15
  • 15
  • 2
    This does not work. At least not in v1.11.1. Internally, the remote method always returns `"pending"` (not true or false), so even if the validation is performed synchronously, the validation framework treats it as not when you manually call `valid()`, and `valid()` will always return true. However, it's always sort of synchronous when actually submitting the form: form submission will be suppressed until all asynchronous requests complete. So, even if you set the request to be synchronous, it will never cause `valid()` to return `false`. You need to modify the source to achieve that. – Glazed Dec 23 '13 at 19:04
  • 1
    This worked for me in version 1.8. I also set onkeyup to false, to avoid making a remote call for every key press. You can also set onkeyup to a function that returns false for the inputs that require remote validation, in case you still want to run validation on non-remote inputs as they are being modified. – Derek Kurth May 27 '15 at 17:01
  • Well this worked for me. Using jQuery v3.6.0 to validate a form field by calling a API controller endpoint. – Jan Mar 23 '21 at 13:12
8

Here's a solution that we came up with on my project.

var validPendingTimeout;

function doSomethingWithValid() {
  var isValid = $("#form").valid();
  var isPending = $("#form").validate().pendingRequest !== 0;

  if (isPending) {
    if (typeof validPendingTimeout !== "undefined") {
      clearTimeout(validPendingTimeout);
    }
    validPendingTimeout = setTimeout(doSomethingWithValid, 200);
  }

  if (isValid && !isPending) {
    // do something when valid and not pending
  } else {
    // do something else when not valid or pending
  }
}

This takes advantage of the fact that $("#form").validate().pendingRequest will always be > 0 whenever there is a remote validation.

Note: this will not work with setting async: true in the remote request.

Paul B.
  • 153
  • 2
  • 8
7

The easiest way to fix this problem (it's reported here: https://github.com/jzaefferer/jquery-validation/issues/361) is to check twice if the form is valid:

if (myForm.valid() && myForm.valid()) {
    ...
}

Setting the remote rules to async:false and checking for validity using valid() actually waits for the calls to finish, but their return values are not used. Calling valid() again takes them into account.

A bit ugly but it works. I'm using jquery 2.1.0 and jquery-validation 1.11.1, BTW.

emilast
  • 559
  • 1
  • 5
  • 11
  • This answer is old as shit but i'm facing this problem right now because jquery-steps plugin. Whenever user click next and there is a form to validate with remotes actions, wizard go forward to next step because .valid() return true. This answer solve my problem, but after fixing some detail I've observed, OP uses "&&" exclusive and, and that's wrong, must be "&" standard and so second expression got evaluated, otherwise it doesn't. Hope this help. – ClownCoder Jun 21 '19 at 01:04
6

I solved this by creating a custom function that checks both $("#form").valid() and $("#form").validate().pendingRequest:

function formIsValid() {
    return $("#form").valid() && $("#form").validate().pendingRequest === 0;
}

This works on the assumption that $("#form").validate().pendingRequest will always be > 0 during a remote validation request. This works using jquery-validation 1.19.1.

Gilbert
  • 311
  • 1
  • 4
  • 6
1

You could validate the form twice or check for the current ajax call to finish.

if (myForm.valid() && myForm.valid()) {

}

or

if ($.active == 0) {

}

So far the best solution i found was mentioned in the top of this thread. https://stackoverflow.com/a/20750164/11064080

But there is no proper solution. If you use two remote validation function in the same form all the above approaches will fail.

Muditha
  • 11
  • 1
0

I know this is patch but I have fixed it by checking current ajax is pending

I check current ajax call by $.active, It will give you 0 if not ajax is running

$('#frmControlFamily #Identifier').valid();            
if ($.active == 0) {
    // my save logic
}
programtreasures
  • 4,250
  • 1
  • 10
  • 29
0

You have to stop the normal submit of the browser in case that the validation returns false.

$("#new_component_form").submit(function() {
    if ($(this).valid())) {
        console.log("I'm a valid form!");
    } else {
        console.log("I'm NOT a valid form!");
        //Stop the normal submit of the browser
        return false;
    }
}
ElHacker
  • 1,687
  • 17
  • 18
  • 6
    My issue is that if ($(this).valid())) { console.log("I'm a valid form!"); is returning true even if the form is invalid – oreoshake Sep 07 '11 at 17:23
0

2022 answer: This is for Kendo but I don't see why you couldn't convert it to jQuery. It checks if ajax is active on an interval (it will be during remote validation).

async function asyncValidate() {
    const isAjaxActive = () => $.active > 0;
    validator.validate();
    return new Promise(resolve => {
        var interval = setInterval(() => {
            if (!isAjaxActive()) {
                clearInterval(interval);
                resolve(validator.errors()?.length == 0 ?? true);
            }
        }, 25);
    });
}

if (!await asyncValidate()) {
    // failed validation
}
M W
  • 86
  • 6