      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 });