doing an ajax call on window.unload - javascript

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,
...
});

Related

Firefox return false; (preventDefault();) and window.location.reload(); together

I think it very stupid question, but after hours of google it - i have no idea or solution.
Point is that i need reload page after handling "click" event on my web-site. In chrome\opera it's works without problems. But in firefox i have some bug. My JS code with comments:
$('#project_create').click(function() {
var projectname = $("#project_name").val();
var projectdescription = $("#project_description").val();
$.post("projects/add_project.php", {
project_name: projectname,
project_description: projectdescription
});
$("#create_project_form").hide("slow");
$("#project_list").show("slow");
//return false; - if it uncomment, all work, but next page reloader doesn't work.
window.location.reload(); //but if it's work, FireFox doesn't send $.post-query
});
I need to work booth methods, because after click - script put in $_SESSION['my_var'] some variable, and it variable is avaliable after reload page only. Maybe there are other ways to do it? As I understand the problem here in features with firefox and preventDefault();
Thanks!
The issue is just you reload the page before performing the ajax request.
Try to reload page in the ajax success callback handler :
$.post("projects/add_project.php", {
project_name: projectname,
project_description: projectdescription
}, function(){
window.location.reload();
});
And remove your old window.location.reload()
When you do a return, code after that line will not be reached anymore and is considered "dead code". One does not simply put code after a return.
Another is that there's and issue when using return false to prevent default default actions. It prevents delegation/bubbling. Event handlers hooked higher up in the DOM tree (especially ones hooked with on()) won't be executed. If delegation matters, don't use it.
If your goal is to prevent the default action of the link and do stuff in JS, use event.preventDefault instead. The event object is passed in as the first argument in the handler.
$('#project_create').click(function(event) {
event.preventDefault();
// rest of the code
});
In addition to what the other answers suggest, you can also execute the location.reload method asynchronously using setTimeout:
setTimeout(function() { location.reload(); }, 1);
return false;
EDIT: The entire idea of running an asynchonous AJAX request and reloading the page immediately afterwards is flawed, of course, since the AJAX request may not have been completed by the time you reload the page.
You should therefore use a callback, as suggested by the accepted answer. Alternatively, you could use a synchronous AJAX request, but that would freeze execution until the request has completed, which is generally not desirable.

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";
}

$(window).unload not working as expected

I'm making a small chat application with PHP + MySQL + JavaScript, I've written a function disonnectUser(), which is called when the user press the disconnect button. Here it is:
function disconnectUser(){
$.post('web/WEB-INF/classes/handleChatUser.php',{ action: 'disconnect',nick: localNickname});
$('#chat').stop(true,true).fadeOut(2000,function(){
nicknameDialog();
});
$('#messageInput').val(null);
$('#clientList').html(null);
$('#chatScreen').html(null);
clearInterval(refreshIntervalId);
clearInterval(refreshIntervalId2);
connected = false;
}
And it works like a charm, but when I call this very function in another context, when the user instead of pressing disconnect just exit the page, in this function
$(window).unload(function() {
if(connected){
disconnectUser();
connected = false;
}
});
it doesn't work. And I'm sure it's being called, because if I put an alert it's called normally before closing the page. I think the page is closing before the code runs completely, so I think if I put some block there until the code finish running it would work?
The problem is that $(window).unload() doesn't waits any AJAX call before closing the window (what is right because AJAX is assync).
You need to force the AJAX to be sync, ie, wait the response. Inside your disconnectUser function:
$.ajax({
type: 'POST',
async: false, // This is the guy.
url: '/blablabla'
});
You can read more about it here: $(window).unload wait for AJAX call to finish before leaving a webpage
Instead of unload, how about beforeunload?
window.onbeforeunload = function() {
if(connected){
disconnectUser();
connected = false;
}
};
Also, your disconnectUser method already sets connected to false, no need to do it here also.
It also seems that jQuery doesn't really handle the beforeunload event, which is why you'll need to revert to native JS to handle this:
http://groups.google.com/group/jquery-en/browse_thread/thread/4e5b25fa1ff5e5ee?pli=1
Try using a synchronous request. Perhaps in combination with onbeforunload like the other poster suggested. If that doesn't work, I suppose you're out of luck. A request that is synchronous blocks the browser while it's happening, so you might want to use it only for the unload function, assuming the method even works.
function disconnectUser(){
jQuery.ajax({
url: 'web/WEB-INF/classes/handleChatUser.php',
data: { action: 'disconnect',nick: localNickname},
type: 'POST',
async: false
});
$('#chat').stop(true,true).fadeOut(2000,function(){
nicknameDialog();
});
$('#messageInput').val(null);
$('#clientList').html(null);
$('#chatScreen').html(null);
clearInterval(refreshIntervalId);
clearInterval(refreshIntervalId2);
connected = false;
}

Categories