• Alexios
  • javascript
  • recipes

A short Javascript program to improve detection of users idling or away from their keyboards, which should in turn improve how onlineusers are detected. The file works with both jQuery and Prototype. It autodetects which is available at load time. Putting an AJAX request in one of the handlers can notify the back-end about the online state of a user. Using it purely on the front-end, periodic AJAX requests can be stopped or run more sparsely, for both user intuitiveness and saving bandwidth.

The advantage of this implementation is it uses JavaScript timers that tick once every idle timeout, not every second. User activity doesn't create new timers with every mouse or keyboard event, either. It just updates a timestamp, which is then checked when the timer runs. The timer uses the timestamp to tell if user activity has been detected. This makes the whole process very light on resources.

By 2018 standards, this code is about as ancient as it is evil. You should be ignoring it and/or learning from it. Or just see it as some sort of ancient artifact, best seen but not touched.

A Modern, Simpler jQuery-only Solution

My solution is clunky by modern standards, it pollutes the JavaScript namespace, it displays all sorts of bad coding practices. Things were simpler when I wrote this.

If you're after a modern, simpler, jQuery-only solution to this problem without an API, look at this Stack Overflow question. One of the answers may work for you.

API

This is very simple. There are two functions, setIdleTimeout(ms) and setAwayTimeout(ms) to set the idle and away timeouts respectively.

There are three event-like callbacks: document.onIdle and document.onAway are called without parameters when the idle and away timeouts occur. The third callback, document.onBack(isIdle, isAway), is called when user activity is detected. The parameters are either true or false, depending on whether idle and/or away states have been established. Binding any of these events is optional.

Example

Here's a simple example, using jQuery (the only jQuery-specific thing is the code inside the function() event handlers):

You can see this running with exaggerated timeouts (two and four seconds for idle and away respectively). Wait a few seconds and the ‘idle’ and ‘away’ boxes will highlight. Moving the mouse or pressing keys should fade them again.

Idle
Away

Source

// idle.js (c) Alexios Chouchoulas 2009
// Released under the terms of the GNU Public License version 2.0 (or later).

var _API_JQUERY = 1;
var _API_PROTOTYPE = 2;
var _api;

var _idleTimeout = 30000;       // 30 seconds
var _awayTimeout = 600000;      // 10 minutes

var _idleNow = false;
var _idleTimestamp = null;
var _idleTimer = null;
var _awayNow = false;
var _awayTimestamp = null;
var _awayTimer = null;

function setIdleTimeout(ms)
{
    _idleTimeout = ms;
    _idleTimestamp = new Date().getTime() + ms;
    if (_idleTimer != null) {
        clearTimeout (_idleTimer);
    }
    _idleTimer = setTimeout(_makeIdle, ms + 50);
    //console.log('idle in ' + ms + ', tid = ' + _idleTimer);
}

function setAwayTimeout(ms)
{
    _awayTimeout = ms;
    _awayTimestamp = new Date().getTime() + ms;
    if (_awayTimer != null) {
        clearTimeout (_awayTimer);
    }
    _awayTimer = setTimeout(_makeAway, ms + 50);
    //console.log('away in ' + ms);
}

function _makeIdle()
{
    var t = new Date().getTime();
    if (t < _idleTimestamp) {
        //console.log('Not idle yet. Idle in ' + (_idleTimestamp - t + 50));
        _idleTimer = setTimeout(_makeIdle, _idleTimestamp - t + 50);
        return;
    }
    //console.log('** IDLE **');
    _idleNow = true;

    try {
        if (document.onIdle) document.onIdle();
    } catch (err) {
    }
}

function _makeAway()
{
    var t = new Date().getTime();
    if (t < _awayTimestamp) {
        //console.log('Not away yet. Away in ' + (_awayTimestamp - t + 50));
        _awayTimer = setTimeout(_makeAway, _awayTimestamp - t + 50);
        return;
    }
    //console.log('** AWAY **');
    _awayNow = true;

    try {
        if (document.onAway) document.onAway();
    } catch (err) {
    }
}


function _initPrototype()
{
    _api = _API_PROTOTYPE;
}

function _active(event)
{
    var t = new Date().getTime();
    _idleTimestamp = t + _idleTimeout;
    _awayTimestamp = t + _awayTimeout;
    //console.log('not idle.');

    if (_idleNow) {
        setIdleTimeout(_idleTimeout);
    }

    if (_awayNow) {
        setAwayTimeout(_awayTimeout);
    }

    try {
        //console.log('** BACK **');
        if ((_idleNow || _awayNow) && document.onBack) {
            document.onBack(_idleNow, _awayNow);
        }
    } catch (err) {
    }

    _idleNow = false;
    _awayNow = false;
}

function _initJQuery()
{
    _api = _API_JQUERY;
    var doc = $(document);
    doc.ready(function(){
        doc.mousemove(_active);
        try {
            doc.mouseenter(_active);
        } catch (err) { }
        try {
            doc.scroll(_active);
        } catch (err) { }
        try {
            doc.keydown(_active);
        } catch (err) { }
        try {
            doc.click(_active);
        } catch (err) { }
        try {
            doc.dblclick(_active);
        } catch (err) { }
    });
}

function _initPrototype()
{
    _api = _API_PROTOTYPE;
    var doc = $(document);
    Event.observe (window, 'load', function(event) {
        Event.observe(window, 'click', _active);
        Event.observe(window, 'mousemove', _active);
        Event.observe(window, 'mouseenter', _active);
        Event.observe(window, 'scroll', _active);
        Event.observe(window, 'keydown', _active);
        Event.observe(window, 'click', _active);
        Event.observe(window, 'dblclick', _active);
    });
}

// Detect the API
try {
    if (Prototype) _initPrototype();
} catch (err) { }

try {
    if (jQuery) _initJQuery();
} catch (err) { }

// End of file.