Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JS onunload event won't always work

I want to count how much time visitors spend on a certain page and store it in my MySQL DB.

I thought of just starting a timer on window.onload like this:

window.onload= startCount;
window.onunload= sendCount;

var b=0;
var y;
function startCount() {
    document.getElementById('livecount').innerHTML=b;
    b=b+1;
    y=setTimeout("startCount()",1000);
}

and after the visitor leaves the page (window.onunload), I'm sending the time through an XMLHttpRequest to a PHP file, which will store it in my DB:

function sendCount() {
    clearTimeout(y);    
if (window.XMLHttpRequest)
  {// code for IE7+, Firefox, Chrome, Opera, Safari
  xmlhttp=new XMLHttpRequest();
  }
else
  {// code for IE6, IE5
  xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
  }  
xmlhttp.open("GET","count.php?q="+b,true);
xmlhttp.send();
}

The problem is it doesn't always work. I would say it works like 3 out of 10 times I try it. Could it be that there is not enough time left for the PHP and SQL to be completely executed?

like image 583
LightDark Avatar asked Nov 29 '10 19:11

LightDark


3 Answers

As onunload is unreliable, I would consider an AJAX solution. I am not an expert in this at all, but the thought that comes to mind is a ping to your counter every so often. I wouldn't ping your counter every second, as that seems excessive.

I would break down times into durations you care about, such as <5 seconds, <30 seconds, <1 minute, <5 minutes, 5+ minutes.

Then I would write a piece of JavaScript like so:

window.onload = soCounter;

var iter=0;
var times=[0,5,30,60,300];  // Your duration choices

function doCounter() {

    sendCountPing(times(iter));

    iter++;

    if(iter < times.length) {
        setTimeout(doCounter, times[iter]*1000);
    }
} 

function sendCountPing(dur) {
    if (window.XMLHttpRequest)
    {  // code for IE7+, Firefox, Chrome, Opera, Safari
        xmlhttp=new XMLHttpRequest();
    }
    else
    {  // code for IE6, IE5
        xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
    }  

    xmlhttp.open("GET","count.php?q="+b,true);
    xmlhttp.send();

}

The trick is that for every count.php?q=30 that gets sent, you need to subtract one from the count.php?q=5 count. This does not have to be programmatic, it is easily post-processed.

To clarify, if you had 3 visitors, one spending 3 seconds, one spending 20 seconds, and one spending 30 minutes, you would see the following counts:

  0:  3   // 3 people spent at least 0 seconds
  5:  2   // 2 people spent at least 5 seconds
 30:  1
 60:  1
300:  1   // 1 person spent at least 5 minutes

Make sense?

like image 198
Jeff B Avatar answered Oct 27 '22 10:10

Jeff B


by the time the onload handler runs, the page is in the process of being taken down. I'm not sure what guarantees you have for the state of the DOM or other services, but you certainly can't expect asynch events or timeouts to fire. Though not part of the standard, most (all?) browsers today provide an onbeforeunload hook which works much the same way as other event handlers, except that you can also return false to cancel the unload if you choose.

like image 34
peller Avatar answered Oct 27 '22 12:10

peller


You could set up a timer as suggested which runs as often as you want (ever few seconds or so), and store the information in a cookie (or cookies). You can send this information to the server if needed when you want to do something more than displaying the value to the user. So don't send it to the server every cycle, just send it to the server when you want to do something with that information (like display to the user a list of all the time he spent on various pages on your site).

like image 1
Allan Nienhuis Avatar answered Oct 27 '22 12:10

Allan Nienhuis