Originally posted on 1st March but significantly updated since, therefore the change of date.
A few days ago I posted a blog entry describing a possible method for unobtrusively tracking all links clicked on a web site using JavaScript and the DOM. Whilst I was generally happy with the approach I took there is one aspect of the implementation which has been bothering me and still isn't right. The problem is that the current mechanism resets the "href" attribute of each link to include the redirect URL. This means that when a user hovers over a link they see the redirect information. Worse still if they want to copy a link to the clipboard the redirect part of the URL is copied too. This behaviour hardly subscribes to the unobtrusive approach so some new thinking is needed.
Originally I had planned to dynamically set up a "click" handler for each link which would fetch a link's "href" attribute when clicked, call "document.location.href" and prefix the redirect URL. This would log the click, redirect the user to the site referenced in the link while a final "return false;" statement would prevent the link's default navigation behaviour. This method worked well for opening links in the same window but didn't work well when middle clicking (mouse scroll wheel) to open a new tab. It seemed that the "click" event didn't fire and to add to this there seemed to be no way to have "document.location.href" reference the newly opened tab.
Today I tried a new mechanism and it seems to work quite well. It solves the link display and copy to clipboard problems whilst at the same maintaining the functionality of the original implementation.
In this version I used an "mouseup" handler to log the link click. When the user clicks a link the script loads a server side tracking script through the "src" attribute of a newly created JavaScript image object. A short delay is added to give this request time to complete before the browser loads the requested URL.
For users browsing links with the keyboard the same mechanism is used the only addition being a surrounding "if" statement which looks for character 13 (the return key) before logging the "click".
JavaScript Class
The new JavaScript routine is shown below:
// create objectvar linkTracking = {}// assign propertieslinkTracking.redirectUrl = '/redirect.php';// constructor (sort of)linkTracking.init = function() {// check for support of required methodsif (!document.getElementsByTagName) {return false;}// get all links on the pagevar links = document.getElementsByTagName('a');// assign event handlersYAHOO.util.Event.addListener(links, 'mouseup', linkTracking.mouseUp);YAHOO.util.Event.addListener(links, 'keypress', linkTracking.keyPress);return true;}linkTracking.mouseUp = function(e) {linkTracking.redirectHandler(YAHOO.util.Event.getTarget(e));}linkTracking.keyPress = function(e) {// check for return key pressif (YAHOO.util.Event.getCharCode(e) == 13) {linkTracking.redirectHandler(YAHOO.util.Event.getTarget(e));}}linkTracking.delay = function(mseconds) {var currentTime = new Date();var endTime = currentTime.getTime() + mseconds;while (currentTime.getTime() < endTime) {currentTime = new Date();}}linkTracking.redirectHandler = function(link) {// check for valid href, don't want to track anchorsif (link.getAttribute('href')) {var url = link.getAttribute('href');var title = link.childNodes[0].nodeValue;// check for title attribute, otherwise use link textif (link.getAttribute('title')) {title = link.getAttribute('title');}// track click by requesting URL through new image objectvar currentDate = new Date();var trackImg = new Image();trackImg.src = linkTracking.redirectUrl + '?url=' + escape(url) + '&title=' + escape(title) + '&rnd=' + currentDate.getTime();linkTracking.delay(300);}}YAHOO.util.Event.addListener(window, 'load', linkTracking.init);
I've used the newly released Yahoo! User Interface Library to handle the brunt of the event work in a reliable cross browser fashion. It's been a joy to use and if you haven't already checked it out I recommend you take a look.
This mean the script tags you need to include in your HTML are now as follows:
<script type="text/javascript" src = "/js/YAHOO.js" ></script><script type="text/javascript" src = "/js/event.js" ></script><script type="text/javascript" src="/js/link-tracking.js"></script>
You can download the Yahoo! libraries here.
PHP Tracking Script
The SQL to log the click uses "insert delayed" which buffers insertion of the record and allows the PHP script to return immediately regardless of how busy the DB server is.
<?phpif (isset($_GET['url']) && isset($_GET['title'])) {require('includes/db.inc.php');$oDb = new Database();$sUrl = $oDb->EscapeQuotes($_GET['url']);$sTitle = $oDb->EscapeQuotes($_GET['title']);$oDb->Run("insert delayed into click_logs (url, title, click_date) values ('$sUrl', '$sTitle', now())");$oDb->Close();} else {echo 'URL or title not set.';}?>
Other Details
All other details, including the DB structure and SQL queries stay the same as before.
The mousedown action doesn't guarantee a link being open. If you mousedown on a link and suddenly you change your mind not to open it, you just drag away from the link and release the button (mouseup) and the link won't open. With your code, it will be tracked even if the link is not opened.
@glenngv - Yes quite true. I've updated the code to use the mouseup event instead. The normal click event is no good as it doesn't fire for middle button clicks.
With this method of tracking I do think that one has to accept overall percentages rather than exact figures as there are also a number of other situations where tracking won't take place as well, for example if a user has JavaScript disabled or if they right click a link and select open in a new window.
This works a treat. Thanks for sharing it. A small question:
As far as I can see it is not implemented in your pages. Did you have to remove it for a certain reason, say it was offending search engines? Thanks.
When testing this in IE, it seems to insert 2 records into my database, whereas (correctly) in Firefox only one. I'm having trouble tracking the source of this, but it must be sending 2 requests to my tracking script. Do you find the same with yours in IE or have I messed something up?
My bad! I had left in the original addLoadEvent function and call which made IE load it twice. I have taken it out and now just use the YAHOO YAHOO.util.Event.addListener(window, 'load', linkTracking.init);
All works very well. Thanks for this article!
@Ingram - I'm pleased it works well for you. I originally wrote the script just to see how feasible it was. I don't particularly need to track clicks on my site so it's not there at the moment - I'm certainly not aware of it causing any problems with search engines.
Cheers for this one Ed!
I think i will be implementing a slightly tweaked version of this on nildram.net and most likely f2s.net too!
Hi Simon - np, I'm glad the tracking is useful to you. I look forward to seeing it on your sites.
I can't get this working; I'm having trouble seeing what I need to merge between the two blog posts, it would be super if you could just condense them into a single post.
The yahoo scripts make simon's scripts unneeded, right?
My page isn't even calling the redirect script, so I'm not exactly sure what I'm missing. I've got the 3 yahoo library JS calls and the redirect script just dumps some info into the db (it works when i call redirect.php directly) but the php isn't executed when I'm clicking links.
What could be the issue? I have no JS errors/warnings in firefox.
I spoke too soon. My issues were: couldn't call ../redirect.php, so I moved redirect.php into the JS folder and had to rename yahoo.js YAHOO.js (it came lowercase from the yahoo library download)
All is working great. Excellent script!
I just wrote a link tracker that's purely JavaScript - no need of the Yahoo! JS libraries. It's in such a less amount of code, I wonder how it even works! :)
Hi Ed, the tracker works fine in Ie and Firefox but I've problems getting it to work in Opera (9.21), did you test the script with this browser ?
I would be interested in hearing the response to mutui's post about Opera issues.
I would also love to see Aalaap's JS-only link tracker.