Friday, June 6th, 2008

Google Analytics after onLoad and document.write for XHTML

Category: JavaScript

I saw two articles that were both looking at similar issues.

Firstly, Pete Higgins has looked at the fact that Google Analytics is normally loaded via serial script tags which cause the rest of the application to wait and created a simple wrapper that changes things.

We have all seen the tell tale: “waiting for … “.

This is usually before the body tag is closed, which causes [at least] Dojo’s addOnLoad() function to wait until after ga.js has been loaded, and executed. I notice it most on the SitePen Dojo QuickStart guide. The whole guide is sent as valid HTML and CSS, then enhanced to break major sections into a custom tabbed view, with simple navigation. Unfortunately, all the code to do that is run from within an addOnLoad function, and my poor pitiful ethernets here in Tennessee make ga.js take upwards of five seconds to load. Long story short, sometimes I’m presented with a nasty jolt of rendering as the navigation is added, and the unselected chapters go hidden.

I decided it was a safe bet I could just append a script tag to my head element, and run that onLoad, allowing the rest of the code to execute as soon as the Dom was ready, regardless of Google being ready.

He pasted in the code (with his hard coded agent…. change that):


  1. dojo.provide("");
  2. dojo.mixin(, {
  3.     // _acct: String
  4.     //      your GA urchin tracker account numbers.
  5.     _acct: dojo.config.urchin || "",
  7.     // _loadInterval: Integer
  8.     //      time in ms to wait between checking again
  9.     _loadInterval: 420,
  11.     _loadGA: function(){
  12.         // summary: load the ga.js file and begin initialization process
  13.         var gaHost = ("https:" == document.location.protocol) ? "https://ssl." : "http://www.";
  14.         var s = dojo.doc.createElement('script');
  15.         s.src = gaHost + "";
  16.         dojo.doc.getElementsByTagName("head")[0].appendChild(s);
  17.         setTimeout(dojo.hitch(this, "_checkGA"), this._loadInterval);
  18.     },
  20.     _checkGA: function(){
  21.         // summary: sniff the global _gat variable Google defines.
  22.         //      if it exists, run _gotGA, otherwise, do another interval
  23.         setTimeout(dojo.hitch(this, window['_gat'] ? "_gotGA" : "_checkGA"), this._loadInterval);
  24.     },
  26.     _gotGA: function(){
  27.         // summary: initialize the tracker, we've got ga.js loaded
  28.         var ga = this._tracker = dojo.hitch(_gat, "_getTracker", this._acct)();
  29.         ga._initData();
  30.         ga._trackPageview();
  31.         this.GAonLoad.apply(this, arguments);
  32.     },
  34.     GAonLoad: function(){
  35.         // stub function to fire when urchin is complete
  36.         // you also have access in this function to this._tracker, which is the
  37.         // root tracker instance you called _initData() on
  38.     }
  40. });
  42. // start it all up after body is ready:
  43. dojo.addOnLoad(,"_loadGA");

This will be in Dojo itself soon, so you will be able too:


  1. var pagetracker = new{ ua: "UA-123456-7" });
  2. pagetracker.trackPageView("/ajaxy-notification");

Secondly, Weston Ruter noted the other problem where Google AdSense and the AJAX Libraries API are using document.write() to inline script tags. In the past John Resig wrote a little wrapper to make document.write to DOM work instead. Weston builds on this work:

My solution, however, has a couple advantages as I see it. First, his solution uses some regular expression hacks to attempt to make the HTML markup well-formed enough for the browser’s XML parser, but as he notes, it is not very robust. Secondly, John’s solution relies on innerHTML which causes it to completely fail in Safari 2 (although this implementation also fails for an unknown reason). I’m trying a different approach. Instead of using innerHTML, this implementation of document.write() parses the string argument of HTML markup into DOM nodes; if the DOM has not been completely loaded yet, it appends these DOM nodes to the document immediately after the requesting script element; otherwise, it appends the parsed nodes to the end of the body.

I’ve incorporated John Resig’s own HTML Parser (via Erik Arvidsson), but I’ve made a couple key modifications to make it play nice with document.write(). I turned HTMLParser into a class with member properties in order to save the end state of the parser after all of the buffer has been processed.

To this class I added a parse(moreHTML) method which allows additional markup to be passed into the parser for handling so that it can continue parsing from where it had finished from the previous buffer. And by removing the last parseEndTag() cleanup call (for document.write() is anything but clean), it then became possible for multiple document.write() calls to be made with arguments consisting of chopped up HTML fragments like just a start tag or end tag, which is exactly what AdSense does and is a common usage of the method.

Check out the
full source code.

Posted by Dion Almaer at 7:35 am

4.1 rating from 20 votes


Comments feed TrackBack URI

I still want to know why sites with all the power of Google are still making this so frigging difficult. Is there a reason they can’t just offer savvier users a DOM-based method for accomplishing this, and eliminate the need for elaborate document.write() hacks?

Comment by rdmey — June 6, 2008

I still can’t stand Google’s namespace pollution for Analytics or Maps. Thinking of doing an iFrame for both.

Comment by matanlurey — June 6, 2008

Good solution! I like document.write hacks. I have my solution too. See Press links: Google Adsense ans GoogleMap.

Comment by sirus — June 6, 2008

Weird. Sorry previous link did not work. See:

Comment by raefa — July 1, 2008

Lightweight standalone crossbrowser (even IE 5.5) and multiple scripts batched:
My 0.02$ … for me it does what it was designed for.

Comment by FrankThuerigen — November 10, 2008

Leave a comment

You must be logged in to post a comment.