Google Analytics Tagging with HTML5 data-* Attributes

Imagine if you will, a page with a significant amount of dynamic page elements. For instance, a slide-out panel containing a number of ‘panes’ containing topical information on various ‘factors’. Let’s assume that once this slide-out is open, we wish the user to be able to jump from factor to factor (similar to a slideshow) using buttons along the bottom of each factor. Let’s go even deeper and say that within each factor, we have a collection of close-up images. Again, the user should be able to navigate the close-ups via next/previous buttons similar to a slideshow. In addition to the next/previous buttons, along the top edge of the ‘close-ups’ section are progress indicator dots that highlight according to which close-up is active (and link directly to individual close-ups).  Given that this functionality should work without JavaScript, all of these UI controls are marked up as links. And we have quite a few of them—to wit, x*(x-1) factor links (for x factors) plus x*y (direct links to close ups per factor) plus x*(2y) (next/prev links for each y close-up for each factor). So, let’s say we have 3 factors and 3 close-ups per factor. We now have 33 links! And now the task is to tag each of these links with unique labels that will be sent back to Google Analytics upon each click event. I’ve broken down a brief subset of the analytics tags for each of the three types of links below.

Tag Templates

Tagging template for the factor navigation along the bottom of each [x] factor:

/page_name/factor_[x]/factor_1_icon
/page_name/factor_[x]/factor_2_icon
/page_name/factor_[x]/factor_3_icon

Tagging template for the prev/next buttons on each [y] close-up on each [x] factor:

/page_name/factor_[x]/closeup_[y]/next_arrow
/page_name/factor_[x]/closeup_[y]/prev_arrow

Tagging template for the close-up direct links (also used as progress indicator) on each each [x] factor:

/page_name/factor_[x]/closeup_dot_1
/page_name/factor_[x]/closeup_dot_2
/page_name/factor_[x]/closeup_dot_3

Approach

As with anything, I try to keep my code DRY. Considering that these tags will likely end up as magic strings in some form or another, I’d like to reduce the maintenance overhead of these tags as new factors or close-ups are added or removed. The tags themselves convey the hierarchy of the structure in which they are contained. So let’s map the tagging hierarchy onto the structural hierarchy. This tagging information could be embedded in the id or class attributes of an element, though I think that would be coupling two separate needs (JS behavior/CSS styling + Analytics) onto the same data. This information could also conceivably go into the title attribute, though this attribute is meant for human (read: end-user) consumption. Nothing seems to fit, so let’s try out an HTML5 data-* attribute: data-ga.

First, the /page_name segment should map to the page:

<body data-ga="/page_name">

Each ‘factor_[x]’ segment should map to its own factor pane:

<ul class="factors">
  <li data-ga="/factor_1" />
  <li data-ga="/factor_2" />
  <li data-ga="/factor_3" />
</ul>

Each ‘closeup_[y]’ segment should mapt to its own close-up pane:

<ul class="closeups">
  <li data-ga="/closeup_1" />
  <li data-ga="/closeup_2" />
  <li data-ga="/closeup_3" />
</ul>

 

And each link or button gets its own respective value. Keep in mind, multiple data-ga values will be ‘scoped’ by their ancestors’ data-ga values:

<a href="#factor-1" data-ga="/factor_1_icon" />
<a href="#factor-1-closeup-1" data-ga="/closeup_dot_1" />
<a href="#factor-1-closeup-2" data-ga="/next_arrow" />
<a href="#factor-1-closeup-3" data-ga="/prev_arrow" />

Now, whenever a link is clicked, we simply concatenate the data-ga values from each ancestor! The method below should have the context of this as an anchor element with a data-ga attribute. Generally, it would be in the click handler of any element matching the selector: "a[data-ga]". Once ga is concatenated, it can be used as the tag for a Google Analytics API call (_trackPageview or _trackEvent).

$("a[data-ga]").click(function(event){
  var ga = $(this).parents('[data-ga]').andSelf()
           .map(function(){return $(this).attr('data-ga');});
  ga = $.makeArray(ga).join('');
  _pageTracker._trackPageview(ga);
});

Demo

//jsfiddle.net/eY4cq/1/embed/

A few additional notes

Performance

It would be much better to calculate the ga-tag for every link on the page during page load and cache the result in the data store of the element. As written, the ancestor traversal is executed on every click, and would result in the same return value each time. However, as a special case when I was first implementing this, there were some widgets that altered the hierarchy of the page, thus it was necessary to only perform the concatenation at event-time rather than load-time.

As an additional performance boost, I added a class of ga-scope to each ancestor element that contained a data-ga attribute. This allowed me to use a class selector in the .parents() filter. This will only yield a performance boost in browsers that support a native implementation for querying by class name, thus allowing the selector engine (MooTools or jQuery) to avoid stopping to inspect every single ancestor on its way up the tree.

MooTools

I originally implemented this with MooTools. During the implementation I discovered a bug in the MooTools selector parser where the attribute selector doesn’t properly find attributes with hyphens. So, I created a custom pseudo-selector as a workaround:

Selectors.Pseudo.data_ga = function(){
  return Boolean($(this).get('data-ga'));
};
$$('a:data_ga').addEvent('click', function(event){
  var ga_code = this.getParents('.ga-scope')
                .get('data-ga').reverse().join('')
                + this.get('data-ga');
});

jQuery.Firebug: A call for feedback.

As a result of some of the discussion following from my post on my new jQuery plugin, jQuery.Firebug I’m soliciting feedback for its desired behavior. Example:

$('.setA').log();
$('.setB').log("some", "information");
$('.setC').log("title attribute is: ", ".attr('title')");

Some explanation. The log method follows the same rules as the Firebug console.log method. It can take 0 or more arguments that are concatenated into a space-separated string when finally printed to the console. For some jQuery-specific behavior, I have added a little wrinkle as shown with the log statement following SetC. If an argument to the log method:

  1. is a string
  2. begins with a period (dot)
  3. is a valid jQuery method

then the jQuery method specified is executed on the jQuery selection and the result is printed to the console. In the example above, if the title attribute on the first element of SetC is 'example title' then the final log message would be "title attribute is: example title".

Further, my the plugin will feature an additional option (off by default) that will explicitly print each element in the jQuery selection wrapped in a console.group. In the example above, say SetC contains 2 elements. If the option were turned on, the output would be similar to the output of the following:

console.log("title attribute is: example title");
console.group($(".setC"));
console.log($(".setC").get(0));
console.log($(".setC").get(1));
console.groupEnd();

So, back to the problem at hand. My issue, is when and where to print the jQuery selection itself. The different options are:

  1. only print the jQuery selection when there are no arguments to the log method
  2. only print the jQuery selection when there are no arguments to the log method but also print the jQuery selection in place of any string argument equalling "this" (similar to my jQuery method replacement demonstrated above with .attr("title"))
  3. always prepend the jQuery selection to the arguments (so the jQuery selection is printed before the rest of the arguments)
  4. always append the jQuery selection to the arguments (so the jQuery selection is printed after the rest of the arguments)

I’m leaning towards either #3 or #4 but am open to feedback. Please comment with your suggestions. Keep in mind that all four above choices will still result in just one log message per log() call. Turning on the ‘explicit‘ option is the only thing that will result in more console messages than log() calls. Also, keep in mind that printing the jQuery selection itself to the console will allow deep inspection. For instance, clicking on the jQuery selection in Firebug shows what elements are selected, etc.

Announcing jQuery.Firebug

I have been sitting on my latest jQuery plugin for some time now. Although I realize that the code is not yet of production quality and there are certainly bugs and features that remain to be addressed, I’ve decided that I should at least release this plugin to the wild. At the very least, I would love some feedback on it and possibly new features to be added. “So let’s see it!” you ask?

jQuery.Firebug is a jQuery plugin that simply exposes the Firebug Console API to the jQuery object. That’s about it. Under the covers, it bridges some functionality between Firebug and Firebug Lite and has a host of other small feature. But all in all, it simply adds the Console API methods directly to the jQuery object.

The goal of this plugin is to allow inspection of your jQuery selections while in the middle of a chain. For those of you who have ever had a jQuery chain like:

$(".elements").parents("div")
.find(".new").show().end()
.find(".old").hide();

and you load up the page and it doesn’t work. How do you begin debugging? You open up Firebug but are unable to easily ‘step through’ the jQuery chain. Inevitably, you have to break up each selector, assign it to a temporary variable solely to call console.log(temp) on your selection. Enter jQuery.Firebug:

$(".elements").log()
.parents("div").log()
.find(".new").log()
.show().end().log()
.find(".old").log()
.hide();

Each log method returns the same selection that was passed to it, so you can simply continue your chain as if it weren’t even there. Every Firebug method (as of Firebug 1.2) is supported so you can call debug(), assert(), info(), dir(), profile(), etc.

There are a few additional features that I will address later as the code begins to settle down. For now, the source and documentation can be found in Subversion at svn.jasonkarns.com/jquery/firebug.  There is much work to be done on the plugin as well as on the documentation. Until then, let me hear any feedback you may have.