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