18

I have a website which is using Google Analytics newer asynchronous tracking method (_gaq). The problem I've run into is that I want to institute some specific link tracking and am worried that I will be creating a race condition.

Basically, it's a news website so it has headlines which link to stories all over the place. A headline for a story might appear in 3 different places on a page, and appear on hundreds of other pages. Thus, in order to understand how our audience is interacting with the site we have to track how each specific headline block is used, and not just the destination. Because of those two stipulations tracking individual pages, nor tracking referred pages won't be enough, we have to track individual links.

So if I have a link.

<a href="http://www.blah.com" onclick="_gaq.push('_trackEvent','stuff')">Here</a>

Because _gaq.push() is an asynchronous call, isn't it possible that the page change will occur prior to Google's completion of the click tracking? If so is there a way to prevent that, or do I have a misunderstanding about the way that Google Analytics Async functions (http://code.google.com/apis/analytics/docs/tracking/asyncUsageGuide.html).

Owen Allen
  • 411
  • 4
  • 11

6 Answers6

11

You're right. If the browser leaves the page before it sends the GA tracking beacon (gif hit) for the event, the event will not be recorded. This is not new to the async code however, because the process of sending the tracking beacon is asynchronous; the old code worked the same way in that respect. If tracking is really that important, you could do something like this:

function track(link) {
  if (!_gat) return true;
  _gaq.push(['_trackEvent', 'stuff']);
  setTimeout(function() {location.href=link.href'}, 200);
  return false;
}

...

<a href="example.com" onclick="return track(this);"></a>

This will stop the browser from going to the next page when the link is clicked if GA has been loaded already (it's probably best to not make the user wait that long). Then it sends the event and waits 200 milliseconds to send the user to the href of the link they clicked on. This increases the likelihood that the event will be recorded. You can increase the likelihood even more by making the timeout longer, but that also may be hurting user-experience in the process. It's a balance you'll have to experiment with.

Brian
  • 2,342
  • 2
  • 23
  • 18
  • 2
    Nope. The goal of that check is to find out if GA has loaded yet. If you're using the async syntax, checking for _gaq won't tell you anything; you create it in your snippet code. When using async, _gat is only created when ga.js executes, so checking for it will tell you if tracking beacons will be sent without delay. – Brian Aug 09 '10 at 01:29
  • Theoretically, I could set the timeout to a fairly low number like 100ms. While imperfect, every millisecond of delay would mean that more clicks would be registered and less lost in the race. Unless there is a way for the _gaq.push() to return a value, and thus wait for that value, there really isn't an ideal solution it seems. – Owen Allen Aug 09 '10 at 17:19
  • This doesn't really have anything to do with _gaq.push or async. The same situation holds for the old GA syntax because callbacks on the tracking beacons aren't supported by GA. I really wish they were for this exact situation. – Brian Aug 11 '10 at 21:05
  • 2
    Google has a doc on this: http://support.google.com/analytics/bin/answer.py?hl=en&answer=1136920 – jfroom Dec 14 '12 at 17:42
9

I've got this problem too, and am determined to find a real solution.

What about pushing the function into the queue?

  // Log a pageview to GA for a conversion
  _gaq.push(['_trackPageview', url]);
  // Push the redirect to make sure it happens AFTER we track the pageview
  _gaq.push(function() { document.location = url; });
waterlooalex
  • 13,642
  • 16
  • 78
  • 99
  • Another idea: manually create the utm.gif URL, insert it into the DOM, bind its load event, wait for the image to load, then send the user on their way. – waterlooalex Mar 09 '11 at 18:32
  • Hmm, I looked at the source for ga.js, and it appears that when functions are pushed onto the _gaq they are excecuted immediately? http://code.google.com/apis/analytics/docs/tracking/asyncUsageGuide.html#PushingFunctions – waterlooalex Mar 09 '11 at 19:02
  • 1
    So I think the value of pushing your function into _gaq is that if the ga.js is not yet loaded then the document.location call won't be executed until it is. – waterlooalex Mar 09 '11 at 19:27
  • Anyone know if this works? Can I use that code snippet in an anchor like so? `` – Mark McDonald Jun 04 '11 at 02:58
  • Alex - Actually, I think this works. Functions pushed onto the queue are executed synchronously. And even though the `_trackEvent` method itself is asynchronous, it's only the firing of the XHR request that matters. So execution of the 2nd function should be blocked until `_trackEvent` has executed, no? – Rob Flaherty Jun 12 '11 at 18:04
4

From Google's documentation for universal analytics (new version since most other answers for this question). You can now easily specify a callback.

var trackOutboundLink = function(url) {
   ga('send', 'event', 'outbound', 'click', url, {'hitCallback':
     function () {
     document.location = url;
     }
   });
}

For clarity I'd recommend using this syntax, which makes it clearer which properties you're sending and easier to add more :

            ga('send', 'event', {
                'eventCategory': 'Homepage',
                'eventAction': 'Video Play',
                'eventLabel': label,
                'eventValue': null,

                'hitCallback': function()
                {
                    // redirect here
                },

                'transport': 'beacon',

                'nonInteraction': (interactive || true ? 0 : 1)
            });

[Here's a complete list of parameters for all possible ga calls.]

In addition I've added the transport parameter set to beacon (not actually needed because it's automatically set if appropriate):

This specifies the transport mechanism with which hits will be sent. The options are 'beacon', 'xhr', or 'image'. By default, analytics.js will try to figure out the best method based on the hit size and browser capabilities. If you specify 'beacon' and the user's browser does not support the navigator.sendBeacon method, it will fall back to 'image' or 'xhr' depending on hit size.

So when using navigator.beacon the navigation won't interrupt the tracking . Unfortunately Microsoft's support for beacon is non existent so you should still put the redirect in a callback.

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
2

In event handler you should setup hit callback:

_gaq.push(['_set', 'hitCallback', function(){
  document.location = ...
}]);

send you data

_gaq.push(['_trackEvent'

and stop event event processing

e.preventDefault();
e.stopPropagation();
Yura
  • 1,733
  • 1
  • 20
  • 19
1

I'm trying out a new approach where we build the URL for utm.gif ourselves, and request it, then only once we've received the response (the gif) we send the user on their way:

Usage:

  trackPageview(url, function() { document.location = url; });

Code (CrumbleCookie from: http://www.dannytalk.com/read-google-analytics-cookie-script/)

/** 
 * Use this to log a pageview to google and make sure it gets through
 * See: http://www.google.com/support/forum/p/Google%20Analytics/thread?tid=5f11a529100f1d47&hl=en 
 */
function trackPageview(url, fn) {
  var utmaCookie = crumbleCookie('__utma');
  var utmzCookie = crumbleCookie('__utmz');

  var cookies = '__utma=' + utmaCookie + ';__utmz=' + utmzCookie;

  var requestId = '' + (Math.floor((9999999999-999999999)*Math.random()) + 1000000000);
  var hId = '' + (Math.floor((9999999999-999999999)*Math.random()) + 1000000000);

  var utmUrl = 'http://www.google-analytics.com/__utm.gif';
  utmUrl += '?utmwv=4.8.9';
  utmUrl += '&utmn=' + requestId;
  utmUrl += '&utmhn=' + encodeURIComponent(window.location.hostname);
  utmUrl += '&utmhid=' + hId;
  utmUrl += '&utmr=-';
  utmUrl += '&utmp=' + encodeURIComponent(url);
  utmUrl += '&utmac=' + encodeURIComponent(_gaProfileId); 
  utmUrl += '&utmcc=' + encodeURIComponent(cookies);

  var image = new Image();
  image.onload = function() { fn(); };
  image.src = utmUrl;
}

/**
 *  @author:    Danny Ng (http://www.dannytalk.com/read-google-analytics-cookie-script/)
 *  @modified:  19/08/10
 *  @notes:     Free to use and distribute without altering this comment. Would appreciate a link back :)
 */

// Strip leading and trailing white-space
String.prototype.trim = function() { return this.replace(/^\s*|\s*$/g, ''); }

// Check if string is empty
String.prototype.empty = function() {
    if (this.length == 0)
        return true;
    else if (this.length > 0)
        return /^\s*$/.test(this);
}

// Breaks cookie into an object of keypair cookie values
function crumbleCookie(c)
{
    var cookie_array = document.cookie.split(';');
    var keyvaluepair = {};
    for (var cookie = 0; cookie < cookie_array.length; cookie++)
        {
        var key = cookie_array[cookie].substring(0, cookie_array[cookie].indexOf('=')).trim();
        var value = cookie_array[cookie].substring(cookie_array[cookie].indexOf('=')+1, cookie_array[cookie].length).trim();
        keyvaluepair[key] = value;
    }

    if (c)
        return keyvaluepair[c] ? keyvaluepair[c] : null;

    return keyvaluepair;
}
waterlooalex
  • 13,642
  • 16
  • 78
  • 99
  • 2
    What happens if Google change the API? – Ricky Hewitt Mar 15 '11 at 12:16
  • I imagine it breaks if Google changes the API. However, google *gives* out code that uses this API for google analytics mobile, so that code would break too. https://ssl.gstatic.com/analytics/20110301/mobile/ga.php – waterlooalex Mar 16 '11 at 18:29
0

Using onmousedown instead of onclick may also help. It doesn't eliminate the race condition, but it gives GA a head start. There's also the concern of someone clicking on a link and dragging away before letting go of the mouse button, but that's probably a negligible case.

Brian
  • 2,342
  • 2
  • 23
  • 18