Action method execution sequence - javascript

I'm stuck in a really bizarre situation here. It's complicated to explain but I'll try my best.
I have a UI with 4 navigation <a> buttons on top - in the center there's always a form - and at the bottom I have Previous & Next buttons.
Forms are constructed in MVC using Ajax.BeginForm
For each Nav link <a> element on top, I have a JavaScript function
var LoadTabs = function (e, arg) {
// This is to validate a form if one of the top links is clicked and form has incomplete fields...
if (arg !== "prev" && arg !== "next") {
if (!window.ValidateForm(false)) return false;
}
var url = $(this).attr('data'); // this contains link to a GET action method
if (typeof url != "undefined") {
$.ajax(url, { context: { param: arg } }).done(function (data) {
$('#partialViewContainer').html(data);
});
}
}
This function above binds to each top link on page load.
$('.navLinks').on('click', LoadTabs);
My Next & Previous buttons basically trigger the click event i.e. LoadTabs function.
$('button').on('click', function () {
if (this.id === "btnMoveToNextTab") {
if (!window.ValidateForm(true)) return false;
$.ajax({
url: url,
context: { param: 'next' },
method: "GET",
data: data,
success: function(response) {
if (typeof response == 'object') {
if (response.moveAhead) {
MoveNext();
}
} else {
$('#mainView').html(response);
}
ScrollUp(0);
}
});
}
if (this.id === "btnMoveToPreviousTab") {
MoveBack();
}
return false;
});
MoveNext() Implementation is as below:
function MoveNext() {
var listItem = $('#progressbarInd > .active').next('li');
listItem.find('.navLink').trigger('click', ['next']);
ScrollUp(0);
}
The problem is, for some reasons, when Nav Link 3 is active and I hit the NEXT button - Instead of posting the form first via form.submit() - the nav 4 gets triggered - hence GET for nav 4 runs before form POST of nav 3.
My ValidateForm method is basically just checking if the form exists and is valid then Submit, else returns false. It's as below:
function ValidateForm(submit) {
var form = $('form');
// if form doesn't exist on the page - return true and continue
if (typeof form[0] === "undefined") return true;
// now check for any validation errors
if (submit) {
if (!$(form).valid()) {
return false;
} else {
$(form).submit();
}
}
else {
return true;
}
return true;
}
My speculation is that form.submit does get triggered as it should be but since submit takes a little longer to finish it continues with the next code block in the button onclick event.
I first thought that this is C# issue as in the POST I'm saving a big chunk of data with a few loops, and any code block that's process heavy I have that part in
var saveTask = Task.Factory.StartNew(() => ControllerHelper.SomeMethod(db, model));
Task.WaitAll(saveTask);
WaitAll will wait and pause the execution until SomeMethod finishes executing. I'm not sure how can I lock a process in javascript and wait for it to finish execution. Because I think If i can somehow lock the form.submit() in ValidateForm until its finished processing .. via a callback method perhaps...
Please if anyone can put me in right direction, I'd greatly appreciate the help. If you need more information please let me know I'd be happy to provide!

I'm posting this is a note to Alan since it's multi-line code and I can't make it readable in a comment. To avoid the promise anti-pattern when you sometimes run an async operation and sometimes don't, one can use this:
function ValidateForm(){
// do your logic
if (pass) {
return $.post("urlToPost");
else {
return $.Deferred().reject(xxx).promise();
}
}
This way, you're always returning a promise. In one case, the promise comes from $.ajax(). In the other case, you're just returning a rejected promise since the operation has already failed.

Edit regard to return promise anti pattern
as jfriend00 commented the way original answer is returning the promise is anti pattern, better way would be:
function ValidateForm(){
// do your logic
if (pass) {
return $.post("urlToPost");
else {
return $.Deferred().reject(xxx).promise();
}
}
more info on this, checkout https://github.com/petkaantonov/bluebird/wiki/Promise-anti-patterns
original answer
I will try picture your problem:
what you want is run ValidateForm first, when it passess (such that when form post is done), then do MoveNext
your problem is, MoveNext is called before ValidateForm has finished.
If I am correct, then the reason is because Javascript by nature is async programing, hence use callback to fire when something has done.
So in your situation, you need to use promise to acheive it.
essentialy what you need to do is:
let ValiateForm returns promise
only execute MoveNext once the ValidateForm has finish
So code would be like:
function ValiateForm(){
var dfd = jQuery.Deferred();
// do your logic
if(pass){
$.post("urlToPost"
} else {
// also failed
dfd.reject();
}
// return the promise
return dfd.promise();
}
then your move next button would be like
$('button').on('click', function () {
if (this.id === "btnMoveToNextTab") {
$.when(window.ValidateForm(true)).then(function(){
// after success post form
$.ajax({
url: url,
context: { param: 'next' },
method: "GET",
data: data,
success: function(response) {
if (typeof response == 'object') {
if (response.moveAhead) {
MoveNext();
}
} else {
$('#mainView').html(response);
}
ScrollUp(0);
}
});
if (this.id === "btnMoveToPreviousTab") {
MoveBack();
}
}
return false;
});
});
more info on promise checkout
https://api.jquery.com/deferred.promise/
and for run when finish
https://api.jquery.com/deferred.then/

Related

Validation using JQuery $.post

I got a question concerning asynchronous calls. What I try to do is validate if a given "code" is valid. Refreshing the page is not an option.
The case in a nutshell:
I need to set a variable to true or false based on the reply from the server.
That call is done by using ajax.
The ajax ($.post) request processes the reply from the server and thus sets the variable to true or false.
The value of the variable will then be used as the return value for that function, true or false.The problem is to only return AFTER the ajax has completed. The asynchronous behavior of ajax prevents this and hanging the browser is not done.
The validation is done by using a function (it will en up being used in an if).
The if looks the following:
if(codeGiven()) {
// Code ok. Continue to process
} else { msgCodeInvalid(); }
The function codeGiven is the following
function codeGiven(toChk) {
var codeOK = false; // default answer
if(toChk) { // only perform call if toChk is not "" of undefined
$.post(
"validate.aspx", // server script
{ code: toChk }, // value
function(data) { // function
if(data == "true") {
codeOK = true; // when true => return will be true
} else {
codeOK = false; // when false => return will be false
}
}
);
}
return codeOK; // will be false by default but true when the code is OK
}
the aspx will use the posted "code" and match it against other codes in my database. If a match is found, the reply will be "true". If not, the reply will be "false".
Because an ajax call ($.post) is asynchronous, the result will always be false.
Any suggestions on how to hack my way out of this, besides my idea with a setInterval (a dirty method I know)? A return inside the post didn't work.
Any ideas?
Thanks in advance
Final solution
I came to the realization that I could put my entire logic inside the $.post.
so basically, my logic is the following (warning, it's a deep tree structure):
if(myvar) { // if pid not empty
$.post( // start validation
"validator.aspx", // server side validation script
{ code : myvar }, // data to be passed to the server
function(data, status) { // execute when post happend
if(data == "true") { // if answer from server == "true"
/*
assign variables
apply other logic
process effects
...
*/
} else { /* if answer from server == "false" => error: invalid code given */ }
}
).error( /* error: validator did a boom boom. We're sorry :( */ );
} else { /* error: please enter a code */ }
Do your check in the callback or promise of your function, for example:
function checkCodeGiven(toChk) {
return $.post("validate.aspx", { code: toChk });
}
And then something like:
checkCodeGiven(someVar).then(function(data) {
if(data == "true") {
// do whatever you wanted to do for true
} else {
// do whatever you wanted to do for false
}
});
you can :
. call the function you want inside the success function of the ajax
OR
. the delayer way :calling the part where you need the if(codeGiven) many times with delays about 100ms so you are sure that it's false or it becomes true at one call, replace this part
if(codeGiven()) {
// Code ok. Continue to process
} else { msgCodeInvalid(); }
with codeGiven();Delayer(0); and declair this
function Delayer(counter) {
if (codeOK) {
// Code ok. Continue to process
} else if (counter <10) { //not sure if it may become true later
setTimeout(Delayer, 100, counter++);} //call after 0.1 sec
else {
msgCodeInvalid(); // no hope to be true
}
}
plus codeOK should be declaired globally
hope this works for you
EDIT just clarified a bit

How to continue executing JS after deferred.fail()

Here is some code that takes a Backbone model, saves it and then waits for the response and fires the jQuery .done() or .fail() code. This is working fine but on fail we actually want to get the returned message from the service add it to our errors object. This is all within a Backbone validate() function; after this code, we check the errors object and display the message if there is one.
It seems like if we .fail(), everything stops executing. We need to continue the validate function. I found this question/answer but it didn't seem to make a difference: Is there a way to continue after one deferred fails?
Any way to continue executing code after hitting a deferred.fail()?
addressModel.save().done(function() {
console.log("promise done");
model.set("...", false);
}).fail(function(response) {
console.log("promise fail");
if (response.responseJSON && response.responseJSON._messages) {
_.each(response.responseJSON._messages, function(value, key) {
errors[key] = value[0];
});
}
});
It's possible but tricky - at least until 3.0. The trick is:
Don't use .fail use .then.
Return a resolved deferred from the fail.
This is like signalling that we've dealt with the exception we got here:
var p = addressModel.save().then(function() {
console.log("promise done");
model.set("...", false);
}, function(response) {
console.log("promise fail");
if (response.responseJSON && response.responseJSON._messages) {
_.each(response.responseJSON._messages, function(value, key) {
errors[key] = value[0];
});
}
return $.Deferred().resolve(); // return a resolved deferred
});
This will let you do:
p.then(function(){
// this code runs when either failure or success happened, can also chain
});
We never could get this to work with a promise, whether it was returned by a function within Backbone's validate() or by validate() itself. But I found a solution within Backbone itself--save() will accept async: false and stall execution until a response is received, then continue from there. It probably uses a promise to do this behind the scenes.
addressModel.save(null, {
async: false,
success: function() {
model.set("...", false);
},
error: function(model, response) {
if (response.responseJSON && response.responseJSON._messages) {
_.each(response.responseJSON._messages, function(value, key) {
errors[key] = value[0];
});
}
}
});

Function return doesn't wait for angularjs promises

I have a problem about the angularjs promises, maybe someone can point me to right direction. I have a validation function in my controller which should return true if everything is good, returns false, if there is a problem. Single page app works on this assumption, so I can't easily change this assumption...
window.customerValidation = function ($scope, $rootScope, $alert, $q) {
var isValidated = false;
if (!$scope.scopeData.customer.nationalId && !$scope.scopeData.customer.passportNumber && !$scope.scopeData.customer.driverLicenseNumber) {
$alert.error("Please enter at least one identification info.");
return false;
}
$scope.scopeData.customer.checkForDuplicateIdentity().then(function () {
var customer = $scope.scopeData.customer;
if (!customer.idValidated) {
$alert.error(customer.idValidationMessage);
}
if (!customer.idValidated)
{
return false;
}
if ($scope.customerDataForm && $scope.customerDataForm.$error && $scope.customerDataForm.$error.required) {
var missingFields = [];
angular.forEach($scope.customerDataForm.$error.required, function (value, key) {
missingFields.push("-" + value.$name);
});
$alert.error("Please fill in the all required fields.\r\n" + missingFields.join("\r\n"));
}
else if ($scope.customerDataForm && $scope.customerDataForm.$error && $scope.customerDataForm.$error.email) {
var invalidEmailFields = [];
angular.forEach($scope.customerDataForm.$error.email, function (value, key) {
invalidEmailFields.push("-" + value.$name);
});
$alert.error("Please enter valid e-mail addresses.\r\n" + invalidEmailFields.join("\r\n"));
}
else if (!Date.parse($scope.scopeData.customer.dateOfBirth)) {
$alert.error("Please enter a valid date for Date of Birth.");
}
else {
$scope.scopeData.customer.updateIndividualCustomerInfoRequest();
$scope.scopeData.customer.updateOrder();
$rootScope.customerName = $scope.scopeData.customer.firstName + " " + $scope.scopeData.customer.lastName;
isValidated = true;
}
});
return isValidated;
};
Everything was working fine until requirements changed lately, I am trying to check server to see if id values have been used in the system before. Therefore I have to make an async call to server and check it, you can see that I used
$scope.scopeData.customer.checkForDuplicateIdentity() method and promises to get the async call work.
Call works fine I tested it, only problem I have is my customerValidation method finishes before async call completes and returns false all the time. Which is to say
return isValidated line always executes before isValidated becomes true.
I can't make the outer return statement wait for the async call's completion. If I return isValidated from inner function, it won't return the value to customerValidation function. Can somebody tell me how to achieve this?
A function that calls an async function must be an async function itself. Change window.customerValidation so it returns a promise.
remove var isValidated = false;
replace return false by return $q.reject() on line 7
add return in front of $scope.scopeData.customer.checkForDuplicateIdentity()
inside the callback you give to then, return when the validation passes, and throw an error when it fails
Then in the caller of window.customerValidation replace
if (window.customerValidation()) {
// validation passed
} else {
// validation failed
}
by
window.customerValidation()
.then(function () {
// validation passed
})
.catch(function (error) {
// validation failed
})
You can always use the timeout functions provided by javascript, like
setTimeout(function {DO_SOMETHING}, 50);
This will wait 50ms before calling the function. This way you can wait for the async call to complete.
Hope this helps.

jQuery submit form calls a function with ajax

jQuery(document).ready(function(jQuery) {
function checkEmail(email){
jQuery.post('someroute.php',
{email:eamil},
function(data){
if(data==error){
return false;
}
);
return true;
}
jQuery('#myform').submit(function(){
// como code and at the momment this
var result true;
//then I check email
var email = ('#myemail').val();
result = checkEmail(email);
return result;
});
The problem is this, a checkEmail function, first return true, and then return value jQuery.post function. Why?
I checked and in submit, first return true, and if you stop submit then you release that post return the value. Post works fine, but I don't understand why function checkEmail does not wait until post returns the value and goes on until the end to return true.
Because the jQuery.post() function is an AJAX request, and the first A in AJAX stands for asynchronous. That's exactly what asynchronous is, it doesn't stop code execution while it waits for a response. It fires the request and then immediately moves on to any code after the line making the request, in this case your return true statement.
Like Anthony Grist said. .post is an asynchronous call which means it doesn't wait to finish.
I've looked up the .post method (http://api.jquery.com/jQuery.post/).
It's basicly a shortcut for .ajax. You're missing the fail method in here so I would say use the .ajax call.
var value = 1;
var handlerUrl = "someroute.php";
//Do the Ajax Call
jQuery.ajax(
{
url: handlerUrl,
data: { email:eamil },
type: 'POST',
success: function (data)
{
return true;
},
error: function (jxhr, msg, err)
{
return false;
}
});
#user1727336 : Hi! You might want to edit your code and add the missing } and });. Here's a fix:
// enter code here
jQuery(document).ready(function(jQuery) {
function checkEmail(email){
jQuery.post('someroute.php', {email:eamil},
function(data){
if(data==error){
return false;
}
});
return true;
}
});

Javascript rendering. How to write Javascript so it doesn't continue executing code before a function call has ended

Not sure if my question is subjective/objective but as a JavaScript newbie i'm encountering this problem quite a lot. So here I go.
I'm used to write C#, so my JavaScript structure looks like C#. And just that, that gives problems I think ;-)
Let's give a simple example where I met my problem again today:
MyLibrary.fn.InitAddEntityForm = function () {
$('a#btnAddEntity').click(function () {
//post data and receive object with guid and isPersisted boolean
var persistedObject = MyLibrary.fn.CheckAndSendAddEntityForm("name", "avatarurl.png");
console.log("test");
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject.gdEntityId);
} else {
alert("Oops, something went wrong. Please call 911");
}
});
};
//////*****/////
//SOME FUNCTION THAT SENDS MY FORM AND RETURNS AN OBJECT WITH TRUE VALUE AND POSTED ENTITY ID
/////*****//////
MyLibrary.fn.CheckAndSendAddForm = function (txtName, ImageUrl) {
var postUrl = "/admin/add";
var persistedObject = new Object();
$.post(
postUrl,
{ Name: txtName, ImageUrl: txtImageUrl},
function (data) {
if (data.Status == 200) {
console.log("Post status:" + data.Message);
persistedObject.isPersisted = true;
persistedObject.gdEntityId = data.Data;
} else if (data.Status == 500) {
console.log("Failed to post entitiy");
} else {
console.log("Fault with Javascript");
}
}, "json"
);
return persistedObject;
};
Okay, thats it. Everything looks okay right? Browser says no.
I tried to debug it using firebug, looping over my code line by line, and that way the browser does what I want: Execute a new function to show the next panel in my wizard.
After placing a lot of Console.logs() in my code I figured out that this must be something about timing in JavaScript. In C# the code executes line by line, but apparently JavaScript doesn't.
By placing that Console.log("test") I noticed that "test" appeared in my console before "Post status: Success!".
So here's my question, how should I write my JavaScript code so I have control over the way the browser executes my code?
Should I really replace the code below to the end of my CheckAndSendAddEntityForm()?
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject.gdEntityId);
} else {
alert("fout");
}
Is this how I have to write JavaScript: One big domino effect or am I just doing something wrong?
$.post is a shortcut for an AJAX call, AJAX is by definition asynchronous, which means it won't wait on a response before continuing processing. If you switch it to a regular AJAX() method, there is an async option you can set to false, which will make it behave as you are expecting.
Alternatively you can also define a function to execute on successful return of the AJAX request, in which you can call the next step in your process chain.
The AJAX call is asychronous; that means that the callback method exposes by $.post will be executed when the request completes, but your javascript will continue executing as soon as the invoke to $.post finishes. If you want to do something after the ajax call is done, you need to provide a callback method and do something else, ex:
MyLibrary.fn.CheckAndSendAddForm = function (txtName, ImageUrl, callback) {
var postUrl = "/admin/add";
var persistedObject = new Object();
$.post(
postUrl,
{ Name: txtName, ImageUrl: txtImageUrl},
function (data) {
if (data.Status == 200) {
console.log("Post status:" + data.Message);
persistedObject.isPersisted = true;
persistedObject.gdEntityId = data.Data;
} else if (data.Status == 500) {
console.log("Failed to post entitiy");
} else {
console.log("Fault with Javascript");
}
callback(); // This is where you return flow to your caller
}, "json"
);
};
Then you invoke like so:
var persistedObject = MyLibrary.fn.CheckAndSendAddEntityForm("name", "avatarurl.png", function()
{
console.log("test");
//check if persisted and go to next step
if (persistedObject.isPersisted) {
MyLibrary.fn.InitAddAnotherEntityForm(persistedObject .gdPronoId);
} else {
alert("Oops, something went wrong. Please call 911");
}
});
JavaScript is single-threaded. If you have asynchronous functionality, a simple boolean semaphore variable will help not to allow invocations of a function while some processes are running.
If you want to execute asynchronous tasks one by one (like a domino line), you will need to use callback functions.
What you're encountering is the "asynchronous" bit of AJAX. If you want to physically (as in the line line by line in the Javascript file) you can use the .success,.pipe or .done jQuery methods to add a callback to process the data further. Don't embed your callbacks if you can help it, or you will get a "domino effect" as you call it.

Categories

Resources