28

In the EU, there's a cookie law that requires you to load third-party scripts after the user expresses consent, by clicking, scrolling or navigating for instance. So I load 3 scripts by executing a function that's called after document loads. This is the code:

enter image description here

The problem is that it doesn't always work, nor always fail. I see sessions and activity but I also know for a fact that there are visits that don't trigger the scripts because when I tested it myself on several other computers, not all activity was saved in analytics.

What should I fix in the function to make it work all the time?

frenchie
  • 51,731
  • 109
  • 304
  • 510
  • I'm not looking for a solution to load just GA, needs to be all 3 tags. – frenchie Aug 24 '17 at 19:35
  • Just to clarify the problem with another example, there are sessions that are recorded in Hotjar but that don't appear in GA and vice versa. – frenchie Aug 29 '17 at 19:28

8 Answers8

13
$(document).ajaxComplete(function() {
        var _gaq = _gaq || [];
      _gaq.push(['_setAccount', 'UA-XXXXX-X']);
      _gaq.push(['_trackPageview']);

       var loadGoogleAnalytics = function(){
         var ga = document.createElement('script');
           ga.type = 'text/javascript';
           ga.async = true;
           ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';

           var s = document.getElementsByTagName('script')[0];
           s.parentNode.insertBefore(ga, s);
        }
    });
Farhad Bagherlo
  • 6,725
  • 3
  • 25
  • 47
7

If you have a look at this post, you can modify it slightly to achieve what you want:

<script type="text/javascript">
  var _gaq = _gaq || [];
  _gaq.push(['_setAccount', 'UA-XXXXX-X']);
  _gaq.push(['_trackPageview']);

   var loadGoogleAnalytics = function(){
     var ga = document.createElement('script');
       ga.type = 'text/javascript';
       ga.async = true;
       ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';

       var s = document.getElementsByTagName('script')[0];
       s.parentNode.insertBefore(ga, s);
    }
</script>

Then just call loadGoogleAnalytics() whenever the user agrees with your Cookie Usage display

wjvander
  • 601
  • 6
  • 16
  • There are 2 other scripts (for now) and so I'm trying to keep it all organized in one function call. – frenchie Aug 23 '17 at 12:20
  • Wow this is amazing thanks a lot! Here is a way to load Adsense after consent is given https://stackoverflow.com/a/70958220/12668719 – human Feb 02 '22 at 17:18
6

Other response seems to be targeting ga.js which is legacy, whereas your question seems to be oriented towards analytics.js (current).

Studying the analytics.js snippet a little you can easily find an answer (below, formatted with prettier):

(function(i, s, o, g, r, a, m) {
  i["GoogleAnalyticsObject"] = r;
  (i[r] =
    i[r] ||
    function() {
      (i[r].q = i[r].q || []).push(arguments);
    }), (i[r].l = 1 * new Date());
  (a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]);
  a.async = 1;
  a.src = g;
  m.parentNode.insertBefore(a, m);
})(
  window,
  document,
  "script",
  "https://www.google-analytics.com/analytics.js",
  "ga"
);

ga("create", "UA-XXXXX-Y", "auto");
ga("send", "pageview");

The trick is easy : Google creates a global var GoogleAnalyticsObject which is a string representing the name of the global GoogleAnalytics. You can see that by default it's "ga".

So, at loading you need to create two global vars : GoogleAnalyticsObject which is a string and window[GoogleAnalyticsObject] which is the function ( which just accumulates events, this function is replaced when the lib is loaded I guess ). Then when your user accepts the cookies you can load the lib and you're done :)

adz5A
  • 2,012
  • 9
  • 10
4

You can try use this function that works fine to me:

function loadGoogleAnalytics(trackingId) {
                var deferred = $q.defer();

                function loadScript() {
                    var scriptId = 'google-analytics';

                    if (document.getElementById(scriptId)) {
                        deferred.resolve();
                        return;
                    }

                    var s = document.createElement('script');
                    s.id = scriptId;
                    s.innerText = "(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)})(window,document,'script','//www.google-analytics.com/analytics.js','ga');ga('create', '" + trackingId + "', 'auto');";

                    // most browsers
                    s.onload = initGA();
                    // IE 6 & 7
                    s.onreadystatechange = function () {
                        if (this.readyState == 'complete') {
                            initGA();
                        }
                    }

                    document.getElementsByTagName('head')[0].appendChild(s);
                }

                $window.initGA = function () {
                    deferred.resolve();
                }

                loadScript();

                return deferred.promise;
            }
Leonardo Oliveira
  • 1,349
  • 1
  • 12
  • 14
4

Could you try adding the following instead of a call to .innerHTML()

//...text above...
TheAnalyticsScript.text = ['ga-script-string', 'fb-script.string','hotjar.string'].join('\n');
$('body').append(TheAnalyticsScript);

Please let me know if that works.

trk
  • 2,106
  • 14
  • 20
  • According to docs, the scripts need to be appended to the head, not the body. – frenchie Aug 29 '17 at 19:27
  • @frenchie I see. The reason I appended them to the body was because I wasn't sure if they 'd work if you appended something to the head after DOM load; especially since you have to load them after user consent. Did you try changing `body` to `head`. Does it work then ? – trk Aug 29 '17 at 19:33
  • Alright, let's try it out for a day; you can go visit the site and browse a few pages to see if a session gets saved. The other reason I load my scripts this way is so that I can exclude myself from the analytics. The site is at www.goyaphone.eu and I'll test your solution for a day to see if it works. – frenchie Aug 29 '17 at 19:44
  • @frenchie I just came across this read as well: http://www.jspatterns.com/the-ridiculous-case-of-adding-a-script-element/. I think you might want to look at that. Please checkout option #4 on that url; apparently the author mentions that to be the closest best that it can get to appending new scripts to a loaded DOM. Hope that helps. – trk Aug 29 '17 at 19:55
  • Ok, I'll look at it; I'm on my mobile – frenchie Aug 29 '17 at 20:49
3

Here's my pick on the problem. The solution simply patches the default scripts to return function which then adds the script tag only after the consent is granted. Have a look:

// for GoogleAnalytics
var loadGA = (function(i, s, o, g, r, a, m) {
  i['GoogleAnalyticsObject'] = r;
  i[r] = i[r] || function() {
      (i[r].q = i[r].q || []).push(arguments)
    },
    i[r].l = 1 * new Date();

  // call this function after receiving consent
  return function loadGA() {
    a = s.createElement(o),
      m = s.getElementsByTagName(o)[0];
    a.async = 1;
    a.src = g;
    m.parentNode.insertBefore(a, m)
  }
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');

// for Facebook
window.fbAsyncInit = function() {
  FB.init({
    appId: 'your-app-id',
    autoLogAppEvents: true,
    xfbml: true,
    version: 'v2.10'
  });
  FB.AppEvents.logPageView();
};

var loadFB = (function(d, s, id) {
  //- call this function after receiving consent
  return function loadFB() {
    var js, fjs = d.getElementsByTagName(s)[0];
    if (d.getElementById(id)) {
      return;
    }
    js = d.createElement(s);
    js.id = id;
    js.src = "//connect.facebook.net/en_US/sdk.js";
    fjs.parentNode.insertBefore(js, fjs);
  }
}(document, 'script', 'facebook-jssdk'));

// for HotJar
var loadHJ = (function(h, o, t, j, a, r) {
  h.hj = h.hj || function() {
    (h.hj.q = h.hj.q || []).push(arguments)
  };
  h._hjSettings = {
    hjid: 1,
    hjsv: 5
  };

  return function loadHJ() {
    a = o.getElementsByTagName('head')[0];
    r = o.createElement('script');
    r.async = 1;
    r.src = t + h._hjSettings.hjid + j + h._hjSettings.hjsv;
    a.appendChild(r);
  }
})(window, document, '//static.hotjar.com/c/hotjar-', '.js?sv=');

// finally when you receive the consent
function onConsentReceived() {
  loadGA();
  loadFB();
  loadHJ();
}

//- meanwhile you can continue using ga(), hj() whatever here...

Each of the loader can be in it's own script tag but make sure about the ordering of those tags.

riyaz-ali
  • 8,677
  • 2
  • 22
  • 35
1

Rather than appending the entire analytics script, you could have it included always but remove the following line:

ga("send", "pageview");

Then, when the user accepts the cookies.. fire the analytics function that sends the beacon using that same line above. Analytics does not actually send any data unless being told to specifically. That would be the most logical way of doing what you are attempting.

Tex0gen
  • 236
  • 1
  • 2
  • 16
  • I guess the load itself too sends many user-identifiable data and that may violate the law... not sure though, and also this only applies to Google Analytics and not Facebook or Hotjar, which may still record the data (and violate the law) – riyaz-ali Aug 30 '17 at 12:51
  • I can see facebook has the same sort of function. `FB.AppEvents.logPageView();` I can't see enough of the code for hotjar. The loading of the scripts themselves would not send or store any data about the visitors unless told to do so with the beacons. You are only loading in the library in which to carry out those beacon functions and to gather the type of data. I believe the law is regarding stored data. After all, as soon as you hit a server you effectively hand over data about yourself anyway. – Tex0gen Aug 30 '17 at 13:02
  • The cookie law only needs to notify a user regarding that cookies are used and which ones and why. You only need to display a notice that states cookies are used and turn back if they would rather they not proceed. Compliance with the cookie law comes down to three basic steps: - Work out what cookies your site sets, and what they are used for, with a cookie audit - Tell your visitors how you use cookies. - Obtain their consent, such as by using Optanon, and give them some control. The control i always give, is the option to go away. – Tex0gen Aug 30 '17 at 13:05
  • agreed!!! I read Google Analytics documentation and checked my sites network activity and can confirm this behaviour now.... but as far as the law is concerned I still doubt that whether the law is only about storage of cookies (or any storage for that sake) and does it also cover tracking operations... maybe you can provide some more detail (or link to some documentation) – riyaz-ali Aug 30 '17 at 14:10
  • The problem with the EU cookie law, is it's very ambiguous. I have always gone on the basis of setting cookies and allowing the visitor to leave upon notice of the cookies. The user SHOULD accept the conditions in the notice but more often than not, they don't. Short of forcing a user to interact with the notice before website access.. this is still an issue for many developers here in the UK. [See Cookie Law Information](https://www.cookielaw.org/the-cookie-law/) – Tex0gen Aug 30 '17 at 14:15
0

Thinking about what your code does, this function:

  1. Creates a <script> element filled with strings
  2. Adds it to the <head>

Note that the <head> is afaik supposed to be loaded only at the initial load. Depending on the browser implementation etc it might be the case that some of your visitor's browsers ignore the <script> tag you add after the page load is completed.

If I understand when you want to do right you want:

  1. To load the page without the tracking scripts
  2. Show a consent modal/popup
  3. On 'agree' click trigger this function without a page reload

note: many EU websites reload the page after cookie consent because they add the <script> tags with tracking to the page using backend rendering. Often clicking 'I agree to cookies' they store a agreed: true type cookie that changes what webpage their server sends.

If you don't care about reloads:

  1. Set a cookie when the user agrees
  2. Reload the page
  3. Have your backend (php/node/ruby/whatever) add a script tag based on this cookie

If you do care

Change your function to run the tracking codes instead of adding them to the DOM.

Just look at the tracking codes and break them down into easy to read code. For example the Ga code is actually simply:

var ga = document.createElement('script')
ga.type = 'text/javascript'
ga.async = true
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);

And then

function addTrackers() {

// Execute the tracking things instead of adding them to the DOM

}
Mentor
  • 965
  • 9
  • 21