How to reliably send tracking event on link click? - javascript

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

Related

Window.onUnload inconsistently sends results to server

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

Google Analytics, track page unload event

I'm trying to accomplish tracking an event when a user leaves the page with Google Analytics (analytics.js). Though it is unknown how the user will leave, it may be because of an external link or just closing the tab. So my thought was to hook onto the beforeunload or unload event and then:
window.addEventListener("beforeunload", function() {
ga('send', 'event', 'some', 'other', 'data');
});
Now my question is, will the request to the GA server be synchronous or can I somehow force that behaviour with the hitCallback property? If that is not possible, how else can I achieve this? Preferably without having to set a timeout or fixed waiting time for the user!
There is a way to make sure the request will be sent to GA. Simo Ahava wrote a very good blog post titled - "Leverage useBeacon And beforeunload In Google Analytics".
Utilizing the brilliant sendBeacon solution. Here's quote which addresses the selected answer of this question:
User agents will typically ignore asynchronous XMLHttpRequests made in
an unload handler. To solve this problem, analytics and diagnostics
code will typically make a synchronous XMLHttpRequest in an unload or
beforeunload handler to submit the data. The synchronous
XMLHttpRequest forces the User Agent to delay unloading the document,
and makes the next navigation appear to be slower. There is nothing
the next page can do to avoid this perception of poor page load
performance.
There are other techniques used to ensure that data is submitted. One
such technique is to delay the unload in order to submit data by
creating an Image element and setting its src attribute within the
unload handler. As most user agents will delay the unload to complete
the pending image load, data can be submitted during the unload.
Another technique is to create a no-op loop for several seconds within
the unload handler to delay the unload and submit data to a server.
Not only do these techniques represent poor coding patterns, some of
them are unreliable and also result in the perception of poor page
load performance for the next navigation.
The request will not be synchronous, GA tracking calls never are.
The only way to ensure the call completes is to make sure the page stays open long enough - for an event on a link you would normally do this with a timeout potentially combined with a hitCallback, as you mentioned.
The only way to keep a window open when the user closes a tab is to return a value from your beforeunload handler, which will prompt a "Confirm Navigation" alert. That would be a really bad solution just to track a GA event, obviously.
Set transport to beacon, with ga:
window.addEventListener("beforeunload", function() {
ga('send', 'event', 'page_unload', 'bye bye', {transport: 'beacon'});
});
Or transport_type to beacon, with gtag:
window.addEventListener("beforeunload", function() {
gtag('event', 'page_unload', {
'event_category': 'my cat',
'event_label': 'my label',
'transport_type': 'beacon' // <--- important part here
});
});
For what is worth, beacon should become the default transport mode anyway. As of 2020-09:
Currently, gtag.js only uses navigator.sendBeacon if the transport
mechanism is set to 'beacon'. However, in the future, gtag.js will
likely switch to using 'beacon' as the default mechanism in browsers
that support it.
As pointed out by tomconnors, this does NOT work. I'm leaving the answer to help warn anyone thinking about doing it this way. Beacon transport is probably the way to go, but wasn't widely supported at the time of the original answer.
You can wait for a hit to be sent to Google Analytics in the page onunload, but it does require a busy loop. In my case this did not delay user navigation, as the page was a popup window that is dedicated to a webapp. I'd be more concerned about doing this inline with normal web page navigation. Still, I had to take 2 showers to get the filth off after committing code with a busy loop.
var MAX_WAIT_MS = 1000;
var _waitForFinalHit = false;
function recordFinalHit() {
_waitForFinalHit = true;
ga('send', 'event', 'some', 'other', 'data', {
'hitCallback': function() {
_waitForFinalHit = false;
}
});
}
function waitForFinalHit() {
var waitStart = new Date().getTime();
while (_waitForFinalHit
&& (new Date().getTime() - waitStart < MAX_WAIT_MS)) { }
}
function myOnUnload() {
recordFinalHit();
// Do your other final stuff here...
waitForFinalHit();
}
window.onunload = myOnUnload;

Javascript code isn't getting into my document ready listener. (forge iOS)

This is my entire javascript file for the home page of my app. Any ideas as to why it never gets into the document ready listener?
var photos;
forge.request.ajax({
url: "http://photos-url.com/pics.json",
dataType: "json",
success: function(data) {
photos = data;
},
error: function(error) {
forge.logging.info("Couldn't fetch pics!");
}
});
//logging output works here
$(function() {
//logging output doesn't work here
//I'm trying to append to the html here, but it never gets into this code
});
Cross-domain requests are prohibited for security reasons (same as in desktop browsers). You must configure environment to allow requests to your domain. Look at https://trigger.io/docs/current/api/modules/request.html for details.
json files are usually allowed to be read from cross domain and even if this one would't be, I still doubt it could affect ready event. I'm not using document ready function on my page as I was having simillar issues (it fires few minutes after page is loaded, or doesn't fire at all). You could try window.onload or document.onload events. I'd also try to find out how document.readyState behaves and eventually check it manually with interval or try to bind event listener to it.

An event when document stops loading

I have a web page whose large parts are constructed on the front-end, in JavaScript. When the basic HTML is parsed and the DOM tree is constructed, the "ready" event triggers. At this event, a JavaScript code launches that continues with the construction of the web page.
$().ready(function() {
$(document.body).html('<img src = "img.png" />');
$.ajax({
url: 'text.txt'
});
});
As you can see in this tiny example,
new elements are added to DOM
more stuff, like images is being loaded
ajax requests are being sent
Only when all of this finishes, when the browser's loading progress bar disappears for the first time, I consider my document to be actually ready. Is there a reasonable way to handle this event?
Note that this:
$().ready(function() {
$(document.body).html('<img src = "img.png" />');
$.ajax({
url: 'text.txt'
});
alert('Document constructed!');
});
does not suffice, as HTTP requests are asynchronous. Maybe attaching handlers to every single request and then checking if all have finished is one way to do what I want, but I'm looking for something more elegant.
Appendix:
A more formal definition of the event I'm looking for:
The event when both the document is ready and the page's dynamic content finishes loading / executing for the first time, unless the user triggers another events with mouse moving, clicking etc.
As described here, you can sign up for load events for individual images when they finally load. However, as also described there, that may not be reliable across browsers or if the image is already in the browser's cache. So I think at least part of that is not 100% trustworthy. With that said, you can easily hook to a bunch of different asynchronous events and then only react when all of them have completed. For example:
$(document.body).html('<img src="img.png" id="porcupine"/>');
var ajaxPromise1 = $.get(...);
var ajaxPromise2 = $.get(...);
var imgLoadDeferred1 = $.Deferred();
$("#porcupine").load(
function() {
imgLoadDeferred1.resolve();
}
);
$.when(ajaxPromise1, ajaxPromise2, imgLoadDeferred1).done(
function () {
console.log("Everything's done.");
}
);
If you want to allow for the fact that the image load may never generate an event, you could also set a timer which would go off eventually and try to resolve the imgLoadDeferred1 if it is not already resolved. That way you don't get stuck waiting for an event that never happens.
You are looking for .ajaxStop().
Used like so:
$("#loading").ajaxStop(function(){
$(this).hide();
});
It does't really matter what DOM element you attach to unless you want to use this as is done in the above example.
Source: http://api.jquery.com/ajaxStop/
Add global counter to ajax calls in way that when ajax request is ready it calls addToReadyCounter(); function that will +1 to glogal var readyCount;.
Within same function check your readyCount and trigger event handler
var readyCount = 0, ajaxRequestCount = 5;
function addToReadyCount() {
++readyCount;
if (readyCount >= ajaxRequestCount) {
documentReadyEvent();
}
}
$.ajax(
...
success: function(data){
addToReadyCount();
}
...);
If you are only using success: ... for addToReaadyCount documentReadyEvent() only triggers if all call succeed, use other .ajax() event handlers to increment counter in case of error.

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