9 Pro Tips for GTM Auto Event Tracking and the Click Element Variable

This week I did a Google Tag Manager implementation where I had no control over the site’s source code and no access to the site’s developers. It’s an imperfect situation but luckily, GTM Auto Event Tracking affords a solution that is very effective considering these constraints.

Google Tag Manager Should Be Easy

This post is meant share a few tips on how to implement, customize, and debug Google Tag Manager in a situation where you are using Auto Event Tracking. This is especially helpful when you want  to track events on pages that you do not have any control over. The heart of Auto Event Tracking and the focus is this post is the {{Click Element}} Auto Event Variable, also known in the dataLayer as “gtm.element.” Through the whole event tracking process, there are three tips:

  1. Setting up Click Event Tracking using “phantom” event triggers
  2. Custom JavaScript Variables to collect data about the page/interaction
  3. Debugging Auto Event Variables and Trigger

Read More

Automate Optimizely Reports With the Results API

“Report automation” is my favorite two-word combo besides “free pizza.” And because of the nature of the testing and optimization game, and the speed it happens (in comparison to something like SEO), reporting can can pile up rather quickly. This is why I was as happy as [see picture below] when Optimizely recently released a test result endpoint from their REST API. To celebrate, I hid in my basement for hours on end and coded an Optimizely + Google Spreadsheet Report Automator that makes it easy to send email reports and import data directly into Google Spreadsheets.

Report Automation Greatness

A REST API, for those of you who haven’t fallen in love with HTTP yet, is essentially a menu of available data that you can request from a service, like Optimizely, Google Analytics, or Moz, based on the service’s URL that you request. (I borrowed that explanation from this great explanation of REST API’s on Quora.)

The Optimizely Results API

The Optimizely Results REST API makes available all the data about your Projects, Experiments, Experiments’ Variations, Goals, and Visitor Segments. If you’re really ambitious you can use the service to create, update, and delete experiments. For me, and hopefully you since you are reading this, the most useful part of the API is the Results endpoint. When you request the Results endpoint, the Optimizely API returns a list of all possible combinations of Goals and Variations. So if you have two variations: ‘Original’ and ‘Variation #1’ and three goals: ‘Engagement’, ‘Users Getting Totally Stoked’, and ‘Revenue,’ the API will return a list of six objects that describe how each variation performed for its corresponding goal.

I thought this was an interesting way of doing this but the awesome @OptimizelyDevs tell me that this is how it must be done in order to keep the service truly RESTful. (See: super-geek read about REST)

Automated Reports > Free Pizza

Back to delicious automation… I started automating reports for myself and team members as soon as I found out that the results endpoint was available and, ever since then, my life has been better. If you think about it, making data more available to your team is going to have two awesome results:

  • Less time wrangling data means more time analyzing and ideating!
  • Promotes an open culture of testing by giving everybody a stronger connection to the experiments and results.

How to Automate Your Optimizely Reporting

With the Results endpoint, there are a number of ways you could set up experiment reporting, like notifications when experiment goals have reached thresholds, notifications when experiments start or end, or time-based reporting to keep your team up-to-date. I tried to make this as broadly accessible to as many experiment driven optimizers as possible so I used the Optizely’s generic export format that you get when you share an experiment’s results. You can either email this to a list of emails or print it to a spread sheet. I made the assumption that you would only want to report on active experiments so that’s what this does. Please comment below to suggest changes. Let’s make this awesome!

Step 1: Enter Your API Token

Go to the Optimizely + Google Spreadsheet Report Automator and make a copy.

To get your Optimizely API token, go to www.optimizely.com/tokens, and click “Generate.” Copy the active token into the Optimizely API Token box then go to the Optimizely Reports menu and click “Test API.”

 

Optimizely Report Automation API Key

This should bring up the following. Please comment if it doesn’t.

Optimizely Report Success

Step 2: Send Email Reports

Enter a list of emails and click “Email Reports” in the Optimizely Reports dropdown. Wait a tick and check your inbox. You have become a reporting spam master!    

Step 3: Auto-create Spreadsheet Reports

Click the Spreadsheet Reports menu item, wait for some HTTP calls and  watch the sheets populate.

Step 4: Set Triggers and Automate Everything!

To set up a time-based trigger, from the spreadsheet click “Tools” > “Script editor…” to open the script editor.

Optimizely Report Menu

Don’t worry, there’s no need to touch the code. In the code editor window click “Resources” > “Current Project’s Triggers.”

Optimizely Report Google Apps Script

Then setup up your time-based trigger for the reporting method that you want.

Optimizely Report Triggers

Test Like Crazy! Report Like a Sane Person!

I hope this works out for you. Please comment with any issues that you might face. If you think this could really be improved. Let me know. Let’s make something great!

Inspecting and Debugging the Google Tag Manager dataLayer

the datalayer in action

ah data jokes…

The Data Layer Inspector and Debugger

This project began as a solution to a problem that I often faced setting up Google Tag Manager and specifically the data layer. The solution that I ended making was dataLayer logger, inspector, and debugger that offered the same features as the Google Analytics Debugger but for the data that was being held in or pushed to the dataLayer. It pretty-logs all the new contents of the dataLayer as it is pushed no matter if the dataLayer.push() happens on the page, in a script or in a custom HTML tag within GTM. Its all there as soon as it happens and it saves a fair bit of time.

datalayer debugger

Each object that is pushed to the dataLayer is logged like this.

One interesting difference between what I ended up with is that unlike the Chrome extension, this works across browsers (as far as I cared to test) and can be run as a standalone script, a custome HTML tag in GTM or even as bookmarklet. (Bare in mind I started this project about a month ago and, if you don’t already know, you will see why declaring that now makes sense shortly.) Also, Simo, if you ever read this, please integrate this into your Chrome extension.

The Google Tag Manager Debugging Problem

If you have ever worked with Google Tag Manager, you recognize that one of the most important, if not the most important feature of GTM is the dataLayer. This was a very conscious choice by the makers of GTM and other tag management companies to solve a problem that has arisen as websites have invariably become web apps with multiple players playing roles on a page, consuming and passing in data. That the data layer offered is a central stage for all the actors in the data game to meet and access the data that they require. It was an elegant solution to eliminated a lot of redundancy and has truly made data access and use a lot more agile.

The problem was that the Google Tag Manager interface, in its infancy was a bit less transparent than was ideal. GTM initially made claims that it would not require IT but that, for all but the most trivial cases was flat out untrue. In the beginning and still at the time that I write this, the process involves a fair bit of Javascript coding and hence, quality analysis and debugging that comes along with it. So if you have pushed to or pulled from the GTM dataLayer, you have entered this into your console more than once … many times more than one.

datalayer array

The tedious way to inspect the dataLayer.

You have inspected the dataLayer array and to see if that event was fired at the appropriate time or if all customer or event attributes made it through accurately. I did, quite often and that why I said, “These nine keystrokes, they can be automated.”

The Rabbit Hole of Problem Solving

Initially I thought it would be cool just to copy the style of the Google Analytics Chrome extension and just show every new push. But two things changed that initially course, this is not the same problem as that and I was getting really interested in the jQuery source. (Which is amazing if you every want to learn some really killer idioms.)

I learned about the Chrome Console API which as luck has it means I learned about the other cool Webkit browser, Firefox’s Console API. After that I realized I was half way there so I learned about decent but not quite as advanced Safari and IE console APIs. (Can you believe there are even cross browser considerations for the Javascript console!?)

So the main feature is just a nifty combo of the console.group method and string styling in the better two browsers to display the pushed object. This all goes on because the main change that the script makes is that it redefines the dataLayer.push method to wrap around the original GTM version of the dataLayer.push method. Once I got that far I had to ask myself why don’t I do this, why don’t I do that and so on until I ended up with a console API of sorts specifically for one, in my opinion conceptually significant, Javascript array. If you want to learn more about all the properties and methods of the Data Layer Debugger I will write a bit of an API reference for kick later. Otherwise the comments in the code do a decent job explaining the what everything does.

The Bookmarklet

If you want to try it out click the bookmarklet below and open up your Javascript console and click around a bit. You’ll see it working, hopefully as expected. If you want yours to keep, drag the bookmarklet into your bookmark bar and click it on any page with a dataLayer and watch the interaction take place.

dataLayer_debugger

The Custom HTML Script

Here is the GTM debugging script on GitHub. Its kind of useless now that the good folks at Google Tag Manager have baked this functionality in to the GTM interface. ( I like to think they saw my code on github and said, “that guy Trevor, he’s on to something…” Try it out, maybe you will find it useful.

(function (window) {

  'use strict'; // if you say so Mr. Crockford.

  // In case the dataLayer_debugger obj does not exist, make it.

  if (window.dataLayer_debugger === undefined) {

      // Set the value of the dataLayer_debugger variable
      // to the return value of an immediately evoked function.

      window.dataLayer_debugger = (function(){

      // Instantiate the main dldb object to be returned as mentioned above

      var dldb = {

        // Cache the dataLayer array.

        "dlCache" : window.dataLayer,

        // Cache the default dataLayer.push method.

        "pushCache" : window.dataLayer.push,

        // Acts as the on/of switch for on,off methods or book marklet.

        "keepState" : false,

        // Counts how many pushes occur.

        "pushCount" : window.dataLayer.length || 0,

        // time starts when GTM rule is fired or bookmarklet click

        "startTime" : new Date(),

        // to hold array of callback functions to call inside new .push method

        "callbacks" : [],

        // Does this debugger off cool logging features?

        "coolConsole" : navigator.userAgent.toLowerCase().match(/chrome|firefox/) ? true : false,

        // An object that holds all the current dataLayer values

        "current" : google_tag_manager.dataLayer

      };

      // Append methods to dldb object:

      // Returns time elapsed from startTime.
      // Used for timestamping dataLayer.push logs.

      dldb.now = function () {

        var now =  (new Date() - dldb.startTime) / 1000;

        return now;
      };

      // Returns whether the debugger is on or off.

      dldb.state = function () {

        var state = window.dataLayer_debugger.keepState ? "On" : "Off";

        return state;
      };


      // Turns the debugger on by changing the keepState variable,
      // changes the dataLayer.push method to add debugging functionality

      dldb.on = function () {

        dldb.keepState = true;
        window.dataLayer.push = window.dataLayer_debugger.push;

        console.log ("The dataLayer debugger is On");
        console.log ("Time is: " + window.dataLayer_debugger.now() + " To reset timer: dataLayer_debugger.reset('time')");

        for (var d = 0; d < window.dataLayer.length; d++){

          var obj = window.dataLayer[d],
          keys = Object.keys(obj);

          for (var k = 0; k < keys.length; k++){

            var key = keys[k],
            val = obj[key];

            // Set the new value of current

            //dldb.current[key] = val;
          }
        }

        window.dataLayer_debugger.log(window.dataLayer_debugger.current,"current dataLayer");

      };

      // Turns debugger off,
      // changes dataLayer.push method back to normal.

      dldb.off = function () {

        dldb.keepState = false;

        window.dataLayer = dldb.dlCache;
        window.dataLayer.push = dldb.pushCache;

        console.log("The dataLayer debugger is " + window.dataLayer_debugger.state() + ".");
        console.log("Current time is: " + window.dataLayer_debugger.now());
      };

      // Set one or many callback functions
      // to the debugging version of dataLayer.push method.

      dldb.setCallback = function (callback){

        window.dataLayer_debugger.callbacks.push(callback);
      };

      // Resets the timer, counter, and/or callbacks depending on arguments.
      // No arguments resets all- essentially the same as page refresh.

      dldb.reset = function () {

        for (var r = 0; r < arguments.length; r++){

          var arg = arguments[r];

          if (arg === "time") {

            dldb.startTime = new Date();

          }  if (arg === "count") {

            dldb.pushCount = 0;

          } else if (arg === "callbacks") {

            dldb.callbacks = [];

          } else {

            dldb.startTime = new Date();
            dldb.pushCount = 0;
            dldb.callbacks = [];

          }
        }
      };

      // Redefines the dataLayer.push method to add debugging functionality,
      // calls (applys) all the functions in the dldb.callbacks array,
      // calls the original dataLayer.push methon on all function arguments.

      dldb.push = function () {

        for (var a = 0; a < arguments.length; a++) {

          var obj = arguments[a];


          dldb.pushCount += 1;

          console.group( 'dataLayer.push: #' + dldb.pushCount + ", Time: " + window.dataLayer_debugger.now());

          window.dataLayer_debugger.log(obj,window.dataLayer_debugger.pushCount);

          console.groupEnd();
          console.dir(window.dataLayer);

          window.dataLayer_debugger.validate(obj, ['transactionTotal','transactionId'], ['transactionAffiliation', 'transactionShipping', 'transactionTax', 'transactionProducts'], "transaction");
          window.dataLayer_debugger.validate(obj, ['network','socialAction'],[], "social");

          // Call all callbacks within the context of the pushed object.

          if (window.dataLayer_debugger.callbacks){

            var callbacks = window.dataLayer_debugger.callbacks;

            for (var j = 0; j < callbacks.length; j++) {

              var callback = callbacks[j];

              callback.apply(obj);

            }
          }

          // Calls original cached version of dataLayer.push.

          dldb.pushCache(obj);
        }
      };

      // Pretty-logs an object's contents.

      dldb.log = function (object, optName){

        var ks = Object.keys(object).sort();

        for (var v = 0; v < ks.length; v++){

          if (ks[v] === 'event'){

            ks.unshift(ks.splice(v,1)[0]);
          }
        }

        console.group("object: " + (optName || ""));

        // Check for "event" property.
        // Put "event" property of pushed object first in list.

        try {

          for (var i = 0; i< ks.length; i++) {

            var key = ks[i],
            val = object[key],
            space = 25 - key.length;


            var logMessage = key + new Array(space).join(' ') + ': ';

            if (window.dataLayer_debugger.coolConsole) {

              var valType = (typeof(val) === 'object') ? '%O' : '%s';

              console.log( logMessage + valType, val);

            } else {

              console.log( logMessage + (typeof(val) !== 'object' ? val : "") );

              if (typeof(val) === 'object') {

                console.dir(val);

              }
            }

            // Set the new value of current

            dldb.current[key] = val;
          }
        }

        catch (e) {

          console.log("dataLayer error: " + e.message);

          if (window.dataLayer_debugger.coolConsole) {

            console.log("object was %O", object);

          } else {

            console.log( "object was:" );
            console.dir(val);
          }
        }
        console.groupEnd();
      };

      // Validates an object against an array mandatory valid key
      // and a second optional array of optional keys.
      // The last argument is the is a string name of the type of objec. ie 'social'

      dldb.validate = function (testObj, validKeys, optKeys, type) {

        var checked,
        validKey,
        optKey,
        checkedKeys = [],
        checks = validKeys.length > optKeys.length ? validKeys : optKeys;


        for (var j = 0; j < checks; j++) {

          validKey = validKeys[j];

          if (testObj[validKey]){

            checked = validKeys.splice(j,1);

            checkedKeys.push(checked);
          }

          else if (optKeys && testObj[optKey]) {

            optKey = optKey[j];

            checked = optKeys.splice(j,1);

            checkedKeys.push(checked);

            if (optKey === "transactionProducts" ) {

              var products = testObj.transactionProducts;

              for (var p = 0; p < products.length; p++) {

                var product = products[p];

                window.dataLayer_debugger.validate(product, ["name","sku","price","quantity"],  "product");

              }
            }
          }
        }

        if (validKeys.length) {

          console.log("Invalid " + (type ? type + " " : "") + "object pushed to dataLayer. Missing: " + validKeys.join(", "));

          return false;

        } else if (optKeys.length) {

          console.log("Valid " + (type ? type + " " : "") + "object pushed to dataLayer. Optional keys not used: " + validKeys.join(", "));
        }

        return true;
      };

      // End method definitions and return the created object.

      return dldb;

    })();

  }

  // Logic responsible for turning dataLayer_debugger on/off.

  if (!window.dataLayer_debugger.keepState) {

    window.dataLayer_debugger.on();

  }
  else {

    window.dataLayer_debugger.off();
  }

}(window));

 

Event Tracking on Squarespace Using Google Tag Manager

I like Google Tag Manager and Squarespace for the same reason: they simplify and streamline the processes website design and Google Analytics implementation while maintaining power and functionality of the product.  For the old sliced bread analogy, they are the greatest things to come along since the knife.

google-analytics-squarespaceSquarespace is a great plug-and-play, drag-and-drop Content Management System (CMS). It allows users to easily add all kinds of content in a simple non-technical way. 

Google Tag Manager is a Tag Management System for Google Analytics . (You could say TMS but nobody does.) It allows for on-page tagging and most other advanced analytics capabilities of Google Analytics. It greatly minimizes technical implementation and actual code that must be added to a website in order to track user interaction. Its semi-technical, in fact, it’s still pretty technical but once the code is implemented by your friendly webmaster or neighborhood hacker, the backend is pretty accessible to fairly tech savvy person.  I recommend checking out this short Google Tag Manager video to get a basic understanding of its features and capabilities. When I watched it, it blew my mind, but then again, I love analytics.

“Stepping Into” Google Tag Manager

That clever heading is a JavaScript joke. Don’t worry if you don’t get, I wouldn’t have either until this year.  If you do, then you are in pretty good shape to follow this guide. To get you up to speed, here are a few great references to learn what’s going on with the technical side of the following implementation.

If you were to do this from scratch, you would need a basic understanding of JavasScript, HTML, CSS and jQuery. Actually, since Squarespace uses YUI instead of jQuery you will have to also understand YUI. I’ll explain later. You can find everything you need to know about learning how to code here.

For the YUI to jQuery translation, use jsrosettastone.com. It does exactly what it sounds like: lists translations of all the functionalities of jQuery into YUI.

For help, references use Google Tag Manager Help, Squarespace Developer Center and the Holy Grail: stackoverflow.com if you ever get hung up.

Step 1: Get Your Google Tag Manager Account and Container

Now that you are a technically proficient hack, let’s dive in!

This has been covered many times so I will let Google take it from here. Go to Google Tag Manager’s website and follow steps 1-3. Do not follow step 4! Do it yourself!

Also check out Justin Cutroni’s extremely helpful guide for setting up your account.

 

Step 2:  Insert the Container and Initialize the Data Layer

This is where the fun starts; the beginning of analytics tracking glory!

Inserting the Container:

Google Tag Manager documentation recommends that you place your container just below the opening body tag. One way to do this is by adding the container into a code block at the top of each page, but that is just a lot of work. Instead, the functional alternative, which works just as well, is “injecting” the container in the header section.

Initializing the Data Layer:

The container is the brain that runs Google Tag Manager on your site but it doesn’t really do anything too special without the data layer.

You will also have to insert a bit of code above the container to initialize the data layer. Technically, this bit of code creates an empty data layer object, which will be filled as user interactions push information to the data layer array. The code looks like this:

<script>
    dataLayer = [];
</script>

<!-- Google Tag Manager -->

Step 3: Insert Code to Push Events to the Data Layer

Edit: Now much of this can be handled by Google Tag Manager’s auto-event listeners.

This method of event tracking offers the agility that differentiates GTM from previous methods of Google Analytics event tracking. It is truly where the magic happens.

Inject this snippet of javascript into the footer. This way loading the script will not slow downloading the visual elements of the page.

This snippet of code says, “every time a user clicks a link to a social media site, I will push the corresponding key/value pair to the data layer.  Most commonly this is done using jQuery, but Squarespace uses YUI so you have to play the game between the lines.

<script>

 YUI().use('event', 'node', function(Y) {
   
   // (1) These variables reference the 'class' link attribute. 
   // Each social media site link has its own 'class' attribute.
   // Note that by using an element class rather than element id,
   // any button that shares that class will fire the GTM rule 
   // triggerd by that class.    
   
   var twitterLink = Y.all('a.social-twitter');
   var gPlusLink = Y.all('a.social-google');
   var linkedInLink = Y.all('a.social-linkedin');
   
   // (2) The 'socialView' function tracks a button click event (2a) 
   // then pushes a corresponding data layer variable to the data layer. (2b)
   
   var socialView= function(socialLink,socialClick){
     socialLink.on("click", function(e){                //(2a)          
       
       dataLayer.push({'event': socialClick});          //(2b)
       
       //These are logs I used for debugging:
       console.log(socialLink + " has been clicked");    
       console.log(socialClick + " was pushed to the datalayer");
   })
 }
   // (3) Finally, 'socialView' (2) is called for each link class variable (1).
   // This pushes a data layer value (2b) into the "key : value: pair below.
   
  socialView(twitterLink, "twitterClick");
  socialView(gPlusLink, "gPlusClick");
  socialView(linkedInLink, "linkedInClick"); 
   
});

//edit: that last bit is pretty sub-par code.
</script>

 

Step 4: Setup Tag
Manager Rules and Tags

The Google Tag Manager interface is not immediately intuitive so give yourself some time to play around with it. Once you get lost in it, you will be amazed by the power it provides.

For this example, we will go through event tracking on the Google+ button on the footer of each page. This will also track any button click for any other button with a designated div “class” attribute of  “social-google.” I use the “class” attribute because my intent is to measure navigation to my social media profiles rather than on-page UX button testing. This can be modified pretty easily to track other events. Let me know if you have questions.

Part 1: Setup the Tag

This tag measures both an Event or Social Event and a Custom Dimensions. This way its possible to track navigation to my Google+ page in aggregate and also at the visitor level.

Part 2: Setup the Rule to fire the Tag

We are tracking a JavaScript event, not to be confused with a Google Analytics event so we use the {{event}} macro to trigger the rule. When the {{event}} matches the string assigned as the data layer variable.

Note: The {{event}} macro must exactly match the data layer and sure that the match is case sensitive!

In the case of the Google+ button, the data layer variable is “gPlusClick.”

Part 3: Create a Container Version

Create a version of your container. Name your container version with a specific and consistent naming convention. This will really help in keeping track of progress and contained tags.

Step 5: Debugging and Google Analytics Real-Time Feature

Good job so far!

Now, to makes sure this is all working properly, preview the new container version. There are few ways to check if your tags are working. First preview your container in “Preview and Debug” mode. If the tags are firing on the target user interaction publish the new container and use the Real-Time feature in Google Analytics to make sure that the virtual pageviews and/or events are being reported properly.

If you followed this whole process or you managed to customize your own event tracking, give yourself a pat on the back! If you have any questions, please comment below or reach me on Twitter.