I'm tracking the Download button click on a website featuring a project of mine with this code:
function trackDownload(link) {
try {
_gaq.push(['_trackEvent', 'Downloads', 'Click', 'Setup executable']);
setTimeout('document.location = "' + link.href + '"', 100);
} catch (err) {}
return false;
}
And the button is something as:
Download
So, when a user clicks it, an event is pushed to Analytics and then the user is redirected to the file.
This is applicable also on external link tracking, no differences.
And now my question. Can I be sure that the Analytics event is "processed" before the user is redirect? If not, that redirection cause the event to be lost? Currently events are being tracked, but I cannot be sure that all of them are.
I read I can also try something a little bit different, pushing the redirection function into Analytics queue:
_gaq.push(function() { document.location = link.href; });
But it's not clear if this works or if it's just equivalent to the previous one. In fact, here it's said that "calls to _gaq.push [...] executes commands as they are pushed".
You are correct in that you can push functions onto the analytics queue. Since functions or tracking events are executed/evaluated in the order that you pushed them on to the array, you should be able to do this:
function trackDownload(link) {
_gaq.push(['_trackEvent', 'Downloads', 'Click', 'Setup executable']);
_gaq.push(function() { document.location = link.href });
return false;
}
Note that the try/catch isn't necessary since push() isn't documented to throw anything (and I'd recommend removing it since empty catch blocks can mask other problems).
You ask:
But it's not clear if this works or if it's just equivalent to the previous one.
In your first example (push, setTimeout), the event will be lost if Analytics hasn't finished loading when you do the redirect (because at that time, _gaq is just an array). In the version with the push(function..., then the event will be recorded before the redirect regardless of whether Analytics has finished loading at the time the user hits the download button. For this reason, I would recommend using push(function....
Be warned that the push(function... version will wait for analytics to finish loading before the redirect happens (which sounds like what you want anyway), but you may want to add a way to handle the case where analytics doesn't load.
Related
I am trying to put the Back Button Redirect Script function to good use. I have a plugin which plays background (user-initiated) music on my site. As long as the user clicks forward, the music streams continuously and nearly uninterrupted from page to page, without restarting. If the user clicks the back button (or refreshes), the music stops and they must manually press play to restart the stream. The author says they have no way to resolve it. I'm not giving up just yet.
My thought is, why not use JavaScript to record the browser's previous page URL, then capture the back button trigger and send the user "forward" to that URL, thus keeping the music stream intact while honoring the user's desire to go back a page?
Conceptually, being a supernoob at JavaScript, I patched this together from different sources on here and codingbeautydev...
$(window).bind("onpopstate", function (e) {
const previousPage = document.getElementById("previous-page");
previousPage.textContent = document.referrer;
window.history.pushState({ page: 1 }, "", "");
window.onpopstate = function (event) {
if (event) {
window.location.href = previousPage;
}
};
});
My first thought is there are surely some syntex errors in there at my doing and potentially much more that need be modified, but I'm hoping someone can easily touch up my rough sketch. Additionally, beyond making this work, I see the limits of this allowing only 1-page of history, and I'm curious if there's a way to nest it into a stack of a few pages to which could be visited in reverse order, all the while moving "forward". First things first though, then on to bigger and better.
Thanks guys! 😀
Mark
You cannot change the default behavior of the browsers's back or forward button unless your app uses URL hashes to navigate, but from my understanding of your question the user actually goes from say .com/paper to .com/boxes and not .com/paper#page1 to .com/paper#page2.
One possible option you could try is using the following (from here):
window.addEventListener('pageshow', function (event) {
if (event.persisted || performance.getEntriesByType("navigation")[0].type === 'back_forward') {
// User got here from using the Back or Forward button
}
});
This will trigger when the user got on the page this code runs on using the back or forward window button, also if the user goes from /boxes back to /paper.
You can try to save the current state of the music playing on the background (which song, timestamp, audio level, etc) in local storage (at max) every second or so, and get the stored values inside the function above to continue the music the user was last listening to when he left the previous page. Not the most elegant solution, but all I think of right now that might actually work.
Edit:
The code you requested. Chrome & Safari will block/ignore it due to history manipulation, except when an user interacts with the page first. It's not how history should be used. Don't use it in production, but play with it all you want. Also, here's an simple example how history can be used.
window.history.pushState({}, '', window.location.pathname);
let previousPage = document.referrer;
window.addEventListener('popstate', (e) => {
window.location.assign(previousPage)
});
So lately I have been learning JS and trying to interact with webpages, scraping at first but now also doing interactions on a specific webpage.
For instance, I have a webpage that contains a button, I want to press this button roughly every 30 seconds and then it refreshes (and the countdown starts again). I wrote to following script to do this:
var klikCount = 0;
function getPlayElement() {
var playElement = document.querySelector('.button_red');
return playElement;
}
function doKlik() {
var playElement = getPlayElement();
klikCount++;
console.log('Watched ' + klikCount);
playElement.click();
setTimeout(doKlik, 30000);
}
doKlik()
But now I need to step up my game, and every time I click the button a new window pops up and I need to perform an action in there too, then close it and go back to the 'main' script.
Is this possible through JS? Please keep in mind I am a total javascript noob and not aware of a lot of basic functionality.
Thank you,
Alex
DOM events have an isTrusted property that is true only when the event has been generated by the user, instead of synthetically, as it is for the el.click() case.
The popup is one of the numerous Web mechanism that works only if the click, or similar action, has been performed by the user, not the code itself.
Giving a page the ability to open infinite amount of popups has never been a great idea so that very long time ago they killed the feature in many ways.
You could, in your own tab/window, create iframes and perform actions within these frames through postMessage, but I'm not sure that's good enough for you.
Regardless, the code that would work if the click was generated from the user, is something like the following:
document.body.addEventListener(
'click',
event => {
const outer = open(
'about:blank',
'blanka',
'menubar=no,location=yes,resizable=no,scrollbars=no,status=yes'
);
outer.document.open();
outer.document.write('This is a pretty big popup!');
// post a message to the opener (aka current window)
outer.document.write(
'<script>opener.postMessage("O hi Mark!", "*");</script>'
);
// set a timer to close the popup
outer.document.write(
'<script>setTimeout(close, 1000)</script>'
);
outer.document.close();
// you could also outer.close()
// instead of waiting the timeout
}
);
// will receive the message and log
// "O hi Mark!"
addEventListener('message', event => {
console.log(event.data);
});
Every popup has an opener, and every different window can communicate via postMessage.
You can read more about window.open in MDN.
I am building AngularJS applications which have common header with links to each of the application:
App1
App2
Each application is running on its own subdomain and when user clicks a link on the header - page redirects to that application.
I have to track user actions with the links, e.g. onClick events with Omniture (but the problem applies to Google Analytics as well). When I add an onClick event that calls a function to send event to Omniture, e.g.:
App1
trackLink() is a function of an AngularJS service, brief implementation:
trackLink: function (eVar8Code) {
s = this.getSVariable(s);
s.eVar8 = eVar8Code;
s.prop28 = s.eVar8;
this.sendOmnitureMessage(s, send, false);
return s;
},
the function executes asynchronously and returns right away. Then standard link's behaviour kicks in: page is redirected to the URL defined in "href" attribute. New page is loaded very quickly (around 70 ms) but AJAX request to Omniture has not been executed: it's all async.
I believe that using events for the links is incorrect approach, one should rather use Query parameters, e.g.:
App1
but it's hard to convince some.
What is a good practise to track events on links?
Change your function to include a short timeout (probably you'd let it return false to suppress default link behaviour, too, and redirect via the location object).
Google Analytics has hit callbacks which are executed after the call to Google was sent, you might want to look if Adobe Analytics has something similar (as this can be used for redirects after the tracking call has been made).
If event tracking and query parameters are interchangeable depends on your use case (they certainly measure different things). However event tracking is a well accepted way for link tracking.
As #Eike Pierstorff suggested - I used capabilities of Adobe Analytics native library to set a delay (200ms) which give the call to Adobe Analytics much better chances to succeed:
in HTML:
App1
in AngularJS service:
sendOmnitureMessageWithDelay: function (s, element, eVar8Code) {
var s = s_gi(s_account); // jshint ignore:line
s.useForcedLinkTracking = true;
s.forcedLinkTrackingTimeout = 200; // Max number of milliseconds to wait for tracking to finish
s.linkTrackVars = 'eVar8,prop28';
s.eVar8 = eVar8Code;
s.prop28 = eVar8Code;
var target = element;
if (!target) {
target = true;
}
s.tl(target, 'o', s.eVar8, null, 'navigate');
this.cleanOmnitureVars();
}
Here, element - is HTML element about.
It works pretty well in 99% of the cases but has issues on the slow and old devices where page loads before call to Adobe has been made. It appears that there is no good solution to this problem and there cannot be guarantee that events would always be recorded in Adobe Analytics (or Google Analytics).
The setup
I have implemented an "onclick" method which calls a bit of third party code asynchronously. It provides a callback, and they suggest using a simple function that sets document.location = href to make sure the page only changes after their method has returned. This works fine if you want to open the link in the same tab, but if you want to open in a new tab then one of two things happens:
The problems
If you middle-clicked, Ctrl+click, Shift+click, or Cmd+click it opens in the same tab after having executed the callback function.
If you right-click and select 'Open in new tab' then the onclick code is not called at all.
The questions
Is it possible to have the callback function return true to the onclick event (in effect, making the entire chain of events synchronous)? Or, can you think of a way to preserve standard browser behavior in Case 1 above?
The code
https://developers.google.com/analytics/devguides/collection/analyticsjs/enhanced-ecommerce:
Product Link
<script>
// Called when a link to a product is clicked.
function clicked() {
ga('ec:addProduct', {
'id': 'P12345',
'name': 'Android Warhol T-Shirt',
'category': 'Apparel',
'brand': 'Google',
'variant': 'black',
'position': 1
});
ga('ec:setAction', 'click', {list: 'Search Results'});
// Send click with an event, then send user to product page.
ga('send', 'event', 'UX', 'click', 'Results', {
'hitCallback': function() {
document.location = '/product_details?id=P12345';
}
});
}
</script>
This is a tricky issue that I've had to tackle multiple times in my career. I've taken various approaches, and in this answer I'll try to summarize my latest thinking:
Don't prevent default.
When you prevent the browser from taking its default action you end up having to recode that stuff yourself, and it gets pretty hairy to manually handle all the possible combinations of user intent and browser/platform variations. It's not worth it. There are some things (like checking which keys are pressed) that can be accounted for, but there are other things (like did the user right click and choose "open in a new tab") that are essentially impossible to detect.
Personally, I think it's best to just let the browser do its thing, and figure out another way to accomplish what you're trying to do.
Make the ga() function sync.
In the sample code you provide, you're trying to send e-commerce and event hits to Google Analytics when the user clicks on a link. The problem is that the ga() call is async, so if the user clicks on a link, the call may or may not finish before the browser loads the new page.
One possible solution would be to override how analytics.js is sending the hit to make it sync instead of async. This will allow you to send the hit without having to prevent default on the event and deal with opening the link manually.
analytics.js recently introduced Tasks, which allow you to override or modify various built-in behaviors of the library. Here's an example of how you'd override the sendHitTask to implement a sync version:
ga('create', 'UA-XXXX-Y', 'auto');
ga(function(tracker) {
// Grab a reference to the default sendHitTask function.
var originalSendHitTask = tracker.get('sendHitTask');
// Modify the send hit task to be sync in cases where an
// external link was clicked.
tracker.set('sendHitTask', function(model) {
if (wasExternalLinkClicked()) {
var xhr = new XMLHttpRequest();
var url = '//www.google-analytics.com/collect?' + model.get('hitPayload');
// Specifying `false` as the third parameter makes the request sync.
xhr.open('GET', url, false);
xhr.send();
} else {
originalSendHitTask(model);
}
});
});
ga('send', 'pageview');
Beware, I haven't tested this code, but I think it should work. And, in case it's not obvious, you'll have to implement wasExternalLinkClicked() yourself, but that shouldn't be too difficult.
Anyway, hopefully that help!
I'm trying to add functionality to a firefox extension to time how long it takes a webpage to perform DNS lookup. Looking at Firebug, I figured it's possible to do so by adding a web progress listener to the browser object and listening for events.
First I register an event listener when a page is loaded:
window.addEventListener("load", function(e) { myObj.onLoad(e); }, false);
Inside myObj.onLoad() I register my web progress listener as such:
gBrowser.addProgressListener(this, Components.interfaces.nsIWebProgress.NOTIFY_ALL);
Finally I implement 'onStatusChange' inside myObj, along with QueryInterface and others:
onStatusChange: function(aWebProgress, aRequest, aStatus, aMessage) {
this.logInfo("onStatusChange: " + aMessage + ". Time = " + (new Date().getTime()));
}
However, when onStatusChange is called, aStatus is always 0 even though aMessage displays the correct event. I've spent hours trying to figure this out. Any ideas why??
Also it seems that onStatusChange with status of 'Ci.nsISocketTransport.STATUS_RESOLVING' is only being called on some components, without being called for others, even though they may have a different domain name that needs to be translated and the DNS has not been cached. Need help plz!
If you attach a progress listener to the tabbed browser you only get a filtered view of progress events. You might find that you need to attach your progress listener to the inner browser instead. (If you want to watch multiple tabs/windows you might find it better to attach your progress listener to the window itself, or, for a service, to the root document loader.)