Extreme Challenge: making this simple script work with Turbo (without changing the script)

Suppose that you have an external script (like gtag) that you cannot modify directly.

For simplicity, instead of a complex external script, consider this example script:

console.log('current page: ', window.location.href)

document.addEventListener('click', function() {
  console.log('document click')
})

document.querySelectorAll('a').forEach(function(a) {
  a.addEventListener('click', function() {
    console.log('a click')
  })
})

Without Turbo, this script is very simple and the result is exactly what you expect.

However, with Turbo, no matter how hard you try, you can’t get a normal execution of this script.:

  • If you include the script only once, current page does not detects the new pages.
  • If you include the script multiple time, you will get duplicate events (click events added with addEventListener).

There are many scripts of third party providers that work similarly to the example script. And they will never work properly with Turbo.

Anyone has a solution? Or it is just impossible and I need to remove Turbo?

1 Like

By your definition, it wouldn’t work in any kind of SPA neither, because the “current page” do not detect new pages…

I think simplifying your problem is not going to let us help you. It is better if you provide the whole external script you are having problems with.

1 Like

Thanks for the reply.

If you want to look at a more practical case, we can talk about Google Ads conversion tracking and gtag in general, together with cookie consent banner. It is trivial on a normal website, but becomes much more complex with Turbo. The hard part is that you cannot adapt, change or read the code of the gtag.

This is what I built:

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = ['banner']
  
  connect() {
    window.dataLayer = window.dataLayer || []
    window.gtag = function() { dataLayer.push(arguments) }

    if (localStorage.getItem('consent') === 'true') {
      this._tracking()
    }
    
    if (localStorage.getItem('consent') === null && sessionStorage.getItem('consent') !== 'false') {
      this.bannerTarget.classList.remove('hidden')
    }
  }
  
  decline() {
    this.bannerTarget.classList.add('hidden')
    sessionStorage.setItem('consent', 'false')
  }
  
  accept() {
    this.bannerTarget.classList.add('hidden')
    localStorage.setItem('consent', 'true')
    this._tracking()
  }
  
  _tracking() {
    var script = document.createElement('script')
    script.src = 'https://www.googletagmanager.com/gtag/js?id=AW-12345'
    script.async = true
    document.head.appendChild(script)
    gtag('set', { 'anonymize_ip': true })
    gtag('js', new Date())
    gtag('config', 'AW-12345')
  }
}
<div data-controller="consent" data-consent-target="banner" data-turbo-temporary>
  <p>
    Please select whether you consent to our use of cookies and related technologies.
  </p>
  <div>
    <button type="button" data-action="click->consent#decline">
      Decline
    </button>
    <button type="button" data-action="click->consent#accept">
      Accept
    </button>
  </div>
</div>

This seems to work, but I see that it includes the gtag script in the head more than 1 time (you can see that if you inspect the document body).

Are there any better solutions?

Do you have any examples of implementation of gtag and Turbo?

1 Like

I don’t have any examples sorry. I will test yours later and look for a turbo way, but meanwhile, for the tag script to not duplicate, it should be enough if you add a unique id to the script tag and you check if that ID exists before adding the script.

if (document.getElementById("gtag")) return;
var script = document.createElement('script')
script.src = 'https://www.googletagmanager.com/gtag/js?id=AW-12345'
script.async = true
script.id = "gtag"
...

I already tried to check for presence of the script and not include it, however this creates other issues with tracking: if I remember properly, it didn’t track all the page views if I don’t include the script again on every page load.

I get it, I had a similar issue, but as I had control over the javascript code, I just modified it to listen for turbo events.

Anyway, when navigating with turbo, your page works similar to an SPA, but turbo properly uses the History API, so maybe it is worth to try the History Change trigger type in Gtag.

History change trigger - Tag Manager Help.

Maybe this helps ruby on rails - Googletagmanager with Turbolinks - Stack Overflow

That event is related to turbolinks, I’m not 100% on this but I think you could use the turbo:visit instead event Turbo Reference

1 Like