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