/** * eGroupWare API: JSON - Contains the client side javascript implementation of class.egw_json.inc.php * * @link http://www.egroupware.org * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package api * @subpackage ajax * @author Andreas Stoeckel * @version $Id$ */ /*egw:uses jsapi.jsapi; // Contains some helper functions jquery.jquery; // Used for the ajax requests */ /* The egw_json_request is the javaScript side implementation of class.egw_json.inc.php.*/ function _egw_json_escape_string(input) { var len = input.length; var res = ""; for (var i = 0; i < len; i++) { switch (input.charAt(i)) { case '"': res += '\\"'; break; case '\n': res += '\\n'; break; case '\r': res += '\\r'; break; case '\\': res += '\\\\'; break; case '\/': res += '\\/'; break; case '\b': res += '\\b'; break; case '\f': res += '\\f'; break; case '\t': res += '\\t'; break; default: res += input.charAt(i); } } return res; } function _egw_json_encode_simple(input) { switch (input.constructor) { case String: return '"' + _egw_json_escape_string(input) + '"'; case Number: return input.toString(); case Boolean: return input ? 'true' : 'false'; default: return null; } } function egw_json_encode(input) { if (input == null || !input && input.length == 0) return 'null'; var simple_res = _egw_json_encode_simple(input); if (simple_res == null) { switch (input.constructor) { case Array: var buf = []; for (var k in input) { //Filter non numeric entries if (!isNaN(k)) buf.push(egw_json_encode(input[k])); } return '[' + buf.join(',') + ']'; case Object: var buf = []; for (var k in input) { buf.push(_egw_json_encode_simple(k) + ':' + egw_json_encode(input[k])); } return '{' + buf.join(',') + '}'; default: switch(typeof input) { case 'array': var buf = []; for (var k in input) { //Filter non numeric entries if (!isNaN(k)) buf.push(egw_json_encode(input[k])); } return '[' + buf.join(',') + ']'; case 'object': var buf = []; for (var k in input) { buf.push(_egw_json_encode_simple(k) + ':' + egw_json_encode(input[k])); } return '{' + buf.join(',') + '}'; } return 'null'; } } else { return simple_res; } } /** * Some variables needed to store which JS and CSS files have already be included */ var egw_json_files = {}; /** * Initialize the egw_json_files object with all files which are already bound in */ $j(document).ready(function() { $j("script, link").each(function() { var file = false; if ($j(this).attr("src")) { file = $j(this).attr("src"); } else if ($j(this).attr("href")) { file = $j(this).attr("href"); } if (file) { egw_json_files[file] = true; } }); }); /** * Variable which stores all currently registered plugins */ var _egw_json_plugins = []; /** * Register a plugin for the egw_json handler */ function egw_json_register_plugin(_callback, _context) { //Default the context parameter to "window" if (typeof _context == 'undefined') { _context = window; } //Add a plugin object to the plugins array _egw_json_plugins[_egw_json_plugins.length] = { 'callback': _callback, 'context': _context }; } /** * Function used internally to pass a response to all registered plugins */ function _egw_json_plugin_handle(_type, _response, _context) { for (var i = 0; i < _egw_json_plugins.length; i++) { try { var plugin = _egw_json_plugins[i]; var context = plugin.context; if (!plugin.context && typeof _context != "undefined") { context = _context; } if (plugin.callback.call(context, _type, _response)) { return true; } } catch (e) { if (typeof console != 'undefined') { console.log(e); } } } return false; } /** The constructor of the egw_json_request class. * * @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 object _context is the context which will be used for the callback function */ function egw_json_request(_menuaction, _parameters, _context) { this.context = window.document; if (typeof _context != 'undefined') this.context = _context; if (typeof _parameters != 'undefined') { this.parameters = _parameters; } else { this.parameters = new Array; } // Check whether the supplied menuaction parameter is a full featured url // or just a menuaction 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; } else { // We only got a menu action, assemble the url manually. this.url = this._assembleAjaxUrl(_menuaction); } this.request = null; this.sender = null; this.callback = null; this.alertHandler = this.alertFunc; this.onLoadFinish = null; this.loadedJSFiles = {}; this.handleResponseDone = false; this.app = null; if (window.egw_alertHandler) { this.alertHandler = window.egw_alertHandler; } } /** * Sets the "application" object which is passed to egw_appWindowOpen when a redirect is done */ egw_json_request.prototype.setAppObject = function(_app) { this.app = _app; } egw_json_request.prototype._assembleAjaxUrl = function(_menuaction) { // Retrieve the webserver url var webserver_url = egw_topWindow().egw_webserverUrl; // Check whether the webserver_url is really set // Don't check for !webserver_url as it might be empty. // Thank you to Ingo Ratsdorf for reporting this. if (typeof webserver_url == "undefined") { throw "Internal JS error, top window not found, webserver url could not be retrieved."; } // Add the ajax relevant parts return webserver_url + '/json.php?menuaction=' + _menuaction; } /** * Sends the AJAX JSON request. * * @param boolean _async specifies whether the request should be handeled asynchronously (true, the sendRequest function immediately returns to the caller) or asynchronously (false, the sendRequest function waits until the request is received) * @param _callback is an additional callback function which should be called upon a "data" response is received * @param _sender is the reference object the callback function should get */ egw_json_request.prototype.sendRequest = function(_async, _callback, _sender) { //Store the sender and callback parameter inside this class this.sender = _sender; if (typeof _callback != "undefined") this.callback = _callback; //Copy the async parameter which defaults to "true" var is_async = true; if (typeof _async != "undefined") is_async = _async; //Assemble the actual request object containing the json data string var request_obj = { "json_data": egw_json_encode( { "request": { "parameters": this.parameters } }) }; //Send the request via the jquery AJAX interface to the server this.request = $j.ajax({url: this.url, async: is_async, context: this, data: request_obj, dataType: 'json', type: 'POST', success: this.handleResponse, error: function(_xmlhttp,_err) { window.console.error('Ajax request to ' + this.url + ' failed: ' + _err); } }); } egw_json_request.prototype.abort = function() { this.request.abort(); } egw_json_request.prototype.alertFunc = function(_message, _details) { alert(_message); if(_details) _egw_json_debug_log(_message, _details); } function _egw_json_debug_log(_msg, _e) { if (typeof console != "undefined" && typeof console.log != "undefined") { console.log(_msg, _e); } } /* Displays an json error message */ egw_json_request.prototype.jsonError = function(_msg, _e) { var msg = 'EGW JSON Error: '+_msg; //Log and show the error message _egw_json_debug_log(msg, _e); this.alertHandler(msg); } /* Internal function which handles the response from the server */ egw_json_request.prototype.handleResponse = function(data, textStatus, XMLHttpRequest) { this.handleResponseDone = false; if (data && data.response) { var hasResponse = false; for (var i = 0; i < data.response.length; i++) { try { var res = data.response[i]; switch (data.response[i].type) { case 'alert': //Check whether all needed parameters have been passed and call the alertHandler function if ((typeof res.data.message != 'undefined') && (typeof res.data.details != 'undefined')) { this.alertHandler( res.data.message, res.data.details) hasResponse = true; } else throw 'Invalid parameters'; break; case 'assign': //Check whether all needed parameters have been passed and call the alertHandler function if ((typeof res.data.id != 'undefined') && (typeof res.data.key != 'undefined') && (typeof res.data.value != 'undefined')) { var obj = document.getElementById(res.data.id); if (obj) { obj[res.data.key] = res.data.value; if (res.data.key == "innerHTML") { egw_insertJS(res.data.value); } hasResponse = true; } } else throw 'Invalid parameters'; break; case 'data': //Callback the caller in order to allow him to handle the data if (this.callback) { this.callback.call(this.sender, res.data); hasResponse = true; } break; case 'script': if (typeof res.data == 'string') { try { var func = new Function(res.data); func.call(window); } catch (e) { e.code = res.data; _egw_json_debug_log(e); } hasResponse = true; } 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.parms); } catch (e) { _egw_json_debug_log(e, {'Function': res.data.func, 'Parameters': res.data.parms}); } hasResponse = true; } else throw 'Invalid parameters'; break; case 'jquery': if (typeof res.data.select == 'string' && typeof res.data.func == 'string') { try { var jQueryObject = $j(res.data.select, this.context); jQueryObject[res.data.func].apply(jQueryObject, res.data.parms); } catch (e) { _egw_json_debug_log(e, {'Function': res.data.func, 'Parameters': res.data.parms}); } hasResponse = true; } else throw 'Invalid parameters'; break; case 'redirect': //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; } else { egw_appWindowOpen(this.app, res.data.url); } hasResponse = true; } else throw 'Invalid parameters'; break; case 'css': if (typeof res.data == 'string') { //Check whether the requested file had already be included if (!egw_json_files[res.data]) { egw_json_files[res.data] = true; //Get the head node and append a new link node with the stylesheet url to it var headID = document.getElementsByTagName('head')[0]; var cssnode = document.createElement('link'); cssnode.type = "text/css"; cssnode.rel = "stylesheet"; cssnode.href = res.data; headID.appendChild(cssnode); } hasResponse = true; } else throw 'Invalid parameters'; break; case 'js': if (typeof res.data == 'string') { //Check whether the requested file had already be included if (!egw_json_files[res.data]) { egw_json_files[res.data] = true; //Get the head node and append a new script node with the js file to it var headID = document.getElementsByTagName('head')[0]; var scriptnode = document.createElement('script'); scriptnode.type = "text/javascript"; scriptnode.src = res.data; scriptnode._originalSrc = res.data; headID.appendChild(scriptnode); //Increment the includedJSFiles count this.loadedJSFiles[res.data] = false; if (typeof console != 'undefined' && typeof console.log != 'undefined') console.log("Requested JS file '%s' from server", res.data); var self = this; //FF, Opera, Chrome scriptnode.onload = function(e) { var file = e.target._originalSrc; if (typeof console != 'undefined' && typeof console.log != 'undefined') console.log("Retrieved JS file '%s' from server", file); self.loadedJSFiles[file] = true; self.checkLoadFinish(); }; //IE if (typeof scriptnode.readyState != 'undefined') { if (scriptnode.readyState != 'complete' && scriptnode.readyState != 'loaded') { scriptnode.onreadystatechange = function() { var node = window.event.srcElement; if (node.readyState == 'complete' || node.readyState == 'loaded') { var file = node._originalSrc; if (typeof console != 'undefined' && typeof console.log != 'undefined') console.log("Retrieved JS file '%s' from server", [file]); self.loadedJSFiles[file] = true; self.checkLoadFinish(); } }; } else { this.loadedJSFiles[res.data] = true; } } } hasResponse = true; } else throw 'Invalid parameters'; break; case 'error': if (typeof res.data == 'string') { this.jsonError(res.data); hasResponse = true; } else throw 'Invalid parameters'; break; } //Try to handle the json response with all registered plugins hasResponse |= _egw_json_plugin_handle(data.response[i].type, res, this.context); } catch(e) { this.jsonError('Internal JSON handler error', e); } } /* If no explicit response has been specified, call the callback (if one was set) */ if (!hasResponse && this.callback && data.response[i]) { this.callback.call(this.sender, data.response[i].data); } this.handleResponseDone = true; this.checkLoadFinish(); } } /** * The "onLoadFinish" handler gets called after all JS-files have been loaded * successfully */ egw_json_request.prototype.checkLoadFinish = function() { var complete = true; for (var key in this.loadedJSFiles) { complete = complete && this.loadedJSFiles[key]; } if (complete && this.onLoadFinish && this.handleResponseDone) { this.onLoadFinish.call(this.sender); } } function egw_json_getFormValues(_form, _filterClass) { var elem = null; if (typeof _form == 'object') { elem = _form; } else { elem = document.getElementsByName(_form)[0]; } var serialized = new Object; if (typeof elem != "undefined" && elem && elem.childNodes) { if (typeof _filterClass == 'undefined') _filterClass = null; _egw_json_getFormValues(serialized, elem.childNodes, _filterClass) } return serialized; } /** * Deprecated legacy xajax wrapper functions for the new egw_json interface */ _xajax_doXMLHTTP = function(_async, _menuaction, _arguments) { /* Assemble the parameter array */ var paramarray = new Array(); for (var i = 1; i < _arguments.length; i++) { paramarray[paramarray.length] = _arguments[i]; } /* Create a new request, passing the menuaction and the parameter array */ var request = new egw_json_request(_menuaction, paramarray); /* Send the request */ request.sendRequest(_async); return request; } xajax_doXMLHTTP = function(_menuaction) { return _xajax_doXMLHTTP(true, _menuaction, arguments); } xajax_doXMLHTTPsync = function(_menuaction) { return _xajax_doXMLHTTP(false, _menuaction, arguments); }; window.xajax = { "getFormValues": function(_form) { return egw_json_getFormValues(_form); } }; /* The following code is adapted from the xajax project which is licensed under the following license @copyright Copyright (c) 2005-2007 by Jared White & J. Max Wilson @copyright Copyright (c) 2008-2009 by Joseph Woolley, Steffen Konerow, Jared White & J. Max Wilson @license http://www.xajaxproject.org/bsd_license.txt BSD License */ /** * used internally by the legacy "egw_json_response.getFormValues" to recursively * run over all form elements * @param serialized is the object which will contain the form data * @param children is the children node of the form we're runing over * @param string _filterClass if given only return */ function _egw_json_getFormValues(serialized, children, _filterClass) { //alert('_egw_json_getFormValues(,,'+_filterClass+')'); for (var i = 0; i < children.length; ++i) { var child = children[i]; if (typeof child.childNodes != "undefined") _egw_json_getFormValues(serialized, child.childNodes, _filterClass); if ((!_filterClass || $j(child).hasClass(_filterClass)) && typeof child.name != "undefined") { //alert('_egw_json_getFormValues(,,'+_filterClass+') calling _egw_json_getFormValue for name='+child.name+', class='+child.class+', value='+child.value); _egw_json_getFormValue(serialized, child); } } } function _egw_json_getObjectLength(_obj) { var res = 0; for (key in _obj) { if (_obj.hasOwnProperty(key)) res++; } return res; } /** * used internally to serialize */ function _egw_json_getFormValue(serialized, child) { //Return if the child doesn't have a name, is disabled, or is a radio-/checkbox and not checked if ((typeof child.name == "undefined") || (child.disabled && child.disabled == true) || (child.type && (child.type == 'radio' || child.type == 'checkbox' || child.type == 'button' || child.type == 'submit') && (!child.checked))) { return; } var name = child.name; var values = null; if ('select-multiple' == child.type) { values = new Array; for (var j = 0; j < child.length; ++j) { var option = child.options[j]; if (option.selected == true) values.push(option.value); } } else { values = child.value; } //Special treatment if the name of the child contains a [] - then all theese //values are added to an array. var keyBegin = name.indexOf('['); if (0 <= keyBegin) { var n = name; var k = n.substr(0, n.indexOf('[')); var a = n.substr(n.indexOf('[')); if (typeof serialized[k] == 'undefined') serialized[k] = new Object; var p = serialized; // pointer reset while (a.length != 0) { var sa = a.substr(0, a.indexOf(']')+1); var lk = k; //save last key var lp = p; //save last pointer a = a.substr(a.indexOf(']')+1); p = p[k]; k = sa.substr(1, sa.length-2); if (k == '') { if ('select-multiple' == child.type) { k = lk; //restore last key p = lp; } else { k = _egw_json_getObjectLength(p); } } if (typeof p[k] == 'undefined') { p[k] = new Object; } } p[k] = values; } else { //Add the value to the result object with the given name if (typeof values != "undefined") { serialized[name] = values; } } }