Forcing post request to finish submitting before new page loads - javascript

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);".

Related

doing an ajax call on window.unload

In my application, there's an object that needs to be ajaxed back to the server before the user switches to another page or closes his browser.
For the moment, I'm using something like this:
$(window).on('unload', function () {
$.ajax(....);
});
Will the ajax call fire in all browsers or are there situations where this will not work and where this situation needs to be handled differently? I don't need to deal with anything in terms of a success function, I'm only concerned about the information making it to the server.
Thanks.
If you're using jQuery, you can set async to false in the ajax call. And it might work, but your results may vary by browser. Here's a jsFiddle example. http://jsfiddle.net/jtaylor/wRkZr/4/
// Note: I came across a couple articles saying we may should to use window.onbeforeunload instead of or in addition to jQuery's unload. Keep an eye on this.
// http://vidasp.net/jQuery-unload.html
// https://stackoverflow.com/questions/1802930/setting-onbeforeunload-on-body-element-in-chrome-and-ie-using-jquery
var doAjaxBeforeUnloadEnabled = true; // We hook into window.onbeforeunload and bind some jQuery events to confirmBeforeUnload. This variable is used to prevent us from showing both messages during a single event.
var doAjaxBeforeUnload = function (evt) {
if (!doAjaxBeforeUnloadEnabled) {
return;
}
doAjaxBeforeUnloadEnabled = false;
jQuery.ajax({
url: "/",
success: function (a) {
console.debug("Ajax call finished");
},
async: false /* Not recommended. This is the dangerous part. Your mileage may vary. */
});
}
$(document).ready(function () {
window.onbeforeunload = doAjaxBeforeUnload;
$(window).unload(doAjaxBeforeUnload);
});
In Google Chrome, the ajax call always completes before I navigate away from the page.
However, I would VERY MUCH NOT RECOMMEND going that route. The "a" in ajax is for "asynchronous", and if you try to force to act like a synchronous call, you're asking for trouble. That trouble usually manifests as freezing the browser -- which might happen if the ajax call took a long time.
If viable, consider prompting the user before navigating away from the page if the page has data that needs to be posted via ajax. For an example, see this question: jquery prompt to save data onbeforeunload
No, unfortunatelly your Ajax call will not get completed as the document will unload during the async call.
You cannot do many things when the user closes the window.
Instead of doing an ajax sync call (deprecated on latest browsers and can get exception), you can open a popup:
$(window).on('unload', function () {
window.open("myscript.php");
});
You can add obviously parameters to the link and you can automatically close the popup if you like.
Popup blocker must be disactivated for your domain in the browser options.
You have to use the onbeforeunload event and make a synchronous AJAX call.
$.ajax({
...
"url": "http://www.example.com",
"async": false,
...
});

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

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

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

$.post() callback method suggestion

I always used something like this:
$("a.button").click(function() {
data = ...;
url = ...;
$.post(url, data, function() {
$(this).toggleClass('active');
});
});
The problem is when an user has a slow connection and clicks on that button, it doesn't seems to do anything, because the button will change the own status (adding the active class) once the request is complete. Of course I can "fix" this behavior by adding a spinner while the request is loading.
Now check out this one:
$("a.button").click(function() {
$(this).toggleClass('active');
data = ...;
url = ...;
$.post(url, data, function() {
// if request is successful do nothing
// else, if there's an error: $(this).toggleClass('active)
});
});
In other words, I change the button status instantly when the button is pressed and after this, I check for success/error. Is this a good way? What you think about? Are there other ways?
This is more of a UI question than code. Personally I prefer to show the spinner in cases where it could be confusing if there is no response. Since I don't know what class you're toggling and what effect it has on the element, I wouldn't know if toggling before success would be confusing at all.
One way or another, everyone alive knows the loading spinner. It's probably safe to go with that.
You've got the general idea there. You can implement it in other ways, for instance by setting global AJAX ajaxStart and ajaxSuccess functions:
$("a.button").click(function() {
data = ...;
url = ...;
$.post(url, data, function() {
// if request is successful do nothing
});
}).ajaxStart(function () {
$(this).toggleClass('active');
}).ajaxComplete(function () {
$(this).toggleClass('active');
}).ajaxError(function () {
//never forget to add error handling, you can show the user a message or maybe try the AJAX request again
});
These methods register handlers to be called when certain events, such
as initialization or completion, take place for any AJAX request on
the page. The global events are fired on each AJAX request if the
global property in jQuery.ajaxSetup() is true, which it is by default.
Note: Global events are never fired for cross-domain script or JSONP
requests, regardless of the value of global.
Source: http://api.jquery.com/category/ajax/global-ajax-event-handlers/
Use $.ajax success:
From jquery docs:
$.ajax({
url: "test.html",
success: function(){
$(this).addClass("done");
}
});
You could do something like:
$("a.button").click(function() {
var old_text = $(this).text();
var button = $(this);
$(this).text('Processing...');
$(this).attr('disabled', 'disabled'); // disable to button to make sure it cannot be clicked
data = ...;
url = ...;
$.post(url, data, function() {
// after request has finished, re-enable the link
$(button).removeAttr('disabled');
$(button).text(old_text);
});
});
Next thing, you should do something similar for catching errors (re-enable the button).
It always depends the way you've built your site, but in my opinion the active state should only be triggered at the instant you click.
So that should be: onmousedown you add your class and onmouseup you remove it.
The Ajax call could trigger a different function maybe showing a loading dialog/spinner.
There are several ways of building it: individually on each element as you did, or through a general styling function. Same for Ajax with the ajaxStart ajaxComplete functions as Jasper said.
Personally I'm using Ajax intensively, always changing the DOM dynamically, so I use livequery to setup style changing with events automatically when elements with given class(es) appear in the DOM, and I use ajaxStart and ajaxComplete for displaying a loading dialog.

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