I have a page which does AJAX validation of an email before continuing on to the next page, using the HTML5 setCustomValidity() method [using the webshims library for older
As of jQuery 1.8, the use of async: false with jqXHR ($.Deferred) is deprecated; you must use the complete/success/error callbacks.
http://api.jquery.com/jQuery.ajax/ (async section)
I found this a bit annoying... developers should be given the choice to make a blocking call with async: false
if its something the platform allows - why restrict it? I'd just set a timeout to minimize the impact of a hang.
Nonetheless, I'm using a queue now in 1.8, which is non-blocking, and works quite nicely. Sebastien Roch created a easy to use utility that allows you to queue up functions and run/pause them. https://github.com/mjward/Jquery-Async-queue
queue = new $.AsyncQueue();
queue.add(function (queue) { ajaxCall1(queue); });
queue.add(function (queue) { ajaxCall2(queue); });
queue.add(function() { ajaxCall3() });
queue.run();
In the first 2 functions I pass the queue
object into the calls, here's what the calls would look like:
function ajaxCall1(queue) {
queue.pause();
$.ajax({
// your parameters ....
complete: function() {
// your completing steps
queue.run();
}
});
}
// similar for ajaxCall2
Notice the queue.pause();
at the beginning of each function, and queue.run()
to continue queue execution at the end of your complete
statement.
You can do it asynchronously.
Create a global variable, I call it ajax_done_and_successful_flag, that you initialize to false at the beginning of the program. You will set this to true or false at various places in your Ajax functions such as your Ajax success function or your Ajax error function.
Then you need to add a submit handler at the bottom of your validate function.
submitHandler: function(form) {
if (ajax_done_and_successful_flag === true) {
form.submit()
}
}
The problem is the code is not executing in a linear way.
Put a bunch of Firebug's console.log statements in your code.
Observe the sequence of execution. You will see that your
Ajax response will come back last, or whenever it feels like it.
That's why you need the submitHandler AND the global flag
to force the validate function to wait for correct Ajax
results before the form is submitted.
Any output to the screen from the Ajax response,
needs to be done in the
Ajax functions, such as the success function and the
error function.
You need to write to the same location
as the validate function's success/error functions.
This way the Ajax error messages blend in with the validate function's
error function.
This concept may seem a bit tricky.
The idea to keep in
mind is that the success and error functions in the validate function
are writing to the same location as the success and error
functions in the Ajax call, and that is okay, that is how
it should be.
The location of my error messages is right next to where the
user types the input. This creates a nice user experience
that I think you are asking for.
Here is my code sample. I simplified it.
I am running jQuery-1.7.1
and jQuery validation plug-in 1.6
I am using Firefox 14.0.1 and I also tried
it on Chrome 21.0 successfully.
var ajax_done_and_successful_flag = false;
// Add methods
...
$.validator.addMethod("USERNAME_NOT_DUPLICATE", function (value, element) {
return this.optional(element) || validate_username_not_duplicate( );
},
"Duplicate user name.");
// validate
$(document).ready(function ( ) {
$('#register_entry_form form').validate({
rules: {
username: {
required: true,
minlength: 2,
maxlength: 20,
USERNAME_PATTERN: true,
USERNAME_NOT_DUPLICATE: true
},
...
errorPlacement: function (error, element) {
element.closest("div").find(".reg_entry_error").html(error);
},
success: function(label) {
label.text('Success!');
} ,
submitHandler: function(form) {
if (ajax_done_and_successful_flag === true ) {
form.submit();
}
}
});
});
/* validation functions*/
function validate_username_not_duplicate() {
var username_value = $('#username').val(); // whatever is typed in
$.ajax({
url: "check_duplicate_username.php",
type: "POST",
data: { username: username_value },
dataType: "text",
cache: false,
//async: false,
timeout: 5000,
error: function (jqXHR, errorType, errorMessage) {
ajax_done_and_successful_flag = false;
if ( errorType === "timeout" ) {
$('#username').closest("div").find(".reg_entry_error").html("Server timeout, try again later" );
} else if ...
},
success: function (retString, textStatus,jqXRH) {
if ( retString === "yes") { // duplicate name
// output message next to input field
$('#username').closest("div").find(".reg_entry_error").html("Name already taken.");
ajax_done_and_successful_flag = false;
} else if ( retString === "no") { // name is okay
// output message next to input field
$('#username').closest("div").find(".reg_entry_error").html("success!");
ajax_done_and_successful_flag = true;
} else {
console.log("in validate_username_duplicate func, success function, string returned was not yes or no." );
$('#username').closest("div").find(".reg_entry_error").html("There are server problems. Try again later.");
ajax_done_and_successful_flag = false;
}
} // end success function
}); // end ajax call
return true; // automatically return true, the true/false is handled in
// the server side program and then the submit handler
}
The reg_entry_error is the place right beside the text input. Here is a simplified code sample of the form.
<label class="reg_entry_label" for="username">Username</label>
<input class="reg_entry_input" type="text" name="username" id="username" value="" />
<span class="reg_entry_error" id="usernameError" ></span>
I hope this answers your question.
http://bugs.jquery.com/ticket/11013#comment:40
The use of the Deferred/Promise functionality in synchronous ajax requests has been deprecated in 1.8. The $.ajax method with async: false is supported but you must use a callback parameter rather than a Promise method such as
.then
or.done
.
So, if you are using the success/complete/error handlers, you can still use async:false
. More info at the jquery ticket above.
I assume you will be doing full form validation on submit anyway, so perhaps it's no problem if the as-you-type email validation is interrupted, and precedence given to the form submission.
I think what I'd do is abort the 'validateEmailRegistered' AJAX request when the user submits the form. Then, perform full server-side form validation as usual - including email validation.
My understanding of as-you-type validation is that it's a nicety, not a substitute for validating the form when it is submitted. So it doesn't make sense to me for it to block the form submission.
If you are really set on not letting them submit the form until you have checked the email validity, start out your submit button with the disabled
attribute, then have the callback set $('form button').removeAttr('disabled');
That being said, I'm with the other people - just let them submit the form! Usually people get it right and you pass them right on through with no errors...
I think you need to change a bit how you it works. Don't try to achieve blocking, but embrace non-blocking.
I would do like this: - Keep the validation on email; make it Asynchronous. When it's valid, set a flag somewhere in a variable to know it's ok. - Add a callback on the form.submit() to check whether the email is ok (with the variable) and prevent the submission if it's not.
This way you can keep asynchronous call without freeze the web browser UI.
-- [edit] --
This is some quick code I just wrote for the example based on what you already have.
For your information, a programming notion called "promises" (futures and deferred are other terms for it) has been invented to solve exactly the problem you have.
Here's an article on what it is and how to use them in JavaScript (using dojo or jQuery): http://blogs.msdn.com/b/ie/archive/2011/09/11/asynchronous-programming-in-javascript-with-promises.aspx
<script>
function validateEmailRegistered(input) {
if (input.setCustomValidity === undefined) return;
var err = 'Email address not found';
$.ajax({
url: 'email-is-registered.php',
data: { email: input.value },
success: function(data) {
var ok = data=='true';
input.setCustomValidity(ok ? '' : err);
// If the form was already submited, re-launch the check
if (form_submitted) {
input.form.submit();
}
}
});
}
var form_submitted = false;
function submitForm(form) {
// Save the user clicked on "submit"
form_submitted = true;
return checkForm(form);
}
function checkForm(form, doSubmit) {
if (doSubmit) {
form_submitted = true;
}
// If the email is not valid (it can be delayed due to asynchronous call)
if (!form.email.is_valid) {
return false;
}
return true;
}
</script>
<form method="get" action="nextpage.php" onsumbit="return submitForm(this);">
<input name="email" id="email" type="email" required
onChange="validateEmailRegistered(this)">
<button type="submit">go</button>
<form>