Why does `$.post` works only if `alert()` presents after it? - javascript

I have the following javascript code:
$.post("<receiver>", postdata);
And gets postdata not always. If I write the following code all works good:
$.post("<receiver>", postdata);
alert('bla-bla-bla, read me for a second');
Why? The page is changing on the save button as the javascript runs. But I need to send post data before redirecting.

You should redirect inside the success callback of your AJAX call:
$.post("<receiver>", postdata, function() {
window.location.href = '...';
});
The reason why your code works if you put an alert immediately after the $.post call is because when this alert pops up, the browser suspends the execution and your AJAX call has enough time to complete.
Don't forget that the first A in AJAX stands for Asynchronous meaning that you could only consume the results returned from the server inside the success callback.
Also if this AJAX call is performed inside some .submit() event handler of a form or inside some .onclick() handler of a submit button or an anchor you should make sure that you have canceled the default action by returning false otherwise your AJAX call will never have the time to execute before the browser redirects away from the page.
Example:
$('#myForm').submit({
$.post("<receiver>", postdata, function() {
...
});
return false; // <!-- That's the important bit
});

Ah, so it seems that the missing portion of your question is you are sending data on click of something yes? Presumably a link? That link causes the browser to follow it immediately, and in your example the alert is delaying the browser enough that your post has enough time to complete.
You need to ensure that the default action of that link is blocked, and do the redirect in the callback of your $.post() instead:
$("a.some_class").click(function(evt)
{
evt.preventDefault(); // makes sure browser doesn't follow the link
// gather your post data here ...
var $this = this;
$.post("<receiver>", postdata, function()
{
window.location.href = $this.attr("href");
});
})

Your alert is causing your script to pause and therefore allowing time for your $.post() to complete.
You should put your redirect script in your $.post() callback.

because it causes a delay. While you press OK the request (which takes at least a few milliseconds) gets finished and the stuff depending on it can follow.
To prevent this, you can pass a callback function that runs after the request got its response.
$.post( url, postdata, function() {
// Success.
} )

The .post is asynchronous.
If you change page during the post process () the POST request will get aborted.
Your alert is preventing this page change
You should replace your .post with a .ajax synchronous request, validating form submission on success ( return true; ) . Or do as suggested by #DarinDimitrov or #Curt

Related

Spinner button that starts spinning when function is called and stops when it ends

I'm trying to make a spinner button that will spin while I make an AJAX request and stop when the answer is received.
I've got the AJAX handled but the spinning doesn't seem to work with the following code:
function refresh (id){
var iconElem = document.getElementById("spinner" + id);
iconElem.classList.add('fa-spin');
sleep(5000);
var buttonRefresh = document.getElementById("refreshButton" + id);
buttonRefresh.classList.remove("fa-spin");
};
Note : I have replaced the ajax function with a sleep (implemented elsewhere, but it works like like it should) since I am in a non-php environment.
What happens here is that the the class "fa-spin" is being added while the sleep is over, even though it comes after in the code... Am I missing some kind of "refresh" that I need to execute in order to make the added class effective ?
You need to stop the spinning in the completion callback of the ajax call as it is a async call.
What you are doing here is starting and then immediately stopping the spinner before the ajax call even finishes.
$.ajax({
url: "test.html",
cache: false,
success: function(html){
// stop the spinner here
}
});
Here is the simplest solution with a callback:
function sleep(callback,timeout){
setTimeout(callback,timeout)
}
sleep(() => {
//stop spinner here
},200)
Anyways, I suggest you to read more here
If you are doing an ajax request, you can also use the async:false header to make your request synced, and then your code should work.
Changes to the style or content of the document become effective only when the JavaScript function finishes and returns to the main event loop. Therefore, assuming your sleep() function works as expected (by doing a busy wait or something like that, although that is not actually sleeping), you can only see the total effect of all changes when the function returns. If you follow the advice of the other answers and remove the style in the callback of the AJAX call, you will be fine.

Ajax inside onbeforeunload is not working the first time

I have been trying to bind beforeunload event by calling the following script so that I can go to the specified URL through AJAX. The problem is that the AJAX is not working the first time as the URL does not get called when the first time I do the page refresh. The second time ajax works. This problem gets fixed when I set async to false but then the alert popup inside success doesn't show up. I need alert box to also show up in success block.
<script type="text/javascript">
$( document ).ready(function() {
// this method will be invoked when user leaves the page, via F5/refresh, Back button, Window close
$(window).bind('beforeunload', function(event){
// invoke the servlet, to logout the user
$.ajax({
cache: false,
type: "GET",
url: "LogoutController" ,
success: function (data) {
alert("You have been logged out");
}
});
});
});
</script>
beforeunload will wait for the event handler to finish its execution before closing the page. Since an ajax call is asynchronous beforeunload is not going to wait for it to finish (your server however should still get the request). This is the expected behaviour and I don't think they is a way around it.
This behaviour can be reproduces using the following code:
window.onbeforeunload = function () {
console.log("bye");
setTimeout(function () {
console.log("bye1");
}, 200);
console.log("bye2")
};
//bye
//bye2
Also, you should note that, according to MDN the specs states that alert() can be ignored:
Since 25 May 2011, the HTML5 specification states that calls to
window.alert(), window.confirm(), and window.prompt() methods may be
ignored during this event.
When this happens on chrome (only browser I checked) you will get the following message in the console:
Blocked alert('test') during beforeunload.

Forcing post request to finish submitting before new page loads

I am trying to use the jQuery $.post method to submit an ajax request to a PHP script whenever a certain element is clicked. I don't care about the value returned by the server - I just want to make sure that my data is submitted.
However, the element that receives the click could contain either a hyperlinked image or a flash element with several links in it. How can I ensure that my script receives the post request (again, I don't care about its response) even if the user navigates away from the page? I am using the code below:
jQuery(document).ready(function($) {
$(".click-track").mousedown(function(e) { // click() will not pass through a flash movie, so we must use mousedown
ad_id = $(this).data("ad-id");
var data = {
action: 'log_click',
adId: ad_id
};
$.ajaxSetup({async: false});
$.post(myscript.php, data, function(data) {
// Do nothing because we don't care about the response
}, 'html');
});
});
I'm no browser scripting guru, and this code has me flummoxed. I would appreciate any help you could give!
If you do an e.preventDefault() within the mousedown handler, you can simply do this inside the $.post call:
$(".click-track").mousedown(function(e) { // click() will not pass through a flash movie, so we must use mousedown
e.preventDefault();
ad_id = $(this).data("ad-id");
var data = {
action: 'log_click',
adId: ad_id
};
$.ajaxSetup({async: false});
var jqxhr = $.post(myscript.php, data, function(data) {
// Do nothing because we don't care about the response
}, 'html');
jqxhr.complete(function(){ $(this).trigger('click'); });
});
This should create an ajax object and attach a function when the POST request completes that should trigger a click on the originally mousedown'd element. I've gotten this to work with non-Flash elements, not sure if it will work on the Flash object, but it should.
Handle the redirect in Javascript, and make it happen after you get a response. Also, you probably don't want AJAX -- you probably want a synchronous post.
If you want to force you code to block until the post finishes, I suggest using .ajax() instead and set the async field to false. Like so:
$.ajax({
//some fields
async:false,
//some more fields
});
Now, your js will block until the call returns. Though I will warn you this is not preferred.
Edit: If the element is a hyperlink you will need to call event.preventDefault(). It is also worthwhile to set the link's href attribute to "javascript:void(0);".

Send information about clicked link to the server before redirect

We're creating a click tracking app, that builds heatmaps. I'm writing a script which users are suppose to insert into their pages for tracking to work.
It works fine on elements, which doesn't require a redirect or form submit. For example, if I click on h1 or p or whatever, it works perfectly correct. But, if I click on a a, request to our server never happens before the normal redirect.
In the last couple of days I tried a lot of ways to do that. First of, I tried a normal AJAX call, since it was a cross-domain request I had to use JSONP, but again, that AJAX call did not have time to execute before the redirect. Adding async: false would have solved the problem, but it doesn't work with JSONP requests. So I decided to add a flag variable which indicates that it is safe to move on with redirect and used an empty while loop to wait until it becomes try in the ajax callback. But the while loop was blocking the execution flow, so callback never got a chance to set that variable to true. Here is some simplified code:
$(document).on('click', function (e) {
//part of the code is omitted
$.ajax({
url: baseUrl,
data: data,
type: "get",
dataType: "jsonp",
crossDomain: true,
complete: function (xhr, status,) {
itsSafeToMoveOn = true;
}
});
while(!itsSafeToMoveOn){}
return true;
});
The next thing I tried is to use unload page event to wait until total ajax calls in progress would become zero (I had a counter implemented) and then to move on with redirect. It worked in Firefox and IE, but in WebKit there was this error:
Error: Too much time spent in unload handler
After that I realized that I don't care about the server response and using img.src for the request would be an ideal fit for this case. So at this point code looks like this:
$(document).click(function (e) {
//part of the code is ommited
(new Image).src = baseUrl + '?' + data;
if (tag === "a" || clickedElement.parents().has("a")) {
sleep(100);
}
return true;
});
That way I increased the overall script performance slightly, but problem with links remains unchanged. The sleep function appears to be also blocking the execution flow and request never happens.
The only idea left is to return false from the event handler and than redirect manually to the clicked element's href or to call submit() on the form, but it will complicate things to much and believe me it's already a huge pain in the ass to debug this script in different browsers.
Does anyone have any other ideas?
var globalStopper = true;
$(document).on('click', function (e) {
if (globalStopper === false)
return true; //proceed with click if stopper is NOT set
else {
globalStopper = false; //release the breaks
$.ajax({
//blahblah
complete: function (xhr, status,) {
$(elem).click(); //when ajax request done - "rerun" the click
}
});
return false; //DO NOT let browser process the click
}
});
Also, instead of adding image, try adding script. And then add the script to the HEAD section. This way the browser will "wait" until it's loaded.
$(document).on('click', function (e) {
var scriptTag = document.createElement("script");
scriptTag.setAttribute("type", "text/javascript");
scriptTag.setAttribute("src", url);
document.getElementsByTagName("head")[0].appendChild(scriptTag);
return true;
}
I would take a look at the navigator sendBeacon API mentioned in this stack overflow answer or directly linked to here.
From the description on the site
navigator.sendBeacon(url, data) - This method addresses the needs of analytics and diagnostics code that typically attempts to send data to a web server prior to the unloading of the document.
You can save information to ajax request in cookies or localStorage and make any worker that will send information. Saving to cookies or localStorage is faster then ajax-request. You can do next:
$(document).click(function (e) {
var queue = localStorage.getItem('requestQueue');
queue.push(data);
localStorage.setItem('requestQueue',queue);
});
$(function(){
setInterval(function(){
var queue = localStorage.getItem('requestQueue');
while (queue.length > 0) {
var data = queue.pop();
$.ajax({
...
success: function(){
localStorage.setItem('requestQueue', queue);
}
});
}
},intervalToSendData);
});
So, when user click on link or send a form, data will be saved to storage and after user go to next page, this worker starts and send data to your server.
The JavaScript is basically executed in single thread. It is not possible to have your callback function executed and at the same time have an infinite loop waiting for a flag variable from it. The infinite loop will occupy the single execution thread and the callback will never be called.
Best approach is to cancel the default handler of your event and bubbling for it (basically return false if you are really building your tracking code with jQuery), and do the necessary actions (redirect page to the necessary address if a link was clicked or trigger other default actions), but this would take a lot of careful work to recreate all the possible combinations of actiona and callbacks.
Another approach is to:
1) Look for something specific to your code in the event data
2) If it is not present - make an AJAX call and in its callback re-trigger the same even on the same element, but this time with your specific bit added to the even data; after the AJAX call return false
3) If your specific bits are present in the data - simply do nothing, allowing the default event processing to take place.
The either approach may bite, however.
So if I understand right, you want your ajax logs completed before the page unloads and follows a link href. This sounds like a perfect case where you could consider using Deferreds in jQuery.
When your user clicks on anything that's supposed to take him away from the page, just check your promise status. If it's not resolved, you could throw a modal window over the page, and ask the user to wait til the progress is complete. Then, add a new pipe to your deferred, telling it to change the location href once everything is complete.
Let me know if this is the scenario. If it is, I'll explain in more detail. No use continuing if I didn't understand your requirement properly

order of events on click on <a> tag

So I have a normal link on my website, and I want to add tracking for it. I could envision a bunch of ways to do this, but I've settled on this as being really easy by writing a small jquery function, and dropping a small snippet in my tags:
click me!
javascript:
function saveClick(someparamhere){
$.ajax({
url: "somepage.php",
data: {param:someparamhere}
});
}
Now, I know my syntax might be bad, I'm just asking about the overall concept here. When you click the link, I want javascript to issue the call to saveClick which immediately makes an ajax call. There's no success handler because I don't really care if or what gets returned. I'll just have somepage.php log the event. Then, after all of that, I want the tag to go to it's href.
Is that the case? Will the ajax call be issued before the document goes to the other page? Will this work in all cases?
Has anybody ever done something like this? Any experience would be appreciated ....
If you want to make sure the AJAX call goes through you could do:
click me!
$('[data-parameters]').bind('click', function (event) {
//cache this element to use in AJAX function
var $this = $(this);
//prevent the default naviation
event.preventDefault();
$.ajax({
url: "somepage.php",
data: {param:$this.attr('data-parameters')}
success : function () {
//now navigate to the requested page
location = $this[0].href;
}
});
});
UPDATE
$.ajax() exposes a timeout function:
timeoutNumber
Set a timeout (in milliseconds) for the request. This will override
any global timeout set with $.ajaxSetup(). The timeout period starts
at the point the $.ajax call is made; if several other requests are in
progress and the browser has no connections available, it is possible
for a request to time out before it can be sent. In jQuery 1.4.x and
below, the XMLHttpRequest object will be in an invalid state if the
request times out; accessing any object members may throw an
exception. In Firefox 3.0+ only, script and JSONP requests cannot be
cancelled by a timeout; the script will run even if it arrives after
the timeout period.
So you could set a timeout and an error function that mimics the success function. The documentation does state that: it is possible for a request to time out before it can be sent but if your timeout is a very small (maybe zero) delay then it could reduce the lag between the user clicking the link and the browser loading the new page.
I simply wouldn't do that... it could bring to situation your onclick event isn't fired.
I think it would be better to call a javascript function on click that does your ajax call and then bring the user to the target page.
You can do this, for example, this way:
...
your javascript function then, shall be something like:
myfunc(paramofpageclickhere) {
//do ajax call
saveClick(someparamhere);
//go to target page
location.href = "target.htm";
}

Categories