1 /* 2 Copyright 2008,2009 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software: you can redistribute it and/or modify 13 it under the terms of the GNU Lesser General Public License as published by 14 the Free Software Foundation, either version 3 of the License, or 15 (at your option) any later version. 16 17 JSXGraph is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 GNU Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public License 23 along with JSXGraph. If not, see <http://www.gnu.org/licenses/>. 24 */ 25 26 /** 27 * @fileoverview The JXG.Server is a wrapper for a smoother integration of server side calculations. on the 28 * server side a python plugin system is used. 29 */ 30 31 /** 32 * @namespace 33 * JXG.Server namespace holding functions to load JXG server modules. 34 */ 35 JXG.Server = function(){}; 36 37 /** 38 * This is where all of a module's handlers are accessed from. If you're loading a module named JXGModule which 39 * provides a handler called ImaHandler, then this handler can be called by invoking JXG.Server.modules.JXGModule.ImaHandler(). 40 * @namespace 41 */ 42 JXG.Server.modules = function(){}; 43 44 /** 45 * Stores all asynchronous calls to server which aren't finished yet. 46 * @private 47 */ 48 JXG.Server.runningCalls = {}; 49 50 /** 51 * Handles errors, just a default implementation, can be overwritten by you, if you want to handle errors by yourself. 52 * @param {object} data An object holding a field of type string named message handling the error described in the message string. 53 */ 54 JXG.Server.handleError = function(data) { 55 alert('error occured, server says: ' + data.message); 56 }; 57 58 /** 59 * The main method of JXG.Server. Actually makes the calls to the server and parses the feedback. 60 * @param {string} action Can be 'load' or 'exec'. 61 * @param {function} callback Function pointer or anonymous function which takes as it's only argument an 62 * object containing the data from the server. The fields of this object depend on the reply of the server 63 * module. See the correspondings server module readme. 64 * @param {object} data What is to be sent to the server. 65 * @param {boolean} sync If the call should be synchronous or not. 66 */ 67 JXG.Server.callServer = function(action, callback, data, sync) { 68 var fileurl, passdata, AJAX, 69 params, id, dataJSONStr, 70 k; 71 72 sync = sync || false; 73 74 params = ''; 75 for(k in data) { 76 params += '&' + escape(k) + '=' + escape(data[k]); 77 } 78 79 dataJSONStr = JXG.toJSON(data); 80 81 // generate id 82 do { 83 id = action + Math.floor(Math.random()*4096); 84 } while(typeof this.runningCalls[id] != 'undefined'); 85 86 // store information about the calls 87 this.runningCalls[id] = {action: action}; 88 if(typeof data.module != 'undefined') 89 this.runningCalls[id].module = data.module; 90 91 fileurl = JXG.serverBase + 'JXGServer.py'; 92 passdata = 'action=' + escape(action) + '&id=' + id + '&dataJSON=' + escape(JXG.Util.Base64.encode(dataJSONStr)); 93 94 this.cbp = function(d) { 95 var str, data, 96 tmp, inject, paramlist, id, 97 i, j; 98 99 str = (new JXG.Util.Unzip(JXG.Util.Base64.decodeAsArray(d))).unzip(); 100 if(JXG.isArray(str) && str.length > 0) 101 str = str[0][0]; 102 103 if(typeof str != 'string') 104 return; 105 106 data = window.JSON && window.JSON.parse ? window.JSON.parse(str) : (new Function('return ' + str))(); 107 //data = eval("(" + str + ")"); 108 109 if(data.type == 'error') { 110 this.handleError(data); 111 } else if (data.type == 'response') { 112 id = data.id; 113 114 // inject fields 115 for(i=0; i<data.fields.length; i++) { 116 tmp = data.fields[i]; 117 //inject = tmp.namespace + ( typeof eval(tmp.namespace) == 'object' ? '.' : '.prototype.') + tmp.name + ' = ' + tmp.value; 118 inject = tmp.namespace + ( typeof ((new Function('return ' + tmp.namespace))()) == 'object' ? '.' : '.prototype.') + tmp.name + ' = ' + tmp.value; 119 //eval(inject); 120 (new Function(inject))(); 121 } 122 123 // inject handlers 124 for(i=0; i<data.handler.length; i++) { 125 tmp = data.handler[i]; 126 paramlist = []; 127 128 for(j=0; j<tmp.parameters.length; j++) { 129 paramlist[j] = '"' + tmp.parameters[j] + '": ' + tmp.parameters[j]; 130 } 131 // insert subnamespace named after module. 132 inject = 'if(typeof JXG.Server.modules.' + this.runningCalls[id].module + ' == "undefined")' + 133 'JXG.Server.modules.' + this.runningCalls[id].module + ' = {};'; 134 135 // insert callback method which fetches and uses the server's data for calculation in JavaScript 136 inject += 'JXG.Server.modules.' + this.runningCalls[id].module + '.' + tmp.name + '_cb = ' + tmp.callback + ';'; 137 138 // insert handler as JXG.Server.modules.<module name>.<handler name> 139 inject += 'JXG.Server.modules.' + this.runningCalls[id].module + '.' + tmp.name + ' = function (' + tmp.parameters.join(',') + ', __JXGSERVER_CB__, __JXGSERVER_SYNC) {' + 140 'if(typeof __JXGSERVER_CB__ == "undefined") __JXGSERVER_CB__ = JXG.Server.modules.' + this.runningCalls[id].module + '.' + tmp.name + '_cb;' + 141 'var __JXGSERVER_PAR__ = {' + paramlist.join(',') + ', "module": "' + this.runningCalls[id].module + '", "handler": "' + tmp.name + '" };' + 142 'JXG.Server.callServer("exec", __JXGSERVER_CB__, __JXGSERVER_PAR__, __JXGSERVER_SYNC);' + 143 '};'; 144 //eval(inject); 145 (new Function(inject))(); 146 } 147 148 delete this.runningCalls[id]; 149 150 // handle data 151 callback(data.data); 152 } 153 }; 154 155 // bind cbp callback method to JXG.Server to get access to JXG.Server fields from within cpb 156 this.cb = JXG.bind(this.cbp, this); 157 158 // we're using our own XMLHttpRequest object in here because of a/sync and POST 159 if (window.XMLHttpRequest) { 160 AJAX = new XMLHttpRequest(); 161 AJAX.overrideMimeType('text/plain; charset=iso-8859-1'); 162 } else { 163 AJAX = new ActiveXObject("Microsoft.XMLHTTP"); 164 } 165 if (AJAX) { 166 // POST is required if data sent to server is too long for a url. 167 // some browsers/http servers don't accept long urls. 168 AJAX.open("POST", fileurl, !sync); 169 AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); 170 171 if(!sync) { 172 // Define function to fetch data received from server 173 // that function returning a function is required to make this.cb known to the function. 174 AJAX.onreadystatechange = (function(cb){ return function () { 175 switch(AJAX.readyState) { 176 // server is ready for take-off 177 case 4: 178 if(AJAX.status != 200) { } 179 //alert("Fehler:" + AJAX.status); 180 else { // grab it and call the server callback to debase64, unzip, and parse the data 181 cb(AJAX.responseText); 182 } 183 break; 184 default: 185 return false; 186 break; 187 } 188 }})(this.cb); 189 } 190 191 // send the data 192 AJAX.send(passdata); 193 if(sync) 194 this.cb(AJAX.responseText); 195 } else { 196 return false; 197 } 198 199 // JXG.FileReader.parseFileContent(fileurl, this.cb, 'raw', !sync); 200 }; 201 202 /** 203 * Callback for the default action 'load'. 204 */ 205 JXG.Server.loadModule_cb = function(data) { 206 var i; 207 for(i=0; i<data.length; i++) 208 alert(data[i].name + ': ' + data[i].value); 209 }; 210 211 /** 212 * Loads a module from the server. 213 * @param {string} module A string containing the module. Has to match the filename of the Python module on the server exactly including 214 * lower and upper case letters without the file ending .py. 215 */ 216 JXG.Server.loadModule = function(module) { 217 return JXG.Server.callServer('load', JXG.Server.loadModule_cb, {'module': module}, true); 218 }; 219 220 JXG.Server.load = JXG.Server.loadModule; 221 222