So you want to know how much time users spend on any specific page of your web application? This article explains how to log what pages your users visit and how much time they spend on each page. This kind of data can provide valuable insight into what forms of your application are too complicated and need to be split into several smaller forms. Or if they keep coming back to the welcome page this may mean your navigation inside the app can be improved.
1. Create log table
The following SQL script creates the log table in the database. The syntax is for MySQL, you can create a similar table in any other database manually, just make sure that field names and datatypes stay the same.
CREATE TABLE `timetracker` ( `trackerId` int(11) NOT NULL AUTO_INCREMENT, `pagename` varchar(250) DEFAULT NULL, /* page URL */ `timeon` datetime DEFAULT NULL, /* when user entered the page */ `timeoff` datetime DEFAULT NULL, /* when user left the page */ `userID` varchar(100) DEFAULT NULL, /* username */ `recordID` varchar(100) DEFAULT NULL, /* in the case of edit/view pages this field will store the ID of the record */ PRIMARY KEY (`trackerId`) )
A few notes here:
Note 1: Login is not required. If your project doesn't use login, 'userID' field will be empty.
Note 2: if 'timeoff' field is empty that means user spent less than five seconds on the page.
Note 3: it makes sense to add 'timetracker' table to the project as well so you can test this functionality. If you do so, set 'View as' time of timeon/timeoff fields to 'Datetime'.
2. Javascript code
The following Javascript code needs to be added under Event Editor -> custom_functions.js
$("document").ready(function() { Runner.customEvents = []; // every notifyInterval seconds we execute an AJAX requests that tells the server that user is still ont he page var notifyInterval = 5; // this function is executed on every page load, here we tell the server what page the user currently on function setPageTimer(pageObj) { // ajax- parameters with the page URL var notify_params = { pageOpen: 1, pageName: Runner.pages.getUrl(pageObj.shortTName,pageObj.pageType) }; // if this is an Edit/View page we also pass an ID of the record if (pageObj.pageType === "edit" || pageObj.pageType === "view") notify_params.recordID = pageObj.keys[0]; // we send AJAX request and get back trackerId value of the current log table recod $.get("", notify_params, function(TrackerID) { // send AJAX request with the current notifyInterval value, that tells the server the user is still on the page interval = setInterval(function() { $.get("", { TrackerID: TrackerID }); }, notifyInterval * 1000); }); } var originalInit = Runner.pages.RunnerPage.prototype.init; Runner.pages.RunnerPage.prototype.init = function() { var pageObj = this; var isTab = typeof this.tabControl !== "undefined"; // check if the current page a details tab if (isTab) { if (!Runner.customEvents.includes(this.tName + "_" + this.pageType)) { Runner.customEvents.push(this.tName + "_" + this.pageType); pageObj.on("afterPageReady", function() { // when tab is closed we clear the interval counter pageObj.tabControl.off("hide.bs.tab").on("hide.bs.tab", function(e) { clearInterval(interval); }); // when details tab is open we start the counter pageObj.tabControl.off("show.bs.tab").on("show.bs.tab", function(e) { var activeTab = $(e.target); var panelContent = activeTab.parents("ul").next(); var activePanel = panelContent.find(".tab-pane.active"); setPageTimer(Runner.pages.PageManager.getById(activePanel.find(".r-form").attr("data-pageid"))); }); }); } } if (!isTab || (isTab && this.$panel.parents(".tab-pane").hasClass("active"))) { setPageTimer(pageObj); } originalInit.call(this); } });
3. Server-side code (PHP and C#)
The following code goes to the AfterAppInit event.
PHP:
$currentDateTimeForDb = localdatetime2db( runner_date_format("m-d-y H:i:s") ); // receiving AJAX request with the new page URL // in timetracker table we create a new record // and return the TrackerID value of the new record if( postvalue("pageOpen") != false ){ $data = array(); $data["pagename"] = postvalue("pageName"); $data["timeon"] = $currentDateTimeForDb; $data["userID"] = Security::getUserName(); if(postvalue("recordID") != false){ $data["recordID"] = postvalue("recordID"); } DB::Insert("timetracker", $data); //return TrackerID echo DB::LastId(); exit(); } // receiving AJAX request that tell us we are still on the same pageпродолжается // we just update the value of timeoff field for the current TrackerID if( postvalue("TrackerID") !=false ){ $now_datetime = $currentDateTimeForDb; DB::Update("timetracker",array("timeoff"=> $now_datetime ),array("trackerId" => postvalue("TrackerID") )); exit(); }
C#:
dynamic currentDateTimeForDb = null; currentDateTimeForDb = XVar.Clone(CommonFunctions.localdatetime2db((XVar)(MVCFunctions.runner_date_format(new XVar("m-d-y H:i:s"))))); if(MVCFunctions.postvalue(new XVar("pageOpen")) != false) { data = XVar.Clone(XVar.Array()); data.InitAndSetArrayItem(MVCFunctions.postvalue(new XVar("pageName")), "pagename"); data.InitAndSetArrayItem(currentDateTimeForDb, "timeon"); data.InitAndSetArrayItem(Security.getUserName(), "userID"); if(MVCFunctions.postvalue(new XVar("recordID")) != false) { data.InitAndSetArrayItem(MVCFunctions.postvalue(new XVar("recordID")), "recordID"); } DB.Insert(new XVar("timetracker"), (XVar)(data)); MVCFunctions.Echo(DB.LastId()); MVCFunctions.ob_flush(); HttpContext.Current.Response.End(); throw new RunnerInlineOutputException(); } if(MVCFunctions.postvalue(new XVar("TrackerID")) != false) { dynamic now_datetime = null; now_datetime = XVar.Clone(currentDateTimeForDb); DB.Update(new XVar("timetracker"), (XVar)(new XVar("timeoff", now_datetime)), (XVar)(new XVar("trackerId", MVCFunctions.postvalue(new XVar("TrackerID"))))); MVCFunctions.ob_flush(); HttpContext.Current.Response.End(); throw new RunnerInlineOutputException(); } return null;