Window.onUnload inconsistently sends results to server - javascript

I am trying to have the user get an alert when they try to leave the page, and make a post request if they leave, if not, do nothing. Here is my code (using JQuery):
$(window).on("unload", function() {
$.post("/delete-room", {
roomID: roomId
});
});
$(window).on("beforeunload", function() {
return true
})
I (my servers) sometimes get the post request, but only about 1/5 times when I close the tab/unload the page. Is there a reason for this inconsistency? Thanks

Unfortunately making ajax call in an unload or onBeforenUnload event handler is not reliable. Most of the calls never reach the backend. Consider using sendBeacon instead.
$(window).on("unload", function() {
var formData = new FormData();
formData.append('roomID', roomId);
navigator.sendBeacon("/delete-room", formdata);
});
The browser will POST the data without preventing or delaying, whatever is happening (closing tab or window, moving to a new page, etc.).

Related

How to reliably send tracking event on link click?

I have a tracking event (in the form of an http call to a tracking server) that fires on the clicking of a link, fired by an onclick event.
However, it appears that fairly often, the event is not registered by the tracking server because the browser cuts off the (long-running) event call when it loads the new page.
I'm reluctant to wait for a reply before forwarding the user to the new page, in case the reply is delayed and the user has to wait.
Is there a way to ensure the event call completes and forward the user on immediately?
Maybe preventDefault helps you. You don't share your code, it's because I explain you this way:
$('a.link').on('click', function(e) {
e.preventDefault();
var trackingLink = $(this).attr('href');
// do ajax stuff
$.ajax().success(function() {
window.location.href = trackingLink;
});
});
With preventDefault() you avoid links to load the page, then make the ajax requests and if it's successful redirects to the page of the link
One option is to end an 'unload' event handler, and send the data it the handler. This will delay the unload of the page.
Example:
$(window).on('unload', function() {
$.ajax({
method: "POST",
url: "serverUrl",
data: { logData: '....' }
})
});
Maybe you can use the .sendBeacon method. It's only supported by Chrome and Firefox right now, but the description from MDN seems to fit you needs:
This method addresses the needs of analytics and diagnostics code that
typically attempt to send data to a web server prior to the unloading
of the document.
Example (from the MDN article):
window.addEventListener('unload', logData, false);
function logData() {
navigator.sendBeacon("/log", analyticsData);
}

How to avoid automatic page reload after XMLHttpRequest call?

I know this is a bit paradoxal because XMLHttpRequest shouldn't be reloading the page, and it's the whole point of it.
Tried in Chrome latest version, Safari on iOS and on an Android. All same result.
I am sending a form through it, with files. Works great, the destination site receive correctly the data, and displays it. Sends back a 200, "OK". (It's facebook)
But then my page reloads automatically. Just like if I submitted the form using HTML form and a submit button. (Which was my original problem)
Here is how I do it, from Javascript
// Get the form element
var formData = new FormData(document.getElementById("photosubmitform"));
var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://graph.facebook.com/' + facebookWallId + '/photos', false);
xhr.onload = function(event)
{
var json = xhr.responseText; // Response, yay!
}
xhr.send(formData); // Sending it, will reload the page on success...
Any chance you're triggering this by submitting a form? If you don't return false in the onsubmit handler, the form will still be submitted.
Use event.preventDefault() when observing button click in your form.

Javascript window.open not working

Ok. I'm trying to login to twitter. The window is not opening in this code. The response that gets alerted is not null and is a link to a login screen. Any ideas?
var url = "./twitter_login.php";
var con = createPHPRequest();
con.open("POST",url,true);
con.setRequestHeader("Content-type","application/x-www-form-urlencoded");
con.send("");
var response = "";
con.onreadystatechange = function() {
if(con.readyState==4 && con.status==200) {
response = con.responseText;
alert(response);
window.open(response,"twitter","menubar=1,resizable=1,width=350,height=500");
}
}
The standard popup-blocker logic contained in most browsers these days will block any calls to window.open() that are not the direct result of a user action. Code that is triggered by timers or by any asynchronous callback (like your ajax ready function) will be treated as NOT caused directly by user actions and the new popup window will generally be blocked.
You can verify this is what is happening by temporarily changing your browser's popup blocking (turning it off) and see that it then starts working.
Probably what you need to do as a work-around is to create the window upon the user action that started this thread of code and then put the content into the window when you get your ajax response. The browser will probably allow that. I know that's less desirable from a visual perspective, but you can put some temporary content in the window until the ajax response comes in (something like "loading...").
Just had this exact same issue. Just in case you wanted the code that fixed it. I used this:
newWindow = window.open("", "_blank");
request = $.ajax({ ... my request which returns a url to load ... })
request.done((function(_this) {
return function(data, textStatus, jqXHR) {
return newWindow.location = data.Url;
};
})(this));

Is there a way to halt an HTTP AJAX call in the browser so that another one can proceed?

I'm working on an application that makes a lot of usage of AJAX capabilities. I'm currently working on a page where there are a large number of ajax calls made to render an initial page. All these calls are routed through the same AJAX script, so they have to be made one at a time by the browser. (Yeah, it's a bit inefficient)
I've got an event that loads another page, but unfortunately it needs to make an ajax call before it can re-render the page. The result of this is that if a user clicks on that link right when the page is first loading, the browser waits for all other ajax calls before making the call to re-render the page according to the event raised by the user's click. This has been resulting in some noticeably slow load times for the page when clicking on the link to it right as the page is loading.
My question is: Is there a way to use Javascript or something to have the web browser cancel http calls to a web script so that another event can then make a call to that same script? I know that it would be nice to make each call to different scripts so they could happen concurrently, but unfortunately the application I'm working on funnels all ajax requests through a central script, and that's not going to change anytime soon.
Use the abort() method on the XMLHttpRequest object to cancel any ongoing request and return the object to a reusable state.
You could wrap it in a helper object, eg:
function HttpQueue(uri) {
var queue= [];
var xhr= new XMLHttpRequest();
xhr.onreadystatechange= function() {
if (xhr.readyState===4 && queue.length>=1) {
queue.unshift()[1](xhr);
if (queue.length>=1)
next();
}
};
this.send= function (data, callback, isurgent) {
if (isurgent && queue.length>=1) {
queue.length= 0;
xhr.abort();
}
queue.push([data, callback]);
if (queue.length==1)
next();
};
function next() {
xhr.open('POST', uri, true);
xhr.send(queue[0][0]);
}
}
var queue= new HttpQueue('/script');
queue.send('action=foo', function() {
alert('task 1 done');
}, false);
queue.send('action=bar', function() {
alert('task 2 done');
}, false);
queue.send('action=bof', function() {
alert('task 3 is urgent! tasks 1 and 2 can get knotted');
}, true);
(not tested, may work)
You can use the jQuery $.ajax method.
at which point you would specify a timeout for each call, and then set the aSync settings to false. That way you would only run one ajax request at a time.
Documentation can be found here:
http://api.jquery.com/jQuery.ajax/

window.onbeforeunload ajax request in Chrome

I have a web page that handles remote control of a machine through Ajax. When user navigate away from the page, I'd like to automatically disconnect from the machine. So here is the code:
window.onbeforeunload = function () {
bas_disconnect_only();
}
The disconnection function simply send a HTTP GET request to a PHP server side script, which does the actual work of disconnecting:
function bas_disconnect_only () {
var xhr = bas_send_request("req=10", function () {
});
}
This works fine in FireFox. But with Chrome, the ajax request is not sent at all. There is a unacceptable workaround: adding alert to the callback function:
function bas_disconnect_only () {
var xhr = bas_send_request("req=10", function () {
alert("You're been automatically disconnected.");
});
}
After adding the alert call, the request would be sent successfully. But as you can see, it's not really a work around at all.
Could somebody tell me if this is achievable with Chrome? What I'm doing looks completely legit to me.
Thanks,
This is relevant for newer versions of Chrome.
Like #Garry English said, sending an async request during page onunload will not work, as the browser will kill the thread before sending the request. Sending a sync request should work though.
This was right until version 29 of Chrome, but on Chrome V 30 it suddenly stopped working as stated here.
It appears that the only way of doing this today is by using the onbeforeunload event as suggested here.
BUT NOTE: other browsers will not let you send Ajax requests in the onbeforeunload event at all. so what you will have to do is perform the action in both unload and beforeunload, and check whether it had already taken place.
Something like this:
var _wasPageCleanedUp = false;
function pageCleanup()
{
if (!_wasPageCleanedUp)
{
$.ajax({
type: 'GET',
async: false,
url: 'SomeUrl.com/PageCleanup?id=123',
success: function ()
{
_wasPageCleanedUp = true;
}
});
}
}
$(window).on('beforeunload', function ()
{
//this will work only for Chrome
pageCleanup();
});
$(window).on("unload", function ()
{
//this will work for other browsers
pageCleanup();
});
I was having the same problem, where Chrome was not sending the AJAX request to the server in the window.unload event.
I was only able to get it to work if the request was synchronous. I was able to do this with Jquery and setting the async property to false:
$(window).unload(function () {
$.ajax({
type: 'GET',
async: false,
url: 'SomeUrl.com?id=123'
});
});
The above code is working for me in IE9, Chrome 19.0.1084.52 m, and Firefox 12.
Checkout the Navigator.sendBeacon() method that has been built for this purpose.
The MDN page says:
The navigator.sendBeacon() method can be used to asynchronously
transfer small HTTP data from the User Agent to a web server.
This method addresses the needs of analytics and diagnostics code that
typically attempt to send data to a web server prior to the unloading
of the document. Sending the data any sooner may result in a missed
opportunity to gather data. However, ensuring that the data has been
sent during the unloading of a document is something that has
traditionally been difficult for developers.
This is a relatively newer API and doesn't seems to be supported by IE yet.
Synchronous XMLHttpRequest has been deprecated (Synchronous and asynchronous requests). Therefore, jQuery.ajax()'s async: false option has also been deprecated.
It seems impossible (or very difficult) to use synchronous requests during beforeunload or unload
(Ajax Synchronous Request Failing in Chrome). So it is recommended to use sendBeacon and I definitely agree!
Simply:
window.addEventListener('beforeunload', function (event) { // or 'unload'
navigator.sendBeacon(URL, JSON.stringify({...}));
// more safely (optional...?)
var until = new Date().getTime() + 1000;
while (new Date().getTime() < until);
});
Try creating a variable (Boolean preferably) and making it change once you get a response from the Ajax call. And put the bas_disconnect_only() function inside a while loop.
I also had a problem like this once. I think this happens because Chrome doesn't wait for the Ajax call. I don't know how I fixed it and I haven't tried this code out so I don't know if it works. Here is an example of this:
var has_disconnected = false;
window.onbeforeunload = function () {
while (!has_disconnected) {
bas_disconnect_only();
// This doesn't have to be here but it doesn't hurt to add it:
return true;
}
}
And inside the bas_send_request() function (xmlhttp is the HTTP request):
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200)
has_disconnected = true;
}
Good luck and I hope this helps.
I had to track any cases when user leave page and send ajax request to backend.
var onLeavePage = function() {
$.ajax({
type: 'GET',
async: false,
data: {val1: 11, val2: 22},
url: backend_url
});
};
/**
* Track user action: click url on page; close browser tab; click back/forward buttons in browser
*/
var is_mobile_or_tablet_device = some_function_to_detect();
var event_name_leave_page = (is_mobile_or_tablet_device) ? 'pagehide' : 'beforeunload';
window.addEventListener(event_name_leave_page, onLeavePage);
/**
* Track user action when browser tab leave focus: click url on page with target="_blank"; user open new tab in browser; blur browser window etc.
*/
(/*#cc_on!#*/false) ? // check for Internet Explorer
document.onfocusout = onLeavePage :
window.onblur = onLeavePage;
Be aware that event "pagehide" fire in desktop browser, but it doesn't fire when user click back/forward buttons in browser (test in latest current version of Mozilla Firefox).
Try navigator.sendBeacon(...);
try {
// For Chrome, FF and Edge
navigator.sendBeacon(url, JSON.stringify(data));
}
catch (error)
{
console.log(error);
}
//For IE
var ua = window.navigator.userAgent;
var isIEBrowser = /MSIE|Trident/.test(ua);
if (isIEBrowser) {
$.ajax({
url: url,
type: 'Post',
.
.
.
});
}
I felt like there wasn't an answer yet that summarized all the important information, so I'm gonna give it a shot:
Using asynchronous AJAX requests is not an option because there is no guarantee that it will be sent successfully to the server. Browsers will typically ignore asynchronous requests to the server. It may, or may not, be sent. (Source)
As #ghchoi has pointed out, synchronous XMLHTTPRequests during page dismissal have been disallowed by Chrome (Deprecations and removals in Chrome 80). Chrome suggests using sendBeacon() instead.
According to Mozilla's documentation though, it is not reliable to use sendBeacon for unload or beforeunload events.
In the past, many websites have used the unload or beforeunload events to send analytics at the end of a session. However, this is extremely unreliable. In many situations, especially on mobile, the browser will not fire the unload, beforeunload, or pagehide events.
Check the documentation for further details: Avoid unload and beforeunload
Conclusion: Although Mozilla advises against using sendBeacon for this use case, I still consider this to be the best option currently available.
When I used sendBeacon for my requirements, I was struggling to access the data sent at the server side (PHP). I could solve this issue using FormData as recommended in this answer.
For the sake of completeness, here's my solution to the question:
window.addEventListener('beforeunload', function () {
bas_disconnect_only();
});
function bas_disconnect_only () {
const formData = new FormData();
formData.append(name, value);
navigator.sendBeacon('URL', formData);
}
I've been searching for a way in which leaving the page is detected with AJAX request. It worked like every time I use it, and check it with MySQL. This is the code (worked in Google Chrome):
$(window).on("beforeunload", function () {
$.ajax({
type: 'POST',
url: 'Cierre_unload.php',
success: function () {
}
})
})
To run code when a page is navigated away from, you should use the pagehide event over beforeunload. See the beforeunload usage notes on MDN.
On that event callback, you should use Navigator.sendBeacon(), as Sparky mentioned.
// use unload as backup polyfill for terminationEvent
const terminationEvent = "onpagehide" in self ? "pagehide" : "unload";
window.addEventListener(terminationEvent, (event) => {
navigator.sendBeacon("https://example.com/endpoint");
});

Categories

Resources