ability to queue ajax requests on the client and send them as a single ajax request every N ms to the server

a) instead of a single request like: new egw_json_request(menuaction, params).sendRequest(true, callback, context); 
b) you call: egw.jsonq(menuaction,params,callback,context)
The server callback is identical for both kinds of requests. All egw_json_response methods can be used and the callback is optional.
This commit is contained in:
Ralf Becker 2011-09-13 17:27:02 +00:00
parent 077acb3fb0
commit 516b977472
4 changed files with 219 additions and 31 deletions

View File

@ -100,7 +100,7 @@ if (isset($_GET['menuaction']))
{
throw new egw_exception_assertion_failed("JSON Data contains script tags. Aborting...");
}
$json->parseRequest($_GET['menuaction'], (array)$_POST['json_data']);
$json->parseRequest($_GET['menuaction'], $_POST['json_data']);
egw_json_response::get();
common::egw_exit();
}

View File

@ -27,6 +27,8 @@ class egw_json_request
* Parses the raw input data supplied with the input_data parameter and calls the menuaction
* passing all parameters supplied in the request to it.
*
* Also handle queued requests (menuaction == 'home.queue') containing multiple requests
*
* @param string menuaction to call
* @param string $input_data is the RAW input data as it was received from the client
*/
@ -35,36 +37,34 @@ class egw_json_request
// Remember that we currently are in a JSON request - e.g. used in the redirect code
self::$_hadJSONRequest = true;
if (empty($input_data))
if (get_magic_quotes_gpc()) $input_data = stripslashes($input_data);
$json_data = json_decode($input_data,true);
if (is_array($json_data) && isset($json_data['request']) && isset($json_data['request']['parameters']))
{
$this->handleRequest($menuaction, array());
}
$parameters =& $json_data['request']['parameters'];
}
else
{
if (get_magic_quotes_gpc())
$parameters = array();
}
// do we have a single request or an array of queued requests
if ($menuaction == 'home.queue')
{
$responses = array();
$response = egw_json_response::get();
foreach($parameters as $uid => $data)
{
$input_data[0] = stripslashes($input_data[0]);
}
//Decode the JSON input data into associative arrays
if (($json = json_decode($input_data[0], true)) !== false)
{
$parameters = array();
//Get the request array
if (isset($json['request']))
{
$request = $json['request'];
//Check whether any parameters were supplied along with the request
if (isset($request['parameters']))
{
$parameters = $request['parameters'];
}
}
//Call the supplied callback function along with the menuaction and the passed parameters
$this->handleRequest($menuaction, $parameters);
//error_log("$uid: menuaction=$data[menuaction], parameters=".array2string($data['parameters']));
$this->handleRequest($data['menuaction'], $data['parameters']);
$responses[$uid] = $response->initResponseArray();
//error_log("responses[$uid]=".array2string($responses[$uid]));
}
$response->data($responses); // send all responses as data
}
else
{
$this->handleRequest($menuaction, $parameters);
}
}
@ -246,6 +246,20 @@ class egw_json_response
);
}
/**
* Init responseArray
*
* @param array $arr
* @return array previous content
*/
public function initResponseArray()
{
$return = $this->responseArray;
$this->responseArray = array();
$this->hasData = false;
return $return;
}
/**
* Adds a "data" response to the json response.
*
@ -309,9 +323,56 @@ class egw_json_response
}
/**
* Allows to add a global javascript function with giben parameters
* Allows to call a global javascript function with given parameters: window[$func].apply(window, $parameters)
*
* @param string $script the script code which should be executed upon receiving
* @param string $func name of the global (window) javascript function to call
* @param array $parameters=array()
*/
public function apply($function,array $parameters=array())
{
if (is_string($function))
{
$this->addGeneric('apply', array(
'func' => $function,
'parms' => $parameters,
));
}
else
{
throw new Exception("Invalid parameters supplied.");
}
}
/**
* Allows to call a global javascript function with given parameters: window[$func].call(window[, $param1[, ...]])
*
* @param string $func name of the global (window) javascript function to call
* @param mixed $parameters variable number of parameters
*/
public function call($function)
{
$parameters = func_get_args();
array_shift($parameters); // shift off $function
if (is_string($function))
{
$this->addGeneric('apply', array(
'func' => $function,
'parms' => $parameters,
));
}
else
{
throw new Exception("Invalid parameters supplied.");
}
}
/**
* Allows to call a jquery function on a selector with given parameters: $j($selector).$func($parmeters)
*
* @param string $selector jquery selector
* @param string $method name of the jquery to call
* @param array $parameters=array()
*/
public function jquery($selector,$method,array $parameters=array())
{
@ -498,7 +559,7 @@ class xajaxResponse extends egw_json_response
$args = func_get_args();
$func = array_shift($args);
$this->script("try{window['".$func."'].apply(window, ".json_encode($args).");} catch(e) {_egw_json_debug_log(e);}");
$this->apply($func, $args);
}
public function addIncludeCSS($url)

View File

@ -223,7 +223,7 @@ function egw_json_request(_menuaction, _parameters, _context)
if (_menuaction.match(/json.php\?menuaction=[a-z_0-9]*\.[a-z_0-9]*\.[a-z_0-9]*/i))
{
// Menuaction is a full featured url
this.url = _menuaction
this.url = _menuaction;
}
else
{
@ -401,7 +401,7 @@ egw_json_request.prototype.handleResponse = function(data, textStatus, XMLHttpRe
{
try
{
var func = function() {eval(res.data);};
var func = new Function(res.data);
func.call(window);
}
catch (e)
@ -413,6 +413,22 @@ egw_json_request.prototype.handleResponse = function(data, textStatus, XMLHttpRe
} else
throw 'Invalid parameters';
break;
case 'apply':
if (typeof res.data.func == 'string' && typeof window[res.data.func] == 'function')
{
try
{
window[res.data.func].apply(window, res.data.parameters);
}
catch (e)
{
e.code = res.data.func;
_egw_json_debug_log(e);
}
hasResponse = true;
} else
throw 'Invalid parameters';
break;
case 'jquery':
if (typeof res.data.select == 'string' &&
typeof res.data.func == 'string')

View File

@ -639,6 +639,117 @@ else
}
}
return query.length ? _url+'?'+query.join('&') : _url;
},
/**
* Queued json requests (objects with attributes menuaction, parameters, context, callback, sender and callbeforesend)
*
* @access private, use jsonq method to queue requests
*/
jsonq_queue: {},
/**
* Next uid (index) in queue
*/
jsonq_uid: 0,
/**
* Running timer for next send of queued items
*/
jsonq_timer: null,
/**
* Send a queued JSON call to the server
*
* @param string _menuaction the menuaction function which should be called and
* which handles the actual request. If the menuaction is a full featured
* url, this one will be used instead.
* @param array _parameters which should be passed to the menuaction function.
* @param _callback callback function which should be called upon a "data" response is received
* @param _sender is the reference object the callback function should get
* @param _callbeforesend optional callback function which can modify the parameters, eg. to do some own queuing
*/
jsonq: function(_menuaction, _parameters, _callback, _sender, _callbeforesend)
{
this.jsonq_queue['u'+(this.jsonq_uid++)] = {
menuaction: _menuaction,
parameters: _parameters,
callback: _callback,
sender: _sender,
callbeforesend: _callbeforesend
};
if (this.jsonq_time == null)
{
// check / send queue every N ms
var self = this;
this.jsonq_timer = window.setInterval(function(){ self.jsonq_send();}, 100);
}
},
/**
* Send the whole job-queue to the server in a single json request with menuaction=queue
*/
jsonq_send: function()
{
if (this.jsonq_uid > 0 && typeof this.jsonq_queue['u'+(this.jsonq_uid-1)] == 'object')
{
var jobs_to_send = {};
var something_to_send = false;
for(var uid in this.jsonq_queue)
{
var job = this.jsonq_queue[uid];
if (job.menuaction == 'send') continue; // already send to server
// if job has a callbeforesend callback, call it to allow it to modify pararmeters
if (typeof job.callbeforesend == 'function')
{
job.callbeforesend.apply(job.context, job.parameters);
}
jobs_to_send[uid] = {
menuaction: job.menuaction,
parameters: job.parameters
};
job.menuaction = 'send';
job.parameters = null;
something_to_send = true;
}
if (something_to_send)
{
new egw_json_request('home.queue', jobs_to_send, this).sendRequest(true, this.jsonq_callback, this);
}
}
},
/**
* Dispatch responses received
*
* @param object _data uid => response pairs
*/
jsonq_callback: function(_data)
{
if (typeof _data != 'object') throw "jsonq_callback called with NO object as parameter!";
var json = new egw_json_request('none');
for(var uid in _data)
{
if (typeof this.jsonq_queue[uid] == 'undefined')
{
console.log("jsonq_callback received response for not existing queue uid="+uid+"!");
console.log(_data[uid]);
continue;
}
var job = this.jsonq_queue[uid];
var response = _data[uid];
// fake egw_json_request object, to call it with the current response
json.callback = job.callback;
json.sender = job.sender;
json.handleResponse({response: response});
delete this.jsonq_queue[uid];
}
}
};
}