.. File nm/logger/activity_tracker.js This is a comment in the documentation, which is in reStructured text. The official documentation starts here, with the title: ===================== activity_tracker.js ===================== :Author: Jaap Murre :Version: 1.0b :Date: May, 2011 *********** User Manual *********** Introduction ------------ This is a JavaScript library for tracking activity. It extracts 'periods of activity' from large numbers of events. For example, suppose you want to know how much time users spend reading materials on a certain web page. With the activity tracker you can get an idea of the total time based on the evidence such as mouse moves, key presses, etc. When the user does not interact with the page at all, the activity tracker assumes that the period of activity is over and (only then) sends a 'activity' event to the server. As soon as the user starts interacting, another period starts. The 'micro events' that form the evidence of inferred activities such attending or reading are not stored permanently, only the global 'activity' event with the length in milliseconds. Usage ----- You setup an activity tracker like this:: dojo.registerModulePath("nm","../../nm"); // path to 'nm' is relative to dojo.js file dojo.require("nm.logger.activity_tracker"); nm.logger.activity_tracker.register("reading",{ url:"process_data.php", password:"pietje", maxInactivity: 10000, // in ms }); Legal arguments of the ``details`` argument of :func:`logger.register` can be used here too. In addition you can still access ``nm.logger`` as usual, e.g., to set the namespace. Now, you can send events that are evidence of activity, e.g., that the user is still attending to the page or section, presumably reading or studying the materials. Evidence of activity could be mouse movements, mouse clicks, hover on elements, key presses, etc. You must send this 'evidence' to the activity tracker yourself, like this:: dojo.publish('reading',[{name:'Lesson 1'}]); The activity tracker then logs these and keeps track of idle time. A lapse in activity is detected, if nothing happens for a while (set this inactivity threshold with :attr:`maxInactivity`), an 'activity period' is logged and eventually sent to the server. The activity tracker does not save the possibly very large volume of 'evidence' events to the server but keeps these in memory (preferably in local storage). Only when a lapse in activity is detected (set by :attr:`maxInactivity`), is an event sent to the server using the original name of the event plus a suffix (``"-activity"`` by default). The activity tracker registers two event types with ``nm.logger``: (1) the one mentioned in the registration (i.e., named "reading" above), which has :attr:`saveToServer` set to false; and (2) one with the suffixed type (i.e., "reading-activity" above), which is sent to the server with an estimate of the period of activity given in the :attr:`data` attribute, in milliseconds, and with :attr:`name` as the attribute sent with the ``dojo.publish`` (i.e., "Lesson 1" above, or "default" if no event name was provided). Methods ------- The following methods are available (for ``event_type`` it is best to always use a string): .. function:: activity_tracker.register(event_type[,details]) Send data to server, via local storage. See ``nm.logger.register()`` for legal values of ``details``. In addition, one may specify the idle time in ms that is used to mark the end of a period of activity, :attr:`maxInactivity`, which is 5000 by default (5 seconds). This, if no events are received for 5000 ms, the period of activity is sent to the server and a new one is started. The :attr:`maxInactivity` time itself is not added to the total activity period. :param string event_type: event type :param object details: properties overriding default settings The ``details`` parameter includes those of :func:`logger.register` and adds: .. attribute:: maxInactivity (integer) If nothing happens for ``maxInactivity`` ms, an 'activity period' is logged and eventually sent to the server. .. NOTE:: This function does not duplicate :func:`logger.register` which is called itself in :func:`activity_tracker.register`. The function in the activity tracker only tracks those events that are not directly sent to the server. .. function:: activity_tracker.setSuffix(suffix) Set the suffix attached to the global activity event. The suffix is called ``"_activity"`` by default. :param string suffix: new suffix .. function:: activity_tracker.getSuffix() :returns: the current suffix .. function:: activity_tracker.saveOldEvents(event_type, event_name) Check whether there are any old events that form periods of activity. This function may be called when the page was for example clicked away and reloaded. Old events are not lost, but kept in local storage. This function is always called in :func:`register` but can also be called elsewhere. :param string event_type: event type :param string event_name: event name ------------------------------------------------------------------------------------- ********** Change Log ********** 2011-06-09 - Jaap ----------------- Updated documentation. 2011-06-06 - Jaap ----------------- Updated the file so that the reStructured documentation can be used directly from the .js file. 2011-05-08 - Jaap ----------------- Created the demo test_activity_tracker.html and started testing the activity tracker. .. The actual code follows below. Notice that it is indented so that it is considered one long comment by restructured text. */ dojo.provide("nm.logger.activity_tracker"); dojo.require("nm.logger"); //nm = {} //nm.logger = {} nm.logger.activity_tracker = new function() { var _SUFFIX = "_activity", event_timers = {}, event_details = {}; function setSuffix(/*String*/suffix) { _SUFFIX = suffix; } function getSuffix() { return _SUFFIX; } function clearTimer(/*String*/event_type,/*String*/event_name) { if (event_timers[event_type][event_name]) { clearInterval(event_timers[event_type][event_name]); event_timers[event_type][event_name] = null; } } function register(/*String*/event_type,/*Object?*/details) { // Temporary store for large amounts of mouse and key (etc.) events var d = dojo.clone(details); d.saveToServer = false; d.repeatSaveEvents = 25; nm.logger.register(event_type,d); event_details[event_type] = {} event_details[event_type].maxInactivity = details ? details.maxInactivity || 5000 : 5000; event_timers[event_type] = {} // Logging of activity time nm.logger.register(event_type + _SUFFIX,details); dojo.subscribe(event_type,null,function(event) { trackIdleTime(event_type,event||"default"); }); // TODO: trackIdleTime(event_type) to reactivate old data in local storage saveOldEvents(); } // Check whether there are any old events that form periods of activity. This // function can be called when the page was for example clicked away and reloaded. // Old events are not lost, but kept in local storage. // This function is called in ``register()`` and can also be called elsewhere. // // .. NOTE:: This function does not duplicate the one in ``nm.logger``, which is // called as well. This function only tracks the events that are not directly sent // to the server, see documentation. // function saveOldEvents(/*String*/event_type) { var stored, i, name, names = {}; stored = dojox.storage.get(event_type,namespace) || []; for (i = 0; i < stored.length; i++) { names[stored[i].name] = stored[i].name; // Use dictionary as quick-and-dirty set } // Check for each stored event name, whether an activity period needs to be // saved. for (name in names) { trackIdleTime(event_type,name,true); } } // returns ms since epoch // the '+' in +new Date() forces convert to number function msSince() { return +new Date(); } // Returns true if the last activity was more than // ``maxInactivity`` ms ago (see ``register()`` arguments), // false otherwise. function isTimeOutNow(/*String*/event_type,/*String*/event_name) { var times = nm.logger.getDataByName(event_type,event_name), now = msSince(), end = times[times.length-1]? times[times.length-1].created : now; return now - end > event_details[event_type].maxInactivity; } function getActivityPeriod(/*String*/event_type,/*String*/event_name) { var now = msSince(), times = nm.logger.getDataByName(event_type,event_name), start = times[0] ? times[0].created : now, end = times[times.length-1]? times[times.length-1].created : now; return end - start; } function trackIdleTime(/*String*/event_type,/*String|Object*/event_name,/*bool?*/end_of_possible_idle_period) { clearTimer(event_type,event_name); if (!dojo.isString(event_name)) { event_name = event_name.name; } if (isTimeOutNow(event_type,event_name)) { dojo.publish(event_type + _SUFFIX, [{ name: event_name, data: getActivityPeriod(event_type,event_name) }]); nm.logger.clearByName(event_type,event_name); } else { if (!end_of_possible_idle_period) // only user-initiated events cause a timer call-back { event_timers[event_type][event_name] = setInterval(function() { var e_type = event_type, e_name = event_name; trackIdleTime(e_type,e_name,true); }, event_details[event_type].maxInactivity+5); // Add a few ms for slow processing } } } return { register: register, getSuffix: getSuffix, setSuffix: setSuffix, saveOldEvents: saveOldEvents } }();