1 /*
  2  Copyright 2008-2011
  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 JSXGraph object is defined in this file. JXG.JSXGraph controls all boards.
 28  * It has methods to create, save, load and free boards. Additionally some helper functions are
 29  * defined in this file directly in the JXG namespace.
 30  * @version 0.83
 31  */
 32 
 33 
 34 // We need the following two methods "extend" and "shortcut" to create the JXG object via JXG.extend.
 35 
 36 /**
 37  * Copy all properties of the <tt>extension</tt> object to <tt>object</tt>.
 38  * @param {Object} object
 39  * @param {Object} extension
 40  * @param {Boolean} [onlyOwn=false] Only consider properties that belong to extension itself, not any inherited properties.
 41  * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
 42  */
 43 JXG.extend = function (object, extension, onlyOwn, toLower) {
 44     var e, e2;
 45 
 46     onlyOwn = onlyOwn || false;
 47     toLower = toLower || false;
 48 
 49     for(e in extension) {
 50         if (!onlyOwn || (onlyOwn && extension.hasOwnProperty(e))) {
 51             if (toLower) {
 52                 e2 = e.toLowerCase();
 53             } else {
 54                 e2 = e;
 55             }
 56 
 57             object[e2] = extension[e];
 58         }
 59     }
 60 };
 61 
 62 /**
 63  * Creates a shortcut to a method, e.g. {@link JXG.Board#create} is a shortcut to {@link JXG.Board#createElement}.
 64  * Sometimes the target is undefined by the time you want to define the shortcut so we need this little helper.
 65  * @param {Object} object The object the method we want to create a shortcut for belongs to.
 66  * @param {Function} fun The method we want to create a shortcut for.
 67  * @returns {Function} A function that calls the given method.
 68  */
 69 JXG.shortcut = function (object, fun) {
 70     return function () {
 71         return object[fun].apply(this, arguments);
 72     };
 73 };
 74 
 75 
 76 JXG.extend(JXG, /** @lends JXG */ {
 77 
 78     /**
 79      * Determines the property that stores the relevant information in the event object.
 80      * @type {String}
 81      * @default 'touches'
 82      */
 83     touchProperty: 'touches',
 84 
 85 
 86     /**
 87      * Represents the currently used JSXGraph version.
 88      * @type {String}
 89      */
 90     version: '0.96.01',
 91 
 92     /**
 93      * Detect browser support for VML.
 94      * @returns {Boolean} True, if the browser supports VML.
 95      */
 96     supportsVML: function () {
 97         // From stackoverflow.com
 98         return typeof document != 'undefined' && !!document.namespaces;
 99     },
100 
101     /**
102      * Detect browser support for SVG.
103      * @returns {Boolean} True, if the browser supports SVG.
104      */
105     supportsSVG: function () {
106         return typeof document != 'undefined' && document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure", "1.1");
107     },
108 
109     /**
110      * Detect browser support for Canvas.
111      * @returns {Boolean} True, if the browser supports HTML canvas.
112      */
113     supportsCanvas: function () {
114         return typeof document != 'undefined' && !!document.createElement('canvas').getContext;
115     },
116 
117     isNode: function () {
118         // this is not a 100% sure but should be valid in most cases
119         return typeof document === 'undefined' && typeof window === 'undefined' && typeof module !== 'undefined' && module.exports;
120     },
121 
122     /**
123      * Determine if the current browser supports touch events
124      * @returns {Boolean} True, if the browser supports touch events.
125      */
126     isTouchDevice: function () {
127         return 'ontouchstart' in document.documentElement;
128     },
129 
130     /**
131      * Detects if the user is using an Android powered device.
132      * @returns {Boolean}
133      */
134     isAndroid: function () {
135         return JXG.exists(navigator) && navigator.userAgent.toLowerCase().search("android") > -1;
136     },
137 
138     /**
139      * Detects if the user is using the default Webkit browser on an Android powered device.
140      * @returns {Boolean}
141      */
142     isWebkitAndroid: function () {
143         return this.isAndroid() && navigator.userAgent.search(" AppleWebKit/") > -1;
144     },
145 
146     /**
147      * Detects if the user is using a Apple iPad / iPhone.
148      * @returns {Boolean}
149      */
150     isApple: function () {
151         return JXG.exists(navigator) && (navigator.userAgent.search(/iPad/) != -1 || navigator.userAgent.search(/iPhone/) != -1);
152     },
153 
154     /**
155      * Detects if the user is using Safari on an Apple device.
156      * @returns {Boolean}
157      */
158     isWebkitApple: function () {
159         return this.isApple() && (navigator.userAgent.search(/Mobile *.*Safari/) > -1);
160     },
161 
162     /**
163      * Returns true if the run inside a Windows 8 "Metro" App.
164      * @return {Boolean}
165      */
166     isMetroApp: function () {
167         return typeof window !== 'undefined' && window.clientInformation && window.clientInformation.appName && window.clientInformation.appName.indexOf('MSAppHost') > -1;
168     },
169 
170     /**
171      * Resets visPropOld of <tt>el</tt>
172      * @param {JXG.GeometryElement} el
173      */
174     clearVisPropOld: function (el) {
175         el.visPropOld = {
176             strokecolor: '',
177             strokeopacity: '',
178             strokewidth: '',
179             fillcolor: '',
180             fillopacity: '',
181             shadow: false,
182             firstarrow: false,
183             lastarrow: false,
184             cssclass: '',
185             fontsize: -1
186         };
187     },
188 
189     /**
190      * Internet Explorer version. Works only for IE > 4.
191      * @type Number
192      */
193     ieVersion: (function() {
194         var undef;
195 
196         if (typeof document == 'undefined') {
197             return undef;
198         }
199 
200         var v = 3,
201             div = document.createElement('div'),
202             all = div.getElementsByTagName('i');
203 
204         while (
205             div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i><' + '/i><![endif]-->',
206                 all[0]
207             );
208 
209         return v > 4 ? v : undef;
210 
211     }()),
212 
213     /**
214      * s may be a string containing the name or id of an element or even a reference
215      * to the element itself. This function returns a reference to the element. Search order: id, name.
216      * @param {JXG.Board} board Reference to the board the element belongs to.
217      * @param {String} s String or reference to a JSXGraph element.
218      * @returns {Object} Reference to the object given in parameter object
219      */
220     getReference: function (board, s) {
221         if (typeof(s) == 'string') {
222             if (JXG.exists(board.objects[s])) { // Search by ID
223                 s = board.objects[s];
224             } else if (JXG.exists(board.elementsByName[s])) { // Search by name
225                 s = board.elementsByName[s];
226             } else if (JXG.exists(board.groups[s])) { // Search by group ID 
227                 s = board.groups[s];
228             }
229         }
230 
231         return s;
232     },
233 
234     /**
235      * This is just a shortcut to {@link JXG.getReference}.
236      */
237     getRef: JXG.shortcut(JXG, 'getReference'),
238 
239     /**
240      * Checks if the given string is an id within the given board.
241      * @param {JXG.Board} board
242      * @param {String} s
243      * @returns {Boolean}
244      */
245     isId: function (board, s) {
246         return typeof(s) == 'string' && !!board.objects[s];
247     },
248 
249     /**
250      * Checks if the given string is a name within the given board.
251      * @param {JXG.Board} board
252      * @param {String} s
253      * @returns {Boolean}
254      */
255     isName: function (board, s) {
256         return typeof(s) == 'string' && !!board.elementsByName[s];
257     },
258 
259     /**
260      * Checks if the given string is a group id within the given board.
261      * @param {JXG.Board} board
262      * @param {String} s
263      * @returns {Boolean}
264      */
265     isGroup: function (board, s) {
266         return typeof(s) == 'string' && !!board.groups[s];
267     },
268 
269     /**
270      * Checks if the value of a given variable is of type string.
271      * @param v A variable of any type.
272      * @returns {Boolean} True, if v is of type string.
273      */
274     isString: function (v) {
275         return typeof v === "string";
276     },
277 
278     /**
279      * Checks if the value of a given variable is of type number.
280      * @param v A variable of any type.
281      * @returns {Boolean} True, if v is of type number.
282      */
283     isNumber: function (v) {
284         return typeof v === "number";
285     },
286 
287     /**
288      * Checks if a given variable references a function.
289      * @param v A variable of any type.
290      * @returns {Boolean} True, if v is a function.
291      */
292     isFunction: function (v) {
293         return typeof v === "function";
294     },
295 
296     /**
297      * Checks if a given variable references an array.
298      * @param v A variable of any type.
299      * @returns {Boolean} True, if v is of type array.
300      */
301     isArray: function (v) {
302         var r;
303 
304         // use the ES5 isArray() method and if that doesn't exist use a fallback.
305         if (Array.isArray) {
306             r = Array.isArray(v);
307         } else {
308             r = (v !== null && typeof v === "object" && 'splice' in v && 'join' in v);
309         }
310 
311         return r;
312     },
313 
314     /**
315      * Checks if a given variable is a reference of a JSXGraph Point element.
316      * @param v A variable of any type.
317      * @returns {Boolean} True, if v is of type JXG.Point.
318      */
319     isPoint: function (v) {
320         if (typeof v == 'object') {
321             return (v.elementClass == JXG.OBJECT_CLASS_POINT);
322         }
323 
324         return false;
325     },
326 
327     /**
328      * Checks if a given variable is neither undefined nor null. You should not use this together with global
329      * variables!
330      * @param v A variable of any type.
331      * @returns {Boolean} True, if v is neither undefined nor null.
332      */
333     exists: (function (undefined) {
334         return function (v) {
335             return !(v === undefined || v === null);
336         }
337     })(),
338 
339     /**
340      * Handle default parameters.
341      * @param {} v Given value
342      * @param {} d Default value
343      * @returns {} <tt>d</tt>, if <tt>v</tt> is undefined or null.
344      */
345     def: function (v, d) {
346         if (JXG.exists(v)) {
347             return v;
348         } else {
349             return d;
350         }
351     },
352 
353     /**
354      * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value.
355      * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>.
356      * @returns {Boolean} String typed boolean value converted to boolean.
357      */
358     str2Bool: function (s) {
359         if (!JXG.exists(s)) {
360             return true;
361         }
362         if (typeof s == 'boolean') {
363             return s;
364         }
365         //return (s.toLowerCase()=='true');
366 
367         if (JXG.isString(s)) {
368             return (s.toLowerCase()=='true');
369         } else {
370             return false;
371         }
372 
373     },
374 
375     /**
376      * Shortcut for {@link JXG.JSXGraph.initBoard}.
377      */
378     _board: function (box, attributes) {
379         return JXG.JSXGraph.initBoard(box, attributes);
380     },
381 
382     /**
383      * Convert a String, a number or a function into a function. This method is used in Transformation.js
384      * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
385      * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
386      * values is of type string.
387      * @param {Array} param An array containing strings, numbers, or functions.
388      * @param {Number} n Length of <tt>param</tt>.
389      * @returns {Function} A function taking one parameter k which specifies the index of the param element
390      * to evaluate.
391      */
392     createEvalFunction: function (board, param, n) {
393         // convert GEONExT syntax into function
394         var f = [], i, str;
395 
396         for (i=0;i<n;i++) {
397             if (typeof param[i] == 'string') {
398                 str = JXG.GeonextParser.geonext2JS(param[i],board);
399                 str = str.replace(/this\.board\./g,'board.');
400                 f[i] = new Function('','return ' + (str) + ';');
401             }
402         }
403 
404         return function (k) {
405             var a = param[k];
406             if (typeof a == 'string') {
407                 return f[k]();
408             } else if (typeof a=='function') {
409                 return a();
410             } else if (typeof a=='number') {
411                 return a;
412             }
413             return 0;
414         };
415     },
416 
417     /**
418      * Convert a String, number or function into a function.
419      * @param term A variable of type string, function or number.
420      * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
421      * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
422      * values is of type string.
423      * @param {String} variableName Only required if evalGeonext is set to true. Describes the variable name
424      * of the variable in a GEONE<sub>X</sub>T string given as term.
425      * @param {Boolean} evalGeonext Set this true, if term should be treated as a GEONE<sub>X</sub>T string.
426      * @returns {Function} A function evaluation the value given by term or null if term is not of type string,
427      * function or number.
428      */
429     createFunction: function (term, board, variableName, evalGeonext) {
430         var f = null;
431 
432         if ((!JXG.exists(evalGeonext) || evalGeonext) && JXG.isString(term)) {
433             // Convert GEONExT syntax into  JavaScript syntax
434             //newTerm = JXG.GeonextParser.geonext2JS(term, board);
435             //return new Function(variableName,'return ' + newTerm + ';');
436             f = board.jc.snippet(term, true, variableName, true);
437         } else if (JXG.isFunction(term)) {
438             f = term;
439         } else if (JXG.isNumber(term)) {
440             f = function () { return term; };
441         } else if (JXG.isString(term)) {        // In case of string function like fontsize
442             f = function () { return term; };
443         }
444 
445         if (f !== null) {
446             f.origin = term;
447         }
448 
449         return f;
450     },
451 
452     /**
453      * Checks given parents array against several expectations.
454      * @param {String} element The name of the element to be created
455      * @param {Array} parents A parents array
456      * @param {Array} expects Each possible parents array types combination is given as
457      * an array of element type constants containing the types or names of elements that are
458      * accepted and in what order they are accepted. Strings can be given for basic data types
459      * are <em>number, string, array, function, object</em>. We accept non element JSXGraph
460      * types like <em>coords</em>, too.
461      * @returns {Array} A new parents array prepared for the use within a create* method
462      */
463     checkParents: function (element, parents, expects) {
464         // some running variables
465         var i, j, k, len,
466 
467         // collects the parent elements that already got verified
468             new_parents = [],
469 
470         // in case of multiple parent array type combinations we may have to start over again
471         // so hold the parents array in an temporary array in case we need the original one back
472             tmp_parents = parents.slice(0),
473 
474         // test the given parent element against what we expect
475             is = function (expect, parent) {
476                 // we basically got three cases:
477                 // expect is of type
478                 // number => test for parent.elementClass and parent.type
479                 //     lucky us elementClass and type constants don't overlap \o/
480                 // string => here we have two sub cases depending on the value of expect
481                 //   string, object, function, number => make a simple typeof
482                 //   array => check via isArray
483 
484                 var type_expect = (typeof expect).toLowerCase();
485 
486                 if (type_expect === 'number') {
487                     return parent && ((parent.type && parent.type === expect) || (parent.elementClass && parent.elementClass === expect))
488                 } else {
489                     switch(expect.toLowerCase()) {
490                         case 'string':
491                         case 'object':
492                         case 'function':
493                         case 'number':
494                             return (typeof parent).toLowerCase() === expect.toLowerCase();
495                             break;
496                         case 'array':
497                             return JXG.isArray(parent);
498                             break;
499                     }
500                 }
501 
502 
503                 return false;
504             };
505 
506 
507         for(i = 0; i < expects.length; i++) {
508             // enter the next loop only if parents has enough elements
509             for(j = 0; j < expects[i].length && parents.length >= expects[i].length; j++) {
510                 k = 0;
511                 while (k < tmp_parents.length && !is(expects[i][j], tmp_parents[k]))
512                     k++;
513 
514                 if (k<tmp_parents.length) {
515                     new_parents.push(tmp_parents.splice(len-k-1, 1)[0]);
516                 }
517             }
518 
519             // if there are still elements left in the parents array we need to
520             // rebuild the original parents array and start with the next expect array
521             if (tmp_parents.length) {
522                 tmp_parents = parents.slice(0);
523                 new_parents = [];
524             } else // yay, we found something \o/
525                 return new_parents;
526         }
527     },
528 
529     /**
530      * Reads the configuration parameter of an attribute of an element from a {@link JXG.Options} object
531      * @param {JXG.Options} options Reference to an instance of JXG.Options. You usually want to use the
532      * options property of your board.
533      * @param {String} element The name of the element which options you wish to read, e.g. 'point' or
534      * 'elements' for general attributes.
535      * @param {String} key The name of the attribute to read, e.g. 'strokeColor' or 'withLabel'
536      * @returns The value of the selected configuration parameter.
537      * @see JXG.Options
538      */
539     readOption: function (options, element, key) {
540         var val = options.elements[key];
541 
542         if (JXG.exists(options[element][key]))
543             val = options[element][key];
544 
545         return val;
546     },
547 
548     /**
549      * Checks an attributes object and fills it with default values if there are properties missing.
550      * @param {Object} attributes
551      * @param {Object} defaults
552      * @returns {Object} The given attributes object with missing properties added via the defaults object.
553      * @deprecated replaced by JXG#copyAttributes
554      */
555     checkAttributes: function (attributes, defaults) {
556         var key;
557 
558         // Make sure attributes is an object.
559         if (!JXG.exists(attributes)) {
560             attributes = {};
561         }
562 
563         // Go through all keys of defaults and check for their existence
564         // in attributes. If one doesn't exist, it is created with the
565         // same value as in defaults.
566         for (key in defaults) {
567             if (!JXG.exists(attributes[key])) {
568                 attributes[key] = defaults[key];
569             }
570         }
571 
572         return attributes;
573     },
574 
575     /**
576      * Generates an attributes object that is filled with default values from the Options object
577      * and overwritten by the user speciified attributes.
578      * @param {Object} attributes user specified attributes
579      * @param {Object} options defaults options
580      * @param {String} % variable number of strings, e.g. 'slider', subtype 'point1'.
581      * @returns {Object} The resulting attributes object
582      */
583     copyAttributes: function (attributes, options) {
584         var a, i, len, o, isAvail,
585             primitives = {
586                 'circle': 1,
587                 'curve': 1,
588                 'image': 1,
589                 'line': 1,
590                 'point': 1,
591                 'polygon': 1,
592                 'text': 1,
593                 'ticks': 1,
594                 'integral': 1
595             };
596 
597 
598         len = arguments.length;
599         if (len < 3 || primitives[arguments[2]]) {
600             a = this.deepCopy(options.elements, null, true);       // default options from Options.elements
601         } else {
602             a = {};
603         }
604 
605         // Only the layer of the main element is set.
606         if (len < 4 && this.exists(arguments[2]) && this.exists(options.layer[arguments[2]])) {
607             a.layer = options.layer[arguments[2]];
608         }
609 
610         o = options;                                                // default options from specific elements
611         isAvail = true;
612         for (i = 2; i < len; i++) {
613             if (JXG.exists(o[arguments[i]])) {
614                 o = o[arguments[i]];
615             } else {
616                 isAvail = false;
617                 break;
618             }
619         }
620         if (isAvail) {
621             a = this.deepCopy(a, o, true);
622         }
623 
624         o = attributes;                                             // options from attributes
625         isAvail = true;
626         for (i=3;i<len;i++) {
627             if (JXG.exists(o[arguments[i]])) {
628                 o = o[arguments[i]];
629             } else {
630                 isAvail = false;
631                 break;
632             }
633         }
634         if (isAvail) {
635             this.extend(a, o, null, true);
636         }
637 
638         /**
639          * Special treatment of labels
640          */
641         o = options;
642         isAvail = true;
643         for (i = 2; i < len; i++) {
644             if (JXG.exists(o[arguments[i]])) {
645                 o = o[arguments[i]];
646             } else {
647                 isAvail = false;
648                 break;
649             }
650         }
651         if (isAvail) {
652             a.label =  JXG.deepCopy(o.label, a.label);
653         }
654         a.label = JXG.deepCopy(options.label, a.label);
655 
656         return a;
657     },
658 
659     /**
660      * Reads the width and height of an HTML element.
661      * @param {String} elementId The HTML id of an HTML DOM node.
662      * @returns {Object} An object with the two properties width and height.
663      */
664     getDimensions: function (elementId) {
665         var element, display, els, originalVisibility, originalPosition,
666             originalDisplay, originalWidth, originalHeight;
667 
668         if (typeof document == 'undefined' || elementId == null) {
669             return {
670                 width: 500,
671                 height: 500
672             };
673         }
674 
675         // Borrowed from prototype.js
676         element = document.getElementById(elementId);
677         if (!JXG.exists(element)) {
678             throw new Error("\nJSXGraph: HTML container element '" + (elementId) + "' not found.");
679         }
680 
681         display = element.style.display;
682         if (display != 'none' && display != null) {// Safari bug
683             return {width: element.offsetWidth, height: element.offsetHeight};
684         }
685 
686         // All *Width and *Height properties give 0 on elements with display set to none,
687         // hence we show the element temporarily
688         els = element.style;
689 
690         // save style
691         originalVisibility = els.visibility;
692         originalPosition = els.position;
693         originalDisplay = els.display;
694 
695         // show element
696         els.visibility = 'hidden';
697         els.position = 'absolute';
698         els.display = 'block';
699 
700         // read the dimension
701         originalWidth = element.clientWidth;
702         originalHeight = element.clientHeight;
703 
704         // restore original css values
705         els.display = originalDisplay;
706         els.position = originalPosition;
707         els.visibility = originalVisibility;
708 
709         return {
710             width: originalWidth,
711             height: originalHeight
712         };
713     },
714 
715     /**
716      * Adds an event listener to a DOM element.
717      * @param {Object} obj Reference to a DOM node.
718      * @param {String} type The event to catch, without leading 'on', e.g. 'mousemove' instead of 'onmousemove'.
719      * @param {Function} fn The function to call when the event is triggered.
720      * @param {Object} owner The scope in which the event trigger is called.
721      */
722     addEvent: function (obj, type, fn, owner) {
723         var el = function () {
724             return fn.apply(owner, arguments);
725         };
726 
727         el.origin = fn;
728         owner['x_internal'+type] = owner['x_internal'+type] || [];
729         owner['x_internal'+type].push(el);
730 
731         if (JXG.exists(obj) && JXG.exists(obj.addEventListener)) { // Non-IE browser
732             obj.addEventListener(type, el, false);
733         } else {  // IE
734             obj.attachEvent('on'+type, el);
735         }
736     },
737 
738     /**
739      * Removes an event listener from a DOM element.
740      * @param {Object} obj Reference to a DOM node.
741      * @param {String} type The event to catch, without leading 'on', e.g. 'mousemove' instead of 'onmousemove'.
742      * @param {Function} fn The function to call when the event is triggered.
743      * @param {Object} owner The scope in which the event trigger is called.
744      */
745     removeEvent: function (obj, type, fn, owner) {
746         var i;
747 
748         if (!JXG.exists(owner)) {
749             JXG.debug('no such owner');
750             return;
751         }
752 
753         if (!JXG.exists(owner['x_internal' + type])) {
754             JXG.debug('no such type: ' + type);
755             return;
756         }
757 
758         if (!JXG.isArray(owner['x_internal' + type])) {
759             JXG.debug('owner[x_internal + ' + type + '] is not an array');
760             return;
761         }
762 
763         i = JXG.indexOf(owner['x_internal' + type], fn, 'origin');
764 
765         if (i === -1) {
766             JXG.debug('no such event function in internal list: ' + fn);
767             return;
768         }
769 
770         try {
771             if (JXG.exists(obj.addEventListener)) { // Non-IE browser
772                 obj.removeEventListener(type, owner['x_internal' + type][i], false);
773             } else {  // IE
774                 obj.detachEvent('on' + type, owner['x_internal' + type][i]);
775             }
776 
777         } catch(e) {
778             JXG.debug('event not registered in browser: (' + type + ' -- ' + fn + ')');
779         }
780 
781         owner['x_internal' + type].splice(i, 1);
782     },
783 
784     /**
785      * Removes all events of the given type from a given DOM node; Use with caution and do not use it on a container div
786      * of a {@link JXG.Board} because this might corrupt the event handling system.
787      * @param {Object} obj Reference to a DOM node.
788      * @param {String} type The event to catch, without leading 'on', e.g. 'mousemove' instead of 'onmousemove'.
789      * @param {Object} owner The scope in which the event trigger is called.
790      */
791     removeAllEvents: function(obj, type, owner) {
792         var i, len;
793         if (owner['x_internal' + type]) {
794             len = owner['x_internal' + type].length;
795 
796             for (i = len - 1; i >= 0; i--) {
797                 JXG.removeEvent(obj, type, owner['x_internal' + type][i].origin, owner);
798             }
799 
800             if (owner['x_internal' + type].length > 0) {
801                 JXG.debug('removeAllEvents: Not all events could be removed.');
802             }
803         }
804     },
805 
806     /**
807      * Generates a function which calls the function fn in the scope of owner.
808      * @param {Function} fn Function to call.
809      * @param {Object} owner Scope in which fn is executed.
810      * @returns {Function} A function with the same signature as fn.
811      */
812     bind: function (fn, owner) {
813         return function () {
814             return fn.apply(owner,arguments);
815         };
816     },
817 
818     /**
819      * Removes an element from the given array
820      * @param {Array} ar
821      * @param {%} el
822      * @returns {Array}
823      */
824     removeElementFromArray: function(ar, el) {
825         var i;
826 
827         for (i = 0; i < ar.length; i++) {
828             if (ar[i] === el) {
829                 ar.splice(i, 1);
830                 return ar;
831             }
832         }
833 
834         return ar;
835     },
836 
837     /**
838      * Cross browser mouse / touch coordinates retrieval relative to the board's top left corner.
839      * @param {Object} [e] The browsers event object. If omitted, <tt>window.event</tt> will be used.
840      * @param {Number} [index] If <tt>e</tt> is a touch event, this provides the index of the touch coordinates, i.e. it determines which finger.
841      * @returns {Array} Contains the position as x,y-coordinates in the first resp. second component.
842      */
843     getPosition: function (e, index) {
844         var i, len, posx = 0, posy = 0,
845             evtTouches;
846 
847         if (!e) {
848             e = window.event;
849         }
850         evtTouches = e[JXG.touchProperty];
851 
852         if (JXG.exists(index)) {
853 
854             if (index == -1) {
855 
856                 len = evtTouches.length;
857                 for (i=0; i<len; i++) {
858                     if (evtTouches[i]) {
859                         e = evtTouches[i];
860                         break;
861                     }
862                 }
863 
864             } else
865                 e = evtTouches[index];
866         }
867 
868         if (e.pageX || e.pageY) {
869             posx = e.pageX;
870             posy = e.pageY;
871         } else if (e.clientX || e.clientY)    {
872             posx = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
873             posy = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
874         }
875     
876         return [posx,posy];
877     },
878 
879     /**
880      * Calculates recursively the offset of the DOM element in which the board is stored.
881      * @param {Object} obj A DOM element
882      * @returns {Array} An array with the elements left and top offset.
883      */
884     getOffset: function (obj) {
885         var o = obj,
886             o2 = obj,
887             l = o.offsetLeft - o.scrollLeft,
888             t = o.offsetTop - o.scrollTop,
889             cPos;
890 
891         cPos = this.getCSSTransform([l,t],o);
892         l = cPos[0];
893         t = cPos[1];
894 
895         /*
896          * In Mozilla and Webkit: offsetParent seems to jump at least to the next iframe,
897          * if not to the body. In IE and if we are in an position:absolute environment 
898          * offsetParent walks up the DOM hierarchy.
899          * In order to walk up the DOM hierarchy also in Mozilla and Webkit
900          * we need the parentNode steps.
901          */
902         while (o=o.offsetParent) {
903             l+=o.offsetLeft;
904             t+=o.offsetTop;
905             if (o.offsetParent) {
906                 l+=o.clientLeft - o.scrollLeft;
907                 t+=o.clientTop - o.scrollTop;
908             }
909             
910             cPos = this.getCSSTransform([l,t],o);
911             l = cPos[0];
912             t = cPos[1];
913             
914             o2 = o2.parentNode;
915             while (o2!=o) {
916                 l += o2.clientLeft - o2.scrollLeft;
917                 t += o2.clientTop - o2.scrollTop;
918                 
919                 cPos = this.getCSSTransform([l,t],o2);
920                 l = cPos[0];
921                 t = cPos[1];
922 
923                 o2 = o2.parentNode;
924             }
925 
926         }
927         return [l,t];
928     },
929 
930     /**
931      * Access CSS style sheets.
932      * @param {Object} obj A DOM element
933      * @param {String} stylename The CSS property to read.
934      * @returns The value of the CSS property and <tt>undefined</tt> if it is not set.
935      */
936     getStyle: function (obj, stylename) {
937         var r;
938 
939         if (window.getComputedStyle) {
940             // Non-IE
941             r = document.defaultView.getComputedStyle(obj, null).getPropertyValue(stylename);
942         } else if (obj.currentStyle && JXG.ieVersion >= 9) {
943             // IE
944             r = obj.currentStyle[stylename];
945         } else {
946             if (obj.style) {
947                 // make stylename lower camelcase
948                 stylename = stylename.replace(/-([a-z]|[0-9])/ig, function (all, letter) {
949                     return ( letter + "" ).toUpperCase();
950                 });
951                 r = obj.style[stylename]
952             }
953         }
954 
955         return r;
956     },
957 
958     /**
959      * Correct position of upper left corner in case of 
960      * a CSS transformation.
961      * @param {Array} cPos Previously determined position
962      * @param {Object} obj A DOM element
963      * @returns {Array} The corrected position.
964      */
965     getCSSTransform: function(cPos, obj) {
966         var t = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'oTransform'],
967             i, j, str, arrStr, 
968             start, len, len2,
969             arr, mat;
970             
971         // Take the first transformation matrix
972         len = t.length;
973         for (i=0, str=''; i<len; i++) {
974             if (JXG.exists(obj.style[t[i]])) {
975                 str = obj.style[t[i]];
976                 break;
977             }
978         }
979 
980         /**
981         * Extract the coordinates and apply the transformation
982         * to cPos
983         */
984         if (str!='') {
985             start = str.indexOf('(');
986             if (start>0) {
987                 len = str.length;
988                 arrstr = str.substring(start+1,len-1);
989                 arr = arrstr.split(',');
990                 for (j=0, len2=arr.length; j<len2; j++) {
991                     arr[j] = parseFloat(arr[j]);
992                 }
993             
994                 if (0==str.indexOf('matrix')) {    
995                     mat = [[arr[0], arr[1]],
996                            [arr[2], arr[3]]];
997                     //cPos = JXG.Math.matVecMult(mat, cPos);
998                     cPos[0] += arr[4];
999                     cPos[1] += arr[5];
1000                 } else if (0==str.indexOf('translateX')) {    
1001                     cPos[0] += arr[0];
1002                 } else if (0==str.indexOf('translateY')) {    
1003                     cPos[1] += arr[0];
1004                 } else if (0==str.indexOf('translate')) {    
1005                     cPos[0] += arr[0];
1006                     cPos[1] += arr[1];
1007                 // The following trannsformations do not work
1008                 // and require more tests.
1009                 // Missing are rotate, skew, skewX, skewY
1010                 } else if (0==str.indexOf('scaleX')) {    
1011                     mat = [[arr[0], 0],
1012                            [0, 1]];
1013                     cPos = JXG.Math.matVecMult(mat, cPos);
1014                 } else if (0==str.indexOf('scaleY')) {    
1015                     mat = [[1, 0],
1016                            [0, arr[0]]];
1017                     cPos = JXG.Math.matVecMult(mat, cPos);
1018                 } else if (0==str.indexOf('scale')) {    
1019                     mat = [[arr[0], 0],
1020                            [0, arr[1]]];
1021                     cPos = JXG.Math.matVecMult(mat, cPos);
1022                 }
1023             }
1024         }
1025         return cPos;
1026     },
1027 
1028     /**
1029      * TODO
1030      */
1031     getCSSTransformMatrix: function(obj) {
1032         var t = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'oTransform'],
1033             i, j, str, arrStr, 
1034             start, len, len2,
1035             arr, mat;
1036 
1037         mat = [[1, 0, 0],
1038                [0, 1, 0],
1039                [0, 0, 1]];
1040                
1041        // Take the first transformation matrix
1042         len = t.length;
1043         for (i=0, str=''; i<len; i++) {
1044             if (JXG.exists(obj.style[t[i]])) {
1045                 str = obj.style[t[i]];
1046                 break;
1047             }
1048         }
1049         
1050         if (str!='') {
1051             start = str.indexOf('(');
1052             if (start>0) {
1053                 len = str.length;
1054                 arrstr = str.substring(start+1,len-1);
1055                 arr = arrstr.split(',');
1056                 for (j=0, len2=arr.length; j<len2; j++) {
1057                     arr[j] = parseFloat(arr[j]);
1058                 }
1059             
1060                 if (0==str.indexOf('matrix')) {  
1061                     mat = [[1, 0, 0],
1062                            [arr[4], arr[0], arr[1]],
1063                            [arr[5], arr[2], arr[3]]];
1064                 } else if (0==str.indexOf('translateX')) {    
1065                     mat[1][0] = arr[0];
1066                 } else if (0==str.indexOf('translateY')) {    
1067                     mat[2][0] = arr[0];
1068                 } else if (0==str.indexOf('translate')) {    
1069                     mat[1][0] = arr[0];
1070                     mat[2][0] = arr[1];
1071                 // Missing are rotate, skew, skewX, skewY
1072                 } else if (0==str.indexOf('scaleX')) { 
1073                     mat[1][1] = arr[0];
1074                 } else if (0==str.indexOf('scaleY')) {    
1075                     mat[2][2] = arr[0];
1076                 } else if (0==str.indexOf('scale')) {    
1077                     mat[1][1] = arr[0];
1078                     mat[2][2] = arr[1];
1079                 }
1080             }
1081         }
1082         return mat;
1083     },
1084     
1085     /**
1086      * Extracts the keys of a given object.
1087      * @param object The object the keys are to be extracted
1088      * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected
1089      * the object owns itself and not some other object in the prototype chain.
1090      * @returns {Array} All keys of the given object.
1091      */
1092     keys: function (object, onlyOwn) {
1093         var keys = [], property;
1094 
1095         for (property in object) {
1096             if (onlyOwn) {
1097                 if (object.hasOwnProperty(property)) {
1098                     keys.push(property);
1099                 }
1100             } else {
1101                 keys.push(property);
1102             }
1103         }
1104         return keys;
1105     },
1106 
1107     /**
1108      * Search an array for a given value.
1109      * @param {Array} array
1110      * @param {%} value
1111      * @param {String} sub Use this property if the elements of the array are objects.
1112      * @returns {Number} The index of the first appearance of the given value, or
1113      * <tt>-1</tt> if the value was not found.
1114      */
1115     indexOf: function (array, value, sub) {
1116         var i, s = JXG.exists(sub);
1117 
1118         if (Array.indexOf && !s) {
1119             return array.indexOf(value);
1120         }
1121 
1122         for (i = 0; i < array.length; i++) {
1123             if ((s && array[i][sub] === value) || (!s && array[i] === value)) {
1124                 return i;
1125             }
1126         }
1127 
1128         return -1;
1129     },
1130 
1131     /**
1132      * Replaces all occurences of & by &amp;, > by &gt;, and < by &lt;.
1133      * @param str
1134      */
1135     escapeHTML: function (str) {
1136         return str.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
1137     },
1138 
1139     /**
1140      * Eliminates all substrings enclosed by < and > and replaces all occurences of
1141      * &amp; by &, &gt; by >, and &lt; by <.
1142      * @param str
1143      */
1144     unescapeHTML: function (str) {
1145         return str.replace(/<\/?[^>]+>/gi, '').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
1146     },
1147 
1148     /**
1149      * This outputs an object with a base class reference to the given object. This is useful if
1150      * you need a copy of an e.g. attributes object and want to overwrite some of the attributes
1151      * without changing the original object.
1152      * @param {Object} obj Object to be embedded.
1153      * @returns {Object} An object with a base class reference to <tt>obj</tt>.
1154      */
1155     clone: function (obj) {
1156         var cObj = {};
1157         cObj.prototype = obj;
1158         return cObj;
1159     },
1160 
1161     /**
1162      * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object
1163      * to the new one. Warning: The copied properties of obj2 are just flat copies.
1164      * @param {Object} obj Object to be copied.
1165      * @param {Object} obj2 Object with data that is to be copied to the new one as well.
1166      * @returns {Object} Copy of given object including some new/overwritten data from obj2.
1167      */
1168     cloneAndCopy: function (obj, obj2) {
1169         var cObj = function(){}, r;
1170         cObj.prototype = obj;
1171         //cObj = obj;
1172         for(r in obj2)
1173             cObj[r] = obj2[r];
1174 
1175         return cObj;
1176     },
1177 
1178     /**
1179      * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp.
1180      * element-wise instead of just copying the reference. If a second object is supplied, the two objects
1181      * are merged into one object. The properties of the second object have priority.
1182      * @param {Object} obj This object will be copied.
1183      * @param {Object} obj2 This object will merged into the newly created object
1184      * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
1185      * @returns {Object} copy of obj or merge of obj and obj2.
1186      */
1187     deepCopy: function (obj, obj2, toLower) {
1188         var c, i, prop, j, i2;
1189 
1190         toLower = toLower || false;
1191 
1192         if (typeof obj !== 'object' || obj == null) {
1193             return obj;
1194         }
1195 
1196         if (this.isArray(obj)) {
1197             c = [];
1198             for (i = 0; i < obj.length; i++) {
1199                 prop = obj[i];
1200                 if (typeof prop == 'object') {
1201                     c[i] = this.deepCopy(prop);
1202                 } else {
1203                     c[i] = prop;
1204                 }
1205             }
1206         } else {
1207             c = {};
1208             for (i in obj) {
1209                 i2 = toLower ? i.toLowerCase() : i;
1210 
1211                 prop = obj[i];
1212                 if (typeof prop == 'object') {
1213                     c[i2] = this.deepCopy(prop);
1214                 } else {
1215                     c[i2] = prop;
1216                 }
1217             }
1218 
1219             for (i in obj2) {
1220                 i2 = toLower ? i.toLowerCase() : i;
1221 
1222                 prop = obj2[i];
1223                 if (typeof prop == 'object') {
1224                     if (JXG.isArray(prop) || !JXG.exists(c[i2])) {
1225                         c[i2] = this.deepCopy(prop);
1226                     } else {
1227                         c[i2] = this.deepCopy(c[i2], prop, toLower);
1228                     }
1229                 } else {
1230                     c[i2] = prop;
1231                 }
1232             }
1233         }
1234         return c;
1235     },
1236 
1237     /**
1238      * Converts a JavaScript object into a JSON string.
1239      * @param {Object} obj A JavaScript object, functions will be ignored.
1240      * @param {Boolean} [noquote=false] No quotes around the name of a property.
1241      * @returns {String} The given object stored in a JSON string.
1242      */
1243     toJSON: function (obj, noquote) {
1244         var s, val;
1245 
1246         noquote = JXG.def(noquote, false);
1247 
1248         // check for native JSON support:
1249         if (window.JSON && window.JSON.stringify && !noquote) {
1250             try {
1251                 s = JSON.stringify(obj);
1252                 return s;
1253             } catch(e) {
1254                 // if something goes wrong, e.g. if obj contains functions we won't return
1255                 // and use our own implementation as a fallback
1256             }
1257         }
1258 
1259         switch (typeof obj) {
1260             case 'object':
1261                 if (obj) {
1262                     var list = [];
1263                     if (obj instanceof Array) {
1264                         for (var i=0;i < obj.length;i++) {
1265                             list.push(JXG.toJSON(obj[i], noquote));
1266                         }
1267                         return '[' + list.join(',') + ']';
1268                     } else {
1269                         for (var prop in obj) {
1270 
1271                             try {
1272                                 val = JXG.toJSON(obj[prop], noquote);
1273                             } catch (e) {
1274                                 val = '';
1275                             }
1276 
1277                             if (noquote) {
1278                                 list.push(prop + ':' + val);
1279                             } else {
1280                                 list.push('"' + prop + '":' + val);
1281                             }
1282                         }
1283                         return '{' + list.join(',') + '} ';
1284                     }
1285                 } else {
1286                     return 'null';
1287                 }
1288             case 'string':
1289                 return '\'' + obj.replace(/(["'])/g, '\\$1') + '\'';
1290             case 'number':
1291             case 'boolean':
1292                 return new String(obj);
1293         }
1294     },
1295 
1296     /**
1297      * Makes a string lower case except for the first character which will be upper case.
1298      * @param {String} str Arbitrary string
1299      * @returns {String} The capitalized string.
1300      */
1301     capitalize: function (str) {
1302         return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
1303     },
1304 
1305     /**
1306      * Process data in timed chunks. Data which takes long to process, either because it is such
1307      * a huge amount of data or the processing takes some time, causes warnings in browsers about
1308      * irresponsive scripts. To prevent these warnings, the processing is split into smaller pieces
1309      * called chunks which will be processed in serial order.
1310      * Copyright 2009 Nicholas C. Zakas. All rights reserved. MIT Licensed
1311      * @param {Array} items to do
1312      * @param {Function} process Function that is applied for every array item
1313      * @param {Object} context The scope of function process
1314      * @param {Function} callback This function is called after the last array element has been processed.
1315      */
1316     timedChunk: function (items, process, context, callback) {
1317         var todo = items.concat();   //create a clone of the original
1318         setTimeout(function (){
1319             var start = +new Date();
1320             do {
1321                 process.call(context, todo.shift());
1322             } while (todo.length > 0 && (+new Date() - start < 300));
1323             if (todo.length > 0){
1324                 setTimeout(arguments.callee, 1);
1325             } else {
1326                 callback(items);
1327             }
1328         }, 1);
1329     },
1330 
1331     /**
1332      * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
1333      * @param {String} str
1334      * @returns {String}
1335      */
1336     trimNumber: function (str) {
1337         str = str.replace(/^0+/, "");
1338         str = str.replace(/0+$/, "");
1339         if (str[str.length-1] == '.' || str[str.length-1] == ',') {
1340             str = str.slice(0, -1);
1341         }
1342         if (str[0] == '.' || str[0] == ',') {
1343             str = "0" + str;
1344         }
1345 
1346         return str;
1347     },
1348 
1349     /**
1350      * Remove all leading and trailing whitespaces from a given string.
1351      * @param {String} str
1352      * @returns {String}
1353      */
1354     trim: function (str) {
1355         str = str.replace(/^\s+/, "");
1356         str = str.replace(/\s+$/, "");
1357 
1358         return str;
1359     },
1360 
1361     /**
1362      * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value
1363      * is just returned.
1364      * @param val Could be anything. Preferably a number or a function.
1365      * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned.
1366      */
1367     evaluate: function (val) {
1368         if (JXG.isFunction(val)) {
1369             return val();
1370         } else {
1371             return val;
1372         }
1373     },
1374 
1375     /**
1376      * Eliminates duplicate entries in an array.
1377      * @param {Array} a An array
1378      * @returns {Array} The array with duplicate entries eliminated.
1379      */
1380     eliminateDuplicates: function (a) {
1381         var i, len = a.length,
1382             result = [],
1383             obj = {};
1384 
1385         for (i = 0; i < len; i++) {
1386             obj[a[i]] = 0;
1387         }
1388 
1389         for (i in obj) {
1390             if (obj.hasOwnProperty(i)) {
1391                 result.push(i);
1392             }
1393         }
1394 
1395         return result;
1396     },
1397 
1398     /**
1399      * Compare two arrays.
1400      * @param {Array} a1
1401      * @param {Array} a2
1402      * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value.
1403      */
1404     cmpArrays: function (a1, a2) {
1405         var i;
1406 
1407         // trivial cases
1408         if (a1 === a2) {
1409             return true;
1410         }
1411 
1412         if (a1.length !== a2.length) {
1413             return false;
1414         }
1415 
1416         for (i = 0; i < a1.length; i++) {
1417             if ((typeof a1[i] !== typeof a2[i]) || (a1[i] !== a2[i])) {
1418                 return false;
1419             }
1420         }
1421 
1422         return true;
1423     },
1424 
1425     /**
1426      * Truncate a number <tt>n</tt> after <tt>p</tt> decimals.
1427      * @param n
1428      * @param p
1429      * @returns {Number}
1430      */
1431     trunc: function (n, p) {
1432         p = JXG.def(p, 0);
1433 
1434         if (p == 0) {
1435             n = ~~n;
1436         } else {
1437             n = n.toFixed(p);
1438         }
1439 
1440         return n;
1441     },
1442 
1443     /**
1444      * Truncate a number <tt>val</tt> automatically.
1445      * @param val
1446      * @returns {Number}
1447      */
1448     autoDigits: function(val) {
1449         var x = Math.abs(val);
1450         if (x>0.1) {
1451             x = val.toFixed(2);
1452         } else if (x>=0.01) {
1453             x = val.toFixed(4);
1454         } else if (x>=0.0001) {
1455             x = val.toFixed(6);
1456         } else {
1457             x = val;
1458         }
1459         return x;
1460     },
1461 
1462     /**
1463      * Add something to the debug log. If available a JavaScript debug console is used. Otherwise
1464      * we're looking for a HTML div with id "debug". If this doesn't exist, too, the output is omitted.
1465      * @param {%} An arbitrary number of parameters.
1466      */
1467     debug: function (s) {
1468         var i;
1469 
1470         for(i = 0; i < arguments.length; i++) {
1471             s = arguments[i];
1472             if (window.console && console.log) {
1473                 //if (typeof s === 'string') s = s.replace(/<\S[^><]*>/g, "");
1474                 console.log(s);
1475             } else if (document.getElementById('debug')) {
1476                 document.getElementById('debug').innerHTML += s + "<br/>";
1477             }
1478             // else: do nothing
1479         }
1480     },
1481 
1482     debugWST: function (s) {
1483         var e;
1484         JXG.debug(s);
1485 
1486         if (window.console && console.log) {
1487             e = new Error();
1488             if (e && e.stack) {
1489                 console.log('stacktrace');
1490                 console.log(e.stack.split('\n').slice(1).join('\n'));
1491             }
1492         }
1493     },
1494 
1495     /**
1496      * Generates a deep copy of an array and removes the duplicate entries.
1497      * @param {Array} arr
1498      * @returns {Array}
1499      */
1500     uniqueArray: function(arr) {
1501         var i, j, isArray, ret = [];
1502 
1503         if (arr.length === 0) {
1504             return [];
1505         }
1506 
1507         isArray = JXG.isArray(arr[0]);
1508 
1509         for (i=0; i<arr.length; i++) {
1510             for (j=i+1; j<arr.length; j++) {
1511                 if (isArray && JXG.cmpArrays(arr[i], arr[j])) {
1512                     arr[i] = [];
1513                 } else if (!isArray && arr[i] === arr[j]) {
1514                     arr[i] = '';
1515                 }
1516             }
1517         }
1518 
1519         j = 0;
1520 
1521         for (i=0; i<arr.length; i++) {
1522             if (!isArray && arr[i] !== "") {
1523                 ret[j] = arr[i];
1524                 j++;
1525             } else if (isArray && arr[i].length !== 0) {
1526                 ret[j] = (arr[i].slice(0));
1527                 j++;
1528             }
1529         }
1530 
1531         return ret;
1532     },
1533 
1534     /**
1535      * Checks if an array contains an element equal to <tt>val</tt> but does not check the type!
1536      * @param {Array} arr
1537      * @param {} val
1538      * @returns {Boolean}
1539      */
1540     isInArray: function(arr, val) {
1541         var i;
1542 
1543         for (i=0; i<arr.length; i++) {
1544             if (arr[i] == val) {
1545                 return true;
1546             }
1547         }
1548 
1549         return false;
1550     },
1551 
1552     /**
1553      * Tests if the input variable is an Array
1554      * @param input
1555      */
1556     isArray2: function(input) {
1557         return typeof(input) == 'object' && (input instanceof Array);
1558     },
1559 
1560     /**
1561      * Tests if the input variable is an Object
1562      * @param input
1563      */
1564     isObject: function(input) {
1565         return typeof(input) == 'object' && (input instanceof Object) && !(input instanceof Array);
1566     },
1567 
1568     /**
1569      * Checks if an object contains a key, whose value equals to val
1570      */
1571     isInObject: function(lit, val) {
1572 
1573         for (var el in lit)
1574             if (lit.hasOwnProperty(el))
1575                 if (lit[el] == val)
1576                     return true;
1577 
1578         return false;
1579     },
1580 
1581     collectionContains: function(arr, val) {
1582 
1583         if (JXG.isArray2(arr))
1584             return JXG.isInArray(arr, val);
1585         else if (JXG.isObject(arr))
1586             return JXG.isInObject(arr, val);
1587         else
1588             return arr == val;
1589     }
1590 });
1591 
1592 // JessieScript startup: Search for script tags of type text/jessiescript and interpret them.
1593 if (typeof window !== 'undefined' && typeof document !== 'undefined') {
1594     JXG.addEvent(window, 'load', function () {
1595         var scripts = document.getElementsByTagName('script'), type,
1596             i, j, div, board, width, height, bbox, axis, grid, code;
1597 
1598         for(i=0;i<scripts.length;i++) {
1599             type = scripts[i].getAttribute('type', false);
1600             if (!JXG.exists(type)) continue;
1601             if (type.toLowerCase() === 'text/jessiescript' || type.toLowerCase() === 'jessiescript' || type.toLowerCase() === 'text/jessiecode' || type.toLowerCase() === 'jessiecode') {
1602                 width = scripts[i].getAttribute('width', false) || '500px';
1603                 height = scripts[i].getAttribute('height', false) || '500px';
1604                 bbox = scripts[i].getAttribute('boundingbox', false) || '-5, 5, 5, -5';
1605                 bbox = bbox.split(',');
1606                 if (bbox.length!==4) {
1607                     bbox = [-5, 5, 5, -5];
1608                 } else {
1609                     for(j=0;j<bbox.length;j++) {
1610                         bbox[j] = parseFloat(bbox[j]);
1611                     }
1612                 }
1613                 axis = JXG.str2Bool(scripts[i].getAttribute('axis', false) || 'false');
1614                 grid = JXG.str2Bool(scripts[i].getAttribute('grid', false) || 'false');
1615 
1616                 div = document.createElement('div');
1617                 div.setAttribute('id', 'jessiescript_autgen_jxg_'+i);
1618                 div.setAttribute('style', 'width:'+width+'; height:'+height+'; float:left');
1619                 div.setAttribute('class', 'jxgbox');
1620                 try {
1621                     document.body.insertBefore(div, scripts[i]);
1622                 } catch (e) {
1623                     // there's probably jquery involved...
1624                     if (typeof jQuery !== 'undefined') {
1625                         jQuery(div).insertBefore(scripts[i]);
1626                     }
1627                 }
1628 
1629                 if (document.getElementById('jessiescript_autgen_jxg_' + i)) {
1630                     board = JXG.JSXGraph.initBoard('jessiescript_autgen_jxg_' + i, {boundingbox: bbox, keepaspectratio:true, grid: grid, axis: axis});
1631 
1632                     code = scripts[i].innerHTML;
1633                     code = code.replace(/<!\[CDATA\[/g, '').replace(/\]\]>/g, '');
1634                     scripts[i].innerHTML = code;
1635                     if (type.toLowerCase().indexOf('script') > -1) {
1636                         board.construct(code);
1637                     } else {
1638                         try {
1639                             board.jc.parse(code);
1640                         } catch (e) {
1641                             JXG.debug(e);
1642                         }
1643                     }
1644                 } else {
1645                     JXG.debug('JSXGraph: Apparently the div injection failed. Can\'t create a board, sorry.');
1646                 }
1647             }
1648         }
1649     }, window);
1650 }
1651