Using jQuery properly in Google Tag Manager
You probably shouldn't be using jQuery in GTM script tags, but if you must, here's how to do it properly.
For reasons outside your control, you may face a situation where using jQuery in custom html tags in Google Tag Manager is necessary.
It may be tempting to add the entire jQuery code in a custom HTML tag, or fetch it from a CDN like this in the tag:
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.2/jquery.min.js"></script>
If you are using jQuery on your web site as well, this will most likely cause conflicts, especially if you're using jQuery plugins.
The jQuery you download through GTM will load asynchronously, and will overwrite your site's jQuery if it loads last. Calls to jQuery plugins after the page has loaded will result in errors, because the plugins were registered with the previous instance of jQuery.
Alternatively, if you load jQuery plugin based code asynchronously and independently of jQuery on your site, and GTM's jQuery loads first, the plugins will be registered with the GTM-loaded jQuery, and when your site's jQuery kicks in, it overwrites the jQuery instance and calls to the plugins will result in errors.
But what about jQuery's noConflict
method?
noConflict
race conditions
The thing about using noConflict
in this situation is that it must be called after the second instance of jQuery has been run, which gets sticky when one or both of them are loading asynchronously.
Translated to real world conditions: the ad agency is using a different version of jQuery in GTM than we are using on the site otherwise, and we'd like to prevent GTM's jQuery from overwriting and messing up the site's jQuery-code.
Is it enough to run noConflict after fetching jQuery asynchronously from a CDN? Nope! You have to wait for jQuery to have actually downloaded and run before calling on noConflict.
This leaves us with the following code:
(function() {
var script = document.createElement('script');
script.src = 'https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.0.min.js';
document.getElementsByTagName('head')[0].appendChild(script);
var done = false;
script.onload = handleLoad;
script.onreadystatechange = handleReadyStateChange;
function handleLoad() {
if (!done) {
done = true;
runNoConflict();
}
}
function handleReadyStateChange() {
var state;
if (!done) {
state = script.readyState;
if (state === "complete") {
handleLoad();
}
}
}
function runNoConflict() {
if (window.jQuery) {
window.$ = jQuery.noConflict(true);
}
}
})();
Let me go through my code bit by bit to explain what's going on.
Setting up for noConflict in GTM
First off, we load jQuery from the CDN:
var script = document.createElement('script');
script.src = 'https://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.1.0.min.js';
document.getElementsByTagName('head')[0].appendChild(script);
Now we have to set up some functions to use when jQuery has actually downloaded from the CDN and been executed:
var done = false;
script.onload = handleLoad;
script.onreadystatechange = handleReadyStateChange;
function handleLoad() {
if (!done) {
done = true;
runNoConflict();
}
}
function handleReadyStateChange() {
var state;
if (!done) {
state = script.readyState;
if (state === "complete") {
handleLoad();
}
}
}
function runNoConflict() {
if (window.jQuery) {
window.$ = jQuery.noConflict(true);
}
}
We have to use both the onload
and the onreadystatechange
to cover our bases due to browser differences in handling load events for script elements.
If the browser doesn't support the onload method for scripts, we have to check each time the state of the script changes with onreadystatechange, and if the state changes to "complete", we run the same code we had run if onload was supported.
Lastly, we defer running the noConflict code until we know the downloaded jQuery has executed, and check this explicitly before calling noConflict.
So what does noConflict do exactly?
The ways of noConflict
There are two use cases for noConflict:
- Needing to allow another script library to use the
$
variable - Needing to run two (or more?) versions of jQuery in parallel
For the first, you simply need to call jQuery.noConflict()
; done! That means the jQuery that has loaded will only be accessible through jQuery
, and $
is now reserved for the other library.
For our current topic, running two versions of jQuery in parallel, we need to add the true value for the deep parameter: jQuery.noConflict(true)
.
This moves the previously loaded version of jQuery into both the $ and jQuery variables. To use the last loaded version of jQuery, you need to save the return value to a new variable:
window.$ = jQuery.noConflict(true);
In my example, I'm saving the last loaded version (the GTM version) back to the $
variable; GTM jQuery = $
, site jQuery = jQuery
.
For this to work properly, all the jQuery calls in GTM need to be done via $
, whereas the site jQuery code needs to be called via jQuery
(or pass jQuery as the variable to a function enclosure that uses $
or whatever you want as jQuery inside).
Only need jQuery to be available in GTM?
You might not actually have a need for two different versions of jQuery to run side by side for GTM-code and on your site in general, and the version on your site is new/good enough to use in GTM.
If you just need to guarantee that jQuery is available for the GTM tags that need it, add the following to the top of the code snippet I showed earlier:
if (window.jQuery) {
return;
}
This prevents the need for noConflict and fetching jQuery from the CDN if jQuery has already loaded when GTM is being executed. Your GTM tags will just use the jQuery from your site, and you prevent creating collision errors for plugins due to loading a new instance of jQuery after they've been initiated.
Don't forget tag sequencing!
After all this hard work it would be a shame to ruin it all by not making it matter at all!
Save the jQuery fetch/noConflict code as its own tag, and have other jQuery-based tags rely on it through Tag Sequencing.
For a tag that uses jQuery code, open Advanced Settings, Tag Sequencing, and then check off "Fire a tag before [name of your tag] fires", and then select the jQuery tag we just set up.
You should probably check the box for "Don't fire [name of your tag] if [name of jQuery tag] fails or is paused", too.
This will ensure that your tags that rely on jQuery load in sequence after the jQuery tag has ensured jQuery is available.