1 /* 2 Copyright 2008-2016 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' && !JXG.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 neither undefined nor null. You should not use this together with global 175 * variables! 176 * @param v A variable of any type. 177 * @returns {Boolean} True, if v is neither undefined nor null. 178 */ 179 exists: (function (undef) { 180 return function (v) { 181 return !(v === undef || v === null); 182 }; 183 }()), 184 185 /** 186 * Handle default parameters. 187 * @param v Given value 188 * @param d Default value 189 * @returns <tt>d</tt>, if <tt>v</tt> is undefined or null. 190 */ 191 def: function (v, d) { 192 if (JXG.exists(v)) { 193 return v; 194 } 195 196 return d; 197 }, 198 199 /** 200 * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value. 201 * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>. 202 * @returns {Boolean} String typed boolean value converted to boolean. 203 */ 204 str2Bool: function (s) { 205 if (!JXG.exists(s)) { 206 return true; 207 } 208 209 if (typeof s === 'boolean') { 210 return s; 211 } 212 213 if (JXG.isString(s)) { 214 return (s.toLowerCase() === 'true'); 215 } 216 217 return false; 218 }, 219 220 /** 221 * Convert a String, a number or a function into a function. This method is used in Transformation.js 222 * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given 223 * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param 224 * values is of type string. 225 * @param {Array} param An array containing strings, numbers, or functions. 226 * @param {Number} n Length of <tt>param</tt>. 227 * @returns {Function} A function taking one parameter k which specifies the index of the param element 228 * to evaluate. 229 */ 230 createEvalFunction: function (board, param, n) { 231 var f = [], i; 232 233 for (i = 0; i < n; i++) { 234 f[i] = JXG.createFunction(param[i], board, '', true); 235 } 236 237 return function (k) { 238 return f[k](); 239 }; 240 }, 241 242 /** 243 * Convert a String, number or function into a function. 244 * @param {String|Number|Function} term A variable of type string, function or number. 245 * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given 246 * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param 247 * values is of type string. 248 * @param {String} variableName Only required if evalGeonext is set to true. Describes the variable name 249 * of the variable in a GEONE<sub>X</sub>T string given as term. 250 * @param {Boolean} [evalGeonext=true] Set this true, if term should be treated as a GEONE<sub>X</sub>T string. 251 * @returns {Function} A function evaluation the value given by term or null if term is not of type string, 252 * function or number. 253 */ 254 createFunction: function (term, board, variableName, evalGeonext) { 255 var f = null; 256 257 if ((!JXG.exists(evalGeonext) || evalGeonext) && JXG.isString(term)) { 258 // Convert GEONExT syntax into JavaScript syntax 259 //newTerm = JXG.GeonextParser.geonext2JS(term, board); 260 //return new Function(variableName,'return ' + newTerm + ';'); 261 262 //term = JXG.GeonextParser.replaceNameById(term, board); 263 //term = JXG.GeonextParser.geonext2JS(term, board); 264 f = board.jc.snippet(term, true, variableName, true); 265 } else if (JXG.isFunction(term)) { 266 f = term; 267 } else if (JXG.isNumber(term)) { 268 /** @ignore */ 269 f = function () { 270 return term; 271 }; 272 } else if (JXG.isString(term)) { 273 // In case of string function like fontsize 274 /** @ignore */ 275 f = function () { 276 return term; 277 }; 278 } 279 280 if (f !== null) { 281 f.origin = term; 282 } 283 284 return f; 285 }, 286 287 /** 288 * Test if the parents array contains existing points. If instead parents contains coordinate arrays or function returning coordinate arrays 289 * free points with these coordinates are created. 290 * 291 * @param {JXG.Board} board Board object 292 * @param {Array} parents Array containing parent elements for a new object. This array may contain 293 * <ul> 294 * <li> {@link JXG.Point} objects 295 * <li> {@link JXG.Element#name} of {@link JXG.Point} objects 296 * <li> {@link JXG.Element#id} of {@link JXG.Point} objects 297 * <li> Coordinates of points given as array of numbers of length two or three, e.g. [2, 3]. 298 * <li> Coordinates of points given as array of functions of length two or three. Each function returns one coordinate, e.g. 299 * [function(){ return 2; }, function(){ return 3; }] 300 * <li> Function returning coordinates, e.g. function() { return [2, 3]; } 301 * </ul> 302 * In the last three cases a new point will be created. 303 * @param {String} attrClass Main attribute class of newly created points, see {@link JXG@copyAttributes} 304 * @param {Array} attrArray List of subtype attributes for the newly created points. The list of subtypes is mapped to the list of new points. 305 * @returns {Array} List of newly created {@link JXG.Point} elements or false if not all returned elements are points. 306 */ 307 providePoints: function (board, parents, attributes, attrClass, attrArray) { 308 var i, j, 309 len, 310 lenAttr = 0, 311 points = [], attr, val; 312 313 if (!this.isArray(parents)) { 314 parents = [parents]; 315 } 316 len = parents.length; 317 if (JXG.exists(attrArray)) { 318 lenAttr = attrArray.length; 319 } 320 if (lenAttr === 0) { 321 attr = this.copyAttributes(attributes, board.options, attrClass); 322 } 323 324 for (i = 0; i < len; ++i) { 325 if (lenAttr > 0) { 326 j = Math.min(i, lenAttr - 1); 327 attr = this.copyAttributes(attributes, board.options, attrClass, attrArray[j]); 328 } 329 if (this.isArray(parents[i]) && parents[i].length > 1) { 330 points.push(board.create('point', parents[i], attr)); 331 } else if (this.isFunction(parents[i])) { 332 val = parents[i](); 333 if (this.isArray(val) && (val.length > 1)) { 334 points.push(board.create('point', [parents[i]], attr)); 335 } 336 } else { 337 points.push(board.select(parents[i])); 338 } 339 340 if (!this.isPoint(points[i])) { 341 return false; 342 } 343 } 344 345 return points; 346 }, 347 348 /** 349 * Generates a function which calls the function fn in the scope of owner. 350 * @param {Function} fn Function to call. 351 * @param {Object} owner Scope in which fn is executed. 352 * @returns {Function} A function with the same signature as fn. 353 */ 354 bind: function (fn, owner) { 355 return function () { 356 return fn.apply(owner, arguments); 357 }; 358 }, 359 360 /** 361 * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value 362 * is just returned. 363 * @param val Could be anything. Preferably a number or a function. 364 * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned. 365 */ 366 evaluate: function (val) { 367 if (JXG.isFunction(val)) { 368 return val(); 369 } 370 371 return val; 372 }, 373 374 /** 375 * Search an array for a given value. 376 * @param {Array} array 377 * @param value 378 * @param {String} [sub] Use this property if the elements of the array are objects. 379 * @returns {Number} The index of the first appearance of the given value, or 380 * <tt>-1</tt> if the value was not found. 381 */ 382 indexOf: function (array, value, sub) { 383 var i, s = JXG.exists(sub); 384 385 if (Array.indexOf && !s) { 386 return array.indexOf(value); 387 } 388 389 for (i = 0; i < array.length; i++) { 390 if ((s && array[i][sub] === value) || (!s && array[i] === value)) { 391 return i; 392 } 393 } 394 395 return -1; 396 }, 397 398 /** 399 * Eliminates duplicate entries in an array consisting of numbers and strings. 400 * @param {Array} a An array of numbers and/or strings. 401 * @returns {Array} The array with duplicate entries eliminated. 402 */ 403 eliminateDuplicates: function (a) { 404 var i, 405 len = a.length, 406 result = [], 407 obj = {}; 408 409 for (i = 0; i < len; i++) { 410 obj[a[i]] = 0; 411 } 412 413 for (i in obj) { 414 if (obj.hasOwnProperty(i)) { 415 result.push(i); 416 } 417 } 418 419 return result; 420 }, 421 422 /** 423 * Swaps to array elements. 424 * @param {Array} arr 425 * @param {Number} i 426 * @param {Number} j 427 * @returns {Array} Reference to the given array. 428 */ 429 swap: function (arr, i, j) { 430 var tmp; 431 432 tmp = arr[i]; 433 arr[i] = arr[j]; 434 arr[j] = tmp; 435 436 return arr; 437 }, 438 439 /** 440 * Generates a copy of an array and removes the duplicate entries. The original 441 * Array will be altered. 442 * @param {Array} arr 443 * @returns {Array} 444 */ 445 uniqueArray: function (arr) { 446 var i, j, isArray, ret = []; 447 448 if (arr.length === 0) { 449 return []; 450 } 451 452 for (i = 0; i < arr.length; i++) { 453 isArray = JXG.isArray(arr[i]); 454 455 for (j = i + 1; j < arr.length; j++) { 456 if (isArray && JXG.cmpArrays(arr[i], arr[j])) { 457 arr[i] = []; 458 } else if (!isArray && arr[i] === arr[j]) { 459 arr[i] = ''; 460 } 461 } 462 } 463 464 j = 0; 465 466 for (i = 0; i < arr.length; i++) { 467 isArray = JXG.isArray(arr[i]); 468 469 if (!isArray && arr[i] !== '') { 470 ret[j] = arr[i]; 471 j += 1; 472 } else if (isArray && arr[i].length !== 0) { 473 ret[j] = (arr[i].slice(0)); 474 j += 1; 475 } 476 } 477 478 arr = ret; 479 return ret; 480 }, 481 482 /** 483 * Checks if an array contains an element equal to <tt>val</tt> but does not check the type! 484 * @param {Array} arr 485 * @param val 486 * @returns {Boolean} 487 */ 488 isInArray: function (arr, val) { 489 return JXG.indexOf(arr, val) > -1; 490 }, 491 492 /** 493 * Converts an array of {@link JXG.Coords} objects into a coordinate matrix. 494 * @param {Array} coords 495 * @param {Boolean} split 496 * @returns {Array} 497 */ 498 coordsArrayToMatrix: function (coords, split) { 499 var i, 500 x = [], 501 m = []; 502 503 for (i = 0; i < coords.length; i++) { 504 if (split) { 505 x.push(coords[i].usrCoords[1]); 506 m.push(coords[i].usrCoords[2]); 507 } else { 508 m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]); 509 } 510 } 511 512 if (split) { 513 m = [x, m]; 514 } 515 516 return m; 517 }, 518 519 /** 520 * Compare two arrays. 521 * @param {Array} a1 522 * @param {Array} a2 523 * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value. 524 */ 525 cmpArrays: function (a1, a2) { 526 var i; 527 528 // trivial cases 529 if (a1 === a2) { 530 return true; 531 } 532 533 if (a1.length !== a2.length) { 534 return false; 535 } 536 537 for (i = 0; i < a1.length; i++) { 538 if (this.isArray(a1[i]) && this.isArray(a2[i])) { 539 if (!this.cmpArrays(a1[i], a2[i])) { 540 return false; 541 } 542 } 543 else if (a1[i] !== a2[i]) { 544 return false; 545 } 546 } 547 548 return true; 549 }, 550 551 /** 552 * Removes an element from the given array 553 * @param {Array} ar 554 * @param el 555 * @returns {Array} 556 */ 557 removeElementFromArray: function (ar, el) { 558 var i; 559 560 for (i = 0; i < ar.length; i++) { 561 if (ar[i] === el) { 562 ar.splice(i, 1); 563 return ar; 564 } 565 } 566 567 return ar; 568 }, 569 570 /** 571 * Truncate a number <tt>n</tt> after <tt>p</tt> decimals. 572 * @param {Number} n 573 * @param {Number} p 574 * @returns {Number} 575 */ 576 trunc: function (n, p) { 577 p = JXG.def(p, 0); 578 579 /*jslint bitwise: true*/ 580 581 /* 582 * The performance gain of this bitwise trick is marginal and the behavior 583 * is different from toFixed: toFixed rounds, the bitweise operation truncateds 584 */ 585 //if (p === 0) { 586 // n = ~n; 587 // n = ~n; 588 //} else { 589 n = n.toFixed(p); 590 //} 591 592 return n; 593 }, 594 595 /** 596 * Truncate a number <tt>val</tt> automatically. 597 * @param val 598 * @returns {Number} 599 */ 600 autoDigits: function (val) { 601 var x = Math.abs(val); 602 603 if (x > 0.1) { 604 x = val.toFixed(2); 605 } else if (x >= 0.01) { 606 x = val.toFixed(4); 607 } else if (x >= 0.0001) { 608 x = val.toFixed(6); 609 } else { 610 x = val; 611 } 612 return x; 613 }, 614 615 /** 616 * Extracts the keys of a given object. 617 * @param object The object the keys are to be extracted 618 * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected 619 * the object owns itself and not some other object in the prototype chain. 620 * @returns {Array} All keys of the given object. 621 */ 622 keys: function (object, onlyOwn) { 623 var keys = [], property; 624 625 // the caller decides if we use hasOwnProperty 626 /*jslint forin:true*/ 627 for (property in object) { 628 if (onlyOwn) { 629 if (object.hasOwnProperty(property)) { 630 keys.push(property); 631 } 632 } else { 633 keys.push(property); 634 } 635 } 636 /*jslint forin:false*/ 637 638 return keys; 639 }, 640 641 /** 642 * This outputs an object with a base class reference to the given object. This is useful if 643 * you need a copy of an e.g. attributes object and want to overwrite some of the attributes 644 * without changing the original object. 645 * @param {Object} obj Object to be embedded. 646 * @returns {Object} An object with a base class reference to <tt>obj</tt>. 647 */ 648 clone: function (obj) { 649 var cObj = {}; 650 651 cObj.prototype = obj; 652 653 return cObj; 654 }, 655 656 /** 657 * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object 658 * to the new one. Warning: The copied properties of obj2 are just flat copies. 659 * @param {Object} obj Object to be copied. 660 * @param {Object} obj2 Object with data that is to be copied to the new one as well. 661 * @returns {Object} Copy of given object including some new/overwritten data from obj2. 662 */ 663 cloneAndCopy: function (obj, obj2) { 664 var r, 665 cObj = function () {}; 666 667 cObj.prototype = obj; 668 669 // no hasOwnProperty on purpose 670 /*jslint forin:true*/ 671 /*jshint forin:true*/ 672 673 for (r in obj2) { 674 cObj[r] = obj2[r]; 675 } 676 677 /*jslint forin:false*/ 678 /*jshint forin:false*/ 679 680 return cObj; 681 }, 682 683 /** 684 * Recursively merges obj2 into obj1. Contrary to {@link JXG#deepCopy} this won't create a new object 685 * but instead will 686 * @param {Object} obj1 687 * @param {Object} obj2 688 * @returns {Object} 689 */ 690 merge: function (obj1, obj2) { 691 var i, j; 692 693 for (i in obj2) { 694 if (obj2.hasOwnProperty(i)) { 695 if (this.isArray(obj2[i])) { 696 if (!obj1[i]) { 697 obj1[i] = []; 698 } 699 700 for (j = 0; j < obj2[i].length; j++) { 701 if (typeof obj2[i][j] === 'object') { 702 obj1[i][j] = this.merge(obj1[i][j], obj2[i][j]); 703 } else { 704 obj1[i][j] = obj2[i][j]; 705 } 706 } 707 } else if (typeof obj2[i] === 'object') { 708 if (!obj1[i]) { 709 obj1[i] = {}; 710 } 711 712 obj1[i] = this.merge(obj1[i], obj2[i]); 713 } else { 714 obj1[i] = obj2[i]; 715 } 716 } 717 } 718 719 return obj1; 720 }, 721 722 /** 723 * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp. 724 * element-wise instead of just copying the reference. If a second object is supplied, the two objects 725 * are merged into one object. The properties of the second object have priority. 726 * @param {Object} obj This object will be copied. 727 * @param {Object} obj2 This object will merged into the newly created object 728 * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes 729 * @returns {Object} copy of obj or merge of obj and obj2. 730 */ 731 deepCopy: function (obj, obj2, toLower) { 732 var c, i, prop, i2; 733 734 toLower = toLower || false; 735 736 if (typeof obj !== 'object' || obj === null) { 737 return obj; 738 } 739 740 // missing hasOwnProperty is on purpose in this function 741 if (this.isArray(obj)) { 742 c = []; 743 for (i = 0; i < obj.length; i++) { 744 prop = obj[i]; 745 if (typeof prop === 'object') { 746 // We certainly do not want to recurse into a JSXGraph object. 747 // This would for sure result in an infinite recursion. 748 // As alternative we copy the id of the object. 749 if (this.exists(prop.board)) { 750 c[i] = prop.id; 751 } else { 752 c[i] = this.deepCopy(prop); 753 } 754 } else { 755 c[i] = prop; 756 } 757 } 758 } else { 759 c = {}; 760 for (i in obj) { 761 i2 = toLower ? i.toLowerCase() : i; 762 prop = obj[i]; 763 if (prop !== null && typeof prop === 'object') { 764 if (this.exists(prop.board)) { 765 c[i2] = prop.id; 766 } else { 767 c[i2] = this.deepCopy(prop); 768 } 769 } else { 770 c[i2] = prop; 771 } 772 } 773 774 for (i in obj2) { 775 i2 = toLower ? i.toLowerCase() : i; 776 777 prop = obj2[i]; 778 if (typeof prop === 'object') { 779 if (JXG.isArray(prop) || !JXG.exists(c[i2])) { 780 c[i2] = this.deepCopy(prop); 781 } else { 782 c[i2] = this.deepCopy(c[i2], prop, toLower); 783 } 784 } else { 785 c[i2] = prop; 786 } 787 } 788 } 789 790 return c; 791 }, 792 793 /** 794 * Generates an attributes object that is filled with default values from the Options object 795 * and overwritten by the user speciified attributes. 796 * @param {Object} attributes user specified attributes 797 * @param {Object} options defaults options 798 * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'. 799 * @returns {Object} The resulting attributes object 800 */ 801 copyAttributes: function (attributes, options, s) { 802 var a, i, len, o, isAvail, 803 primitives = { 804 'circle': 1, 805 'curve': 1, 806 'image': 1, 807 'line': 1, 808 'point': 1, 809 'polygon': 1, 810 'text': 1, 811 'ticks': 1, 812 'integral': 1 813 }; 814 815 816 len = arguments.length; 817 if (len < 3 || primitives[s]) { 818 // default options from Options.elements 819 a = JXG.deepCopy(options.elements, null, true); 820 } else { 821 a = {}; 822 } 823 824 // Only the layer of the main element is set. 825 if (len < 4 && this.exists(s) && this.exists(options.layer[s])) { 826 a.layer = options.layer[s]; 827 } 828 829 // default options from specific elements 830 o = options; 831 isAvail = true; 832 for (i = 2; i < len; i++) { 833 if (JXG.exists(o[arguments[i]])) { 834 o = o[arguments[i]]; 835 } else { 836 isAvail = false; 837 break; 838 } 839 } 840 if (isAvail) { 841 a = JXG.deepCopy(a, o, true); 842 } 843 844 // options from attributes 845 o = attributes; 846 isAvail = true; 847 for (i = 3; i < len; i++) { 848 if (JXG.exists(o[arguments[i]])) { 849 o = o[arguments[i]]; 850 } else { 851 isAvail = false; 852 break; 853 } 854 } 855 if (isAvail) { 856 this.extend(a, o, null, true); 857 } 858 859 // Special treatment of labels 860 o = options; 861 isAvail = true; 862 for (i = 2; i < len; i++) { 863 if (JXG.exists(o[arguments[i]])) { 864 o = o[arguments[i]]; 865 } else { 866 isAvail = false; 867 break; 868 } 869 } 870 if (isAvail && JXG.exists(o.label)) { 871 a.label = JXG.deepCopy(o.label, a.label); 872 } 873 a.label = JXG.deepCopy(options.label, a.label); 874 875 return a; 876 }, 877 878 /** 879 * Copy all prototype methods from object "superObject" to object 880 * "subObject". The constructor of superObject will be available 881 * in subObject as subObject.constructor[constructorName]. 882 * @param {Object} subObj A JavaScript object which receives new methods. 883 * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject 884 * @returns {String} constructorName Under this name the constructor of superObj will be available 885 * in subObject. 886 * @private 887 */ 888 copyPrototypeMethods: function (subObject, superObject, constructorName) { 889 var key; 890 891 subObject.prototype[constructorName] = superObject.prototype.constructor; 892 for (key in superObject.prototype) { 893 subObject.prototype[key] = superObject.prototype[key]; 894 } 895 }, 896 897 /** 898 * Converts a JavaScript object into a JSON string. 899 * @param {Object} obj A JavaScript object, functions will be ignored. 900 * @param {Boolean} [noquote=false] No quotes around the name of a property. 901 * @returns {String} The given object stored in a JSON string. 902 */ 903 toJSON: function (obj, noquote) { 904 var list, prop, i, s, val; 905 906 noquote = JXG.def(noquote, false); 907 908 // check for native JSON support: 909 if (typeof JSON && JSON.stringify && !noquote) { 910 try { 911 s = JSON.stringify(obj); 912 return s; 913 } catch (e) { 914 // if something goes wrong, e.g. if obj contains functions we won't return 915 // and use our own implementation as a fallback 916 } 917 } 918 919 switch (typeof obj) { 920 case 'object': 921 if (obj) { 922 list = []; 923 924 if (JXG.isArray(obj)) { 925 for (i = 0; i < obj.length; i++) { 926 list.push(JXG.toJSON(obj[i], noquote)); 927 } 928 929 return '[' + list.join(',') + ']'; 930 } 931 932 for (prop in obj) { 933 if (obj.hasOwnProperty(prop)) { 934 try { 935 val = JXG.toJSON(obj[prop], noquote); 936 } catch (e2) { 937 val = ''; 938 } 939 940 if (noquote) { 941 list.push(prop + ':' + val); 942 } else { 943 list.push('"' + prop + '":' + val); 944 } 945 } 946 } 947 948 return '{' + list.join(',') + '} '; 949 } 950 return 'null'; 951 case 'string': 952 return '\'' + obj.replace(/(["'])/g, '\\$1') + '\''; 953 case 'number': 954 case 'boolean': 955 return obj.toString(); 956 } 957 958 return '0'; 959 }, 960 961 /** 962 * Resets visPropOld. 963 * @param {JXG.GeometryElement} el 964 * @returns {GeometryElement} 965 */ 966 clearVisPropOld: function (el) { 967 el.visPropOld = { 968 strokecolor: '', 969 strokeopacity: '', 970 strokewidth: '', 971 fillcolor: '', 972 fillopacity: '', 973 shadow: false, 974 firstarrow: false, 975 lastarrow: false, 976 cssclass: '', 977 fontsize: -1, 978 left: -100000, 979 top: -100000 980 }; 981 982 return el; 983 }, 984 985 /** 986 * Checks if an object contains a key, whose value equals to val. 987 * @param {Object} obj 988 * @param val 989 * @returns {Boolean} 990 */ 991 isInObject: function (obj, val) { 992 var el; 993 994 for (el in obj) { 995 if (obj.hasOwnProperty(el)) { 996 if (obj[el] === val) { 997 return true; 998 } 999 } 1000 } 1001 1002 return false; 1003 }, 1004 1005 /** 1006 * Replaces all occurences of & by &, > by >, and < by <. 1007 * @param {String} str 1008 * @returns {String} 1009 */ 1010 escapeHTML: function (str) { 1011 return str.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); 1012 }, 1013 1014 /** 1015 * Eliminates all substrings enclosed by < and > and replaces all occurences of 1016 * & by &, > by >, and < by <. 1017 * @param {String} str 1018 * @returns {String} 1019 */ 1020 unescapeHTML: function (str) { 1021 // this regex is NOT insecure. We are replacing everything found with '' 1022 /*jslint regexp:true*/ 1023 return str.replace(/<\/?[^>]+>/gi, '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); 1024 }, 1025 1026 /** 1027 * Makes a string lower case except for the first character which will be upper case. 1028 * @param {String} str Arbitrary string 1029 * @returns {String} The capitalized string. 1030 */ 1031 capitalize: function (str) { 1032 return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase(); 1033 }, 1034 1035 /** 1036 * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes. 1037 * @param {String} str 1038 * @returns {String} 1039 */ 1040 trimNumber: function (str) { 1041 str = str.replace(/^0+/, ''); 1042 str = str.replace(/0+$/, ''); 1043 1044 if (str[str.length - 1] === '.' || str[str.length - 1] === ',') { 1045 str = str.slice(0, -1); 1046 } 1047 1048 if (str[0] === '.' || str[0] === ',') { 1049 str = "0" + str; 1050 } 1051 1052 return str; 1053 }, 1054 1055 /** 1056 * Filter an array of elements. 1057 * @param {Array} list 1058 * @param {Object|function} filter 1059 * @returns {Array} 1060 */ 1061 filterElements: function (list, filter) { 1062 var i, f, item, flower, value, visPropValue, pass, 1063 l = list.length, 1064 result = []; 1065 1066 if (typeof filter !== 'function' && typeof filter !== 'object') { 1067 return result; 1068 } 1069 1070 for (i = 0; i < l; i++) { 1071 pass = true; 1072 item = list[i]; 1073 1074 if (typeof filter === 'object') { 1075 for (f in filter) { 1076 if (filter.hasOwnProperty(f)) { 1077 flower = f.toLowerCase(); 1078 1079 if (typeof item[f] === 'function') { 1080 value = item[f](); 1081 } else { 1082 value = item[f]; 1083 } 1084 1085 if (item.visProp && typeof item.visProp[flower] === 'function') { 1086 visPropValue = item.visProp[flower](); 1087 } else { 1088 visPropValue = item.visProp && item.visProp[flower]; 1089 } 1090 1091 if (typeof filter[f] === 'function') { 1092 pass = filter[f](value) || filter[f](visPropValue); 1093 } else { 1094 pass = (value === filter[f] || visPropValue === filter[f]); 1095 } 1096 1097 if (!pass) { 1098 break; 1099 } 1100 } 1101 } 1102 } else if (typeof filter === 'function') { 1103 pass = filter(item); 1104 } 1105 1106 if (pass) { 1107 result.push(item); 1108 } 1109 } 1110 1111 return result; 1112 }, 1113 1114 /** 1115 * Remove all leading and trailing whitespaces from a given string. 1116 * @param {String} str 1117 * @returns {String} 1118 */ 1119 trim: function (str) { 1120 str = str.replace(/^\s+/, ''); 1121 str = str.replace(/\s+$/, ''); 1122 1123 return str; 1124 }, 1125 1126 /** 1127 * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available. 1128 * @param {String} str 1129 * @param {Boolean} caja 1130 * @returns {String} Sanitized string 1131 */ 1132 sanitizeHTML: function (str, caja) { 1133 if (typeof html_sanitize === 'function' && caja) { 1134 return html_sanitize(str, function () {}, function (id) { return id; }); 1135 } 1136 1137 if (str) { 1138 str = str.replace(/</g, '<').replace(/>/g, '>'); 1139 } 1140 1141 return str; 1142 }, 1143 1144 /** 1145 * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value. 1146 * @param {*} s 1147 * @returns {*} s.Value() if s is an element of type slider, s otherwise 1148 */ 1149 evalSlider: function (s) { 1150 if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === 'function') { 1151 s = s.Value(); 1152 } 1153 1154 return s; 1155 } 1156 }); 1157 1158 return JXG; 1159 }); 1160