/** \file comet_statusupdates.js * \brief File containing all functions to handle ZKL status updates through a COMET/JSON link * \author Bart van Hest, Core|Vision * \version 1.0 * \date 20-10-2008 * \todo * * This file contains the functions needed to perform 'real-time' status updates for the ZKL through a COMET/JSON link. * It works by opening a connection to a blocking PHP script using the XMLHttpRequest object. The PHP * script must block as long as no status updates are available. When a status update is available, * it should spit out JScript code which can be evaluated using eval(). This code should fill in * an array of objects named ZKL_Status, with members of the object named s_. * * The code does only a simple header check to find out if the data send by PHP is valid. The * header should be ZKLsu1.0 in slash-star comment, which stands for ZKL Status Update v1.0 * * After eval(), the Array should look like this: * ZKL_Status[0] * s_param1 : "param1 value" * s_param2 : "param2 value" * ZKL_Status[1] * s_param1 : "param1 value" * s_param2 : "param2 value" * .. * ZKL_Status[n] * s_param1 : "param1 value" * s_param2 : "param2 value" * * Also, the Array sent should contain information for all ZKLs monitored, not only the updated ones. */ var ZKL_Status = null; // The array with new ZKL status information returned during an update var xmlhttp = null; // The XMLHttpRequest object used to get status updates var TimeOut = null; // our timeout counter. We can't add this to the xmlhttp object; IE won't let us. #@$^#$ browser :( var DataRdyCallback = null; // The callback which should handle new data var DataErrorCallback = null; // The callback which should handle an error in the data var ProjectID = -1; // Our project ID for which to ask status updates var LastRequestTime = -1; // The time our last request was made. Used to discriminate between intentional and unintentional timeouts` var Sequence = 0 // Add sequence to be unique and disable cache var ValidRequestTime = -1; // The time our last request was valid. Used to discriminate between intentional and unintentional timeouts var CometServerScriptURL = null; // The URL of our COMET dataserver script. var TimeOutmSecs = 12000; // our timeout in milliseconds /** * Default data-error callback. * * This default callback crates an empty ZKL_Status() array and calls the DataRdyCallback. * This causes all devices to simply disappear from the screen */ function defErrorCallback() { DbgPrint ("Default data-error callback called."); ZKL_Status = []; if (DataRdyCallback != null) { DataRdyCallback(); } } /** * Create the XMLHttpRequest object * * Inputs: * - fnDataRdyCallback: A callback which is called when new statusinfo is received. * - fnDataErrorCallback: A callback which is called when an error occurred during data reception. * - sesson_id: A number which make this a unique session */ function createUpdater(fnDataRdyCallback, fnDataErrorCallback, sessionid) { DbgPrint ("createUpdater()"); OurURL = document.URL; DocRoot = OurURL.split('?'); CometServerScriptURL = DocRoot[0]+"index.php?redirect=scripts/other/rtstatus_datapump.php&id="+sessionid+"&extra_path=../../"; DbgPrint ("COMET server URL: "+CometServerScriptURL); // Create the XMLHttpRequest object in a browser-independent way try { xmlhttp = new XMLHttpRequest(); } catch (e) { try { xmlhttp = new ActiveXObject('Msxml2.XMLHTTP'); } catch (e) { try { xmlhttp = new ActiveXObject('Microsoft.XMLHTTP'); } catch (e) { xmlhttp = null; } } } if (!xmlhttp && window.createRequest) { try { xmlhttp = window.createRequest(); } catch (e) { xmlhttp=null; } } if (typeof(fnDataRdyCallback) != 'undefined') { DataRdyCallback = fnDataRdyCallback; } else { DbgPrint ("INTERNAL ERROR: No DataReadyCallback passed"); } if (typeof(fnDataErrorCallback) != 'undefined' && fnDataErrorCallback != null) { DataErrorCallback = fnDataErrorCallback; } else { // default error callback DataErrorCallback = defErrorCallback; DbgPrint ("No valid error callback given; reverting to default error callback.."); } // Start the first request xmlhttp_startrequest(true); } /** * Abort & destroy the updater. * * Inputs: * none. */ function AbortUpdater() { DataErrorCallback = null; if (xmlhttp!=null) { // Assign an empty function to the state handler; IE doesn't like setting this to null. xmlhttp.onreadystatechange = function () {} // Abort current transfer. This will also call the state handler, which is not what we want xmlhttp.abort(); // Clear object xmlhttp = null; } } /** * Handle a timeout on the xmlhttp request */ function xmlhttp_Timeout() { try { DbgPrint ("XMLHttpRequest timed out. Restarting..."); // Assign an empty function to the state handler; IE doesn't like setting this to null. xmlhttp.onreadystatechange = function () {} // Abort the current status transfer xmlhttp.abort(); // See if we need to call the data error callback if ((new Date().getTime() - LastRequestTime) > TimeOutmSecs) { if (DataErrorCallback != null) { DataErrorCallback(); } } } catch (ex) { // Silent exception } finally { // And start a new one (this will also re-initialise the state handler) xmlhttp_startrequest(); } } /** * Start a request for status updates. * * Inputs: * - bFirstTime: A boolean indicating whether this is the first time we * request data. The first time we need data immediately, * subsequent requests may be stalled until there is new data * available. */ function xmlhttp_startrequest(bFirstTime) { // Check the arguments bFirstTime = typeof(bFirstTime) == 'boolean' ? bFirstTime : false; // start the XHR request if (xmlhttp!=null) { // We put a timeout on this request. Restarting the status updater script once in a while // is not a bad idea since some Web-servers won't allow a script to run for a long time. TimeOut = setTimeout("xmlhttp_Timeout();",TimeOutmSecs+250); // Remember the current times so we know when a timeout was unintentional or not. LastRequestTime = new Date().getTime(); if (ValidRequestTime == -1) { ValidRequestTime = new Date().getTime(); } xmlhttp.onreadystatechange=xmlhttp_statechange; xmlhttp.open("GET",CometServerScriptURL+"&Seq="+(Sequence++)+"&blocking="+(bFirstTime?'false':'true'),true); xmlhttp.send(null); } } /** * Data-present function which is called when new ZKL data arrives */ function xmlhttp_statechange() { // Do we have an update from the server? => Else still in progress if (xmlhttp.readyState == 4) { try { // Cancel settimeout clearTimeout(TimeOut); if (xmlhttp.status == 200 ) { // Reset timeout ValidRequestTime = new Date().getTime(); // Do something with the data. Check our 'magic-ID'. If it passes, var offset = xmlhttp.responseText.search("\/[*]ZKLsu1.0[*]\/"); // Magic code available? if (offset != -1) { //DbgPrint(xmlhttp.responseText); eval(xmlhttp.responseText.substring(offset)); // OK, by now we should have a ZKL_Status array with statuses of devices in the field. // Call the user supplied callback if (DataRdyCallback != null) { DataRdyCallback(); } // We had a valid request, so no DataErrorCallback may be called, so clear LastRequestTime LastRequestTime = new Date().getTime(); } else { DbgPrint("Invalid data from server\n("+xmlhttp.responseText+")"); // Call our data error callback if (DataErrorCallback != null) { DataErrorCallback(); } } } else if ((xmlhttp.status == 0) || (xmlhttp.status == 12029) || (xmlhttp.status == 12007)) { // Catch IE/FF exceptions when no connection available if ((new Date().getTime() - ValidRequestTime) > TimeOutmSecs) { DbgPrint ("XMLHttpRequest FF timed out, status 0. Calling DataErrorCallback().."); if (DataErrorCallback != null) { DataErrorCallback(); } } } else { // Our request failed. Call the data error callback DbgPrint ("xmlhttp_status: "+xmlhttp.status); } } catch(ex) { // Silent exception } finally { // Redo request. // We used to do a xmlhttp_startrequest() again. This works in every browser, except IE. // IE won't allow us to call XMLHttpRequest methods from within the event handler. // So, we use a hack: we shorten the timeout to 250msec. Once we have a timeout, a new // request will be started. TimeOut = setTimeout("xmlhttp_Timeout();",250); } } }