Extending The Functionality Of Google Tag Manager Variables
When you create a Custom JavaScript Variable, Google Tag Manager forces you to use an interesting pattern:
function() {
// return something
}
In your Variable, you can add whatever JavaScript you wish, but you must return something. Typically this is a string or number, or sometimes an array or object.
However, Custom JS Variables can return anything, even functions, and you can pass arguments into those functions to get different results.
Why Would I Want To Do This?
Variables that return a function are good for reusing smaller snippets of code that you use over and over. You might be doing this right now by defining functions on the page or inside of a Custom HTML Tag and then calling them in other Variables or Custom HTML tags.
Defining functions in this manner does work, but it pollutes the global scope with variables that can cause collisions with other code on the page. If you are an agency or consultant adding code in this manner, this can have annoying or even catastrophic results, now or at some point in the future.
This is also a simple way to reuse the same code across many Custom HTML Tags or Custom JavaScript Variables. For larger implementations, this can free up valuable space in your container, which has a limit of 200kb.
Why Wouldn’t I Want To Do This?
Variables are evaluated anew each time they are referenced in a tag. Behind the scenes, Google Tag Manager stores your variables as strings, then passes them into eval() wherever they are required. Although this isn’t the worst thing in the world, your scripts may execute a little more slowly as a result, since there will now be an additional eval() for the Custom JavaScript Variable that you are passing your arguments to.
It’s important to note that this approach also will only work in Custom HTML Tags or Custom JavaScript Variables. Inside of a pre-built tag, like the Action field of a Google Analytics event tag, the Variable will return as the function itself, turned into a string – in other words, not good!
Using Functions with GTM Variables
It’s pretty easy to use a function inside of a variable, you just need to keep a close watch on your syntax. Simply return a function with your Variable, just like this:
function() {
return function( arg ) {
// do something with you argument
// return something
}
}
For a purely hypothetical example, let’s try creating a function inside of a Custom JavaScript Variable. A function is something we’ll want to use over and over again, and that will take some sort of argument that we pass to it.
This example below will count the number of images on the page that match a certain filename:
function () { return function (imageFileName) { var count = 0; var images = document.getElementsByTagName('img'); var i; for (i = 0; i < images.length; i++) { var filePathParts = (images[i].src || '').split('/'); var fileName = filePathParts[filePathParts.length - 1]; if (fileName === imageFileName) { count++; } } return count; } }
Use this concept whenever you've got N of the same thing you need for a particular tag or event. For example, this is a great fit for counting all of the images on a page that contain an array of words. But there are more advanced use cases, too.
An Even Better Example
Here's one of my personal favorites. Sometimes, tracking particular behaviors or metrics for clients requires utilizing JavaScript, either in a Custom HTML Tag or a Custom JavaScript Variable. I wrote this function to wrap all of my custom code inside of a try/catch block that fires a Google Analytics event whenever my code fails. I've got a variable that looks like this:
// Variable Name: Custom Script Error Catcher function() { var qaUANumber = {{Const - Error Logging Property}}; return function (name, fn) { // Name is used for identifying the function later on try { return fn(); } catch (e) { if({{Debug Mode}}) { return console.error(e); } logErr(name, e); } }; function logErr(name, e) { var ga = window[window.GoogleAnalyticsObject]; var trackerName = 'errLog' + new Date().getTime(); var fields, values, msg, i; if (!ga) { return; } if ('lineNumber' in e) { fields = ['fileName', 'lineNumber', 'column', 'message']; values = []; for (i = 0; i < fields.length; i++) { values.push(e[fields[i]]); } msg = values.join(); } else if ('stack' in e) { msg = e.stack.toString().replace(/^\s+|\s+$/, ''); } else { msg = (e.name ? e.name + ':' : '') + (e.message || "No err message."); } ga('create', qaUANumber, { name: trackerName, storage: 'none', clientId: 'rand' + Math.random() }); ga(trackerName + '.send', 'event', { eventCategory: 'GTM Exception', eventAction: name, eventLabel: msg, nonInteraction: true }); } }
Then, when I want to add a Custom JavaScript Variable or a Custom HTML Tag, I wrap all the scripts I use in that Variable, like below. In these examples, I've used a script that I know will fail in Internet Explorer 8.
// Variable example function() { return {{Custom Script Error Catcher}}('My Custom Variable', function() { var str = "This Is A String"; var newStr = str.split( "This Is A " )[ 1 ]; return newStr.toString(); }); }
//Custom HTML example
Here's why it fails: Normally, a split that removes an entire chunk returns "" in that chunks place, so the above would evaluate to ["", "String"]. However, on IE8, the empty string is thrown out of the array, so we're left with just ["String"]. This code would then fail, since it expects a value at [1], which there would not be. Without this helper Variable, the function would simply fail and possibly cause other issues for the browser.
With our custom variable, the function still fails, but this time it sends a dataLayer push with the name of the function and the error message. This is information that the built-in JavaScript error listener doesn't get, because of the way the variables are executed. Our script also prevents an error from being logged to the browser console, since the error is caught.
I then use a different tag/trigger to listen for this dataLayer push, and then to send a Google Analytics event to a property that I keep for QA purposes, though you could certainly see other use cases. If my container is in debug mode, I log this info to the console instead of firing an alert.
Since I use a standard Google Analytics Event, I also get the browser, operating system, page URL, and more, which makes diagnosing the problem easier. Since it fires an Event every time, I can also quickly determine which problems are more or less important, depending on their scale.
These are just a few examples of where you might benefit from returning a function with your variables. I'd be really interested to hear any suggestions you've got for how you can apply this pattern.