1 /* 2 Copyright 2008-2018 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 33 /*global JXG: true, define: true, html_sanitize: true*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 base/constants 39 */ 40 41 /** 42 * @fileoverview type.js contains several functions to help deal with javascript's weak types. This file mainly consists 43 * of detector functions which verify if a variable is or is not of a specific type and converter functions that convert 44 * variables to another type or normalize the type of a variable. 45 */ 46 47 define([ 48 'jxg', 'base/constants' 49 ], function (JXG, Const) { 50 51 "use strict"; 52 53 JXG.extend(JXG, /** @lends JXG */ { 54 /** 55 * Checks if the given string is an id within the given board. 56 * @param {JXG.Board} board 57 * @param {String} s 58 * @returns {Boolean} 59 */ 60 isId: function (board, s) { 61 return (typeof s === 'string') && !!board.objects[s]; 62 }, 63 64 /** 65 * Checks if the given string is a name within the given board. 66 * @param {JXG.Board} board 67 * @param {String} s 68 * @returns {Boolean} 69 */ 70 isName: function (board, s) { 71 return typeof s === 'string' && !!board.elementsByName[s]; 72 }, 73 74 /** 75 * Checks if the given string is a group id within the given board. 76 * @param {JXG.Board} board 77 * @param {String} s 78 * @returns {Boolean} 79 */ 80 isGroup: function (board, s) { 81 return typeof s === 'string' && !!board.groups[s]; 82 }, 83 84 /** 85 * Checks if the value of a given variable is of type string. 86 * @param v A variable of any type. 87 * @returns {Boolean} True, if v is of type string. 88 */ 89 isString: function (v) { 90 return typeof v === "string"; 91 }, 92 93 /** 94 * Checks if the value of a given variable is of type number. 95 * @param v A variable of any type. 96 * @returns {Boolean} True, if v is of type number. 97 */ 98 isNumber: function (v) { 99 return typeof v === "number" || Object.prototype.toString.call(v) === '[Object Number]'; 100 }, 101 102 /** 103 * Checks if a given variable references a function. 104 * @param v A variable of any type. 105 * @returns {Boolean} True, if v is a function. 106 */ 107 isFunction: function (v) { 108 return typeof v === "function"; 109 }, 110 111 /** 112 * Checks if a given variable references an array. 113 * @param v A variable of any type. 114 * @returns {Boolean} True, if v is of type array. 115 */ 116 isArray: function (v) { 117 var r; 118 119 // use the ES5 isArray() method and if that doesn't exist use a fallback. 120 if (Array.isArray) { 121 r = Array.isArray(v); 122 } else { 123 r = (v !== null && typeof v === "object" && typeof v.splice === 'function' && typeof v.join === 'function'); 124 } 125 126 return r; 127 }, 128 129 /** 130 * Tests if the input variable is an Object 131 * @param v 132 */ 133 isObject: function (v) { 134 return typeof v === 'object' && !this.isArray(v); 135 }, 136 137 /** 138 * Checks if a given variable is a reference of a JSXGraph Point element. 139 * @param v A variable of any type. 140 * @returns {Boolean} True, if v is of type JXG.Point. 141 */ 142 isPoint: function (v) { 143 if (v !== null && typeof v === 'object') { 144 return (v.elementClass === Const.OBJECT_CLASS_POINT); 145 } 146 147 return false; 148 }, 149 150 /** 151 * Checks if a given variable is a reference of a JSXGraph Point element or an array of length at least two or 152 * a function returning an array of length two or three. 153 * @param {JXG.Board} board 154 * @param v A variable of any type. 155 * @returns {Boolean} True, if v is of type JXG.Point. 156 */ 157 isPointType: function (board, v) { 158 var val; 159 160 if (this.isArray(v)) { 161 return true; 162 } 163 if (this.isFunction(v)) { 164 val = v(); 165 if (this.isArray(val) && val.length > 1) { 166 return true; 167 } 168 } 169 v = board.select(v); 170 return this.isPoint(v); 171 }, 172 173 /** 174 * Checks if a given variable is a reference of a JSXGraph transformation element or an array 175 * of JSXGraph transformation elements. 176 * @param v A variable of any type. 177 * @returns {Boolean} True, if v is of type JXG.Transformation. 178 */ 179 isTransformationOrArray: function(v) { 180 if (v !== null) { 181 if (this.isArray(v) && v.length > 0) { 182 return this.isTransformationOrArray(v[0]); 183 } else if (typeof v === 'object') { 184 return (v.type === Const.OBJECT_TYPE_TRANSFORMATION); 185 } 186 } 187 return false; 188 }, 189 190 /** 191 * Checks if a given variable is neither undefined nor null. You should not use this together with global 192 * variables! 193 * @param v A variable of any type. 194 * @returns {Boolean} True, if v is neither undefined nor null. 195 */ 196 exists: (function (undef) { 197 return function (v) { 198 return !(v === undef || v === null); 199 }; 200 }()), 201 202 /** 203 * Handle default parameters. 204 * @param v Given value 205 * @param d Default value 206 * @returns <tt>d</tt>, if <tt>v</tt> is undefined or null. 207 */ 208 def: function (v, d) { 209 if (this.exists(v)) { 210 return v; 211 } 212 213 return d; 214 }, 215 216 /** 217 * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value. 218 * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>. 219 * @returns {Boolean} String typed boolean value converted to boolean. 220 */ 221 str2Bool: function (s) { 222 if (!this.exists(s)) { 223 return true; 224 } 225 226 if (typeof s === 'boolean') { 227 return s; 228 } 229 230 if (this.isString(s)) { 231 return (s.toLowerCase() === 'true'); 232 } 233 234 return false; 235 }, 236 237 /** 238 * Convert a String, a number or a function into a function. This method is used in Transformation.js 239 * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given 240 * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param 241 * values is of type string. 242 * @param {Array} param An array containing strings, numbers, or functions. 243 * @param {Number} n Length of <tt>param</tt>. 244 * @returns {Function} A function taking one parameter k which specifies the index of the param element 245 * to evaluate. 246 */ 247 createEvalFunction: function (board, param, n) { 248 var f = [], i; 249 250 for (i = 0; i < n; i++) { 251 f[i] = JXG.createFunction(param[i], board, '', true); 252 } 253 254 return function (k) { 255 return f[k](); 256 }; 257 }, 258 259 /** 260 * Convert a String, number or function into a function. 261 * @param {String|Number|Function} term A variable of type string, function or number. 262 * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given 263 * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param 264 * values is of type string. 265 * @param {String} variableName Only required if evalGeonext is set to true. Describes the variable name 266 * of the variable in a GEONE<sub>X</sub>T string given as term. 267 * @param {Boolean} [evalGeonext=true] Set this true, if term should be treated as a GEONE<sub>X</sub>T string. 268 * @returns {Function} A function evaluation the value given by term or null if term is not of type string, 269 * function or number. 270 */ 271 createFunction: function (term, board, variableName, evalGeonext) { 272 var f = null; 273 274 if ((!this.exists(evalGeonext) || evalGeonext) && this.isString(term)) { 275 // Convert GEONExT syntax into JavaScript syntax 276 //newTerm = JXG.GeonextParser.geonext2JS(term, board); 277 //return new Function(variableName,'return ' + newTerm + ';'); 278 279 //term = JXG.GeonextParser.replaceNameById(term, board); 280 //term = JXG.GeonextParser.geonext2JS(term, board); 281 f = board.jc.snippet(term, true, variableName, true); 282 } else if (this.isFunction(term)) { 283 f = term; 284 } else if (this.isNumber(term)) { 285 /** @ignore */ 286 f = function () { 287 return term; 288 }; 289 } else if (this.isString(term)) { 290 // In case of string function like fontsize 291 /** @ignore */ 292 f = function () { 293 return term; 294 }; 295 } 296 297 if (f !== null) { 298 f.origin = term; 299 } 300 301 return f; 302 }, 303 304 /** 305 * Test if the parents array contains existing points. If instead parents contains coordinate arrays or function returning coordinate arrays 306 * free points with these coordinates are created. 307 * 308 * @param {JXG.Board} board Board object 309 * @param {Array} parents Array containing parent elements for a new object. This array may contain 310 * <ul> 311 * <li> {@link JXG.Point} objects 312 * <li> {@link JXG.Element#name} of {@link JXG.Point} objects 313 * <li> {@link JXG.Element#id} of {@link JXG.Point} objects 314 * <li> Coordinates of points given as array of numbers of length two or three, e.g. [2, 3]. 315 * <li> Coordinates of points given as array of functions of length two or three. Each function returns one coordinate, e.g. 316 * [function(){ return 2; }, function(){ return 3; }] 317 * <li> Function returning coordinates, e.g. function() { return [2, 3]; } 318 * </ul> 319 * In the last three cases a new point will be created. 320 * @param {String} attrClass Main attribute class of newly created points, see {@link JXG@copyAttributes} 321 * @param {Array} attrArray List of subtype attributes for the newly created points. The list of subtypes is mapped to the list of new points. 322 * @returns {Array} List of newly created {@link JXG.Point} elements or false if not all returned elements are points. 323 */ 324 providePoints: function (board, parents, attributes, attrClass, attrArray) { 325 var i, j, 326 len, 327 lenAttr = 0, 328 points = [], attr, val; 329 330 if (!this.isArray(parents)) { 331 parents = [parents]; 332 } 333 len = parents.length; 334 if (this.exists(attrArray)) { 335 lenAttr = attrArray.length; 336 } 337 if (lenAttr === 0) { 338 attr = this.copyAttributes(attributes, board.options, attrClass); 339 } 340 341 for (i = 0; i < len; ++i) { 342 if (lenAttr > 0) { 343 j = Math.min(i, lenAttr - 1); 344 attr = this.copyAttributes(attributes, board.options, attrClass, attrArray[j]); 345 } 346 if (this.isArray(parents[i]) && parents[i].length > 1) { 347 points.push(board.create('point', parents[i], attr)); 348 } else if (this.isFunction(parents[i])) { 349 val = parents[i](); 350 if (this.isArray(val) && (val.length > 1)) { 351 points.push(board.create('point', [parents[i]], attr)); 352 } 353 } else { 354 points.push(board.select(parents[i])); 355 } 356 357 if (!this.isPoint(points[i])) { 358 return false; 359 } 360 } 361 362 return points; 363 }, 364 365 /** 366 * Generates a function which calls the function fn in the scope of owner. 367 * @param {Function} fn Function to call. 368 * @param {Object} owner Scope in which fn is executed. 369 * @returns {Function} A function with the same signature as fn. 370 */ 371 bind: function (fn, owner) { 372 return function () { 373 return fn.apply(owner, arguments); 374 }; 375 }, 376 377 /** 378 * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value 379 * is just returned. 380 * @param val Could be anything. Preferably a number or a function. 381 * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned. 382 */ 383 evaluate: function (val) { 384 if (this.isFunction(val)) { 385 return val(); 386 } 387 388 return val; 389 }, 390 391 /** 392 * Search an array for a given value. 393 * @param {Array} array 394 * @param value 395 * @param {String} [sub] Use this property if the elements of the array are objects. 396 * @returns {Number} The index of the first appearance of the given value, or 397 * <tt>-1</tt> if the value was not found. 398 */ 399 indexOf: function (array, value, sub) { 400 var i, s = this.exists(sub); 401 402 if (Array.indexOf && !s) { 403 return array.indexOf(value); 404 } 405 406 for (i = 0; i < array.length; i++) { 407 if ((s && array[i][sub] === value) || (!s && array[i] === value)) { 408 return i; 409 } 410 } 411 412 return -1; 413 }, 414 415 /** 416 * Eliminates duplicate entries in an array consisting of numbers and strings. 417 * @param {Array} a An array of numbers and/or strings. 418 * @returns {Array} The array with duplicate entries eliminated. 419 */ 420 eliminateDuplicates: function (a) { 421 var i, 422 len = a.length, 423 result = [], 424 obj = {}; 425 426 for (i = 0; i < len; i++) { 427 obj[a[i]] = 0; 428 } 429 430 for (i in obj) { 431 if (obj.hasOwnProperty(i)) { 432 result.push(i); 433 } 434 } 435 436 return result; 437 }, 438 439 /** 440 * Swaps to array elements. 441 * @param {Array} arr 442 * @param {Number} i 443 * @param {Number} j 444 * @returns {Array} Reference to the given array. 445 */ 446 swap: function (arr, i, j) { 447 var tmp; 448 449 tmp = arr[i]; 450 arr[i] = arr[j]; 451 arr[j] = tmp; 452 453 return arr; 454 }, 455 456 /** 457 * Generates a copy of an array and removes the duplicate entries. The original 458 * Array will be altered. 459 * @param {Array} arr 460 * @returns {Array} 461 */ 462 uniqueArray: function (arr) { 463 var i, j, isArray, ret = []; 464 465 if (arr.length === 0) { 466 return []; 467 } 468 469 for (i = 0; i < arr.length; i++) { 470 isArray = this.isArray(arr[i]); 471 472 if (!this.exists(arr[i])) { 473 arr[i] = ''; 474 continue; 475 } 476 for (j = i + 1; j < arr.length; j++) { 477 if (isArray && JXG.cmpArrays(arr[i], arr[j])) { 478 arr[i] = []; 479 } else if (!isArray && arr[i] === arr[j]) { 480 arr[i] = ''; 481 } 482 } 483 } 484 485 j = 0; 486 487 for (i = 0; i < arr.length; i++) { 488 isArray = this.isArray(arr[i]); 489 490 if (!isArray && arr[i] !== '') { 491 ret[j] = arr[i]; 492 j++; 493 } else if (isArray && arr[i].length !== 0) { 494 ret[j] = (arr[i].slice(0)); 495 j++; 496 } 497 } 498 499 arr = ret; 500 return ret; 501 }, 502 503 /** 504 * Checks if an array contains an element equal to <tt>val</tt> but does not check the type! 505 * @param {Array} arr 506 * @param val 507 * @returns {Boolean} 508 */ 509 isInArray: function (arr, val) { 510 return JXG.indexOf(arr, val) > -1; 511 }, 512 513 /** 514 * Converts an array of {@link JXG.Coords} objects into a coordinate matrix. 515 * @param {Array} coords 516 * @param {Boolean} split 517 * @returns {Array} 518 */ 519 coordsArrayToMatrix: function (coords, split) { 520 var i, 521 x = [], 522 m = []; 523 524 for (i = 0; i < coords.length; i++) { 525 if (split) { 526 x.push(coords[i].usrCoords[1]); 527 m.push(coords[i].usrCoords[2]); 528 } else { 529 m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]); 530 } 531 } 532 533 if (split) { 534 m = [x, m]; 535 } 536 537 return m; 538 }, 539 540 /** 541 * Compare two arrays. 542 * @param {Array} a1 543 * @param {Array} a2 544 * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value. 545 */ 546 cmpArrays: function (a1, a2) { 547 var i; 548 549 // trivial cases 550 if (a1 === a2) { 551 return true; 552 } 553 554 if (a1.length !== a2.length) { 555 return false; 556 } 557 558 for (i = 0; i < a1.length; i++) { 559 if (this.isArray(a1[i]) && this.isArray(a2[i])) { 560 if (!this.cmpArrays(a1[i], a2[i])) { 561 return false; 562 } 563 } 564 else if (a1[i] !== a2[i]) { 565 return false; 566 } 567 } 568 569 return true; 570 }, 571 572 /** 573 * Removes an element from the given array 574 * @param {Array} ar 575 * @param el 576 * @returns {Array} 577 */ 578 removeElementFromArray: function (ar, el) { 579 var i; 580 581 for (i = 0; i < ar.length; i++) { 582 if (ar[i] === el) { 583 ar.splice(i, 1); 584 return ar; 585 } 586 } 587 588 return ar; 589 }, 590 591 /** 592 * Truncate a number <tt>n</tt> after <tt>p</tt> decimals. 593 * @param {Number} n 594 * @param {Number} p 595 * @returns {Number} 596 */ 597 trunc: function (n, p) { 598 p = JXG.def(p, 0); 599 600 return this.toFixed(n, p); 601 }, 602 603 /** 604 * Decimal adjustment of a number. 605 * From https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Math/round 606 * 607 * @param {String} type The type of adjustment. 608 * @param {Number} value The number. 609 * @param {Number} exp The exponent (the 10 logarithm of the adjustment base). 610 * @returns {Number} The adjusted value. 611 * 612 * @private 613 */ 614 _decimalAdjust: function(type, value, exp) { 615 // If the exp is undefined or zero... 616 if (exp === undefined || +exp === 0) { 617 return Math[type](value); 618 } 619 620 value = +value; 621 exp = +exp; 622 // If the value is not a number or the exp is not an integer... 623 if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) { 624 return NaN; 625 } 626 627 // Shift 628 value = value.toString().split('e'); 629 value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp))); 630 631 // Shift back 632 value = value.toString().split('e'); 633 return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); 634 }, 635 636 /** 637 * Round a number to given number of decimal digits. 638 * 639 * Example: JXG._toFixed(3.14159, -2) gives 3.14 640 * @param {Number} value Number to be rounded 641 * @param {Number} exp Number of decimal digits given as negative exponent 642 * @return {Number} Rounded number. 643 * 644 * @private 645 */ 646 _round10: function(value, exp) { 647 return this._decimalAdjust('round', value, exp); 648 }, 649 650 /** 651 * "Floor" a number to given number of decimal digits. 652 * 653 * Example: JXG._toFixed(3.14159, -2) gives 3.14 654 * @param {Number} value Number to be floored 655 * @param {Number} exp Number of decimal digits given as negative exponent 656 * @return {Number} "Floored" number. 657 * 658 * @private 659 */ 660 _floor10: function(value, exp) { 661 return this._decimalAdjust('floor', value, exp); 662 }, 663 664 /** 665 * "Ceil" a number to given number of decimal digits. 666 * 667 * Example: JXG._toFixed(3.14159, -2) gives 3.15 668 * @param {Number} value Number to be ceiled 669 * @param {Number} exp Number of decimal digits given as negative exponent 670 * @return {Number} "Ceiled" number. 671 * 672 * @private 673 */ 674 _ceil10: function(value, exp) { 675 return this._decimalAdjust('ceil', value, exp); 676 }, 677 678 /** 679 * Replacement of the default toFixed() method. 680 * It does a correct rounding (independent of the browser) and 681 * returns "0.00" for toFixed(-0.000001, 2) instead of "-0.00" which 682 * is returned by JavaScript's toFixed() 683 * 684 * @param {Number} num Number tp be rounded 685 * @param {Number} precision Decimal digits 686 * @return {String} Rounded number is returned as string 687 */ 688 toFixed: function(num, precision) { 689 return this._round10(num, -precision).toFixed(precision); 690 }, 691 692 /** 693 * Truncate a number <tt>val</tt> automatically. 694 * @param val 695 * @returns {Number} 696 */ 697 autoDigits: function (val) { 698 var x = Math.abs(val), 699 str; 700 701 if (x > 0.1) { 702 str = this.toFixed(val, 2); 703 } else if (x >= 0.01) { 704 str = this.toFixed(val, 4); 705 } else if (x >= 0.0001) { 706 str = this.toFixed(val, 6); 707 } else { 708 str = val; 709 } 710 return str; 711 }, 712 713 /** 714 * Extracts the keys of a given object. 715 * @param object The object the keys are to be extracted 716 * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected 717 * the object owns itself and not some other object in the prototype chain. 718 * @returns {Array} All keys of the given object. 719 */ 720 keys: function (object, onlyOwn) { 721 var keys = [], property; 722 723 // the caller decides if we use hasOwnProperty 724 /*jslint forin:true*/ 725 for (property in object) { 726 if (onlyOwn) { 727 if (object.hasOwnProperty(property)) { 728 keys.push(property); 729 } 730 } else { 731 keys.push(property); 732 } 733 } 734 /*jslint forin:false*/ 735 736 return keys; 737 }, 738 739 /** 740 * This outputs an object with a base class reference to the given object. This is useful if 741 * you need a copy of an e.g. attributes object and want to overwrite some of the attributes 742 * without changing the original object. 743 * @param {Object} obj Object to be embedded. 744 * @returns {Object} An object with a base class reference to <tt>obj</tt>. 745 */ 746 clone: function (obj) { 747 var cObj = {}; 748 749 cObj.prototype = obj; 750 751 return cObj; 752 }, 753 754 /** 755 * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object 756 * to the new one. Warning: The copied properties of obj2 are just flat copies. 757 * @param {Object} obj Object to be copied. 758 * @param {Object} obj2 Object with data that is to be copied to the new one as well. 759 * @returns {Object} Copy of given object including some new/overwritten data from obj2. 760 */ 761 cloneAndCopy: function (obj, obj2) { 762 var r, 763 cObj = function () {}; 764 765 cObj.prototype = obj; 766 767 // no hasOwnProperty on purpose 768 /*jslint forin:true*/ 769 /*jshint forin:true*/ 770 771 for (r in obj2) { 772 cObj[r] = obj2[r]; 773 } 774 775 /*jslint forin:false*/ 776 /*jshint forin:false*/ 777 778 return cObj; 779 }, 780 781 /** 782 * Recursively merges obj2 into obj1. Contrary to {@link JXG#deepCopy} this won't create a new object 783 * but instead will overwrite obj1. 784 * @param {Object} obj1 785 * @param {Object} obj2 786 * @returns {Object} 787 */ 788 merge: function (obj1, obj2) { 789 var i, j; 790 791 for (i in obj2) { 792 if (obj2.hasOwnProperty(i)) { 793 if (this.isArray(obj2[i])) { 794 if (!obj1[i]) { 795 obj1[i] = []; 796 } 797 798 for (j = 0; j < obj2[i].length; j++) { 799 if (typeof obj2[i][j] === 'object') { 800 obj1[i][j] = this.merge(obj1[i][j], obj2[i][j]); 801 } else { 802 obj1[i][j] = obj2[i][j]; 803 } 804 } 805 } else if (typeof obj2[i] === 'object') { 806 if (!obj1[i]) { 807 obj1[i] = {}; 808 } 809 810 obj1[i] = this.merge(obj1[i], obj2[i]); 811 } else { 812 obj1[i] = obj2[i]; 813 } 814 } 815 } 816 817 return obj1; 818 }, 819 820 /** 821 * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp. 822 * element-wise instead of just copying the reference. If a second object is supplied, the two objects 823 * are merged into one object. The properties of the second object have priority. 824 * @param {Object} obj This object will be copied. 825 * @param {Object} obj2 This object will merged into the newly created object 826 * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes 827 * @returns {Object} copy of obj or merge of obj and obj2. 828 */ 829 deepCopy: function (obj, obj2, toLower) { 830 var c, i, prop, i2; 831 832 toLower = toLower || false; 833 834 if (typeof obj !== 'object' || obj === null) { 835 return obj; 836 } 837 838 // missing hasOwnProperty is on purpose in this function 839 if (this.isArray(obj)) { 840 c = []; 841 for (i = 0; i < obj.length; i++) { 842 prop = obj[i]; 843 if (typeof prop === 'object') { 844 // We certainly do not want to recurse into a JSXGraph object. 845 // This would for sure result in an infinite recursion. 846 // As alternative we copy the id of the object. 847 if (this.exists(prop.board)) { 848 c[i] = prop.id; 849 } else { 850 c[i] = this.deepCopy(prop); 851 } 852 } else { 853 c[i] = prop; 854 } 855 } 856 } else { 857 c = {}; 858 for (i in obj) { 859 if (obj.hasOwnProperty(i)) { 860 i2 = toLower ? i.toLowerCase() : i; 861 prop = obj[i]; 862 if (prop !== null && typeof prop === 'object') { 863 if (this.exists(prop.board)) { 864 c[i2] = prop.id; 865 } else { 866 c[i2] = this.deepCopy(prop); 867 } 868 } else { 869 c[i2] = prop; 870 } 871 } 872 } 873 874 for (i in obj2) { 875 if (obj2.hasOwnProperty(i)) { 876 i2 = toLower ? i.toLowerCase() : i; 877 878 prop = obj2[i]; 879 if (typeof prop === 'object') { 880 if (this.isArray(prop) || !this.exists(c[i2])) { 881 c[i2] = this.deepCopy(prop); 882 } else { 883 c[i2] = this.deepCopy(c[i2], prop, toLower); 884 } 885 } else { 886 c[i2] = prop; 887 } 888 } 889 } 890 } 891 892 return c; 893 }, 894 895 /** 896 * Generates an attributes object that is filled with default values from the Options object 897 * and overwritten by the user specified attributes. 898 * @param {Object} attributes user specified attributes 899 * @param {Object} options defaults options 900 * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'. 901 * @returns {Object} The resulting attributes object 902 */ 903 copyAttributes: function (attributes, options, s) { 904 var a, i, len, o, isAvail, 905 primitives = { 906 'circle': 1, 907 'curve': 1, 908 'image': 1, 909 'line': 1, 910 'point': 1, 911 'polygon': 1, 912 'text': 1, 913 'ticks': 1, 914 'integral': 1 915 }; 916 917 918 len = arguments.length; 919 if (len < 3 || primitives[s]) { 920 // default options from Options.elements 921 a = JXG.deepCopy(options.elements, null, true); 922 } else { 923 a = {}; 924 } 925 926 // Only the layer of the main element is set. 927 if (len < 4 && this.exists(s) && this.exists(options.layer[s])) { 928 a.layer = options.layer[s]; 929 } 930 931 // default options from specific elements 932 o = options; 933 isAvail = true; 934 for (i = 2; i < len; i++) { 935 if (this.exists(o[arguments[i]])) { 936 o = o[arguments[i]]; 937 } else { 938 isAvail = false; 939 break; 940 } 941 } 942 if (isAvail) { 943 a = JXG.deepCopy(a, o, true); 944 } 945 946 // options from attributes 947 o = attributes; 948 isAvail = true; 949 for (i = 3; i < len; i++) { 950 if (this.exists(o[arguments[i]])) { 951 o = o[arguments[i]]; 952 } else { 953 isAvail = false; 954 break; 955 } 956 } 957 if (isAvail) { 958 this.extend(a, o, null, true); 959 } 960 961 // Special treatment of labels 962 o = options; 963 isAvail = true; 964 for (i = 2; i < len; i++) { 965 if (this.exists(o[arguments[i]])) { 966 o = o[arguments[i]]; 967 } else { 968 isAvail = false; 969 break; 970 } 971 } 972 if (isAvail && this.exists(o.label)) { 973 a.label = JXG.deepCopy(o.label, a.label); 974 } 975 a.label = JXG.deepCopy(options.label, a.label); 976 977 return a; 978 }, 979 980 /** 981 * Copy all prototype methods from object "superObject" to object 982 * "subObject". The constructor of superObject will be available 983 * in subObject as subObject.constructor[constructorName]. 984 * @param {Object} subObj A JavaScript object which receives new methods. 985 * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject 986 * @returns {String} constructorName Under this name the constructor of superObj will be available 987 * in subObject. 988 * @private 989 */ 990 copyPrototypeMethods: function (subObject, superObject, constructorName) { 991 var key; 992 993 subObject.prototype[constructorName] = superObject.prototype.constructor; 994 for (key in superObject.prototype) { 995 if (superObject.prototype.hasOwnProperty(key)) { 996 subObject.prototype[key] = superObject.prototype[key]; 997 } 998 } 999 }, 1000 1001 /** 1002 * Converts a JavaScript object into a JSON string. 1003 * @param {Object} obj A JavaScript object, functions will be ignored. 1004 * @param {Boolean} [noquote=false] No quotes around the name of a property. 1005 * @returns {String} The given object stored in a JSON string. 1006 */ 1007 toJSON: function (obj, noquote) { 1008 var list, prop, i, s, val; 1009 1010 noquote = JXG.def(noquote, false); 1011 1012 // check for native JSON support: 1013 if (typeof JSON && JSON.stringify && !noquote) { 1014 try { 1015 s = JSON.stringify(obj); 1016 return s; 1017 } catch (e) { 1018 // if something goes wrong, e.g. if obj contains functions we won't return 1019 // and use our own implementation as a fallback 1020 } 1021 } 1022 1023 switch (typeof obj) { 1024 case 'object': 1025 if (obj) { 1026 list = []; 1027 1028 if (this.isArray(obj)) { 1029 for (i = 0; i < obj.length; i++) { 1030 list.push(JXG.toJSON(obj[i], noquote)); 1031 } 1032 1033 return '[' + list.join(',') + ']'; 1034 } 1035 1036 for (prop in obj) { 1037 if (obj.hasOwnProperty(prop)) { 1038 try { 1039 val = JXG.toJSON(obj[prop], noquote); 1040 } catch (e2) { 1041 val = ''; 1042 } 1043 1044 if (noquote) { 1045 list.push(prop + ':' + val); 1046 } else { 1047 list.push('"' + prop + '":' + val); 1048 } 1049 } 1050 } 1051 1052 return '{' + list.join(',') + '} '; 1053 } 1054 return 'null'; 1055 case 'string': 1056 return '\'' + obj.replace(/(["'])/g, '\\$1') + '\''; 1057 case 'number': 1058 case 'boolean': 1059 return obj.toString(); 1060 } 1061 1062 return '0'; 1063 }, 1064 1065 /** 1066 * Resets visPropOld. 1067 * @param {JXG.GeometryElement} el 1068 * @returns {GeometryElement} 1069 */ 1070 clearVisPropOld: function (el) { 1071 el.visPropOld = { 1072 cssclass: '', 1073 cssdefaultstyle: '', 1074 cssstyle: '', 1075 fillcolor: '', 1076 fillopacity: '', 1077 firstarrow: false, 1078 fontsize: -1, 1079 lastarrow: false, 1080 left: -100000, 1081 linecap: '', 1082 shadow: false, 1083 strokecolor: '', 1084 strokeopacity: '', 1085 strokewidth: '', 1086 transitionduration: 0, 1087 top: -100000, 1088 visible: null 1089 }; 1090 1091 return el; 1092 }, 1093 1094 /** 1095 * Checks if an object contains a key, whose value equals to val. 1096 * @param {Object} obj 1097 * @param val 1098 * @returns {Boolean} 1099 */ 1100 isInObject: function (obj, val) { 1101 var el; 1102 1103 for (el in obj) { 1104 if (obj.hasOwnProperty(el)) { 1105 if (obj[el] === val) { 1106 return true; 1107 } 1108 } 1109 } 1110 1111 return false; 1112 }, 1113 1114 /** 1115 * Replaces all occurences of & by &, > by >, and < by <. 1116 * @param {String} str 1117 * @returns {String} 1118 */ 1119 escapeHTML: function (str) { 1120 return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); 1121 }, 1122 1123 /** 1124 * Eliminates all substrings enclosed by < and > and replaces all occurences of 1125 * & by &, > by >, and < by <. 1126 * @param {String} str 1127 * @returns {String} 1128 */ 1129 unescapeHTML: function (str) { 1130 // this regex is NOT insecure. We are replacing everything found with '' 1131 /*jslint regexp:true*/ 1132 return str.replace(/<\/?[^>]+>/gi, '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); 1133 }, 1134 1135 /** 1136 * Makes a string lower case except for the first character which will be upper case. 1137 * @param {String} str Arbitrary string 1138 * @returns {String} The capitalized string. 1139 */ 1140 capitalize: function (str) { 1141 return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase(); 1142 }, 1143 1144 /** 1145 * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes. 1146 * @param {String} str 1147 * @returns {String} 1148 */ 1149 trimNumber: function (str) { 1150 str = str.replace(/^0+/, ''); 1151 str = str.replace(/0+$/, ''); 1152 1153 if (str[str.length - 1] === '.' || str[str.length - 1] === ',') { 1154 str = str.slice(0, -1); 1155 } 1156 1157 if (str[0] === '.' || str[0] === ',') { 1158 str = "0" + str; 1159 } 1160 1161 return str; 1162 }, 1163 1164 /** 1165 * Filter an array of elements. 1166 * @param {Array} list 1167 * @param {Object|function} filter 1168 * @returns {Array} 1169 */ 1170 filterElements: function (list, filter) { 1171 var i, f, item, flower, value, visPropValue, pass, 1172 l = list.length, 1173 result = []; 1174 1175 if (typeof filter !== 'function' && typeof filter !== 'object') { 1176 return result; 1177 } 1178 1179 for (i = 0; i < l; i++) { 1180 pass = true; 1181 item = list[i]; 1182 1183 if (typeof filter === 'object') { 1184 for (f in filter) { 1185 if (filter.hasOwnProperty(f)) { 1186 flower = f.toLowerCase(); 1187 1188 if (typeof item[f] === 'function') { 1189 value = item[f](); 1190 } else { 1191 value = item[f]; 1192 } 1193 1194 if (item.visProp && typeof item.visProp[flower] === 'function') { 1195 visPropValue = item.visProp[flower](); 1196 } else { 1197 visPropValue = item.visProp && item.visProp[flower]; 1198 } 1199 1200 if (typeof filter[f] === 'function') { 1201 pass = filter[f](value) || filter[f](visPropValue); 1202 } else { 1203 pass = (value === filter[f] || visPropValue === filter[f]); 1204 } 1205 1206 if (!pass) { 1207 break; 1208 } 1209 } 1210 } 1211 } else if (typeof filter === 'function') { 1212 pass = filter(item); 1213 } 1214 1215 if (pass) { 1216 result.push(item); 1217 } 1218 } 1219 1220 return result; 1221 }, 1222 1223 /** 1224 * Remove all leading and trailing whitespaces from a given string. 1225 * @param {String} str 1226 * @returns {String} 1227 */ 1228 trim: function (str) { 1229 // str = str.replace(/^\s+/, ''); 1230 // str = str.replace(/\s+$/, ''); 1231 // 1232 // return str; 1233 return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); 1234 }, 1235 1236 /** 1238 * @param {String} str 1239 * @param {Boolean} caja 1240 * @returns {String} Sanitized string 1241 */ 1242 sanitizeHTML: function (str, caja) { 1243 if (typeof html_sanitize === 'function' && caja) { 1244 return html_sanitize(str, function () {}, function (id) { return id; }); 1245 } 1246 1247 if (str) { 1248 str = str.replace(/</g, '<').replace(/>/g, '>'); 1249 } 1250 1251 return str; 1252 }, 1253 1254 /** 1255 * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value. 1256 * @param {*} s 1257 * @returns {*} s.Value() if s is an element of type slider, s otherwise 1258 */ 1259 evalSlider: function (s) { 1260 if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === 'function') { 1261 return s.Value(); 1262 } 1263 1264 return s; 1265 } 1266 }); 1267 1268 return JXG; 1269 }); 1270