1 /*
  2     Copyright 2008-2022
  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, document:true, jQuery:true, define: true, window: true*/
 34 /*jslint nomen: true, plusplus: true*/
 35 
 36 /* depends:
 37  jxg
 38  utils/env
 39  utils/type
 40  base/board
 41  reader/file
 42  options
 43  renderer/svg
 44  renderer/vml
 45  renderer/canvas
 46  renderer/no
 47  */
 48 
 49 /**
 50  * @fileoverview The JSXGraph object is defined in this file. JXG.JSXGraph controls all boards.
 51  * It has methods to create, save, load and free boards. Additionally some helper functions are
 52  * defined in this file directly in the JXG namespace.
 53  * @version 0.99
 54  */
 55 
 56 define([
 57     'jxg', 'utils/env', 'utils/type', 'base/board', 'reader/file', 'options',
 58     'renderer/svg', 'renderer/vml', 'renderer/canvas', 'renderer/no'
 59 ], function (JXG, Env, Type, Board, FileReader, Options, SVGRenderer, VMLRenderer, CanvasRenderer, NoRenderer) {
 60 
 61     "use strict";
 62 
 63     /**
 64      * Constructs a new JSXGraph singleton object.
 65      * @class The JXG.JSXGraph singleton stores all properties required
 66      * to load, save, create and free a board.
 67      */
 68     JXG.JSXGraph = {
 69         /**
 70          * Stores the renderer that is used to draw the boards.
 71          * @type String
 72          */
 73         rendererType: (function () {
 74             Options.board.renderer = 'no';
 75 
 76             if (Env.supportsVML()) {
 77                 Options.board.renderer = 'vml';
 78                 // Ok, this is some real magic going on here. IE/VML always was so
 79                 // terribly slow, except in one place: Examples placed in a moodle course
 80                 // was almost as fast as in other browsers. So i grabbed all the css and
 81                 // lib scripts from our moodle, added them to a jsxgraph example and it
 82                 // worked. next step was to strip all the css/lib code which didn't affect
 83                 // the VML update speed. The following five lines are what was left after
 84                 // the last step and yes - it basically does nothing but reads two
 85                 // properties of document.body on every mouse move. why? we don't know. if
 86                 // you know, please let us know.
 87                 //
 88                 // If we want to use the strict mode we have to refactor this a little bit. Let's
 89                 // hope the magic isn't gone now. Anywho... it's only useful in old versions of IE
 90                 // which should not be used anymore.
 91                 document.onmousemove = function () {
 92                     var t;
 93 
 94                     if (document.body) {
 95                         t = document.body.scrollLeft;
 96                         t += document.body.scrollTop;
 97                     }
 98 
 99                     return t;
100                 };
101             }
102 
103             if (Env.supportsCanvas()) {
104                 Options.board.renderer = 'canvas';
105             }
106 
107             if (Env.supportsSVG()) {
108                 Options.board.renderer = 'svg';
109             }
110 
111             // we are inside node
112             if (Env.isNode() && Env.supportsCanvas()) {
113                 Options.board.renderer = 'canvas';
114             }
115 
116             if (Env.isNode() || Options.renderer === 'no') {
117                 Options.text.display = 'internal';
118                 Options.infobox.display = 'internal';
119             }
120 
121             return Options.board.renderer;
122         }()),
123 
124         /**
125          * Initialize the rendering engine
126          *
127          * @param  {String} box        HTML id of the div-element which hosts the JSXGraph construction
128          * @param  {Object} dim        The dimensions of the board
129          * @param  {Object} doc        Usually, this is document object of the browser window.  If false or null, this defaults
130          * to the document object of the browser.
131          * @param  {Object} attrRenderer Attribute 'renderer', speficies the rendering engine. Possible values are 'auto', 'svg',
132          *  'canvas', 'no', and 'vml'.
133          * @returns {Object}           Reference to the rendering engine object.
134          * @private
135          */
136         initRenderer: function (box, dim, doc, attrRenderer) {
137             var boxid, renderer;
138 
139             // Former version:
140             // doc = doc || document
141             if ((!Type.exists(doc) || doc === false) && typeof document === 'object') {
142                 doc = document;
143             }
144 
145             if (typeof doc === 'object' && box !== null) {
146                 boxid = doc.getElementById(box);
147 
148                 // Remove everything from the container before initializing the renderer and the board
149                 while (boxid.firstChild) {
150                     boxid.removeChild(boxid.firstChild);
151                 }
152             } else {
153                 boxid = box;
154             }
155 
156             // If attrRenderer is not supplied take the first available renderer
157             if (attrRenderer === undefined || attrRenderer === 'auto') {
158                 attrRenderer = this.rendererType;
159             }
160             // create the renderer
161             if (attrRenderer === 'svg') {
162                 renderer = new SVGRenderer(boxid, dim);
163             } else if (attrRenderer === 'vml') {
164                 renderer = new VMLRenderer(boxid);
165             } else if (attrRenderer === 'canvas') {
166                 renderer = new CanvasRenderer(boxid, dim);
167             } else {
168                 renderer = new NoRenderer();
169             }
170 
171             return renderer;
172         },
173 
174         /**
175          * Merge the user supplied attributes with the attributes in options.js
176          *
177          * @param {Object} attributes User supplied attributes
178          * @returns {Object} Merged attributes for the board
179          *
180          * @private
181          */
182         _setAttributes: function(attributes) {
183             // merge attributes
184             var attr = Type.copyAttributes(attributes, Options, 'board');
185 
186             // The attributes which are objects have to be copied separately
187             attr.zoom = Type.copyAttributes(attr, Options, 'board', 'zoom');
188             attr.pan = Type.copyAttributes(attr, Options, 'board', 'pan');
189             attr.drag = Type.copyAttributes(attr, Options, 'board', 'drag');
190             attr.keyboard = Type.copyAttributes(attr, Options, 'board', 'keyboard');
191             attr.selection = Type.copyAttributes(attr, Options, 'board', 'selection');
192             attr.navbar = Type.copyAttributes(attr.navbar, Options, 'navbar');
193             attr.screenshot = Type.copyAttributes(attr, Options, 'board', 'screenshot');
194             attr.resize = Type.copyAttributes(attr, Options, 'board', 'resize');
195             attr.fullscreen = Type.copyAttributes(attr, Options, 'board', 'fullscreen');
196 
197             // Treat moveTarget separately, because deepCopy will not work here.
198             // Reason: moveTarget will be an HTML node and it is prevented that Type.deepCopy will copy it.
199             attr.movetarget = attributes.moveTarget || attributes.movetarget || Options.board.moveTarget;
200 
201             return attr;
202         },
203 
204         /**
205          * Further initialization of the board. Set some properties from attribute values.
206          *
207          * @param {JXG.Board} board
208          * @param {Object} attr attributes object
209          * @param {Object} dimensions Object containing dimensions of the canvas
210          *
211          * @private
212          */
213         _fillBoard: function(board, attr, dimensions) {
214             board.initInfobox();
215             board.maxboundingbox = attr.maxboundingbox;
216             board.resizeContainer(dimensions.width, dimensions.height, true, true);
217             board._createSelectionPolygon(attr);
218             board.renderer.drawZoomBar(board, attr.navbar);
219             JXG.boards[board.id] = board;
220         },
221 
222         /**
223          *
224          * @param {String} container HTML-ID to the HTML-element in which the board is painted.
225          * @param {*} attr An object that sets some of the board properties.
226          *
227          * @private
228          */
229         _setARIA: function(container, attr) {
230             var doc = attr.document || document,
231                 doc_glob,
232                 node_jsx, newNode, parent,
233                 id_label, id_description;
234 
235             if (typeof doc !== 'object') {
236                 return;
237             }
238 
239             node_jsx = doc.getElementById(container);
240             doc_glob = node_jsx.ownerDocument;   // This is the window.document element, needed below.
241             parent = node_jsx.parentNode;
242 
243             id_label = container + '_ARIAlabel';
244             id_description = container + '_ARIAdescription';
245 
246             newNode = doc_glob.createElement('div');
247             newNode.innerHTML = attr.title;
248             newNode.setAttribute('id', id_label);
249             newNode.style.display = 'none';
250             parent.insertBefore(newNode, node_jsx);
251 
252             newNode = doc_glob.createElement('div');
253             newNode.innerHTML = attr.description;
254             newNode.setAttribute('id', id_description);
255             newNode.style.display = 'none';
256             parent.insertBefore(newNode, node_jsx);
257 
258             node_jsx.setAttribute('aria-labelledby', id_label);
259             node_jsx.setAttribute('aria-describedby', id_description);
260         },
261 
262         /**
263          * Remove the two corresponding ARIA divs when freeing a board
264          *
265          * @param {JXG.Board} board
266          *
267          * @private
268          */
269         _removeARIANodes: function(board) {
270             var node, id, doc;
271 
272             doc = board.document || document;
273             if (typeof doc !== 'object') {
274                 return;
275             }
276 
277             id = board.containerObj.getAttribute('aria-labelledby');
278             node = doc.getElementById(id);
279             if (node && node.parentNode) {
280                 node.parentNode.removeChild(node);
281             }
282             id = board.containerObj.getAttribute('aria-describedby');
283             node = doc.getElementById(id);
284             if (node && node.parentNode) {
285                 node.parentNode.removeChild(node);
286             }
287         },
288 
289         /**
290          * Initialise a new board.
291          * @param {String} box HTML-ID to the HTML-element in which the board is painted.
292          * @param {Object} attributes An object that sets some of the board properties. Most of these properties can be set via JXG.Options.
293          * @param {Array} [attributes.boundingbox=[-5, 5, 5, -5]] An array containing four numbers describing the left, top, right and bottom boundary of the board in user coordinates
294          * @param {Boolean} [attributes.keepaspectratio=false] If <tt>true</tt>, the bounding box is adjusted to the same aspect ratio as the aspect ratio of the div containing the board.
295          * @param {Boolean} [attributes.showCopyright=false] Show the copyright string in the top left corner.
296          * @param {Boolean} [attributes.showNavigation=false] Show the navigation buttons in the bottom right corner.
297          * @param {Object} [attributes.zoom] Allow the user to zoom with the mouse wheel or the two-fingers-zoom gesture.
298          * @param {Object} [attributes.pan] Allow the user to pan with shift+drag mouse or two-fingers-pan gesture.
299          * @param {Object} [attributes.drag] Allow the user to drag objects with a pointer device.
300          * @param {Object} [attributes.keyboard] Allow the user to drag objects with arrow keys on keyboard.
301          * @param {Boolean} [attributes.axis=false] If set to true, show the axis. Can also be set to an object that is given to both axes as an attribute object.
302          * @param {Boolean|Object} [attributes.grid] If set to true, shows the grid. Can also be set to an object that is given to the grid as its attribute object.
303          * @param {Boolean} [attributes.registerEvents=true] Register mouse / touch events.
304          * @returns {JXG.Board} Reference to the created board.
305          *
306          * @see JXG.AbstractRenderer#drawZoomBar
307          */
308         initBoard: function (box, attributes) {
309             var originX, originY, unitX, unitY,
310                 renderer,
311                 offX = 0,
312                 offY = 0,
313                 w, h, dimensions,
314                 bbox, attr, axattr, axattr_x, axattr_y,
315                 board;
316 
317             attributes = attributes || {};
318             attr = this._setAttributes(attributes);
319 
320             dimensions = Env.getDimensions(box, attr.document);
321 
322             if (attr.unitx || attr.unity) {
323                 originX = Type.def(attr.originx, 150);
324                 originY = Type.def(attr.originy, 150);
325                 unitX = Type.def(attr.unitx, 50);
326                 unitY = Type.def(attr.unity, 50);
327             } else {
328                 bbox = attr.boundingbox;
329                 if (bbox[0] < attr.maxboundingbox[0]) { bbox[0] = attr.maxboundingbox[0]; }
330                 if (bbox[1] > attr.maxboundingbox[1]) { bbox[1] = attr.maxboundingbox[1]; }
331                 if (bbox[2] > attr.maxboundingbox[2]) { bbox[2] = attr.maxboundingbox[2]; }
332                 if (bbox[3] < attr.maxboundingbox[3]) { bbox[3] = attr.maxboundingbox[3]; }
333 
334                 w = parseInt(dimensions.width, 10);
335                 h = parseInt(dimensions.height, 10);
336 
337                 if (Type.exists(bbox) && attr.keepaspectratio) {
338                     /*
339                      * If the boundingbox attribute is given and the ratio of height and width of the
340                      * sides defined by the bounding box and the ratio of the dimensions of the div tag
341                      * which contains the board do not coincide, then the smaller side is chosen.
342                      */
343                     unitX = w / (bbox[2] - bbox[0]);
344                     unitY = h / (bbox[1] - bbox[3]);
345 
346                     if (Math.abs(unitX) < Math.abs(unitY)) {
347                         unitY = Math.abs(unitX) * unitY / Math.abs(unitY);
348                         // Add the additional units in equal portions above and below
349                         offY = (h / unitY - (bbox[1] - bbox[3])) * 0.5;
350                     } else {
351                         unitX = Math.abs(unitY) * unitX / Math.abs(unitX);
352                         // Add the additional units in equal portions left and right
353                         offX = (w / unitX - (bbox[2] - bbox[0])) * 0.5;
354                     }
355                 } else {
356                     unitX = w / (bbox[2] - bbox[0]);
357                     unitY = h / (bbox[1] - bbox[3]);
358                 }
359                 originX = -unitX * (bbox[0] - offX);
360                 originY = unitY * (bbox[1] + offY);
361             }
362 
363             renderer = this.initRenderer(box, dimensions, attr.document, attr.renderer);
364             this._setARIA(box, attr);
365 
366             // create the board
367             board = new Board(box, renderer, attr.id, [originX, originY],
368                         attr.zoomfactor * attr.zoomx,
369                         attr.zoomfactor * attr.zoomy,
370                         unitX, unitY,
371                         dimensions.width, dimensions.height,
372                         attr);
373 
374             board.keepaspectratio = attr.keepaspectratio;
375 
376             this._fillBoard(board, attr, dimensions);
377 
378             // create elements like axes, grid, navigation, ...
379             board.suspendUpdate();
380             if (attr.axis) {
381                 axattr = typeof attr.axis === 'object' ? attr.axis : {};
382 
383                 // The defaultAxes attributes are overwritten by user supplied axis object.
384                 axattr_x = Type.deepCopy(Options.board.defaultAxes.x, axattr);
385                 axattr_y = Type.deepCopy(Options.board.defaultAxes.y, axattr);
386                 // The user supplied defaultAxes attributes are merged in.
387                 if (attr.defaultaxes.x) {
388                     axattr_x = Type.deepCopy(axattr_x, attr.defaultaxes.x);
389                 }
390                 if (attr.defaultaxes.y) {
391                     axattr_y = Type.deepCopy(axattr_y, attr.defaultaxes.y);
392                 }
393 
394                 board.defaultAxes = {};
395                 board.defaultAxes.x = board.create('axis', [[0, 0], [1, 0]], axattr_x);
396                 board.defaultAxes.y = board.create('axis', [[0, 0], [0, 1]], axattr_y);
397             }
398             if (attr.grid) {
399                 board.create('grid', [], (typeof attr.grid === 'object' ? attr.grid : {}));
400             }
401             board.unsuspendUpdate();
402 
403             return board;
404         },
405 
406         /**
407          * Load a board from a file containing a construction made with either GEONExT,
408          * Intergeo, Geogebra, or Cinderella.
409          * @param {String} box HTML-ID to the HTML-element in which the board is painted.
410          * @param {String} file base64 encoded string.
411          * @param {String} format containing the file format: 'Geonext' or 'Intergeo'.
412          * @param {Object} attributes Attributes for the board and 'encoding'.
413          *  Compressed files need encoding 'iso-8859-1'. Otherwise it probably is 'utf-8'.
414          * @param {Function} callback
415          * @returns {JXG.Board} Reference to the created board.
416          * @see JXG.FileReader
417          * @see JXG.GeonextReader
418          * @see JXG.GeogebraReader
419          * @see JXG.IntergeoReader
420          * @see JXG.CinderellaReader
421          *
422          * @example
423          * // Uncompressed file
424          * var board = JXG.JSXGraph.loadBoardFromFile('jxgbox', 'filename', 'geonext',
425          *      {encoding: 'utf-8'},
426          *      function (board) { console.log("Done loading"); }
427          * );
428          * // Compressed file
429          * var board = JXG.JSXGraph.loadBoardFromFile('jxgbox', 'filename', 'geonext',
430          *      {encoding: 'iso-8859-1'},
431          *      function (board) { console.log("Done loading"); }
432          * );
433          *
434          * @example
435          * // From <input type="file" id="localfile" />
436          * var file = document.getElementById('localfile').files[0];
437          * JXG.JSXGraph.loadBoardFromFile('jxgbox', file, 'geonext',
438          *      {encoding: 'utf-8'},
439          *      function (board) { console.log("Done loading"); }
440          * );
441          */
442         loadBoardFromFile: function (box, file, format, attributes, callback) {
443             var attr, renderer, board, dimensions, encoding;
444 
445             attributes = attributes || {};
446             attr = this._setAttributes(attributes);
447 
448             dimensions = Env.getDimensions(box, attr.document);
449             renderer = this.initRenderer(box, dimensions, attr.document, attr.renderer);
450             this._setARIA(box, attr);
451 
452             /* User default parameters, in parse* the values in the gxt files are submitted to board */
453             board = new Board(box, renderer, '', [150, 150], 1, 1, 50, 50, dimensions.width, dimensions.height, attr);
454             this._fillBoard(board, attr, dimensions);
455             encoding = attr.encoding || 'iso-8859-1';
456             FileReader.parseFileContent(file, board, format, true, encoding, callback);
457 
458             return board;
459         },
460 
461         /**
462          * Load a board from a base64 encoded string containing a construction made with either GEONExT,
463          * Intergeo, Geogebra, or Cinderella.
464          * @param {String} box HTML-ID to the HTML-element in which the board is painted.
465          * @param {String} string base64 encoded string.
466          * @param {String} format containing the file format: 'Geonext', 'Intergeo', 'Geogebra'.
467          * @param {Object} attributes Attributes for the board and 'encoding'.
468          *  Compressed files need encoding 'iso-8859-1'. Otherwise it probably is 'utf-8'.
469          * @param {Function} callback
470          * @returns {JXG.Board} Reference to the created board.
471          * @see JXG.FileReader
472          * @see JXG.GeonextReader
473          * @see JXG.GeogebraReader
474          * @see JXG.IntergeoReader
475          * @see JXG.CinderellaReader
476          */
477         loadBoardFromString: function (box, string, format, attributes, callback) {
478             var attr, renderer, board, dimensions;
479 
480             attributes = attributes || {};
481             attr = this._setAttributes(attributes);
482 
483             dimensions = Env.getDimensions(box, attr.document);
484             renderer = this.initRenderer(box, dimensions, attr.document);
485             this._setARIA(box, attr);
486 
487             /* User default parameters, in parse* the values in the gxt files are submitted to board */
488             board = new Board(box, renderer, '', [150, 150], 1.0, 1.0, 50, 50, dimensions.width, dimensions.height, attr);
489             this._fillBoard(board, attr, dimensions);
490             FileReader.parseString(string, board, format, true, callback);
491 
492             return board;
493         },
494 
495         /**
496          * Delete a board and all its contents.
497          * @param {JXG.Board,String} board HTML-ID to the DOM-element in which the board is drawn.
498          */
499         freeBoard: function (board) {
500             var el;
501 
502             if (typeof board === 'string') {
503                 board = JXG.boards[board];
504             }
505 
506             this._removeARIANodes(board);
507             board.removeEventHandlers();
508             board.suspendUpdate();
509 
510             // Remove all objects from the board.
511             for (el in board.objects) {
512                 if (board.objects.hasOwnProperty(el)) {
513                     board.objects[el].remove();
514                 }
515             }
516 
517             // Remove all the other things, left on the board, XHTML save
518             while (board.containerObj.firstChild) {
519                 board.containerObj.removeChild(board.containerObj.firstChild);
520             }
521 
522             // Tell the browser the objects aren't needed anymore
523             for (el in board.objects) {
524                 if (board.objects.hasOwnProperty(el)) {
525                     delete board.objects[el];
526                 }
527             }
528 
529             // Free the renderer and the algebra object
530             delete board.renderer;
531 
532             // clear the creator cache
533             board.jc.creator.clearCache();
534             delete board.jc;
535 
536             // Finally remove the board itself from the boards array
537             delete JXG.boards[board.id];
538         },
539 
540         /**
541          * @deprecated Use JXG#registerElement
542          * @param element
543          * @param creator
544          */
545         registerElement: function (element, creator) {
546             JXG.deprecated('JXG.JSXGraph.registerElement()', 'JXG.registerElement()');
547             JXG.registerElement(element, creator);
548         }
549     };
550 
551     // JessieScript/JessieCode startup: 
552     // Search for script tags of type text/jessiescript and interprete them.
553     if (Env.isBrowser && typeof window === 'object' && typeof document === 'object') {
554         Env.addEvent(window, 'load', function () {
555             var type, i, j, div,
556                 id, board, txt,
557                 width, height, maxWidth, aspectRatio, cssClasses,
558                 bbox, axis, grid, code,
559                 src, request, postpone = false,
560                 scripts = document.getElementsByTagName('script'),
561                 init = function (code, type, bbox) {
562                     var board = JXG.JSXGraph.initBoard(id, {boundingbox: bbox, keepaspectratio: true, grid: grid, axis: axis, showReload: true});
563 
564                     if (type.toLowerCase().indexOf('script') > -1) {
565                         board.construct(code);
566                     } else {
567                         try {
568                             board.jc.parse(code);
569                         } catch (e2) {
570                             JXG.debug(e2);
571                         }
572                     }
573 
574                     return board;
575                 },
576                 makeReload = function (board, code, type, bbox) {
577                     return function () {
578                         var newBoard;
579 
580                         JXG.JSXGraph.freeBoard(board);
581                         newBoard = init(code, type, bbox);
582                         newBoard.reload = makeReload(newBoard, code, type, bbox);
583                     };
584                 };
585 
586             for (i = 0; i < scripts.length; i++) {
587                 type = scripts[i].getAttribute('type', false);
588 
589                 if (Type.exists(type) &&
590                     (type.toLowerCase() === 'text/jessiescript' || type.toLowerCase() === 'jessiescript' ||
591                      type.toLowerCase() === 'text/jessiecode' || type.toLowerCase() === 'jessiecode')) {
592                     cssClasses = scripts[i].getAttribute('class', false) || '';
593                     width = scripts[i].getAttribute('width', false) || '';
594                     height = scripts[i].getAttribute('height', false) || '';
595                     maxWidth = scripts[i].getAttribute('maxwidth', false) || '100%';
596                     aspectRatio = scripts[i].getAttribute('aspectratio', false) || '1/1';
597                     bbox = scripts[i].getAttribute('boundingbox', false) || '-5, 5, 5, -5';
598                     id = scripts[i].getAttribute('container', false);
599                     src = scripts[i].getAttribute('src', false);
600 
601                     bbox = bbox.split(',');
602                     if (bbox.length !== 4) {
603                         bbox = [-5, 5, 5, -5];
604                     } else {
605                         for (j = 0; j < bbox.length; j++) {
606                             bbox[j] = parseFloat(bbox[j]);
607                         }
608                     }
609                     axis = Type.str2Bool(scripts[i].getAttribute('axis', false) || 'false');
610                     grid = Type.str2Bool(scripts[i].getAttribute('grid', false) || 'false');
611 
612                     if (!Type.exists(id)) {
613                         id = 'jessiescript_autgen_jxg_' + i;
614                         div = document.createElement('div');
615                         div.setAttribute('id', id);
616 
617                         txt = (width !== '') ? ('width:' + width + ';') : '';
618                         txt += (height !== '') ? ('height:' + height + ';') : '';
619                         txt += (maxWidth !== '') ? ('max-width:' + maxWidth + ';') : '';
620                         txt += (aspectRatio !== '') ? ('aspect-ratio:' + aspectRatio + ';') : '';
621 
622                         div.setAttribute('style', txt);
623                         div.setAttribute('class', 'jxgbox ' + cssClasses);
624                         try {
625                             document.body.insertBefore(div, scripts[i]);
626                         } catch (e) {
627                             // there's probably jquery involved...
628                             if (typeof jQuery === 'object') {
629                                 jQuery(div).insertBefore(scripts[i]);
630                             }
631                         }
632                     } else {
633                         div = document.getElementById(id);
634                     }
635 
636                     code = '';
637 
638                     if (Type.exists(src)) {
639                         postpone = true;
640                         request = new XMLHttpRequest();
641                         request.open("GET", src);
642                         request.overrideMimeType("text/plain; charset=x-user-defined");
643                         /* jshint ignore:start */
644                         request.addEventListener("load", function() {
645                             if (this.status < 400) {
646                                 code = this.responseText + '\n' + code;
647                                 board = init(code, type, bbox);
648                                 board.reload = makeReload(board, code, type, bbox);
649                             } else {
650                                 throw new Error("\nJSXGraph: failed to load file", src, ":", this.responseText);
651                             }
652                         });
653                         request.addEventListener("error", function(e) {
654                             throw new Error("\nJSXGraph: failed to load file", src, ":", e);
655                         });
656                         /* jshint ignore:end */
657                         request.send();
658                     } else {
659                         postpone = false;
660                     }
661 
662                     if (document.getElementById(id)) {
663                         code = scripts[i].innerHTML;
664                         code = code.replace(/<!\[CDATA\[/g, '').replace(/\]\]>/g, '');
665                         scripts[i].innerHTML = code;
666 
667                         if (!postpone) {
668                             // Do no wait for data from "src" attribute
669                             board = init(code, type, bbox);
670                             board.reload = makeReload(board, code, type, bbox);
671                         }
672                     } else {
673                         JXG.debug('JSXGraph: Apparently the div injection failed. Can\'t create a board, sorry.');
674                     }
675                 }
676             }
677         }, window);
678     }
679 
680     return JXG.JSXGraph;
681 });
682