2012-03-05 14:07:38 +01:00
|
|
|
/**
|
|
|
|
* EGroupware clientside API object
|
|
|
|
*
|
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
|
|
* @package etemplate
|
|
|
|
* @subpackage api
|
|
|
|
* @link http://www.egroupware.org
|
|
|
|
* @author Andreas Stöckel (as AT stylite.de)
|
|
|
|
* @author Ralf Becker <RalfBecker@outdoor-training.de>
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*egw:uses
|
2016-06-06 17:38:20 +02:00
|
|
|
vendor.bower-asset.jquery.dist.jquery;
|
2012-03-05 14:07:38 +01:00
|
|
|
|
|
|
|
egw_core;
|
|
|
|
egw_utils;
|
|
|
|
egw_files;
|
2012-03-05 16:02:00 +01:00
|
|
|
egw_debug;
|
2012-03-05 14:07:38 +01:00
|
|
|
*/
|
|
|
|
|
2016-02-29 16:50:24 +01:00
|
|
|
/**
|
|
|
|
* Module sending json requests
|
|
|
|
*
|
|
|
|
* @param {string} _app application name object is instanciated for
|
|
|
|
* @param {object} _wnd window object is instanciated for
|
|
|
|
*/
|
|
|
|
egw.extend('json', egw.MODULE_WND_LOCAL, function(_app, _wnd)
|
|
|
|
{
|
|
|
|
"use strict";
|
2012-03-05 14:07:38 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Object which contains all registered handlers for JS responses.
|
|
|
|
* The handlers are organized per response type in the top level of the
|
|
|
|
* object, where each response type can have an array of handlers attached
|
|
|
|
* to it.
|
|
|
|
*/
|
|
|
|
var plugins = {};
|
|
|
|
|
2016-02-04 20:26:37 +01:00
|
|
|
/**
|
|
|
|
* Global json handlers are from global modules, not window level
|
|
|
|
*/
|
|
|
|
if(typeof egw._global_json_handlers == 'undefined')
|
|
|
|
{
|
2016-02-29 16:50:24 +01:00
|
|
|
egw._global_json_handlers = {};
|
2016-02-04 20:26:37 +01:00
|
|
|
}
|
|
|
|
var global_plugins = egw._global_json_handlers;
|
|
|
|
|
2012-03-05 14:07:38 +01:00
|
|
|
/**
|
|
|
|
* Internal implementation of the JSON request object.
|
2014-11-21 17:16:24 +01:00
|
|
|
*
|
|
|
|
* @param {string} _menuaction
|
|
|
|
* @param {array} _parameters
|
|
|
|
* @param {function} _callback
|
|
|
|
* @param {object} _context
|
2020-03-02 10:43:19 +01:00
|
|
|
* @param {boolean|"keepalive"} _async true: asynchronious request, false: synchronious request,
|
|
|
|
* "keepalive": async. request with keepalive===true / sendBeacon, to be used in boforeunload event
|
2014-11-21 17:16:24 +01:00
|
|
|
* @param {object} _sender
|
|
|
|
* @param {egw} _egw
|
2012-03-05 14:07:38 +01:00
|
|
|
*/
|
2012-03-05 16:02:00 +01:00
|
|
|
function json_request(_menuaction, _parameters, _callback, _context,
|
2012-03-23 13:45:13 +01:00
|
|
|
_async, _sender, _egw)
|
2012-03-05 14:07:38 +01:00
|
|
|
{
|
|
|
|
// Copy the parameters
|
2012-03-05 16:02:00 +01:00
|
|
|
this.url = _egw.ajaxUrl(_menuaction);
|
2015-08-31 14:21:11 +02:00
|
|
|
// IE JSON-serializes arrays passed in from different window contextx (eg. popups)
|
|
|
|
// as objects (it looses object-type of array), causing them to be JSON serialized
|
|
|
|
// as objects and loosing parameters which are undefined
|
|
|
|
// JSON.strigify([123,undefined]) --> '{"0":123}' instead of '[123,null]'
|
|
|
|
this.parameters = _parameters ? [].concat(_parameters) : [];
|
2018-10-11 14:14:21 +02:00
|
|
|
this.async = typeof _async != 'undefined' ? _async : true;
|
2012-03-05 16:02:00 +01:00
|
|
|
this.callback = _callback ? _callback : null;
|
|
|
|
this.context = _context ? _context : null;
|
|
|
|
this.sender = _sender ? _sender : null;
|
2012-03-05 14:07:38 +01:00
|
|
|
this.egw = _egw;
|
|
|
|
|
2012-03-05 16:02:00 +01:00
|
|
|
// We currently don't have a request object
|
2012-03-05 14:07:38 +01:00
|
|
|
this.request = null;
|
|
|
|
|
2012-03-05 16:02:00 +01:00
|
|
|
// Some variables needed for notification about a JS files done loading
|
2012-03-05 14:07:38 +01:00
|
|
|
this.onLoadFinish = null;
|
|
|
|
this.jsFiles = 0;
|
|
|
|
this.jsCount = 0;
|
|
|
|
|
2012-03-05 16:02:00 +01:00
|
|
|
// Function which is currently used to display alerts -- may be replaced by
|
|
|
|
// some API function.
|
|
|
|
this.alertHandler = function(_message, _details) {
|
2019-01-15 14:34:52 +01:00
|
|
|
// we need to use the alert function of the window of the request, not just the main window
|
|
|
|
(this.egw ? this.egw.window : window).alert(_message);
|
2012-03-05 16:02:00 +01:00
|
|
|
|
|
|
|
if (_details)
|
|
|
|
{
|
|
|
|
_egw.debug('info', _message, _details);
|
|
|
|
}
|
|
|
|
};
|
2012-03-05 14:07:38 +01:00
|
|
|
}
|
|
|
|
|
2019-11-04 09:29:49 +01:00
|
|
|
/**
|
|
|
|
* Open websocket to push server (and keeps it open)
|
|
|
|
*
|
|
|
|
* @param {string} url this.websocket(s)://host:port
|
2020-04-20 13:07:58 +02:00
|
|
|
* @param {array} tokens tokens to subscribe too: sesssion-, user- and instance-token (in that order!)
|
|
|
|
* @param {number} account_id to connect for
|
2019-11-04 09:29:49 +01:00
|
|
|
* @param {function} error option error callback(_msg) used instead our default this.error
|
|
|
|
* @param {int} reconnect timeout in ms (internal)
|
|
|
|
*/
|
2020-04-20 13:07:58 +02:00
|
|
|
json_request.prototype.openWebSocket = function(url, tokens, account_id, error, reconnect)
|
2019-11-04 09:29:49 +01:00
|
|
|
{
|
|
|
|
const min_reconnect_time = 1000;
|
|
|
|
const max_reconnect_time = 300000;
|
2020-10-14 20:15:41 +02:00
|
|
|
const check_interval = 30000; // 30 sec
|
|
|
|
const max_ping_response_time = 1000;
|
2019-11-04 09:29:49 +01:00
|
|
|
let reconnect_time = reconnect || min_reconnect_time;
|
2020-10-14 20:15:41 +02:00
|
|
|
let check_timer;
|
|
|
|
let check = function()
|
|
|
|
{
|
|
|
|
this.websocket.send('ping');
|
|
|
|
check_timer = window.setTimeout(function()
|
|
|
|
{
|
|
|
|
console.log("Server did not respond to ping in "+max_ping_response_time+" seconds --> try reconnecting");
|
|
|
|
check_timer = null;
|
2020-10-27 14:15:58 +01:00
|
|
|
this.websocket.onclose = function()
|
|
|
|
{
|
|
|
|
this.websocket = null;
|
|
|
|
this.openWebSocket(url, tokens, account_id, error, reconnect_time);
|
2020-10-27 14:19:25 +01:00
|
|
|
}.bind(this);
|
2020-10-15 18:17:16 +02:00
|
|
|
this.websocket.close(); // closing it now, before reopening it, to not end up with multiple connections
|
2020-10-14 22:27:54 +02:00
|
|
|
}.bind(this), max_ping_response_time);
|
2020-10-14 20:15:41 +02:00
|
|
|
}.bind(this);
|
2019-11-04 09:29:49 +01:00
|
|
|
|
|
|
|
this.websocket = new WebSocket(url);
|
|
|
|
this.websocket.onopen = jQuery.proxy(function(e)
|
|
|
|
{
|
2020-10-14 20:15:41 +02:00
|
|
|
check_timer = window.setTimeout(check, check_interval);
|
2019-11-04 09:29:49 +01:00
|
|
|
this.websocket.send(JSON.stringify({
|
2020-04-20 13:07:58 +02:00
|
|
|
subscribe: tokens,
|
|
|
|
account_id: parseInt(account_id)
|
2019-11-04 09:29:49 +01:00
|
|
|
}));
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
this.websocket.onmessage = jQuery.proxy(function(event)
|
|
|
|
{
|
2020-10-02 20:27:43 +02:00
|
|
|
reconnect_time = min_reconnect_time;
|
2019-11-04 09:29:49 +01:00
|
|
|
console.log(event);
|
2020-10-14 20:15:41 +02:00
|
|
|
if (check_timer) window.clearTimeout(check_timer);
|
|
|
|
check_timer = window.setTimeout(check, check_interval);
|
|
|
|
if (event.data === 'pong') return; // just a keepalive message
|
2019-11-04 09:29:49 +01:00
|
|
|
let data = JSON.parse(event.data);
|
|
|
|
if (data && data.type)
|
|
|
|
{
|
|
|
|
this.handleResponse({ response: [data]});
|
|
|
|
}
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
this.websocket.onerror = jQuery.proxy(function(error)
|
|
|
|
{
|
2020-10-02 20:27:43 +02:00
|
|
|
reconnect_time *= 2;
|
|
|
|
if (reconnect_time > max_reconnect_time) reconnect_time = max_reconnect_time;
|
|
|
|
|
2019-11-04 09:29:49 +01:00
|
|
|
console.log(error);
|
|
|
|
(error||this.handleError({}, error));
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
this.websocket.onclose = jQuery.proxy(function(event)
|
|
|
|
{
|
|
|
|
if (event.wasClean)
|
|
|
|
{
|
2020-10-02 20:27:43 +02:00
|
|
|
reconnect_time = min_reconnect_time;
|
2019-11-04 09:29:49 +01:00
|
|
|
console.log(`[close] Connection closed cleanly, code=${event.code} reason=${event.reason}`);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
reconnect_time *= 2;
|
|
|
|
if (reconnect_time > max_reconnect_time) reconnect_time = max_reconnect_time;
|
|
|
|
|
|
|
|
// e.g. server process killed or network down
|
|
|
|
// event.code is usually 1006 in this case
|
|
|
|
console.log('[close] Connection died --> reconnect in '+reconnect_time+'ms');
|
2020-10-14 20:15:41 +02:00
|
|
|
if (check_timer) window.clearTimeout(check_timer);
|
|
|
|
check_timer = null;
|
2020-04-20 13:07:58 +02:00
|
|
|
window.setTimeout(jQuery.proxy(this.openWebSocket, this, url, tokens, account_id, error, reconnect_time), reconnect_time);
|
2019-11-04 09:29:49 +01:00
|
|
|
}
|
|
|
|
}, this);
|
|
|
|
},
|
|
|
|
|
2013-09-10 22:22:47 +02:00
|
|
|
/**
|
|
|
|
* Sends the assembled request to the server
|
2020-03-02 10:43:19 +01:00
|
|
|
* @param {boolean|"keepalive"} _async Overrides async provided in constructor: true: asynchronious request,
|
|
|
|
* false: synchronious request, "keepalive": async. request with keepalive===true / sendBeacon, to be used in beforeunload event
|
2014-11-21 17:16:24 +01:00
|
|
|
* @param {string} method ='POST' allow to eg. use a (cachable) 'GET' request instead of POST
|
2016-07-28 12:02:01 +02:00
|
|
|
* @param {function} error option error callback(_xmlhttp, _err) used instead our default this.error
|
2014-03-26 18:53:09 +01:00
|
|
|
*
|
2020-03-02 10:43:19 +01:00
|
|
|
* @return {jqXHR|boolean} jQuery jqXHR request object or for async==="keepalive" boolean is returned
|
2013-09-10 22:22:47 +02:00
|
|
|
*/
|
2016-07-28 12:02:01 +02:00
|
|
|
json_request.prototype.sendRequest = function(async, method, error)
|
|
|
|
{
|
2013-09-16 16:35:55 +02:00
|
|
|
if(typeof async != "undefined")
|
2013-09-10 22:22:47 +02:00
|
|
|
{
|
|
|
|
this.async = async;
|
|
|
|
}
|
2014-01-09 13:20:13 +01:00
|
|
|
|
2018-08-16 11:06:06 +02:00
|
|
|
if (typeof method === 'undefined') method = 'POST';
|
|
|
|
|
2012-03-05 16:02:00 +01:00
|
|
|
// Assemble the complete request
|
2018-08-16 11:06:06 +02:00
|
|
|
var request_obj = JSON.stringify({
|
|
|
|
request: {
|
|
|
|
parameters: this.parameters
|
|
|
|
}
|
|
|
|
});
|
2012-03-05 16:02:00 +01:00
|
|
|
|
2020-03-02 10:43:19 +01:00
|
|
|
// send with keepalive===true or sendBeacon to be used in beforeunload event
|
|
|
|
if (this.async === "keepalive" && typeof navigator.sendBeacon !== "undefined")
|
|
|
|
{
|
|
|
|
const data = new FormData();
|
|
|
|
data.append('json_data', request_obj);
|
|
|
|
//(window.opener||window).console.log("navigator.sendBeacon", this.url, request_obj, data.getAll('json_data'));
|
|
|
|
return navigator.sendBeacon(this.url, data);
|
|
|
|
}
|
|
|
|
|
2012-03-05 16:02:00 +01:00
|
|
|
// Send the request via AJAX using the jquery ajax function
|
2013-10-18 18:14:36 +02:00
|
|
|
// we need to use jQuery of window of egw object, as otherwise the one from main window is used!
|
2014-01-09 13:20:13 +01:00
|
|
|
// (causing eg. apply from server with app.$app.method to run in main window instead of popup)
|
2016-06-02 16:51:15 +02:00
|
|
|
this.request = (this.egw.window?this.egw.window.jQuery:jQuery).ajax({
|
2012-03-05 16:02:00 +01:00
|
|
|
url: this.url,
|
|
|
|
async: this.async,
|
|
|
|
context: this,
|
2018-08-16 11:06:06 +02:00
|
|
|
// only POST can send JSON as direct payload, GET can not
|
|
|
|
data: method === 'GET' ? { json_data: request_obj } : request_obj,
|
|
|
|
contentType: method === 'GET' ? false : 'application/json',
|
2012-03-05 16:02:00 +01:00
|
|
|
dataType: 'json',
|
2018-08-16 11:06:06 +02:00
|
|
|
type: method,
|
2012-03-05 16:02:00 +01:00
|
|
|
success: this.handleResponse,
|
2019-02-20 13:24:43 +01:00
|
|
|
jsonp: false,
|
2016-07-28 12:02:01 +02:00
|
|
|
error: error || this.handleError
|
2012-03-05 16:02:00 +01:00
|
|
|
});
|
2014-03-26 18:53:09 +01:00
|
|
|
|
|
|
|
return this.request;
|
2014-11-21 17:16:24 +01:00
|
|
|
};
|
2012-03-05 16:02:00 +01:00
|
|
|
|
2016-07-28 12:02:01 +02:00
|
|
|
/**
|
|
|
|
* Default error callback displaying error via egw.message
|
|
|
|
*
|
|
|
|
* @param {XMLHTTP} _xmlhttp
|
|
|
|
* @param {string} _err
|
|
|
|
*/
|
|
|
|
json_request.prototype.handleError = function(_xmlhttp, _err) {
|
|
|
|
// Don't error about an abort
|
|
|
|
if(_err !== 'abort')
|
|
|
|
{
|
2019-02-20 10:41:04 +01:00
|
|
|
this.egw.message.call(this.egw,
|
|
|
|
this.egw.lang('A request to the EGroupware server returned with an error')+
|
|
|
|
': '+_xmlhttp.statusText+' ('+_xmlhttp.status+")\n\n"+
|
|
|
|
this.egw.lang('Please reload the EGroupware desktop (F5 / Cmd+r).')+"\n"+
|
2016-07-28 12:02:01 +02:00
|
|
|
this.egw.lang('If the error persists, contact your administrator for help and ask to check the error-log of the webserver.')+
|
2019-02-20 10:41:04 +01:00
|
|
|
"\n\nURL: "+this.url+"\n"+
|
|
|
|
(_xmlhttp.getAllResponseHeaders() ? (_xmlhttp.getAllResponseHeaders().match(/^Date:.*$/mi) ? _xmlhttp.getAllResponseHeaders().match(/^Date:.*$/mi)[0]:''):'')+
|
2018-11-01 11:59:31 +01:00
|
|
|
// if EGroupware send JSON payload with error, errno show it here too
|
|
|
|
(_err === 'error' && _xmlhttp.status === 400 && typeof _xmlhttp.responseJSON === 'object' && _xmlhttp.responseJSON.error ?
|
2019-02-20 10:41:04 +01:00
|
|
|
"\nError: "+_xmlhttp.responseJSON.error+' ('+_xmlhttp.responseJSON.errno+')' : '')
|
|
|
|
);
|
2016-07-28 12:02:01 +02:00
|
|
|
|
2018-11-01 11:59:31 +01:00
|
|
|
this.egw.debug('error', 'Ajax request to', this.url, ' failed: ', _err, _xmlhttp.status, _xmlhttp.statusText, _xmlhttp.responseJSON);
|
|
|
|
|
|
|
|
// check of unparsable JSON on server-side, which might be caused by some network problem --> resend max. twice
|
|
|
|
if (_err === 'error' && _xmlhttp.status === 400 && typeof _xmlhttp.responseJSON === 'object' &&
|
|
|
|
_xmlhttp.responseJSON.errno && _xmlhttp.responseJSON.error.substr(0, 5) === 'JSON ')
|
|
|
|
{
|
|
|
|
// ToDo: resend request max. twice
|
|
|
|
}
|
2016-07-28 12:02:01 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-03-05 16:02:00 +01:00
|
|
|
json_request.prototype.handleResponse = function(data) {
|
2014-01-22 10:30:22 +01:00
|
|
|
if (data && typeof data.response != 'undefined')
|
2012-03-05 16:02:00 +01:00
|
|
|
{
|
2016-07-20 17:13:11 +02:00
|
|
|
if (egw.preference('show_generation_time', 'common', false) == "1")
|
2016-07-14 17:21:30 +02:00
|
|
|
{
|
|
|
|
var gen_time_div = jQuery('#divGenTime').length > 0 ? jQuery('#divGenTime')
|
|
|
|
:jQuery('<div id="divGenTime" class="pageGenTime"><span class="pageTime"></span></div>').appendTo('#egw_fw_footer');
|
|
|
|
}
|
2013-07-20 15:41:17 +02:00
|
|
|
// Load files first
|
|
|
|
var js_files = [];
|
2019-01-15 14:34:52 +01:00
|
|
|
for (var i = data.response.length - 1; i >= 0; --i)
|
2013-07-20 15:41:17 +02:00
|
|
|
{
|
|
|
|
var res = data.response[i];
|
|
|
|
if(res.type == 'js' && typeof res.data == 'string')
|
|
|
|
{
|
|
|
|
js_files.unshift(res.data);
|
|
|
|
data.response.splice(i,1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(js_files.length > 0)
|
|
|
|
{
|
2014-01-09 13:20:13 +01:00
|
|
|
var start_time = (new Date).getTime();
|
|
|
|
this.egw.includeJS(js_files, function() {
|
|
|
|
var end_time = (new Date).getTime();
|
|
|
|
this.handleResponse(data);
|
2016-07-21 10:50:02 +02:00
|
|
|
if (egw.preference('show_generation_time', 'common', false) == "1")
|
2016-07-15 17:25:07 +02:00
|
|
|
{
|
|
|
|
var gen_time_div = jQuery('#divGenTime');
|
|
|
|
if (!gen_time_div.length) gen_time_div = jQuery('.pageGenTime');
|
|
|
|
var gen_time_async = jQuery('.asyncIncludeTime').length > 0 ? jQuery('.asyncIncludeTime'):
|
2016-07-15 09:34:48 +02:00
|
|
|
gen_time_div.append('<span class="asyncIncludeTime"></span>').find('.asyncIncludeTime');
|
2016-07-15 17:25:07 +02:00
|
|
|
gen_time_async.text(egw.lang('async includes took %1s', (end_time-start_time)/1000));
|
|
|
|
}
|
2014-01-09 13:20:13 +01:00
|
|
|
}, this);
|
2013-07-20 15:41:17 +02:00
|
|
|
return;
|
|
|
|
}
|
2014-01-11 15:47:31 +01:00
|
|
|
|
|
|
|
// Flag for only data response - don't call callback if only data
|
2014-01-22 10:30:22 +01:00
|
|
|
var only_data = (data.response.length > 0);
|
2014-01-11 15:47:31 +01:00
|
|
|
|
2012-03-05 16:02:00 +01:00
|
|
|
for (var i = 0; i < data.response.length; i++)
|
|
|
|
{
|
2012-03-06 14:22:01 +01:00
|
|
|
// Get the response object
|
|
|
|
var res = data.response[i];
|
2014-01-11 15:47:31 +01:00
|
|
|
if(typeof res.type == 'string' && res.type != 'data') only_data = false;
|
2012-03-06 14:22:01 +01:00
|
|
|
|
|
|
|
// Check whether a plugin for the given type exists
|
2016-02-04 20:26:37 +01:00
|
|
|
var handlers = [plugins, global_plugins];
|
|
|
|
for(var handler_idx = 0; handler_idx < handlers.length; handler_idx++)
|
2012-03-06 14:22:01 +01:00
|
|
|
{
|
2016-02-04 20:26:37 +01:00
|
|
|
var handler_level = handlers[handler_idx];
|
|
|
|
if (typeof handler_level[res.type] !== 'undefined')
|
|
|
|
{
|
|
|
|
for (var j = 0; j < handler_level[res.type].length; j++) {
|
|
|
|
try {
|
|
|
|
// Get a reference to the plugin
|
|
|
|
var plugin = handler_level[res.type][j];
|
2016-07-14 17:21:30 +02:00
|
|
|
if (res.type.match(/et2_load/))
|
|
|
|
{
|
2016-07-21 10:50:02 +02:00
|
|
|
if (egw.preference('show_generation_time', 'common', false) == "1")
|
2016-07-14 17:21:30 +02:00
|
|
|
{
|
|
|
|
if (gen_time_div.length > 0)
|
|
|
|
{
|
|
|
|
gen_time_div.find('span.pageTime').text(egw.lang("Page was generated in %1 seconds ", data.page_generation_time));
|
|
|
|
if (data.session_restore_time)
|
|
|
|
{
|
2016-07-15 09:34:48 +02:00
|
|
|
var gen_time_session_span = gen_time_div.find('span.session').length > 0 ? gen_time_div.find('span.session'):
|
|
|
|
gen_time_div.append('<span class="session"></span>').find('.session');
|
2016-07-14 17:21:30 +02:00
|
|
|
gen_time_session_span.text(egw.lang("session restore time in %1 seconds ", data.page_generation_time));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-04 20:26:37 +01:00
|
|
|
// Call the plugin callback
|
|
|
|
plugin.callback.call(
|
|
|
|
plugin.context ? plugin.context : this.context,
|
|
|
|
res.type, res, this
|
|
|
|
);
|
|
|
|
} catch(e) {
|
|
|
|
var msg = e.message ? e.message : e + '';
|
|
|
|
var stack = e.stack ? "\n-- Stack trace --\n" + e.stack : "";
|
|
|
|
this.egw.debug('error', 'Exception "' + msg + '" while handling JSON response from ' +
|
|
|
|
this.url + ' [' + JSON.stringify(this.parameters) + '] type "' + res.type +
|
|
|
|
'", plugin', plugin, 'response', res, stack);
|
|
|
|
}
|
2012-03-06 14:22:01 +01:00
|
|
|
}
|
2012-03-05 16:02:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-01-11 15:47:31 +01:00
|
|
|
// Call request callback, if provided
|
|
|
|
if(this.callback != null && !only_data)
|
|
|
|
{
|
|
|
|
this.callback.call(this.context,res);
|
|
|
|
}
|
2012-03-05 16:02:00 +01:00
|
|
|
}
|
2014-03-26 18:53:09 +01:00
|
|
|
this.request = null;
|
2014-11-21 17:16:24 +01:00
|
|
|
};
|
2012-03-05 14:07:38 +01:00
|
|
|
|
|
|
|
var json = {
|
|
|
|
|
|
|
|
/** The constructor of the egw_json_request class.
|
|
|
|
*
|
|
|
|
* @param _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 _parameters which should be passed to the menuaction function.
|
2012-03-05 16:02:00 +01:00
|
|
|
* @param _async specifies whether the request should be asynchronous or
|
|
|
|
* not.
|
2012-03-05 14:07:38 +01:00
|
|
|
* @param _callback specifies the callback function which should be
|
|
|
|
* called, once the request has been sucessfully executed.
|
2014-01-09 13:20:13 +01:00
|
|
|
* @param _context is the context which will be used for the callback function
|
2012-03-05 16:02:00 +01:00
|
|
|
* @param _sender is a parameter being passed to the _callback function
|
2012-03-05 14:07:38 +01:00
|
|
|
*/
|
2012-03-05 16:02:00 +01:00
|
|
|
json: function(_menuaction, _parameters, _callback, _context, _async,
|
|
|
|
_sender)
|
2012-03-05 14:07:38 +01:00
|
|
|
{
|
2014-01-09 13:20:13 +01:00
|
|
|
return new json_request(_menuaction, _parameters, _callback,
|
2012-03-05 16:02:00 +01:00
|
|
|
_context, _async, _sender, this);
|
|
|
|
},
|
2012-03-05 14:07:38 +01:00
|
|
|
|
2020-10-16 18:32:51 +02:00
|
|
|
/**
|
|
|
|
* Do an AJAX call and get a javascript promise, which will be resolved with the returned data.
|
|
|
|
*
|
|
|
|
* egw.request() returns immediately with a Promise. The promise will be resolved with just the returned data,
|
|
|
|
* any other "piggybacked" responses will be handled by registered handlers. The data will also be passed to
|
|
|
|
* any registered data handlers (egw.data) before it is passed to your handler.
|
|
|
|
*
|
|
|
|
* To use:
|
|
|
|
* @example
|
|
|
|
* egw.request(
|
|
|
|
* "EGroupware\\Api\\Etemplate\\Widget\\Select::ajax_get_options",
|
|
|
|
* ["select-cat"]
|
|
|
|
* )
|
|
|
|
* .then(function(data) {
|
|
|
|
* // Deal with the returned data here. data may be undefined if no data was returned.
|
|
|
|
* console.log("Here's the categories:",data);
|
|
|
|
* });
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* The return is a Promise, so multiple .then() can be chained in the usual ways:
|
|
|
|
* @example
|
|
|
|
* egw.request(...)
|
|
|
|
* .then(function(data) {
|
|
|
|
* if(debug) console.log("Requested data", data);
|
|
|
|
* }
|
|
|
|
* .then(function(data) {
|
|
|
|
* // Change the data for the rest of the chain
|
|
|
|
* if(typeof data === "undefined") return [];
|
|
|
|
* }
|
|
|
|
* .then(function(data) {
|
|
|
|
* // data is never undefined now, if it was before it's an empty array now
|
|
|
|
* for(let i = 0; i < data.length; i++)
|
|
|
|
* {
|
|
|
|
* ...
|
|
|
|
* }
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* You can also fire off multiple requests, and wait for them to all be answered:
|
|
|
|
* @example
|
|
|
|
* let first = egw.request(...);
|
|
|
|
* let second = egw.request(...);
|
|
|
|
* Promise.all([first, second])
|
|
|
|
* .then(function(values) {
|
|
|
|
* console.log("First:", values[0], "Second:", values[1]);
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* @param {string} _menuaction
|
|
|
|
* @param {any[]} _parameters
|
|
|
|
*
|
|
|
|
* @return Promise
|
|
|
|
*/
|
|
|
|
request: function(_menuaction, _parameters)
|
|
|
|
{
|
|
|
|
let request = new json_request(_menuaction, _parameters, null, this, true, this, this);
|
|
|
|
let ajax_promise = request.sendRequest();
|
|
|
|
|
|
|
|
// This happens first, immediately
|
|
|
|
let resolvePromise = function(resolve, reject) {
|
|
|
|
// Bind to ajax response - this is called _after_ any other handling
|
|
|
|
ajax_promise.always(function(response, status, p) {
|
|
|
|
if(status !== "success") reject();
|
|
|
|
|
|
|
|
// The ajax request has completed, get just the data & pass it on
|
|
|
|
if(response && response.response)
|
|
|
|
{
|
|
|
|
for(let value of response.response)
|
|
|
|
{
|
|
|
|
if(value.type && value.type === "data" && typeof value.data !== "undefined")
|
|
|
|
{
|
|
|
|
// Data was packed in response
|
|
|
|
resolve(value.data);
|
|
|
|
}
|
|
|
|
else if (value && typeof value.type === "undefined" && typeof value.data === "undefined")
|
|
|
|
{
|
|
|
|
// Just raw data
|
|
|
|
resolve(value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// No data? Resolve the promise with nothing
|
|
|
|
resolve();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const myPromise = new Promise(resolvePromise);
|
|
|
|
|
|
|
|
return myPromise;
|
|
|
|
},
|
|
|
|
|
2021-03-01 10:50:22 +01:00
|
|
|
/**
|
|
|
|
* Call a function specified by it's name (possibly dot separated, eg. "app.myapp.myfunc")
|
|
|
|
*
|
|
|
|
* @param {string} _func dot-separated function name
|
|
|
|
* @param {mixed} ...args variable number of arguments
|
|
|
|
* @returns {mixed|Promise}
|
|
|
|
*/
|
|
|
|
call: function(_func)
|
|
|
|
{
|
|
|
|
let args = [].slice.call(arguments); // convert arguments to array
|
|
|
|
let func = args.shift();
|
|
|
|
|
|
|
|
return this.apply(func, args);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Call a function specified by it's name (possibly dot separated, eg. "app.myapp.myfunc")
|
|
|
|
*
|
|
|
|
* @param {string} _func dot-separated function name
|
|
|
|
* @param {array} args arguments
|
|
|
|
* @param {object} _context
|
|
|
|
* @returns {mixed|Promise}
|
|
|
|
*/
|
|
|
|
apply: function(_func, args, _context)
|
|
|
|
{
|
|
|
|
let parent = _context || window;
|
|
|
|
let func;
|
|
|
|
|
|
|
|
if (typeof _func === 'string')
|
|
|
|
{
|
|
|
|
let parts = _func.split('.');
|
|
|
|
func = parts.pop();
|
|
|
|
for(var i=0; i < parts.length; ++i)
|
|
|
|
{
|
|
|
|
if (typeof parent[parts[i]] !== 'undefined')
|
|
|
|
{
|
|
|
|
parent = parent[parts[i]];
|
|
|
|
}
|
|
|
|
// check if we need a not yet included app.js object --> include it now and return a Promise
|
|
|
|
else if (i == 1 && parts[0] == 'app' && typeof app.classes[parts[1]] === 'undefined')
|
|
|
|
{
|
|
|
|
const self = this;
|
|
|
|
return new Promise(function(resolve, reject)
|
|
|
|
{
|
|
|
|
self.includeJS('/'+parts[1]+'/js/app.js', function ()
|
|
|
|
{
|
|
|
|
resolve(self.apply(_func, args));
|
|
|
|
}, self, self.webserverUrl);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
// check if we need a not yet instanciated app.js object --> instanciate it now
|
|
|
|
else if (i == 1 && parts[0] == 'app' && typeof app.classes[parts[1]] === 'function')
|
|
|
|
{
|
|
|
|
parent = parent[parts[1]] = new app.classes[parts[1]]();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (typeof parent[func] == 'function')
|
|
|
|
{
|
|
|
|
func = parent[func];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (typeof func != 'function')
|
|
|
|
{
|
|
|
|
throw _func+" is not a function!";
|
|
|
|
}
|
|
|
|
return func.apply(parent, args);
|
|
|
|
},
|
|
|
|
|
2012-03-05 14:07:38 +01:00
|
|
|
/**
|
|
|
|
* Registers a new handler plugin.
|
|
|
|
*
|
|
|
|
* @param _callback is the callback function which should be called
|
|
|
|
* whenever a response is comming from the server.
|
|
|
|
* @param _context is the context in which the callback function should
|
|
|
|
* be called. If null is given, the plugin is executed in the context
|
2012-03-05 16:02:00 +01:00
|
|
|
* of the request object context.
|
2012-03-05 14:07:38 +01:00
|
|
|
* @param _type is an optional parameter defaulting to 'global'.
|
|
|
|
* it describes the response type which this plugin should be
|
|
|
|
* handling.
|
2016-02-04 20:26:37 +01:00
|
|
|
* @param {boolean} [_global=false] Register the handler globally or
|
|
|
|
* locally. Global handlers must stay around, so should be used
|
|
|
|
* for global modules.
|
2012-03-05 14:07:38 +01:00
|
|
|
*/
|
2016-02-04 20:26:37 +01:00
|
|
|
registerJSONPlugin: function(_callback, _context, _type, _global)
|
2012-03-05 14:07:38 +01:00
|
|
|
{
|
|
|
|
// _type defaults to 'global'
|
|
|
|
if (typeof _type === 'undefined')
|
|
|
|
{
|
|
|
|
_type = 'global';
|
|
|
|
}
|
2016-02-04 20:26:37 +01:00
|
|
|
// _global defaults to false
|
|
|
|
if (typeof _global === 'undefined')
|
|
|
|
{
|
|
|
|
_global = false;
|
|
|
|
}
|
|
|
|
var scoped = _global ? global_plugins : plugins;
|
2012-03-05 14:07:38 +01:00
|
|
|
|
|
|
|
// Create an array for the given category inside the plugins object
|
2016-02-04 20:26:37 +01:00
|
|
|
if (typeof scoped[_type] === 'undefined')
|
2012-03-05 14:07:38 +01:00
|
|
|
{
|
2016-02-04 20:26:37 +01:00
|
|
|
scoped[_type] = [];
|
2012-03-05 14:07:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add the entry
|
2016-02-04 20:26:37 +01:00
|
|
|
scoped[_type].push({
|
2012-03-05 14:07:38 +01:00
|
|
|
'callback': _callback,
|
2014-11-21 17:16:24 +01:00
|
|
|
'context': _context
|
2012-03-05 14:07:38 +01:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Removes a previously registered plugin.
|
|
|
|
*
|
|
|
|
* @param _callback is the callback function which should be called
|
|
|
|
* whenever a response is comming from the server.
|
|
|
|
* @param _context is the context in which the callback function should
|
|
|
|
* be called.
|
|
|
|
* @param _type is an optional parameter defaulting to 'global'.
|
|
|
|
* it describes the response type which this plugin should be
|
|
|
|
* handling.
|
2016-02-04 20:26:37 +01:00
|
|
|
* @param {boolean} [_global=false] Remove a global or local handler.
|
2012-03-05 14:07:38 +01:00
|
|
|
*/
|
2016-02-04 20:26:37 +01:00
|
|
|
unregisterJSONPlugin: function(_callback, _context, _type, _global)
|
2012-03-05 14:07:38 +01:00
|
|
|
{
|
|
|
|
// _type defaults to 'global'
|
|
|
|
if (typeof _type === 'undefined')
|
|
|
|
{
|
|
|
|
_type = 'global';
|
|
|
|
}
|
2016-02-04 20:26:37 +01:00
|
|
|
// _global defaults to false
|
|
|
|
if (typeof _global === 'undefined')
|
|
|
|
{
|
|
|
|
_global = false;
|
|
|
|
}
|
|
|
|
var scoped = _global ? global_plugins : plugins;
|
|
|
|
if (typeof scoped[_type] !== 'undefined') {
|
|
|
|
for (var i = 0; i < scoped[_type].length; i++)
|
2012-03-05 14:07:38 +01:00
|
|
|
{
|
2016-02-04 20:26:37 +01:00
|
|
|
if (scoped[_type][i].callback == _callback &&
|
|
|
|
scoped[_type][i].context == _context)
|
2012-03-05 14:07:38 +01:00
|
|
|
{
|
2016-02-04 20:26:37 +01:00
|
|
|
scoped[_type].slice(i, 1);
|
2012-03-05 14:07:38 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Regisert the "alert" plugin
|
2012-03-05 16:02:00 +01:00
|
|
|
json.registerJSONPlugin(function(type, res, req) {
|
2012-03-05 14:07:38 +01:00
|
|
|
//Check whether all needed parameters have been passed and call the alertHandler function
|
2014-01-09 13:20:13 +01:00
|
|
|
if ((typeof res.data.message != 'undefined') &&
|
2012-03-05 14:07:38 +01:00
|
|
|
(typeof res.data.details != 'undefined'))
|
2012-03-05 16:02:00 +01:00
|
|
|
{
|
|
|
|
req.alertHandler(
|
2012-03-05 14:07:38 +01:00
|
|
|
res.data.message,
|
2014-11-21 17:16:24 +01:00
|
|
|
res.data.details);
|
2012-03-05 14:07:38 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
throw 'Invalid parameters';
|
|
|
|
}, null, 'alert');
|
|
|
|
|
2018-06-05 12:29:23 +02:00
|
|
|
// Regisert the "message" plugin
|
|
|
|
json.registerJSONPlugin(function(type, res, req) {
|
|
|
|
//Check whether all needed parameters have been passed and call the alertHandler function
|
|
|
|
if ((typeof res.data.message != 'undefined'))
|
|
|
|
{
|
|
|
|
req.egw.message(res.data.message, res.data.type);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
throw 'Invalid parameters';
|
|
|
|
}, null, 'message');
|
|
|
|
|
2012-03-05 14:07:38 +01:00
|
|
|
// Register the "assign" plugin
|
2012-03-05 16:02:00 +01:00
|
|
|
json.registerJSONPlugin(function(type, res, req) {
|
2012-03-05 14:07:38 +01:00
|
|
|
//Check whether all needed parameters have been passed and call the alertHandler function
|
2014-01-09 13:20:13 +01:00
|
|
|
if ((typeof res.data.id != 'undefined') &&
|
2012-03-05 14:07:38 +01:00
|
|
|
(typeof res.data.key != 'undefined') &&
|
|
|
|
(typeof res.data.value != 'undefined'))
|
2012-03-05 16:02:00 +01:00
|
|
|
{
|
2013-10-28 23:22:09 +01:00
|
|
|
var obj = _wnd.document.getElementById(res.data.id);
|
2012-03-05 14:07:38 +01:00
|
|
|
if (obj)
|
|
|
|
{
|
|
|
|
obj[res.data.key] = res.data.value;
|
|
|
|
|
|
|
|
if (res.data.key == "innerHTML")
|
|
|
|
{
|
|
|
|
egw_insertJS(res.data.value);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
throw 'Invalid parameters';
|
|
|
|
}, null, 'assign');
|
|
|
|
|
|
|
|
// Register the "data" plugin
|
2012-03-05 16:02:00 +01:00
|
|
|
json.registerJSONPlugin(function(type, res, req) {
|
2012-03-05 14:07:38 +01:00
|
|
|
//Callback the caller in order to allow him to handle the data
|
2012-03-05 16:02:00 +01:00
|
|
|
if (req.callback)
|
2012-03-05 14:07:38 +01:00
|
|
|
{
|
2012-03-05 16:02:00 +01:00
|
|
|
req.callback.call(req.sender, res.data);
|
2012-03-05 14:07:38 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}, null, 'data');
|
|
|
|
|
|
|
|
// Register the "script" plugin
|
2012-03-05 16:02:00 +01:00
|
|
|
json.registerJSONPlugin(function(type, res, req) {
|
2012-03-05 14:07:38 +01:00
|
|
|
if (typeof res.data == 'string')
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
|
|
|
var func = new Function(res.data);
|
2013-03-25 17:58:33 +01:00
|
|
|
func.call(req.egw ? req.egw.window : window);
|
2012-03-05 14:07:38 +01:00
|
|
|
}
|
|
|
|
catch (e)
|
|
|
|
{
|
2012-03-05 16:02:00 +01:00
|
|
|
req.egw.debug('error', 'Error while executing script: ',
|
2014-11-21 17:16:24 +01:00
|
|
|
res.data,e);
|
2012-03-05 14:07:38 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
throw 'Invalid parameters';
|
|
|
|
}, null, 'script');
|
|
|
|
|
|
|
|
// Register the "apply" plugin
|
2012-03-05 16:02:00 +01:00
|
|
|
json.registerJSONPlugin(function(type, res, req) {
|
2013-04-12 18:03:45 +02:00
|
|
|
if (typeof res.data.func == 'string')
|
2012-03-05 14:07:38 +01:00
|
|
|
{
|
2021-03-01 10:50:22 +01:00
|
|
|
req.egw.apply(res.data.func, res.data.parms, req.egw.window);
|
|
|
|
return true;
|
2012-03-05 14:07:38 +01:00
|
|
|
}
|
|
|
|
throw 'Invalid parameters';
|
|
|
|
}, null, 'apply');
|
|
|
|
|
|
|
|
// Register the "jquery" plugin
|
2012-03-05 16:02:00 +01:00
|
|
|
json.registerJSONPlugin(function(type, res, req) {
|
2012-03-05 14:07:38 +01:00
|
|
|
if (typeof res.data.select == 'string' &&
|
|
|
|
typeof res.data.func == 'string')
|
|
|
|
{
|
|
|
|
try
|
|
|
|
{
|
2016-06-02 16:51:15 +02:00
|
|
|
var jQueryObject = jQuery(res.data.select, req.context);
|
2012-03-05 14:07:38 +01:00
|
|
|
jQueryObject[res.data.func].apply(jQueryObject, res.data.parms);
|
|
|
|
}
|
|
|
|
catch (e)
|
|
|
|
{
|
2012-03-05 16:02:00 +01:00
|
|
|
req.egw.debug('error', 'Function', res.data.func,
|
2012-03-05 14:07:38 +01:00
|
|
|
'Parameters', res.data.parms);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
throw 'Invalid parameters';
|
2013-10-28 23:22:09 +01:00
|
|
|
}, _wnd, 'jquery');
|
2012-03-05 14:07:38 +01:00
|
|
|
|
|
|
|
// Register the "redirect" plugin
|
2012-03-05 16:02:00 +01:00
|
|
|
json.registerJSONPlugin(function(type, res, req) {
|
2012-03-05 14:07:38 +01:00
|
|
|
//console.log(res.data.url);
|
|
|
|
if (typeof res.data.url == 'string' &&
|
|
|
|
typeof res.data.global == 'boolean')
|
|
|
|
{
|
|
|
|
//Special handling for framework reload
|
|
|
|
res.data.global |= (res.data.url.indexOf("?cd=10") > 0);
|
|
|
|
|
|
|
|
if (res.data.global)
|
|
|
|
{
|
|
|
|
egw_topWindow().location.href = res.data.url;
|
|
|
|
}
|
2014-11-19 17:10:39 +01:00
|
|
|
// json request was originating from a different popup --> redirect that one
|
|
|
|
else if(this && this.DOMContainer && this.DOMContainer.ownerDocument.defaultView != window &&
|
|
|
|
egw(this.DOMContainer.ownerDocument.defaultView).is_popup())
|
2014-10-15 16:55:08 +02:00
|
|
|
{
|
|
|
|
this.DOMContainer.ownerDocument.location.href = res.data.url;
|
|
|
|
}
|
|
|
|
// main window, open url in respective tab
|
2012-03-05 14:07:38 +01:00
|
|
|
else
|
|
|
|
{
|
2014-01-27 17:28:39 +01:00
|
|
|
egw_appWindowOpen(res.data.app, res.data.url);
|
2012-03-05 14:07:38 +01:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
throw 'Invalid parameters';
|
|
|
|
}, null, 'redirect');
|
|
|
|
|
|
|
|
// Register the 'css' plugin
|
2012-03-05 16:02:00 +01:00
|
|
|
json.registerJSONPlugin(function(type, res, req) {
|
2012-03-05 14:07:38 +01:00
|
|
|
if (typeof res.data == 'string')
|
|
|
|
{
|
2012-03-05 16:02:00 +01:00
|
|
|
req.egw.includeCSS(res.data);
|
2012-03-05 14:07:38 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
throw 'Invalid parameters';
|
|
|
|
}, null, 'css');
|
|
|
|
|
|
|
|
// Register the 'js' plugin
|
2012-03-05 16:02:00 +01:00
|
|
|
json.registerJSONPlugin(function(type, res, req) {
|
2012-03-05 14:07:38 +01:00
|
|
|
if (typeof res.data == 'string')
|
|
|
|
{
|
2012-03-05 16:02:00 +01:00
|
|
|
req.jsCount++;
|
|
|
|
req.egw.includeJS(res.data, function() {
|
|
|
|
req.jsFiles++;
|
|
|
|
if (req.jsFiles == req.jsCount && req.onLoadFinish)
|
2012-03-05 14:07:38 +01:00
|
|
|
{
|
2012-03-05 16:02:00 +01:00
|
|
|
req.onLoadFinish.call(req.sender);
|
2012-03-05 14:07:38 +01:00
|
|
|
}
|
|
|
|
});
|
2013-02-14 12:01:50 +01:00
|
|
|
return true;
|
2012-03-05 14:07:38 +01:00
|
|
|
}
|
|
|
|
throw 'Invalid parameters';
|
|
|
|
}, null, 'js');
|
|
|
|
|
2012-03-06 10:50:43 +01:00
|
|
|
// Register the 'html' plugin, replacing document content with send html
|
|
|
|
json.registerJSONPlugin(function(type, res, req) {
|
|
|
|
if (typeof res.data == 'string')
|
|
|
|
{
|
|
|
|
// Empty the document tree
|
|
|
|
while (_wnd.document.childNodes.length > 0)
|
|
|
|
{
|
2012-03-06 14:22:01 +01:00
|
|
|
_wnd.document.removeChild(_wnd.document.childNodes[0]);
|
2012-03-06 10:50:43 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Write the given content
|
|
|
|
_wnd.document.write(res.data);
|
|
|
|
|
|
|
|
// Close the document
|
|
|
|
_wnd.document.close();
|
2013-02-14 12:01:50 +01:00
|
|
|
return true;
|
2012-03-06 10:50:43 +01:00
|
|
|
}
|
|
|
|
throw 'Invalid parameters';
|
|
|
|
}, null, 'html');
|
|
|
|
|
2012-03-05 14:07:38 +01:00
|
|
|
// Return the extension
|
|
|
|
return json;
|
|
|
|
});
|
|
|
|
|