Google Analytics; Track outbound clicks; How? - javascript

I read this article about tracking outbound links on banners and such:
http://seogadget.co.uk/how-to-count-your-outbound-click-stats-with-onclick-in-google-analytics/
So I added this code to the onClick event of my href:
javascript: pageTracker._trackPageview('/outbound/top_banners/banner_name');
Is this enough?
Because I have read some places I need a "link delay" function or something in the HEAD of my document as well, before any javascript is executed!
Also, where exactly in GA (in the interface) will I be able to view the clicks?
Thanks

Here's the problem: every item of data recorded & reported by Google Analytics is sent to the GA Servers in the Request URL component of a Request by the client for the __utm.gif. The function _.trackPageview() provokes the collection/concatenation of the data into a Request URL, as well as the Request itself. In other words, if _trackPageview() isn't called, no data is sent to the GA Servers.
So the question is whether the GA Request (above) is processed before the Request associated with the outbound link. If it is not, then the click on the outbound link is not recorded by GA, which is not what you want.
So what you want to is delay, just slightly--long enough for the GA Request to occur but short enough so that the user doesn't notice the delay--the outbound link Request.
There are a few differences between the code posted in your Q, and the code below--all diffs are directed to this 'race condition'.
First, notice that the return value from the onClick handler is set to "false"--preventing the client browser from (immediately) navigating to http://www.outbound-link.com
The second diff is the call to setTimeout. The third argument passed in, '100' is the number of milliseconds delay.
Third, the onclick handler (fnx) creates its own tracking object, and therefore doesn't rely on a pageTracker object to have been initialized elsewhere.
Your second question is, where in the GA Browser do you view these clicks? Using GA, you can track events in two distinct ways--using _trackEvent() or _trackPageView().
By tracking outbound links the second way (as you did and therefore as i did below) the 'click' shows up not as an event, but as a pageview ('virtual pageview' is the term most often used by the GA Consultants, et al. to indicate something tracked as a page view but which is actually not). So you will see these clicks reported along with other page views--i.e., in Content (along with Traffic and Visitors, the three main headings in the left-hand panel). How can you tell which lines in that report refer to the these outbound clicks? The value of the Page field (usually the left-most column heading) will be the url of the outbound link. Once you know this, of course, you can create an Advanced Segment or Custom Report or even a new Profile, to report these separately.
<script type="text/javascript">
function fnx(that) {
try {
var pageTracker=_gat._getTracker("UA-YOURACCOUNTHERE-PROFILE");
pageTracker._trackPageview("http://www.outbound_link.com");
setTimeout('document.location = "' + that.href + '"', 100)
}catch(err){}
}
</script>
<a href="www.outbound_link.com" onclick='fnx(this);return false;'>"Take Me Here"</a>

This is my 100%-working solution for tracking all outbound links (I love jQuery, so you need to add jquery.js script to page).
function isLinkExternal(link) {
var r = new RegExp('^https?://(?:www.)?' + location.host.replace(/^www./, ''));
return !r.test(link);
}
$(document).ready(function() {
$(document).bind('click', function(e) {
var target = (window.event) ? e.srcElement : e.target;
while (target) {
if (target.href) break;
target = target.parentNode;
}
if (!target || !isLinkExternal(target.href)) return true;
var link = target.href;
link = '/outgoing/' + link.replace(/:\/\//, '/');
_gaq.push(['_trackPageview', link]);
});
});
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-YOURCODE-1']);
_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);
})();
This script give all outbound links to GA like: /outgoing/http/www.example.com.
For better experience you need to create advanced segment for this links. Create it with parameters: "Page" "Starts with" "/outgoing/http".
That's all! Now you have full statistics for your outbound links.

This solution will allow you to click links with target='_blank' and additionally, will allow you to set data attributes on any link and have those automatically send up to GA.
$('a').bind('click', function( event ) {
var target = $(this).attr('href');
if ($(this).attr("data-event-category") !== undefined && $(this).attr("data-event-label") !== undefined) {
gtag('event', 'click', {
'event_category': $(this).attr("data-event-category"),
'event_label': $(this).attr("data-event-label"),
'event_callback': function(){handle_redirect($(this), event);}
});
} else {
handle_redirect($(this), event);
}
});
function handle_redirect(element, event) {
var target = element.attr('href');
var regExp = new RegExp("//" + location.host + "($|/)");
var isLocal = (target.substring(0,4) === "http") ? regExp.test(target) : true;
var url = target;
if(isLocal===true) {
return;
} else {
event.preventDefault();
gtag('event', 'click', {
'event_category': 'outbound',
'event_label': window.location.href + ' --> ' + url,
'transport_type': 'beacon',
'event_callback': function(){element.unbind( "click"); element[0].click();}
});
}
}

Related

Send gtag event from cross domain to parent domain

I'm trying to setup a send event from a iframe originated on my domain and placed on other domain (not mine). I placed the analytics code on the iframe.
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-XXXXXXXX-XX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-XXXXXXXX-XX',{ 'anonymize_ip': true });
</script>
Bellow that analytics code (with the UA-XXXXXXXX-XX from my parentdomain.com), I do a check to see if the iframe is not on my parentdomain.com and then, I set the tracker attribute to the div id ads_close:
<script>
ref = document.referrer;
whitelist = ["parentdomain.com"];
match = false;
for( var i = whitelist.length - 1; i >= 0; i-- ) {
if( ref.indexOf( whitelist[ i ] ) > -1 ) { match = true; }
}
// If is not the parent domain, then add the onClick atributte to the ID "ads_close"
if( ! match ) {
refer = document.referrer;
var str1 ="gtag(\'event\', \'External\', {\'event_category\': \'yes\',\'event_label\': ";
var str2 = "'";
var str3 = refer;
var str4 = "'";
var str5 = "});";
var tracker = str1.concat(str2) + str3 + str4 + str5;
ads_close.setAttribute("onClick", tracker);
}
</script>
The above code renders this way, IF NOT, on parentdomain.com:
<div class="adspop_close" id="adspop_close" onclick="gtag('event', 'Externos', {'event_category': 'yes','event_label': 'https://www.theotherdomain.com/post/'});"></div>
The problem:
Every time i click on the the div with the ID adspop_close, I cannot see the event on my parentdomain.com google analytics account...
The question:
What am'I doing wrong?
If you try to track data from the iframe itself it will appear as if the interaction is happening on another domain in another session, which is what I think you're trying to avoid. If you want to track interactions in an iframe and act as if they were part of the parent container then the best way is by using postMessage to communicate the event to the parent, where it can be handled naturally. The containing page does not have script access to the iframe for security reasons, but the iframe can send communicate to the containing page via postMessage.
solution 1
The Google Development Guide shows us an approach for this cross-domain interaction (scroll down to the IFRAME section).
To link the interactions into the same session you need to share client id's. Unortunately, iframes typically initalize with the HTML of the page, long before google tracking has the client ID ready. So we can't just pass it on load, but need to wait for everything and then use postMessage.
Here's the containing page code example:
<iframe id="destination-frame" src="https://destination.com"></iframe>
<script>
ga('create', 'UA-XXXXX-Y', 'auto');
ga(function(tracker) {
// Gets the client ID of the default tracker.
var clientId = tracker.get('clientId');
// Gets a reference to the window object of the destionation iframe.
var frameWindow = document.getElementById('destination-frame').contentWindow;
// Sends the client ID to the window inside the destination frame.
frameWindow.postMessage(clientId, 'https://destination.com');
});
</script>
And here's the listener that would be in the iframe:
window.addEventListener('message', function(event) {
// Ignores messages from untrusted domains.
if (event.origin != 'https://destination.com') return;
ga('create', 'UA-XXXXX-Y', 'auto', {
clientId: event.data
});
});
That page also has some extra logic to handle the situation where a client id never comes through postMessage. If you need to pass through the 'UA' string as well and wait to initialize gtag in the iframe completely, that's doable as well. Once you recieve the data you need, initialize gtag and track away. You won't need to rewrite any DOM.
solution 2
You can invert the logic of the postMessage communication instead. Rather than doing any tracking in the iframe at all, you can set up any events to trigger a postMessage instead, passing the information like category, action, and label up to the containing page. In the containing page you would add a listener for the postMessage and handle it by triggering a gtag event.
For instance, from the iframe:
<script>
try {
var postObject = JSON.stringify({
event: 'iframeClickEvent',
category: 'someCategory',
action: 'someAction',
label: 'someLabel'
});
parent.postMessage(postObject, 'https://www.YOURWEBSITE.com');
} catch(e) {
window.console && window.console.log(e);
}
</script>
and the containing page:
window.addEventListener('message', function(message) {
try{
var data = JSON.parse(message.data);
var dataLayer = window.dataLayer || (window.dataLayer = []);
if (data.event === 'iframeClickEvent') {
dataLayer.push({ 'event': 'someEvent', .... });
}
} catch(e){}
});

Google AdWords Conversion Tracking on a form send

Good day,
I'm trying to configure the AdWords conversion code for my website and I can't find any of the information I'm looking for in Google documentation. Ultimatly, I want to track a onClick event on the submit input of the form.
I want to know what should I include in my onClick event, since the send button doesn't lead to another page, but have an AJAX loading?
My AdWords tracking code is :
<script type="text/javascript">
/* <![CDATA[ */
goog_snippet_vars = function() {
var w = window;
w.google_conversion_id = 12345678;
w.google_conversion_label = "abcDeFGHIJklmN0PQ";
w.google_remarketing_only = false;
}
// DO NOT CHANGE THE CODE BELOW.
goog_report_conversion = function(url) {
goog_snippet_vars();
window.google_conversion_format = "3";
window.google_is_call = true;
var opt = new Object();
opt.onload_callback = function() {
if (typeof(url) != 'undefined') {
window.location = url;
}
}
var conv_handler = window['google_trackConversion'];
if (typeof(conv_handler) == 'function') {
conv_handler(opt);
}
}
/* ]]> */
</script>
<script type="text/javascript"
src="//www.googleadservices.com/pagead/conversion_async.js">
</script>
Ultimately, I was looking for the conversion to be saved only if the form is sucessfully sent (After Javascript validation). I could not use the onClick on the button, because it would save the conversion even if the form has not submited due to an invalid value in a field.
The easiest way to do so, was to add this in jQuery :
$('#contactform').submit(function(e){
if( $( this ).valid() ) {
goog_report_conversion ('http://example.com/your-link') // Change for your page link.
alert("Conversion!"); // Confirmation that the conversion has been sent. Remove after testing.
}
});
I guess it's too late, but for the future generations. There is a built-in way for AdWords to do this.
It is described in this article.
In brief you would need to change setting in AdWords it would generate you async tag, which you load with the DOM, and activate with onClick handler.
Example:
<a onclick="goog_report_conversion ('http://example.com/your-link')"
href="http://example.com/your-link">Download now!</a>
You can load de of the noscript version.
So, in you onClick event, append the img to some element on the page and it will load it and will count the conversion.

stop firing popstate on hashchange

I am working with the History API and using push and pop state. I want to stop the popstate event from firing in some scenario where I am only appending the hash to URL. for example in some cases on click of anchor it appends # to the URL and popstate is immediately fired) I want to avoid all the scenarios where # or #somehasvalue is appended to the URL and stop popstate from firing. I am mainitaing the URL's with query parameters and I do not have any scenario where I need the popstate event to fire with # in the URL.
Here is my code.
if (supportsHistoryApi()) {
window.onpopstate = function (event) {
var d = event.state || {state_param1: param1, state_param2: param2};
var pathName = window.location.pathname,
params = window.location.search;
loadData(event, pathName + params, d.state_param1, d.state_param2);
}
As far as I found you cannot stop the popstate from firing unfortunately.
What you can do is check for the event.state object. That will be null on a hash change.
So I'd suggest adding a
if(event.state === null) {
event.preventDefault();
return false;
}
To your popstate event handler at the very beginning.
I think that's the best way to prevent firing of the popstate handling code on your side when the hash changes, but i'd be interested to know if there are other solutions.
I have a solution!
When popstate event calls, check the pathname if it has changed, or just the hash changed. Watch below how it works for me:
window.pop_old = document.location.pathname;
window.pop_new = '';
window.onpopstate = function (event) {
window.pop_new = document.location.pathname;
if(pop_new != pop_old){
//diferent path: not just the hash has changed
} else {
//same path: just diferent hash
}
window.pop_old = pop_new; //save for the next interaction
};
In case someone still need this:
var is_hashed = false;
$(window).on('hashchange', function() {
is_hashed = true;
});
window.addEventListener('popstate', function(e){
// if hashchange
if (is_hashed) {
e.preventDefault();
// reset
is_hashed = false;
return false;
}
// Your code to handle popstate here
..................
});
This is pretty simple.
To prevent the popstate event to fire after you click a link with hash you have to eliminate the concequence of clicking the link - which is the addition of the hash to the browser address bar.
Basically you have to create the handler for click event, check if the click is on the element you whant to prevent a hash to appear in the URL and prevent hash to appear by calling event.preventDefault(); in the handler.
Here is the code example:
/**
* This your existing `onpopstate` handler.
*/
window.onpopstate = function(e) {
// You could want to prevent defaut behaviour for `popstate` event too.
// e.preventDefault();
// Your logic here to do things.
// The following reload is just an example of what you could want to make
// in this event handler.
window.location.reload();
};
/**
* This is the `click` event handler that conditionally prevents the default
* click behaviour.
*/
document.addEventListener('click', function(e) {
// Here you check if the clicked element is that special link of yours.
if (e.target.tagName === "A" && e.target.hash.indexOf('#the-link-i-want-make-discernable') > -1) {
// The 'e.preventDefault()' is what prevent the hash to be added to
// the URL and therefore prevents your 'popstate' handler to fire.
e.preventDefault();
processMySpecialLink(e, e.target);
}
});
/**
* Just an example of the link click processor in case you want making something
* more on the link click (e.g. smooth scroll to the hash).
*/
function processMySpecialLink(e, target) {
// Make things here you want at user clicking your discernable link.
}
Here is the matching HTML markup:
<!-- Somewhere in the markup -->
<span id="the-link-i-want-make-discernable"></span>
<!-- Some markup -->
My Special Link
<!-- Another markup -->
Any other link
This all does what is described above: prevents the default behaviour for a special hash link. As a side effect it makes no popstate event to fire as no hash is added to URL for the special case of clicking the #the-link-i-want-make-discernable hash link.
I had the same problem in SPA.
I fixed this by checking if current URL and new URL are the same - technically I don't prevent popstate but I prevent fetch if hash only changed.
So, I get current URL when page loaded. It must be var to be global:
var currentURL = window.location.origin + window.location.pathname + window.location.search;
Then when history change event fired (click by link, click native browser buttons 'back' 'forward') I check if current URL and new URL are the same (hash ignored).
If URLs are the same I make return, otherwise I update current URL.
let newURL = window.location.origin + window.location.pathname + window.location.search;
if ( currentURL == newURL ) return;
currentURL = newURL;
Additionally you can to see controller code. I added it to be able stop loading when user click fast a few times 'back' or 'forward' so a few requests starter - but I need to load last one only.
Full solution of load html file when URL changed (ignore when hash only changed), with push to history, and workable 'back' and 'forward' buttons.
// get current URL when page loaded.
var currentURL = window.location.origin + window.location.pathname + window.location.search;
// function which load html.
window.xhrRequestDoc = ( currentLink, pushToHistory = true, scrollTo = 'body' ) => {
if ( pushToHistory ) {
history.pushState( null, null, currentLink );
}
// get new URL
let newURL = window.location.origin + window.location.pathname + window.location.search;
// return if hash only changed
if ( currentURL == newURL ) return;
// update URL
currentURL = newURL;
document.body.classList.add( 'main_loading', 'xhr_in_progress' );
// create controler to stop loading - used when user clicked a few times 'back' or 'forward'.
controller = new AbortController();
const signal = controller.signal;
fetch( currentLink, { signal: signal })
.then( response => response.text() )
.then( function( html ) {
// initialize the DOM parser and parse as html
let parser = new DOMParser();
let doc = parser.parseFromString( html, "text/html" );
// insert data and classes to 'body'
document.body.innerHTML = doc.querySelector( 'body' ).innerHTML;
} );
}
window.addEventListener( 'popstate', () => {
// if user clicked a few times 'back' or 'forward' - process last only
if ( document.querySelector( '.xhr_in_progress' ) ) controller.abort();
// run xhr
xhrRequestDoc( location.href, false );
})

'load' event not firing when iframe is loaded in Chrome

I am trying to display a 'mask' on my client while a file is dynamically generated server side. Seems like the recommend work around for this (since its not ajax) is to use an iframe and listen from the onload or done event to determine when the file has actually shipped to the client from the server.
here is my angular code:
var url = // url to my api
var e = angular.element("<iframe style='display:none' src=" + url + "></iframe>");
e.load(function() {
$scope.$apply(function() {
$scope.exporting = false; // this will remove the mask/spinner
});
});
angular.element('body').append(e);
This works great in Firefox but no luck in Chrome. I have also tried to use the onload function:
e.onload = function() { //unmask here }
But I did not have any luck there either.
Ideas?
Unfortunately it is not possible to use an iframe's onload event in Chrome if the content is an attachment. This answer may provide you with an idea of how you can work around it.
I hate this, but I couldn't find any other way than checking whether it is still loading or not except by checking at intervals.
var timer = setInterval(function () {
iframe = document.getElementById('iframedownload');
var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
// Check if loading is complete
if (iframeDoc.readyState == 'complete' || iframeDoc.readyState == 'interactive') {
loadingOff();
clearInterval(timer);
return;
}
}, 4000);
You can do it in another way:
In the main document:
function iframeLoaded() {
$scope.$apply(function() {
$scope.exporting = false; // this will remove the mask/spinner
});
}
var url = // url to my api
var e = angular.element("<iframe style='display:none' src=" + url + "></iframe>");
angular.element('body').append(e);
In the iframe document (this is, inside the html of the page referenced by url)
window.onload = function() {
parent.iframeLoaded();
}
This will work if the main page, and the page inside the iframe are in the same domain.
Actually, you can access the parent through:
window.parent
parent
//and, if the parent is the top-level document, and not inside another frame
top
window.top
It's safer to use window.parent since the variables parent and top could be overwritten (usually not intended).
you have to consider 2 points:
1- first of all, if your url has different domain name, it is not possible to do this except when you have access to the other domain to add the Access-Control-Allow-Origin: * header, to fix this go to this link.
2- but if it has the same domain or you have added Access-Control-Allow-Origin: * to the headers of your domain, you can do what you want like this:
var url = // url to my api
var e = angular.element("<iframe style='display:none' src=" + url + "></iframe>");
angular.element(document.body).append(e);
e[0].contentWindow.onload = function() {
$scope.$apply(function() {
$scope.exporting = false; // this will remove the mask/spinner
});
};
I have done this in all kinds of browsers.
I had problems with the iframe taking too long to load. The iframe registered as loaded while the request wasn't handled. I came up with the following solution:
JS
Function:
function iframeReloaded(iframe, callback) {
let state = iframe.contentDocument.readyState;
let checkLoad = setInterval(() => {
if (state !== iframe.contentDocument.readyState) {
if (iframe.contentDocument.readyState === 'complete') {
clearInterval(checkLoad);
callback();
}
state = iframe.contentDocument.readyState;
}
}, 200)
}
Usage:
iframeReloaded(iframe[0], function () {
console.log('Reloaded');
})
JQuery
Function:
$.fn.iframeReloaded = function (callback) {
if (!this.is('iframe')) {
throw new Error('The element is not an iFrame, please provide the correct element');
}
let iframe = this[0];
let state = iframe.contentDocument.readyState;
let checkLoad = setInterval(() => {
if (state !== iframe.contentDocument.readyState) {
if (iframe.contentDocument.readyState === 'complete') {
clearInterval(checkLoad);
callback();
}
state = iframe.contentDocument.readyState;
}
}, 200)
}
Usage:
iframe.iframeReloaded(function () {
console.log('Reloaded');
})
I've just noticed that Chrome is not always firing the load event for the main page so this could have an effect on iframes too as they are basically treated the same way.
Use Dev Tools or the Performance api to check if the load event is being fired at all.
I just checked http://ee.co.uk/ and if you open the console and enter window.performance.timing you'll find the entries for domComplete, loadEventStart and loadEventEnd are 0 - at least at this current time:)
Looks like there is a problem with Chrome here - I've checked it on 2 PCs using the latest version 31.0.1650.63.
Update: checked ee again and load event fired but not on subsequent reloads so this is intermittent and may possibly be related to loading errors on their site. But the load event should fire whatever.
This problem has occurred on 5 or 6 sites for me now in the last day since I noticed my own site monitoring occasionally failed. Only just pinpointed the cause to this. I need some beauty sleep then I'll investigate further when I'm more awake.

Google Analytics - event tracking code in separate <script> than the page tracking code

On a site I'm working on, the main section of the tracking code is located at the bottom of the pages (not my choice of placement, but that's where it is). That code is the main:
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXXXXX-X']);
_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);
})();
On one specific page, I want to track an event that gets auto launched on page load. What I have for code at the top of the page (and therefore in a separate tag than the above code) is:
function getFile()
{
if("<%= getFileURL()%>".length>0){
window.location.href = "<%=getFileURL() %>";
var _gaq = _gaq || [];
_gaq.push(['_trackEvent', 'Downloads', '<%= getFileURL()%>']);
}
}
It doesn't seem to be tracking the event though. I'm sure I'm doing something wrong here and I can take some guesses, but it's not clear enough for me to know exactly what I need to change.
Two problems with your code:
You're redirecting before tracking the event. For best results (like #EkoostikMartin mentioned), you'll want a slight delay between the event tracking and redirect.
You've got a local _gaq defined inside getFile(), so you're not talking to the actual Google analytics code.
Try:
function getFile() {
var href = "<%=getFileURL() %>";
if (href.length > 0) {
_gaq.push(['_trackEvent', 'Downloads', href]);
setTimeout(function(){window.location.href = href}, 100);
}
}
You need to delay the redirect. Something like this below has worked for me in the past:
function getFile() {
var href = "<%=getFileURL() %>";
if(href){
_gaq.push(['_trackEvent', 'Downloads', href]);
setTimeout("gotoUrl('" + href + "')", 100);
}
};
function gotoUrl(href) {
window.location.href = href;
};

Categories

Resources