ajax success callback executes out of expected order - javascript

I have a function that validates some fields when a button is pressed. First one checks if the username and password are authentic or not. then if the password is appropriately secure. Then if it matches the confirm-password box. However, it seems that the ajax that check to see if the user is authentic does not complete before the alert in the first function pops up.
$(document).ready(function() {
$('#submit_pw_ch').on('click', function(){
var alertString = "Please correct the following errors: \n"
//current username and password are correct
var vUsr = valUsr();
//new password is sufficiently secure
var vPwd = valPwd();
//confirmation of new password is same as previous box
var sPwd = samePwd();
console.log('valid user : ' + vUsr + ' valid password : ' + vPwd + ' same password : ' + sPwd);
//append appropriate warnings to alert string
if ( !vUsr ) { alertString += "-Your current username and password are invalid. \n"; }
if ( !vPwd ) { alertString += "-The new password you have selected is not strong enough. \n"; }
if ( !sPwd ) { alertString += "-The confirmation of your new password does not match the previous entry. \n"; }
if ( !vUsr || !vPwd || !sPwd ) {
alert(alertString);
return false;
} else {
//change password
}
});
});
So the line that checks for that is var vUsr = valUsr(); which calls
function valUsr() {
var un = $('#uNameInput').val();
var pw = $('#uPwdInput').val();
//return value
var validUsr = false;
$.ajax({
type: "post",
url: "queries/confirm_user.php?<?=time()?>",
data: "un=" + un + "&pw=" + pw,
dataType: "json",
success: function (returnedData) {
console.log(returnedData)
if (data == 'true') {
validUsr = true;
} else {
validUsr = false;
}
}
});
return validUsr;
}
Somehow though the alert is not waiting for the ajax to finish getting it's data. The console.log(returnedData) in the valUsr() function appears in the console after I've dismissed the alert box. Why is this happening? How can I prevent it? Thanks!

Thomas,
You need to cater for the inherent asynchronicity of ajax, in other words you need to wait until a response to the ajax request has arrived before deciding what to do.
jQuery's Deferreds (and promises) allow us to write simple code but Deferreds tend to blow you mind at first, at least very slightly.
There's no unique way in which to use Deferreds for a problem like this but here's one.
$(document).ready(function() {
$('#submit_pw_ch').on('click', function() {
var form = this.form; //the form containing the submit button and the fields .
//`alertArr` is an array to which messages will be added.
var alertArr = ["Please correct the following errors:"];
//`addAlert` will be called from a `Deferred.resolveWith(this, ...)` statement.
//The context, `this`, is unused.
function addAlert(index, txt) {
alertArr[index] = txt;
}
//`compositeAction` will be called from a promise.done() statement.
function compositeAction() {
//first filter out null messages (ie. validation successes) from alertArr.
var message = $.map(alertArr, function(txt, i){
return txt || null;
});
if(message.length > 1) {
//then alert the complete message with line breaks
alert(message.join("\n"));
} else {
//submit the form to change the password
//or another ajax call as required
form.submit();
}
}
// Invoke ajax validators and assign returned promises.
// An index is passed, so the text messages can be displayed in a logical order,
// regardless of the order in which the validation promises are resolved.
//If we didn't care about the order of the messages then the code would be slighly simpler.
var vUsr = valUsr(0),
vPwd = valPwd(1),
sPwd = samePwd(2);
//All validations adopt a similar pattern regardless of whether ajax is involved or not.
//Here, we establish what is to be done when the promise are resolved, or
//what is to be done immediately if the promise are alrady resolved.
vUsr.done(addAlert);
vPwd.done(addAlert);
sPwd.done(addAlert);
//At this stage, `addAlert` will contain entries for successful as well as unsuccessful validations. Successful entries will be filtered out by `compositeAction`
//Establish what is to be done when all three promises are resolved.
$.when(vUsr, vPwd, sPwd).done(compositeAction);
//Return false unconditionally
return false;
});
function valUsr(index) {
var messages = {
validated: '',//important - this message must be an empty string - do not change
notValidated: '- Your current username and password are invalid.',
ajaxError: '- Validation error: username and password.'
};
//Create a Deferred object, which will be resolved in response to several different outcomes.
var def = $.Deferred();
$.ajax({
type: "post",
url: "queries/confirm_user.php?<?=time()?>",
data: {
'un': $('#uNameInput').val(),
'pw': $('#uPwdInput').val()
},
dataType: "json",
success: function (returnedData) {
if (returnedData == 'true') {
def.resolveWith(this, [index, messages.validated]);
} else {
def.resolveWith(this, [index, messages.notValidated]);
}
},
error: function() {
def.resolveWith(this, [index, messages.ajaxError]);
}
});
return def.promise();//Return a promise derived from the as yet unresolved Deferred.
}
function samePwd(index) {
var pw1 = $('#uPwdInput').val();
var pw2 = $('#uPwdInput2').val();
var errMessage = (pw1 === pw2) ? '' : '-The confirmation of your new password does not match the previous entry';
var def = $.Deferred();//Create a Deferred object, which will be resolved immediately
def.resolveWith(this, [index, errMessage]);
return def.promise();//Return a promise derived from the already resolved Deferred.
}
});
valPwd() will be of the same format as either valUsr() or samePwd(), depending on whether ajax is involved or not.

Ajax is run on the fly, synchronously
You will need to check the validation of the other fields after the ajax request has completed in the success callback. You can turn off the synchronous request but the browser will freeze up 'till it gets one, not advised.
You will need to restructure your calls to reflect this; I would suggest that as soon as they have finished typing the password and the field blurs you send the request to check. That way, if there are any errors you will be able to prevent the wait time at the end of the form.

Related

jQuery Validation with promises

I'm using the jQuery Validation plugin for a signup form and trying to check if an email address is already taken as part of the validation. The problem is that the check happens via a promise, and I don't know how (or if) I can make this work with the validation plugin. Here's what I have currently:
$("#signup form").validate({
rules: {
emailSignup: {
email: true,
required: true,
remote: checkAvailable
}
}
});
function checkAvailable() {
var email = $("#emailSignup").val(),
available;
App.isEmailAvailable(email)
.then(function(response) {
available = response;
})
.error(function() {
available = response;
});
setTimeout(function() {
if (available == true) {
console.log("is available");
return true;
} else {
console.log("not available");
return false;
}
}, 100);
}
The setTimeout is just a hacky way to ensure I have the response before I try to log it (I know that's terrible, but it's just for testing).
The thing is, this will correctly log is available or not available depending on the email address I enter, so it works up to that point. But I can't seem to actually return true/false back up to the remote method so that it kicks off the error handling. So what happens is anything that's a syntactically valid email gets marked as valid, even if it's logging not available based on the response.
Is this at all possible?
Update: I also tried doing this in a custom method:
jQuery.validator.addMethod("email", function(value, element) {
var field = this,
available;
App.isEmailAvailable(value)
.then(function(response) {
available = response;
})
.error(function() {
available = response;
});
setTimeout(function() {
if (available == true) {
console.log("is available");
return field.optional(element) || /^[\w-+\.]+#([\w-]+\.)+[\w-]{2,4}$/.test(value);
} else {
console.log("not available");
return false;
}
}, 100);
}, jQuery.validator.format("Please enter a valid email address."));
So the idea here is it would check if the address is available, and if it is, then it would check that it's syntactically valid. Again, it correctly logs is available/not available based on the address I enter, but doesn't return true/false properly to mark the field as valid or invalid.
Based on the "remote" jquery validate rule :
$.validator.addMethod('users_email_exists', function (value, element) {
var method = 'remote';
var previous = this.previousValue(element, method);
var validator = this;
if (!this.settings.messages[element.name]) {
this.settings.messages[element.name] = {};
}
previous.originalMessage = previous.originalMessage || this.settings.messages[element.name][method];
this.settings.messages[element.name][method] = previous.message;
var optionDataString = $.param({data: value});
if (previous.old === optionDataString) {
return previous.valid;
}
previous.old = optionDataString;
this.startRequest(element);
new Promise(function (fulfill) {
// YOUR STUFF, YOUR AJAX GET/POST REQUEST AND URL WITH PARAMS
$.get('/backend/users/ajax/filtered-users-list', {email: value})
.done(function (data) {
// YOUR STUFF TO VALIDATE DATA
// IF VALID TRUE -> validation success
// IF VALID FALSE -> validation failure
var valid = !data.length;
fulfill(valid);
})
}).then(function(valid) {
validator.settings.messages[ element.name ][ method ] = previous.originalMessage;
if ( valid ) {
submitted = validator.formSubmitted;
validator.resetInternals();
validator.toHide = validator.errorsFor( element );
validator.formSubmitted = submitted;
validator.successList.push( element );
validator.invalid[ element.name ] = false;
validator.showErrors();
} else {
errors = {};
message = validator.defaultMessage( element, { method: method, parameters: value } );
// YOUR STUFF, YOUR VALIDATION MESSAGE HERE
errors[ element.name ] = previous.message = 'EMAIL ALREADY ASSIGNED TO AN USER';
validator.invalid[ element.name ] = true;
validator.showErrors( errors );
}
previous.valid = valid;
validator.stopRequest( element, valid );
});
return "pending";
},
"EMAIL ALREADY ASSIGNED TO AN USER"
);
Then call your custom rule :
$("#signup form").validate({
rules: {
emailSignup: {
email: true,
required: true,
users_email_exists: true
}
}
});
Include this script as Promise class reference :
<!-- promise -->
<script src="https://www.promisejs.org/polyfills/promise-7.0.4.min.js"></script>
<script src="https://www.promisejs.org/polyfills/promise-done-7.0.4.min.js"></script>
The function that you've passed to setTimeout() will execute in future (a.k.a asynchronously) - after your checkAvailable() is completed. So its returning value is meaningless for the checkAvailable().
You should do rather the following:
DisableTheForm();
App.isEmailAvailable(value)
.then(function(response) {
$("#signup form").validate();
if( it is valid) {
EnableTheForm();
PostFormData();
}
})
.error(function() {
CryAsItIsNotAvailable();
EnableTheForm();
});
So to do your validation in response of positive feedback of your isEmailAvailable
You cannot use the remote method because it's looking for a URL parameter to access "remotely" via ajax().
You obviously would not call a JavaScript function with ajax(), so using remote to call a JavaScript function makes no sense.
You might be able to create a custom function using the .addMethod() method. However, you will have issues if any part of that is performed asynchronously as the custom rule will be evaluated before you have the result.

How to stop compiler while ajax request not complete?

I have a form and in this a field is parent keywords as secret key. When i validate this form using javascript it working very well. i also check secret key that i associated with any parent or not. if not then galg set 0.
Ajax working ver well and it check also that secret key found or not. if found then it is correct or not, but compiler not wait to ajax complete. it submit the form and don't wait the ajax response. I want while ajax request not complete then it resume for some time and then next check other filed.
function PiggyBankValidation()
{
var flag = 1;
var Namepattern = /^[a-zA-Z0-9 ]{1,40}$/;
var account_name = $("#account_name");
var holding_terms = $("#holding_terms");
var goal = $("#goal");
var parent_keywords = $("#parent_keywords");
var terms = $("#terms");
//terms.attr("checked",false)
if(account_name.val().trim() == ""){
account_name.next('.JErrorMsg').text("Please Enter Account Name !");
flag = 0;
}else if(Namepattern.test(account_name.val().trim()) == false){
account_name.next('.JErrorMsg').text("Please Enter Correct Account Name !");
flag = 0;
}else{
account_name.next('.JErrorMsg').text("");
}
if(holding_terms.val().trim() == ""){
holding_terms.next('.JErrorMsg').text("Please Select Holding Terms !");
flag = 0;
}else if(Namepattern.test(holding_terms.val().trim()) == false){
holding_terms.next('.JErrorMsg').text("Please Select Holding Terms !");
flag = 0;
}else{
holding_terms.next('.JErrorMsg').text("");
}
if(goal.val().trim() == ""){
goal.next('.JErrorMsg').text("Please Enter Goal !");
flag = 0;
}else if(Namepattern.test(goal.val().trim()) == false){
goal.next('.JErrorMsg').text("Please Enter Correct Goal !");
flag = 0;
}else{
goal.next('.JErrorMsg').text("");
}
if(parent_keywords.val().trim() == ""){
parent_keywords.next('.JErrorMsg').text("Please Enter Parent Keywords !");
flag = 0;
}else if(Namepattern.test(parent_keywords.val().trim()) == false){
parent_keywords.next('.JErrorMsg').text("Please Enter Correct Parent Keywords !");
flag = 0;
}else{
parent_keywords.next('.JErrorMsg').text("Please wait...");
$.ajax({
url:siteurl+"ChildControlPanel/CheckSecretKey",
type: "POST",
data:{key:parent_keywords.val().trim()},
beforesend : function(){flag = 0;},
success : function(data){
if(data){
response = JSON.parse(data);
if(response.success){
parent_keywords.next('.JErrorMsg').text("");
}
if(response.error){
parent_keywords.next('.JErrorMsg').text(response.error);
flag = 0;
}
}else{
parent_keywords.next('.JErrorMsg').text("Please try again !");
flag = 0;
}
},
});
}
if(terms.prop("checked") == false){
terms.next('.JErrorMsg').text("Please Accept terms !");
flag = 0;
}
else
{
terms.next('.JErrorMsg').text("");
}
///alert(flag);
if(flag == 1)
{
alert("submit");
//return true;
return false;
}
else
{
return false;
}
}
i calling this function here
$(document).ready(function(){
$("#piggyBankSetup input").keypress(function(e){
if(e.keyCode == 13){
if(PiggyBankValidation()){
document.piggyBankSetup.submit();
}
}
});
$("#AddAccount").click(function(){
if(PiggyBankValidation()){
document.piggyBankSetup.submit();
}
});
});
When this function call and all fields have correct value only parent keywords (secret key) is wrong then it submit the form.
Try
Inside $.ajax request set 'async':false , this will make wait till request completed , otherwise you can set ajax request outside validation function and check for valid with adding some classes (check with jQuery.hasClass('class name')) to control or setting a global variable
Ajax is asynchronous. That's what the "A" in Ajax stands for. It's also asynchronous for a reason because that keeps the browser responsive during the network request which can take many seconds. You may want to read this reference:
How do I return the response from an asynchronous call?
As such, you need to prevent the default submission of your form (probably using event.preventDefault() but you don't show us the code that that would go in). And, then when the validation passes, you can then programmatically submit your form.
You will also need to correct your code to work with an asynchronous response. ALL code that uses the Ajax response needs to be in the success handler or called from the success handler. You can't set a flag there and then use that flag outside the success handler. The timing will not work properly .
If you need to somehow do multiple operations as part of the validation, then you will have to write async code that can handle multiple operations. This is all part of learning how to write proper async code and is required for good javascript development. You cannot return an async result from your PiggyBankValidation() function because the asynchronous results will not yet be available when PiggyBankValidation returns.
If you have multiple async operations, you can use callbacks or promises to help structure your async code. If you have only the one async operation, then just put all the other code in the success handler (or in a callback called from the success handler).

What is a good strategy for custom form validation in angular?

As my app is growing, I'm finding more need for more effective form validation. I personally don't like the angular built in validation that evaluates on field change. And there are always things it won't account for like verifying that a youtube video id is valid. Currently I'm doing validation in each forms controller. I have a function that looks like this. Each field has a message and if there is an error the message will appear red using ng-class.
$scope.validate = function (callback) {
// reset default messages
setMessages();
// create shorter references
var item = $scope.item,
message = $scope.itemMessage;
// title exists
if (item.title === '') {
message.title.message = 'You must give your item a title.';
message.title.error = true;
message.errors += 1;
}
// extract and clear video id with youtube api
if ($scope.temp.video !== undefined && $scope.temp.video !== '') {
var id = '';
var url = $scope.temp.video.replace(/(>|<)/gi,'').split(/(vi\/|v=|\/v\/|youtu\.be\/|\/embed\/)/);
if(url[2] !== undefined) {
id = url[2].split(/[^0-9a-z_]/i);
id = id[0];
} else {
id = url;
}
$http.get("http://gdata.youtube.com/feeds/api/videos/" + id)
.then(function (res) {
$scope.item.video = id;
}, function (res) {
message.video.message = 'That is not a valid youtube video.';
message.video.error = true;
message.errors += 1;
$scope.item.video = '';
});
}
if (message.errors === 0) {
callback();
}
};
and then my actual form submission function calls $scope.validate(); passing it a function containing the $http.post(). The two major problems I see are that my callback isn't promise base so there's no guarantee it won't be called when an error exists and I've read again and again to keep large chunks of logic outside of your controller. I haven't found great examples of how this should be done but it must be a common problem.
You can still use Angular's built-in validation and have it not evaluate unless the form has been submitted:
http://scotch.io/tutorials/javascript/angularjs-form-validation#only-showing-errors-after-submitting-the-form
Essentially you set $scope.submitted = true when the form is submitted and set a conditional check so that error messages and classes are only shown when $scope.submitted is set.

Use jQuery.post() result in javascript function

I've got no clue how to do the following, so I wasn't sure what to search for either.
For validating my registration form I've a javascript function that checkes the existence of the inserted username in the database onblur of the username textfield.
function checkUsername(username){
$.post("checkmail.php", {username: username} , function(data){
var $response=$(data);
var response = $response.filter('#username-response').text();
if(response == "taken") {
document.getElementById('username').style.borderColor = rood;
valid = false;
}
});
}
This works fine, but now I want to validate it again onsubmit of the form in case users decide to submit an existing username.
function validateForm() {
var valid = true;
//checks different fields
//now check voor username existence
var username = document.getElementById('username').value;
checkUsername.call(username);
if (!valid) {
return false;
}
else {
return true;
}
}
I'm not familiar enough with Javascript to get this working. Probably thinking in the wrong direction...
You can use synchronous ajax call for this as you are using return data for validation.

Why is this variable changing when passing it around functions?

Edit2: Yay, found the problem, there is actually an error in the ajax call, and because of my stupidity, I didn't realise that both the methods - success and error can be ran (I thought it was either one or the other), so formComplete was being set to false every time because there was an error.
Appreciate the time you gave & tip about not using global variable names as function parameters.
Edit: Here's the code where formComplete is set (console.log()'s to check formComplete throughout the process):
validate: function(field) {
if(!field.val()) {
formComplete = false;
// formComplete is false here
console.log(formComplete);
}
else {
if(field.attr('name') === 'queryEmail') {
// Validate the email
if(DMOFFAT.helper.validateEmail(field.val()) === true){
// formComplete is either true or false here, depending on the last validation carried out
console.log(formComplete);
formComplete = true;
// formComplete is true here
console.log(formComplete);
}
else {
formComplete = false;
// formComplete is false here
console.log(formComplete);
}
}
else {
formComplete = true;
// formComplete is true here
console.log(formComplete);
}
}
},
Question: Why is this variable (formComplete) going from true to false?
I've written some basic form validation for a contact form, here's how I've done it:
Defined the fields like so:
var queryTypeField = $('#queryType'),
queryMessageField = $('#queryMessage'),
queryEmailField = $('#queryEmail'),
queryNameField = $('#queryName'),
submitButton = $('#submit');
Adding some event handlers to these like so (FWIW, DMOFFAT variable is just an object which holds different modules of the code e.g. contactForm = contact form javascript etc.):
queryMessageField.on('blur', function() {
DMOFFAT.contactForm.validate(queryMessageField);
});
queryNameField.on('blur', function() {
DMOFFAT.contactForm.validate(queryNameField);
});
queryEmailField.on('blur', function() {
DMOFFAT.contactForm.validate(queryEmailField);
});
submitButton.on('click', function(evt) {
evt.preventDefault();
console.log('Click');
console.log(formComplete);
DMOFFAT.contactForm.send(formComplete);
});
The validate function simply sets 'formComplete' to either true or false, depending on if the field is valid or not.
When my fields are all filled in correctly, formComplete = true.
As you can see from the last line of my code above, I pass formComplete (which is true) over to my send function. My send function simply checks the value of formComplete, and either sends the data off to a php script, or prints an error, here's the code:
send: function(formComplete) {
// This is true when the form is filled in correctly
console.log('In send formComplete is...' + formComplete);
if(formComplete === true) {
// Extract form values
formData['queryMessage'] = queryMessage.value;
formData['queryType'] = queryType.value;
formData['queryName'] = queryName.value;
formData['queryEmail'] = queryEmail.value;
$.ajax({
type: 'POST',
async: true,
url: '....',
dataType: 'json',
data: formData,
success: function(data, status, xhr) {
DMOFFAT.contactForm.writeMessage(formComplete);
},
error: function(xhr, status, err) {
DMOFFAT.contactForm.writeMessage(formComplete);
}
});
}
else {
this.writeMessage(formComplete);
}
Now, I KNOW that formComplete is true when the form is filled in correctly, because my php script creates a .txt file with the data in, if it was false, this file wouldn't be created.
This means that we send the value of formComplete off to writeMessage, which simply writes out some HTML to the page to display whether the submission was successful or not, here's the code:
// Disgusting spaghetti code
writeMessage: function(formComplete) {
// This is false now...
console.log('In writeMessage formComplete is...' + formComplete);
if(formComplete === true) {
contactFormElement.html('<div class="success ui-message cf-message"><p><strong>Thank you</strong> for your query, we\'ll get back to you as soon as we can.</p></div>');
}
else {
// Check if there's already an error, otherwise many will appear
if(errorElement === '') {
errorElement = '<div class="error ui-message cf-message"><p>' + this.config.errorMsg + '</p></div>';
contactFormElement.prepend(errorElement);
}
}
}
formComplete itself is defined like so:
var formComplete;
When I inspect formComplete on the first line of writeMessage, it's now false, I cannot find out why though...even when I explicitly set formComplete to true before it's passed to writeMessage, it's still false.
TLDR: Tell me where I'm being stupid please :)
PS: I know I could use a pre-built contact form validation plugin, but wanted to try build something simple myself.
The problem is, that you are calling writeMessage() from a callback function for your AJAX request, so the interpreter is looking for a global variable on execution time. Anyway, you can simply pass true to writeMessage() in your callback functions, as the calls are only executed if formComplete is true-

Categories

Resources