jQuery: Click tracking with PHP

neezer picture neezer · Dec 11, 2009 · Viewed 8.3k times · Source

Yes, I KNOW about Google Analytics. We use it for our overall site metrics, and I know we can track individual links. However, we needed a tracking solution for very specific links and we need that tracking data available to our web application in real time, so I wrote my own solution:

jQuery:

  $.fn.track = function () {
    var source, url, name, ref, $this;
    $this = $(this);
    if (window.location.search.substring(1) != '') {
      source = window.location.pathname + "?" + window.location.search.substring(1);
    } else {
      source = window.location.pathname;
    }
    url = jQuery.URLEncode($this.attr('href'));
    name = $this.attr('name');
    ref = jQuery.URLEncode(source);
    $this.live('click', function (click) {
      click.preventDefault();
      $.post('/lib/track.php', {
        url: url,
        name: name,
        ref: ref
      }, function () { window.location = $this.attr('href'); });
    });
  };

... using the jQuery URLEncode plugin (http://www.digitalbart.com/jquery-and-urlencode/).

Now, this code works fine with my PHP backend and on my machine, but it doesn't seem to work reliably for everyone else. Sometimes the parameters passed in via jQuery are NOT passed in, resulting in a record in the database with no name, url or ref.

For the life of me, I can't figure out why this might be happening; I know the $.post is triggering, since there are records in the database (in the PHP, I also record the IP of the request along with the timestamp), but in many cases the PHP script is receiving blank $_POST variables from jQuery.

I've tested it live on every browser I have access to at my workplace, and all of them work fine for me; however, about 75% of all the records created (not by my computers) come through as blank (most of them are using the same browsers I am).

Why could this be happening?

Answer

neezer picture neezer · Jan 25, 2010

I think, in the end, my problem ended up being that it was taking too long for the request to be parsed by jQuery, and I'm pretty adamant about not wanting to make the links "dependent" on javascript (either that they wouldn't work without it or that the user would have to wait for the tracking request to complete before they hit the new page).

After browsing many other solutions online--borrowing from some and being inspired by others--I arrived at the solution below in native javascript:

if (document.getElementsByClassName === undefined) { // get elements by class name, adjusted for IE's incompetence
    document.getElementsByClassName = function(className) {
      var hasClassName, allElements, results, element;

        hasClassName = new RegExp("(?:^|\\s)" + className + "(?:$|\\s)");
        allElements = document.getElementsByTagName("*");
        results = [];

        for (var i = 0; (element = allElements[i]) !== null; i++) {
            var elementClass = element.className;
            if (elementClass && elementClass.indexOf(className) != -1 && hasClassName.test(elementClass)) {
                results.push(element);
            }
        }

        return results;
    };
}

function addTracker(obj, type, fn) { // adds a tracker to the page, like $('xxx').event
  if (obj.addEventListener) {
    obj.addEventListener(type, fn, false);
  } else if (obj.addEventListener) {
    obj['e' + type + fn] = fn;
    obj[type + fn] = function() {
      obj['e' + type + fn]( window.event );
    };
    obj.attachEvent('on' + type, obj[type + fn]);
  }
}

function save_click(passed_object) { // this function records a click
  var now, then, path, encoded, to, from, name, img;

  now = new Date();
  path = '/lib/click.php';
  from = (window.decode) ? window.decodeURI(document.URL) : document.URL;
  to = (window.decodeURI) ? window.decodeURI(passed_object.href) : passed_object.href;
  name = (passed_object.name && passed_object.name != '') ? passed_object.name : '[No Name]';

  // timestamp the path!
  path += '?timestamp=' + now.getTime();

  path += '&to=' + escape(to) + '&from=' + escape(from) + '&name=' + name; // compile the path with the recorded information
  img = new Image();
  img.src = path; // when we call the image, we poll the php page; genius!

  while (now.getTime() < then) {
    now = new Date(); // resets the timer for subsequent clicks
  }
}

function get_targeted_links(target) { // finds targeted elements and wires them up with an event handler
  var links, link;
  if (document.getElementsByClassName) {
    links = document.getElementsByClassName(target);
    for (var i = 0; i < links.length; i++) {
      link = links[i];
      if (link.href) {
        addTracker(links[i], 'mousedown', save_click(links[i])); 
      }
    }
  }
}

addTracker(window, 'load', get_targeted_links('trackit'));

... which seems to be much snappier than the jQuery plugin I had written above, and so far has been fast enough to track all the requests I've thrown at it.

Hope that helps someone else!