Simple IFrame/PHP-based progress bar for long-running jsonrpc processes
Just as “normal” http-requests, the json-rpc transport that comes with the qooxdoo library cannot provide very much information on the state of the request. In particular, there is no way for the server to communicate with the client during the request. In “normal” desktop applications, users expect to have some kind of feedback such as progress bars, messages on the state of the operation, etc. In a client-server application based on http, when we have a long running server process (for example, the server is sorting a huge dataset or has to wait for information from a different server), the client has to wait until the response returns, which is often unsatisfactory for a user, since he or she does not know if the request is stalled or just takes very long.
In some rudimentary form, we can emulate server responses and progress bars with an IFrame that is fed with the output of a PHP script, which, in turn, listens for messages from a concurrently running PHP script. There are certainly more elegant ways of doing this, but we use a simple file here for exchanging the messages.
Since this filename has to be unique to each request, we generate a timestamp or random number to use as a filename and transmit it both as part of the URL source of the IFrame and as parameter of the JSONRPC request. Lets look at the javascript source code first:
// create window var windowObj = new qx.ui.window.Window("Server progress"); windowObj.setWidth(400); windowObj.setHeight(200); windowObj.setShowMinimize(false); windowObj.setShowMaximize(false); // create Iframe var iframeObj = new qx.ui.embed.Iframe(null); iframeObj.setHeight("100%"); iframeObj.setWidth("100%"); windowObj.add(this.iframeObj); iframeObj.addEventListener("load",function(event){ // when the document is fully loaded, the window can be closed windowObj.close(); }); // we need a unique request id as name of message transfer file so that different request do not interfere with each other var requestId = "" + (new Date).getTime(); // iframe source var source = "path/to/monitor.php?" iframeObj.setSource(source + requestId); // open window and launch request windowObj.open(); // do your JSONRPC request, telling the service method the name of the requestId/message file var rpc = new ...
Then let us have a look at the IFrame source script. The following PHP script “monitor.php” generates a HTML page with positioned DIVs for message, progressbar, and log messages:
<html>
<head>
<script type="text/javascript">
function progress( p )
{
document.getElementById('progressbar').style.width="" + p + "%";
}
function message (m)
{
document.getElementById('message').innerHTML = "<span>" + m + "</span>";
}
</script>
</head>
<body style="font-family: Arial; font-size: 12px;">
<div id="message" style="margin:2px;padding:3px;border: 1px solid black;text-align:center;left:0px;right:0px;height:20px">
</div>
<div style="margin:2px;padding:1px;border: 1px solid black;left:0px;right:0px;height:20px">
<div id="progressbar" style="width:0%;height:100%;background-color:blue"></div>
</div>
<div id="log" style="margin:2px;padding:3px;border: 1px solid black;overflow:auto;top:40px;left:0px;right:0px;height:90px">
<?php
// prevent script timeout
set_time_limit(0);
// get request id / file name from query string
$requestId = str_replace( "..","", $_SERVER["QUERY_STRING"]);
$socketfile = "path/to/tmpdir/$requestId";
$length = 0;
// create socketfile in case the other script hasn't started yet
touch ( $socketfile );
while ( file_exists($socketfile) )
{
$content = @file_get_contents($socketfile);
$newLength = strlen( $content );
if ( $newLength > $length )
{
echo substr( $content, $length-$newLength );
flush();
}
$length = $newLength;
}
sleep(2); // keep the window open for another 2 seconds after the IFrame is loaded
?>
</div>
</body>
</html>
In the PHP script (it could be any language), we write to the message file:
class class_myRpcServiceClass /** * Write to the message file monitored by the above script */ function progress( $i="" ) { $messagefile = $this->_messagefile; if ( $i === null ) { // delete messagefile on a null value @unlink( $messagefile ); return; } if ( is_numeric($i) ) { $msg = "<script> progress($i);</script>"; } else { $msg = "<script> message('" . addslashes($i) . "');</script>"; $msg .= $i . "<br/>"; } // output to log file error_log( $msg, 3, $messagefile ); } /** * service method which needs the ability to send feedback to the * user during long-running processes. You need to pass the name * of the message file to this method. **/ function method_myLongRunningMethod( $params ) { // store message file name as object variable so that the progress method has access to it $this->_messsagefile = "path/to/tmpdir/" . $params[0]; // request id-filename // simulate a long-running process $this->progress("Starting process"); // string argument - message for($i=0; $i<100; $i++) { $this->progress($i); // numeric argument - progress bar if ( $i % 10 == 0 ) $this->progress("$i % completed."); sleep(1); } $this->progress("Process completed."); sleep(2); // wait 2 seconds so that the user can see the "Process completed" message // delete message file - important! this completes the Iframe request and closes the IFrame $this->progress(null); } }
More elaborate solutions could monitor the content of an invisible Iframe document with an interval timer and control qooxdoo widgets this way, for example a real qooxdoo progress bar.
Have fun!
