Asynchronous Tracking

Hello,

I found Piwik a while ago and found it very useful. One thing I missed, was the ability to implement the code via an ajax request. So I tried to do it

I started with this new tracking code: (GA inspired)

HTML
<script>
 var _piw = [1,['trackPageView']];
 (function(d,t){
	var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
	g.async=true;
	g.src='//www.example.com/piwik/piwik.js';
	s.parentNode.insertBefore(g,s);})(document,'script');

</script>
<noscript>
<p><img src=“http://www.example.com/piwik/piwik.php?idsite=1” style=“border:0” alt="" /></p>
</noscript>

Ok, let me explain:

     var _piw = [1,['trackPageView']];

this will be used later in the piwik.js. The ‘1’ (_piw[0]) represents the siteID and _piw[1] is an array with all functions from the JS API which where called, wenn the piwik.js is loaded.

So it’s possible to add more functions and arguments like this:

     var _piw = [1,['trackPageView'],['enableLinkTracking'],['setLinkTrackingTimer',250]];

as you can see, the ‘setLinkTrackingTimer’ gets an argument (250)

 (function(d,t){
        var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
        g.async=true;
        g.src='//www.example.com/piwik/piwik.js';
        s.parentNode.insertBefore(g,s);})(document,'script');

this is a function which creats a script element add the src attribute to the piwik.js and attach it to the DOM (GA’s async code do nearly the same)

Now I did some changes in the piwik.js:

First i add a global variable PiwikTracker on Line 28:

 var PiwikTracker, Piwik, piwik_log, piwik_track;

on line 125 in the ‘loadHandler’ method I added this:

//if _piw is set
if(_piw){
   //get new Tracker instance an get the first argument of _piw (which is the siteID)
   piwikTracker = Piwik.getTracker(documentAlias.location.protocol+"//www.example.com/piwik/piwik.php",_piw.shift());
  
   //for each argument
   for(var i=0;i<_piw.length;i++){
      //call it as a function and add arguments
      piwikTracker[_piw[i][0]](_piw[i][1],_piw[i][2],_piw[i][3]);
   }
}

no the method looks like this:

function loadHandler(loadEvent /* unused */){
   if (!hasLoaded){
      if(_piw){
         piwikTracker = Piwik.getTracker(documentAlias.location.protocol+"//www.example.com/piwik/piwik.php",_piw.shift());
         for(var i=0;i<_piw.length;i++){
            piwikTracker[_piw[i][0]](_piw[i][1],_piw[i][2],_piw[i][3]);
         }
      }
      hasLoaded = true;
      executePluginMethod('load');
      for (var i = 0; i < registeredOnLoadHandlers.length; i++) {
         registeredOnLoadHandlers[i]();
      }

   }
   return true;
}

This works for me as expected, but is still beta.
I hope someone can improve my code especially the part when calling the tracker functions.

Thats all for now

Xaver

Thanks. This is on our todo list, so I’ve added this to the corresponding ticket in trac.

Can u give me the ticket number to follow up?

http://dev.piwik.org/trac/ticket/739

If you would like to work on Piwik.js asynchronous version (like GA), please let us know, we would really like to help you with code review and add this feature in Piwik!

Yeah, good for you, good for me!

Let me know, where/when I can help you

[quote=Xaver @ Nov 23 2010, 01:35 PM]Yeah, good for you, good for me!

Let me know, where/when I can help you[/quote]

Can you please email me at matt at piwik.org and I’ll let you know, cheers

I found some bugs in my previous code on webkit.
The ‘load’ event only triggers when page is reloaded, but not when a link is clicked.

  1. SSL connection (https://) works correctly (if you have a correct certificate)
  2. Old, ugly code style_emoticons/<#EMO_DIR#>/wink.gif now also works as expected
Successful tested in:
  • IE 6-9
  • Opera 10.63
  • FF 3.6
  • Chrome 5.0
  • Safari 5.0
If you can test it in older browsers you can get the piwik.js at the end of this thread

First I changed the tracking code a little bit:

<script type="text/javascript">
 var _piw = [1,'www.example.com/piwik',['trackPageView']];
 (function(d,t){
     var g=d.createElement(t),
         s=d.getElementsByTagName(t)[0];
         g.async=true;
         g.src='//'+_piw[1]+'/piwik.js';
         s.parentNode.insertBefore(g,s);
  })(document,'script');
</script>

I put the Domain in the _piw so later the piwik.js don’t has to be modified.

 var _piw = [1,'www.example.com/piwik',['trackPageView']];

just use the domain without http:// and the last slash!

Now some changes in the piwik.js:

First the loadHandler function is “normal” again. Webkit (Safari&Chrome) only triggers the load event on refresh.

on line 27 I added this:

var piwikTracker = piwikTracker || null;

if the piwikTracker exists (normal mode) it’s the same, if not (async mode) make new var.

on line 1054 (dev.piwik.org/trac/browser/trunk/js/piwik.js) I added async part:

        /************************************************************
         * Asynchronous tracking part
         ************************************************************/

        //if _piw is set
        if(typeof _piw == 'object'){
            var siteID = _piw.shift(),
                url = documentAlias.location.protocol+'//'+_piw.shift()+'/piwik.php';
                
            piwikTracker = new Tracker(url,siteID);
            for(var i=0;i<_piw.length;i++){
                /*
                * call it as a function and add arguments (max 3 args - trackGoal and trackLink)
                */
                piwikTracker[_piw[i][0]](_piw[i][1], _piw[i][2], _piw[i][3]);
            }
        }

I noticed that IE6 throws some Error on Line 532 where navigatorAlias.javaEnabled = unknown. I changed it to:

                if (navigatorAlias.javaEnabled()) {
                    pluginMap.java[2] = '1';
                }

Hope this will work if no Java is installed.

If you find some spelling mistakes, keep them style_emoticons/<#EMO_DIR#>/smile.gif

You can get the new piwik.js here: http:\piwik.revaxarts.com\piwik.js
(Sorry, I’m not allowed to enter links)

It’s a good start… some quick comments:

PiwikTracker should be a private variable in the Piwik class. This avoids at least one more addition to the global namespace. Furthermore, once the tracker is asynchronous, using PiwikTracker has the same risks as using Piwik, if piwik.js hasn’t been loaded yet.

_piw is unavoidable. However, I suggest we rename it to _paq, so it’s more similar to the GA implementation. (Plus, it sounds better.)

document.location.protocol should stay in the tracking code snippet. We have some users who always use https.

“piwik.php” should stay in the tracking code snippet. Some of us use the js/ proxy.

The site ID and tracker URL are optional parameters to getTracker(). Since we have methods for that (which parallels GA), let’s use that convention. The only change I prefer is to use .apply() internally.

Thus, the initializer would change from:

var _piw = [1,'piwik.org/piwik',['trackPageView']];

to:

var _paq = _paq || [];
_paq.push(['setSideId', 1]);
_paq.push(['setTrackerUrl', 'http://piwik.org/piwik/piwik.php']);
_paq.push(['trackPageView']);

Using the .push() method is important. This allows us to replace _paq with a proxy object once the tracker has loaded. Thus, subsequent _paq.push() calls will indirectly call the tracker (addressing a potential race condition).

Thanks Xaver. The async tracker is now in trunk. It shouldn’t be too much effort for you
to adapt your existing code.

I fixed the IE6 javaEnabled issue differently because your patch fails when java isn’t installed. That too is in trunk.

[quote=vipsoft @ Nov 27 2010, 05:44 PM]Thanks Xaver. The async tracker is now in trunk. It shouldn’t be too much effort for you
to adapt your existing code.

I fixed the IE6 javaEnabled issue differently because your patch fails when java isn’t installed. That too is in trunk.[/quote]

Nice to hear!

The Trackerproxy is just the way I was searching for!

One more thought:

I like to check, if the function is defined, if not just skip it. Secondly I like also have the possibility to pass a function within the _paq, like GA do it (Introduction to ga.js (Legacy)  |  Analytics for Web (ga.js)  |  Google Developers):

  function processAsynchronousTracker()
  {
      if (isDefined(_paq)) {
          asyncTracker = new Tracker();

          for (var i = 0; i < _paq.length; i++) {
              if(isDefined(_paq[i][0])){
                  asyncTracker[_paq[i].shift()].apply(this, _paq[i]);
              }else if(typeof _paq[i] == 'function'){
                  _paq[i];
              }
          }

          // replace initialization array with proxy object
          _paq = new TrackerProxy();
      }
  }

ps: found two apostrophe before ‘trackPageView’:

_paq.push([''trackPageView']);

yes, that’s a typo… I fat-fingered it from my iphone. On that note, skipping undefined methods would mean typo’d method names are ignored – we don’t want that because it increases our support effort. (Why isn’t Piwik tracking?)

I’ll look into pushing functions… might defer to later though. Our tracker doesn’t have any public methods that return a value, so we don’t have a similar use case as in GA.

Ok, pushing functions is implemented too. Grab it from svn.

Great stuff Xaver and Anthon style_emoticons/<#EMO_DIR#>/smile.gif excellent to see Piwik catching up with the competition

Nice to see the next version.

Matt asked some question (dev.piwik.org/trac/ticket/1842) which I try to answer:

Ga tracking tag sets a script.type = ‘text/javascript’; on the DOM node. we shoudl maybe do the same?

Mathias Bynens optimzed the GA async code and sad why you not have to set the text/javascript attribiute: http:\mathiasbynens.be/notes/async-analytics-snippet

Why does the snippet on the doc doesn’t contain the “https:” == document.location.protocol test? Is it relying on the browser to understand that //PIWIK_URL will load with the right protocol?

Paul Irish wrote about the ‘The protocol-relative URL’ on his Blog:
http:\paulirish.com/2010/the-protocol-relative-url/
Also Google use a different subdomain for SSL, so they have to check this.

why is ‘script’ passed as a parameter to the anonymous function rather than harcoded in the 2 places it is used? It saves character space, but it isn’t really a parameter as such… (same with the ‘document’ parameter?).

I think this doesn’t this makes no different exept saving some character space so we should keep it.

UI for ‘show javascript tracking tag’ should probably explain how to use the asynchronous version.

Yes! Also explain how the new _paq is used:

instead of (old tracking code):

pageTracker.setCustomUrl('http://mycustomurl');
pageTracker.trackPageView();

use (async tracking code)

_paq.push(['setCustomUrl','http://mycustomurl']);
_paq.push(['trackPageView']);

Should we make the asynchronous version the default one in the UI (during install and in the SitesManager)?

Documentation could clarify at the start that 2 distinct Tracking techniques exist. Not sure how we should organize it.

I think the old trackingcode should be deprecated because of the use of ‘document.write’.

Thanks for your details, Anthon created a new ticket for the UI changes at http://dev.piwik.org/trac/ticket/1845

Hey Guys!

I don’t know why the new tracking code has to be so huge!

I think you don’t have to explain it within the code, just explain it in the docs. Most of the users will just copy and paste it and don’t waste any time with reading it.

and please, check out this article (with all the comments):

http:\Optimizing the asynchronous Google Analytics snippet · Mathias Bynens

Yes, it can be optimized and minified. (The first iteration on the docs page was very compact.)

But asynchronous tracking abstracts the method calls. This makes it harder to understand for users who are non-programmers but who want/need to extend the tracking code.

Ideally, the proposed Code Generator will have an option to display more compact code.

Ok I see.

So the async tracking code is finished/stable?

I made a “quick 'n dirty” version of a codegenerator:

http:\piwik.revaxarts.com/generator.php

maybe someone find it useful