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 &, > by >, and < by <. 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 * & by &, > by >, and < 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