Rails 4 turbolinks with Google Analytics - ruby-on-rails

I'm wondering what is the best way to implement Google Analytics tracking code along with the turbo linking in Rails 4. Will the normal snippet work? I've also seen a gems for this but I'm not sure what it does.

I like vladCovaliov's solution because it seems most new accounts use Universal Analytics by default (which is also better featured in my opinion). However, I think this answer needs to be combined with the other suggestions that comment out the initial pageview, use the page:change event, and track pageviews, not events.
For example, in your <head>:
<% if Rails.env.production? %>
<script>
(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', 'UA-XXXXXXXX-X', 'auto');
//ga('send', 'pageview');
</script>
<% else %>
<script>
function ga () {
var params = Array.prototype.slice.call(arguments, ga.length);
console.log("GoogleAnalytics: " + params);
};
</script>
<% end %>
And then in a js file you have loaded through the asset pipeline:
$(document).on('page:change', function() {
ga('send', 'pageview', window.location.pathname);
});
This will record a pageview for each page you load with or without Turbolinks. Note that the window.location.pathname is required, otherwise you can get the URL of the first page loaded for all the subsequent page loads. (This also gives you a nice place to edit the URL reported if you wanted to, say, strip out :id path segments from RESTful URLs.)
You can also then easily call:
ga('send', "event", category, action, label, count);
To report Events for other interesting javascript events in your site.

I've went with a different approach that I saw on some web site but it seems reasonable.
Add GA to your header like usual but with a small modification of commenting out the trackPageview event from happening automatically.
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-FOOBAR']);
//_gaq.push(['_trackPageview']);
(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 add this somewhere in your body because the body gets reloaded for turbolinks.
<% if Rails.env.production? %>
<script>_gaq.push(['_trackPageview']);</script>
<% end %>
The production check is optional but this is a nice way to not have GA track localhost hits if you're actively developing. The other perk is you don't have to worry about messing with page:change or page:load bindings and that you can be confident that it'll work on any browser that's trackable by GA without having to worry about double hits or anything weird.

I think a better idea is to use the new Universal Analytics (from analytics.js file).
Universal Analytics Solution
(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', "#{GA_UA}", "#{GA_URL}");
ga('send', 'pageview');
And then when you wanna send an event for example, you can use
<script> ga('send', "event", "#{category}", "#{action}", "#{label}", "#{count}"); </script>
Be careful to render this code in the body, and not in the head. Turbo-links only replaces the body.
And also be careful:
1) The GA_URL needs to match your pages's url
2) The Events show up in real time, but in the events tab, they only appear after 24h +
3) Your account's property need to be 'Universal' for this solution to work
Universal Analytics docs:
https://support.google.com/analytics/answer/2790010?hl=en&ref_topic=2790009

A quick glance into the source shows, that the only thing this gem does is to add some javascript to the asset-pipeline
# google-analytics-turbolinks/lib/assets/javascripts/google-analytics-turbolinks.js.coffee
if window.history?.pushState and window.history.replaceState
document.addEventListener 'page:change', (event) =>
# Google Analytics
if window.ga != undefined
ga('set', 'location', location.href.split('#')[0])
ga('send', 'pageview')
else if window._gaq != undefined
_gaq.push(['_trackPageview'])
else if window.pageTracker != undefined
pageTracker._trackPageview();
That's all there is to it. You can either use the gem or add something like this code-snippet manually to your javascript-assets.

I appreciate scottwb's answer, but unfortunately it does not work with Rails 5. In Rails 5 Turbolinks events were renamed. The 'page:change' event was renamed to 'turbolinks:load'. This is why his example does not work anymore.
You can find an overview of how they were renamed here: https://github.com/turbolinks/turbolinks/blob/master/src/turbolinks/compatibility.coffee
Since this took me some time to figure out, I am posting the proper Rails 5 implementation for everybody coming after me.
Put the following code in your <head>
<script>
(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', 'UA-XXXXXXXX-X', 'auto');
//ga('send', 'pageview'); //moved to an asset file because of turbolinks
</script>
And then in a js file (eg. application.js) in you asset pipeline:
$(document).on('turbolinks:load', function() {
ga('send', 'pageview', window.location.pathname + window.location.search);
});
Remember to replace "UA-XXXXXXXX-X" with your Google Analytics ID.

This solution has worked the best for me: http://reed.github.io/turbolinks-compatibility/google_analytics.html.
It requires at least Turbolinks v2.1.0 or greater.

We just took the standard snippet provided by Google, translated it to CoffeeScript, then modified the last action so it is bound to both the "ready" and "page:load" events. We also added conditional statement to only send the data if the current visitor is not at an admin (so our data remains as clean as possible).
((i, s, o, g, r, a, m) ->
i["GoogleAnalyticsObject"] = r
i[r] = i[r] or ->
(i[r].q = i[r].q or []).push arguments
return
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
return
) window, document, "script", "//www.google-analytics.com/analytics.js", "ga"
ga "create", 'UA-XXXXXXXX-XX', "yourdomain.com"
$(document).on 'ready page:load', ->
ga "send", "pageview", window.location.pathname unless $('body').data('admin') is true

According to this site, you only need to do a slight modification. The problem with Turbolinks is that it only updates parts of the website and therefore, Google Analytics often doesn't perceive that the page has changed. Thus, you must notify it manually by adding the following CoffeeScript to a file in your assets/javascript folder:
$(document).on 'page:change', ->
if window._gaq?
_gaq.push ['_trackPageview']
else if window.pageTracker?
pageTracker._trackPageview()
NOTE: this is not my code, it is taken directly from the previously linked website

Related

Rails 5: Google Tag Manager will not fire

I am using Google Tag Manager and it just stopped working on the landing page that client's are redirected to after filling out a form. It works if you refresh the page, but doesn't work on redirects.
I know this smells like turbolinks, so I've modified the javascript function like so many articles have recommended:
<script>
document.addEventListener('turbolinks:load', function(event) {
console.log(event, dataLayer)
var url = event.data.url;
dataLayer.push({
'event':'pageView',
'virtualUrl': url
});
});
(function(w,d,s,l,i){
console.log("getting it")
w[l]=w[l]||[];w[l].push({
'gtm.start': new Date().getTime(),event:'gtm.js'
});
var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})
(window,document,'script','dataLayer','GTM-######');
</script>
In my console, I see the console.log(event, dataLayer) but there is no request to: https://www.googletagmanager.com/gtm.js?id=GTM-######
When I refresh the page, I see the same things logged to my console, but there IS a request to https://www.googletagmanager.com/gtm.js?id=GTM-######.
Does anyone know how to make this request fire or understand what might be going wrong?

Google Tag Manager interfering with Rails UJS remote:true

I am including GTM in my <head> with the following:
<!-- Google Tag Manager -->
<script>
document.addEventListener('turbolinks:load', function(event) {
var url = event.data.url;
dataLayer.push({
'event':'pageView',
'virtualUrl': url
});
});
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer','GTM-XXXXXXX');
</script>
<!-- End Google Tag Manager -->
And in the <body> with
<!-- Google Tag Manager (noscript) -->
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-XXXXXXX"
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
<!-- End Google Tag Manager (noscript) -->
On my page, I have a link_to helper that deletes an image from the database using rails-ujs.
<%= link_to "Delete", destroy_image_path(image), remote: true, method: "delete" %>
When I remove the GTM code from the page, the Delete link performs as expected.
However, with the GTM code in place, clicking the link results in a Routing Error as it attempts to make a GET request.
No route matches [GET] "/..."
It seems to me that GTM is interfering with rails-ujs but I'm unsure of the best way to run this down.
Edit: I have tried this code with and without the turbolinks:load listener with the same failed result.
I had to do the following:
(function($) {
var handlerElement = document.body;
var selector = 'a[data-method=put],a[data-method=patch],a[data-method=post],a[data-method=delete]';
var events = ['click', 'mousedown', 'mouseup'];
var namespacedEvents = $.map(events, function(event) {
return [event, 'disable_method_links'].join('.');
});
var handlers;
$(handlerElement).on(namespacedEvents.join(' '), selector, function(e) {
e.preventDefault();
});
})(jQuery);
Basically, GTM adds handlers for all of the target anchor tags, delegated from the document node, and wraps the existing handlers, checking whether they prevent the default action / stop propagation, I'm not sure exactly what's causing it, but I think it's that GTM is loading before my events are registered, and not detecting that the default action is prevented, so delegating from the body tag and preventing the default action there ensures that your event is hit before any GTM events (which, as mentioned, are registered on the document node).
I'm not sure whether the mousedown/mouseup events are necessary, but I'm posting this in a hurry before I leave for a long weekend so don't have time to check.
Also, I added a defer tag to the script tag that GTM generates and injects into the document, but I don't think that is necessary either, again, haven't tested.
Also see: https://www.simoahava.com/gtm-tips/fix-problems-with-gtm-listeners/

Twitter/G+ share buttons using wrong URL

I've got an odd issue that I cannot figure out. When using Twitter and Google+ share buttons, it shares the correct page title, but also gives a completely wrong URL.
What's even more weird is that it is using a URL from another website of mine, completely unrelated!
These are the buttons:
<li>Tweet</li>
<li><div class="g-plusone" data-size="medium" data-annotation="inline"></div></li>
Here is the javascript that powers them:
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');</script>
<script type="text/javascript">
(function() {
var po = document.createElement('script'); po.type = 'text/javascript'; po.async = true;
po.src = 'https://apis.google.com/js/plusone.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(po, s);
})();
</script>
I've made sure to be logged into the right account when grabbing the code for the buttons.
I don't get it!
ps. It is being done within my own Wordpress Template if that helps, but I've done this before with no problems.
Make sure you don't have a rel=canonical link on your page pointing to a different URL. Perhaps another plugin is outputting it?

Google Adwords not Tracking Conversions

In my Rails app that uses the Devise gem, it redirects back to the root which has this code:
<script>
(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', 'UA-40000000-1', 'foo.com');
ga('send', 'pageview');
_gaq.push(['_trackPageview', '/home/articles/user_sign_up']);
</script>
In Google Analytics I have Destination set to:
Equals to /home/articles/user_sign_up
However, it's not registering as a conversion. Can someone please help me figure out what I did wrong?
The URL of the page that the code appears on is /home/articles so my intent was to try to overwrite what Google thought the URL was using:
_gaq.push(['_trackPageview', '/home/articles/user_sign_up']);
I think your are mixing up the new analytics.js syntax with the traditional ga.js one.
Try using
ga('send', 'pageview', '/home/articles/user_sign_up');
See the relevant documentation.

Embedded timeline widget not working in SPA

and thanks for taking a moment,
I have gone to the twitter site and created an embedded timeline per their instructions.
if I place the generated code on a simple html page on my desktop, all works as expected. When I place the same code inside my SPA application, i only get a 'follow me' link on the site. The SPA application is built on the John Papa example.
There are no javascript errors thrown. I'm guessing that the heart of the issue may have something to do with the routing, b/c if I navigate directly to the page where I've embedded my timeline, the code works as expected.
i.e. http://localhost:50000/App/views/shared/pillar.html
However, I also have a google calendar widget, and that works as expected.
Tested this in Chrome, FF, IE. Behavior is the same.
Any thoughts on how I might diagnose this further? Or is my approach totally wrong? I'm just looking to add the latest n-number of tweets to what is basically a blog. Nothing too fancy.
<a class="twitter-timeline" href="https://twitter.com/PoundingCode" data-widget-id="313336765203218432">Tweets by #PoundingCode</a>
<script>!function(d,s,id){
var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);
js.id=id;js.src=p+"://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js,fjs);}}
(document,"script","twitter-wjs");
I figured out a solution, but rather than take this down, I hope it might help the next person:
Put the twitter widget code into a new html document.
Have that document take in a querystring called handle
Have that html document parse the querystring and inject it.
Create an iframe bound to an observable that has your twitter handle, passing in that handle as your querystring parameter.
the iFrame data-binding:
iframe data-bind="with: twitter, attr: { src: '../App/views/shared/twitter.html?handle=' + twitter() }" style="height:622px;" seamless="seamless"
The twitter page
<body>
<a class="twitter-timeline" href="https://twitter.com/" + get('handle') data-widget-id="313336765203218432" ></a>
<script>
function get(name) {
if (name = (new RegExp('[?&]' + encodeURIComponent(name) + '=([^&]*)')).exec(location.search))
return decodeURIComponent(name[1]);
}
!function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0], p = /^http:/.test(d.location) ? 'http' : 'https';
if (!d.getElementById(id)) {
js = d.createElement(s); js.id = id;
js.src = p + "://platform.twitter.com/widgets.js";
fjs.parentNode.insertBefore(js, fjs);
}
}(document, "script", "twitter-wjs");</script>
</body>
The Twitter script only checks the DOM once after being loaded. An SPA changes the DOM dynamically, so you have to tell the Twitter script to scan the DOM again:
const twttr = window.twttr
twttr.widgets.load()
If the script was loaded already (usually https://platform.twitter.com/widgets.js), then the twttr object is available in the global namespace.
Here is the relevant documentation: https://developer.twitter.com/en/docs/twitter-for-websites/javascript-api/guides/scripting-loading-and-initialization

Resources