Ever so often I come across a Google Tag Manager setup where GTM’s own auto-event listeners don’t perform the task they were supposed to. Listener problems seem to be a hot topic in Google+ and the Product Forums as well.

There may be many reasons why your listeners don’t work, but a very common trend is that you have conflicting JavaScript libraries or scripts running on your page.

Let’s explore how listeners work before tackling the problem. You see, when you attach an event listener to an element in your Document Object Model (the collection of all elements on your page), the listener waits for the element to produce the action it is listening for.

  • With a link click listener, the handler waits for a link (<a/>) element to fire a click event.

  • With a click listener, the handler waits for any element to fire a click event.

  • With a form submit listener, the handler waits for a form (<form/>) element to fire a submit event.

Now this should still be easy to follow. You set up a link click listener, and when someone clicks a link, the listener pushes the event gtm.linkClick into the dataLayer object. Run-of-the-mill stuff.

Competing listeners

Here’s the gist. If you have another listener ALSO waiting for the same event, there’s a chance that it will interrupt the event from ever reaching the GTM listener, meaning that your link click listener never fires.

How is this possible?

Well, a GTM link click listener, for example, is not actually primed on every single link element on your page. Rather, it’s attached to the highest possible DOM element node: the document itself. It utilizes a JavaScript feature called event delegation. This means that when a click event occurs on a link, the event starts to bubble (yes, that’s an official term) up the DOM tree, until it reaches the topmost node. Any elements along this bubbly path with listeners attached to them will fire when the event reaches them. This is also called event propagation.

The picture below clarifies the difference between a listener on the link node and a listener on the document node. In the example, I have a single link element with a classic onClick=“” attribute. In reality, this attribute is also an event listener. It waits for a click event on this specific tag. Also, I have GTM’s own click listener primed and ready. As you can see, the onClick listener is attached to the link node itself, whereas the click listener is attached to the document node.

A practical example

Let’s say you have a very simple page with a source code that looks something like this:

<html>
 <head>
  <title>My Page</title>
 </head>
 <body>
  <!-- Google Tag Manager Container here -->
  <div id="main">
   <a href="http://www.google.com">Google</a>
  </div>
 </body>
</html>

If I now add a link click listener to my GTM container, it will be attached to the document node, which is a top-level parent to all the elements you see in the code above.

Now, when someone clicks the link, the following happens:

  1. Any listener attached to the <a/> element itself (e.g. onClick) will be fired first, because it is closest to the event occurrence

  2. Any listener attached to the <div/> element will fire next, because it is the immediate parent to the element where the event occurred

  3. And so on until the event bubbles up all the way to the top of the DOM tree and reaches the document node where GTM’s link click listener is waiting

Here’s the thing. If at any point during the event’s bubbly journey to the top of the mountain its process is halted by a conflicting script, for example, the event will never reach GTM’s listener!

The most common culprit is a competing jQuery listener which contains the following, ominous line of code:

return false;

This effectively halts the progress of the event listener, and returns the action to the link element with a “don’t do what you were supposed to do in the first place” type of statement.

A very common occurrence is when you are using jQuery to animate in-page scrolling to anchor links (i.e. smooth scrolling). I’ve seen a bunch of scripts which hijack the link click event, animate the transition to the correct part of the page, and return false;, because, naturally, they don’t want the link to perform its default action of instantly transporting you to the correct part of the page.

What’s the cure?

You’ll have to talk to your developers about this. Tell them that you need event propagation to work all the way up to the document node. Usually this can be done by replacing

return false;

with

event.preventDefault();

Of course, you’ll need to pass the click event to the handler function as a parameter (event in event.preventDefault();) for this to work. There might also be cross-browser concerns, so do some research before making any changes.

.preventDefault() also prevents the default action of the link click event, but it doesn’t stop event propagation from continuing up the DOM tree. You’ll have to test it, of course, since replacing the code might disrupt the functionality of your script.

NOTE! If you use event.preventDefault(); OR return false;, you’ll need to uncheck the Check validation option in your link click listener. If this is left checked, the listener won’t fire even if the event bubbles up all the way to the document node.

Conclusions

Understanding event propagation and how GTM’s listeners work is one of the things you should not have to worry about when using GTM. After all, tag managers are advertised as a be-all and end-all solution to all your data collection woes with punchlines like: “Forget IT” or “No more development needed”.

The harsh reality is that a tag manager like GTM is just one more library in the client’s often congested resource jungle. Conflicts are bound to happen, especially when external resources like jQuery are used to add functionality to the site.

For one, whenever I work with a complex website, I will never, EVER, disregard the client’s development or IT unit. Rather, I will talk to them about GTM, and work together with them to come up with the best possible implementation plan, without compromising the current front-end deployment.