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, window: true*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 base/constants 39 base/coords 40 base/element 41 parser/geonext 42 math/statistics 43 utils/env 44 utils/type 45 */ 46 47 /** 48 * @fileoverview In this file the Text element is defined. 49 */ 50 51 define([ 52 'jxg', 'base/constants', 'base/coords', 'base/element', 'parser/geonext', 'math/statistics', 53 'utils/env', 'utils/type', 'math/math', 'base/coordselement' 54 ], function (JXG, Const, Coords, GeometryElement, GeonextParser, Statistics, Env, Type, Mat, CoordsElement) { 55 56 "use strict"; 57 58 var priv = { 59 HTMLSliderInputEventHandler: function () { 60 this._val = parseFloat(this.rendNodeRange.value); 61 this.rendNodeOut.value = this.rendNodeRange.value; 62 this.board.update(); 63 } 64 }; 65 66 /** 67 * Construct and handle texts. 68 * 69 * The coordinates can be relative to the coordinates of an element 70 * given in {@link JXG.Options#text.anchor}. 71 * 72 * MathJax, HTML and GEONExT syntax can be handled. 73 * @class Creates a new text object. Do not use this constructor to create a text. Use {@link JXG.Board#create} with 74 * type {@link Text} instead. 75 * @augments JXG.GeometryElement 76 * @augments JXG.CoordsElement 77 * @param {string|JXG.Board} board The board the new text is drawn on. 78 * @param {Array} coordinates An array with the user coordinates of the text. 79 * @param {Object} attributes An object containing visual properties and optional a name and a id. 80 * @param {string|function} content A string or a function returning a string. 81 * 82 */ 83 JXG.Text = function (board, coords, attributes, content) { 84 this.constructor(board, attributes, Const.OBJECT_TYPE_TEXT, Const.OBJECT_CLASS_TEXT); 85 86 this.element = this.board.select(attributes.anchor); 87 this.coordsConstructor(coords, this.visProp.islabel); 88 89 this.content = ''; 90 this.plaintext = ''; 91 this.plaintextOld = null; 92 this.orgText = ''; 93 94 this.needsSizeUpdate = false; 95 this.hiddenByParent = false; 96 97 this.size = [1.0, 1.0]; 98 this.id = this.board.setId(this, 'T'); 99 100 // Set text before drawing 101 this._setUpdateText(content); 102 this.updateText(); 103 104 this.board.renderer.drawText(this); 105 this.board.finalizeAdding(this); 106 107 if (typeof this.content === 'string') { 108 this.notifyParents(this.content); 109 } 110 this.elType = 'text'; 111 112 this.methodMap = Type.deepCopy(this.methodMap, { 113 setText: 'setTextJessieCode', 114 // free: 'free', 115 move: 'setCoords' 116 }); 117 }; 118 119 JXG.Text.prototype = new GeometryElement(); 120 Type.copyPrototypeMethods(JXG.Text, CoordsElement, 'coordsConstructor'); 121 122 JXG.extend(JXG.Text.prototype, /** @lends JXG.Text.prototype */ { 123 /** 124 * @private 125 * Test if the the screen coordinates (x,y) are in a small stripe 126 * at the left side or at the right side of the text. 127 * Sensitivity is set in this.board.options.precision.hasPoint. 128 * If dragarea is set to 'all' (default), tests if the the screen 129 * coordinates (x,y) are in within the text boundary. 130 * @param {Number} x 131 * @param {Number} y 132 * @return {Boolean} 133 */ 134 hasPoint: function (x, y) { 135 var lft, rt, top, bot, 136 r = this.board.options.precision.hasPoint; 137 138 if (this.transformations.length > 0) { 139 /** 140 * Transform the mouse/touch coordinates 141 * back to the original position of the text. 142 */ 143 lft = Mat.matVecMult(Mat.inverse(this.board.renderer.joinTransforms(this, this.transformations)), [1, x, y]); 144 x = lft[1]; 145 y = lft[2]; 146 } 147 148 if (this.visProp.anchorx === 'right') { 149 lft = this.coords.scrCoords[1] - this.size[0]; 150 } else if (this.visProp.anchorx === 'middle') { 151 lft = this.coords.scrCoords[1] - 0.5 * this.size[0]; 152 } else { 153 lft = this.coords.scrCoords[1]; 154 } 155 rt = lft + this.size[0]; 156 157 if (this.visProp.anchory === 'top') { 158 bot = this.coords.scrCoords[2] + this.size[1]; 159 } else if (this.visProp.anchory === 'middle') { 160 bot = this.coords.scrCoords[2] + 0.5 * this.size[1]; 161 } else { 162 bot = this.coords.scrCoords[2]; 163 } 164 top = bot - this.size[1]; 165 166 if (this.visProp.dragarea === 'all') { 167 return x >= lft - r && x < rt + r && y >= top - r && y <= bot + r; 168 } 169 170 return (y >= top - r && y <= bot + r) && 171 ((x >= lft - r && x <= lft + 2 * r) || 172 (x >= rt - 2 * r && x <= rt + r)); 173 }, 174 175 /** 176 * This sets the updateText function of this element that depending on the type of text content passed. 177 * Used by {@link JXG.Text#_setText} and {@link JXG.Text} constructor. 178 * @param {String|Function|Number} text 179 * @private 180 */ 181 _setUpdateText: function (text) { 182 var updateText; 183 184 this.orgText = text; 185 if (typeof text === 'function') { 186 this.updateText = function () { 187 if (this.visProp.parse && !this.visProp.usemathjax) { 188 this.plaintext = this.replaceSub(this.replaceSup(this.convertGeonext2CSS(text()))); 189 } else { 190 this.plaintext = text(); 191 } 192 }; 193 } else if (Type.isString(text) && !this.visProp.parse) { 194 this.updateText = function () { 195 this.plaintext = text; 196 }; 197 } else { 198 if (Type.isNumber(text)) { 199 this.content = text.toFixed(this.visProp.digits); 200 } else { 201 if (this.visProp.useasciimathml) { 202 // Convert via ASCIIMathML 203 this.content = "'`" + text + "`'"; 204 } else if (this.visProp.usemathjax) { 205 this.content = "'" + text + "'"; 206 } else { 207 // Converts GEONExT syntax into JavaScript string 208 this.content = this.generateTerm(text); 209 } 210 } 211 updateText = this.board.jc.snippet(this.content, true, '', false); 212 this.updateText = function () { 213 this.plaintext = updateText(); 214 }; 215 } 216 }, 217 218 /** 219 * Defines new content. This is used by {@link JXG.Text#setTextJessieCode} and {@link JXG.Text#setText}. This is required because 220 * JessieCode needs to filter all Texts inserted into the DOM and thus has to replace setText by setTextJessieCode. 221 * @param {String|Function|Number} text 222 * @return {JXG.Text} 223 * @private 224 */ 225 _setText: function (text) { 226 this._setUpdateText(text); 227 228 // First evaluation of the string. 229 // We need this for display='internal' and Canvas 230 this.updateText(); 231 this.prepareUpdate().update().updateRenderer(); 232 233 // We do not call updateSize for the infobox to speed up rendering 234 if (!this.board.infobox || this.id !== this.board.infobox.id) { 235 this.updateSize(); // updateSize() is called at least once. 236 } 237 238 return this; 239 }, 240 241 /** 242 * Defines new content but converts < and > to HTML entities before updating the DOM. 243 * @param {String|function} text 244 */ 245 setTextJessieCode: function (text) { 246 var s; 247 248 this.visProp.castext = text; 249 250 if (typeof text === 'function') { 251 s = function () { 252 return Type.sanitizeHTML(text()); 253 }; 254 } else { 255 if (Type.isNumber(text)) { 256 s = text; 257 } else { 258 s = Type.sanitizeHTML(text); 259 } 260 } 261 262 return this._setText(s); 263 }, 264 265 /** 266 * Defines new content. 267 * @param {String|function} text 268 * @return {JXG.Text} Reference to the text object. 269 */ 270 setText: function (text) { 271 return this._setText(text); 272 }, 273 274 /** 275 * Recompute the width and the height of the text box. 276 * Update array this.size with pixel values. 277 * The result may differ from browser to browser 278 * by some pixels. 279 * In canvas an old IEs we use a very crude estimation of the dimensions of 280 * the textbox. 281 * In JSXGraph this.size is necessary for applying rotations in IE and 282 * for aligning text. 283 */ 284 updateSize: function () { 285 var tmp, s, that; 286 287 if (!Env.isBrowser || this.board.renderer.type === 'no') { 288 return this; 289 } 290 291 /** 292 * offsetWidth and offsetHeight seem to be supported for internal vml elements by IE10+ in IE8 mode. 293 */ 294 if (this.visProp.display === 'html' || this.board.renderer.type === 'vml') { 295 if (JXG.exists(this.rendNode.offsetWidth)) { 296 s = [this.rendNode.offsetWidth, this.rendNode.offsetHeight]; 297 if (s[0] === 0 && s[1] === 0) { // Some browsers need some time to set offsetWidth and offsetHeight 298 that = this; 299 window.setTimeout(function () { 300 that.size = [that.rendNode.offsetWidth, that.rendNode.offsetHeight]; 301 }, 0); 302 } else { 303 this.size = s; 304 } 305 } else { 306 this.size = this.crudeSizeEstimate(); 307 } 308 } else if (this.visProp.display === 'internal') { 309 if (this.board.renderer.type === 'svg') { 310 try { 311 tmp = this.rendNode.getBBox(); 312 this.size = [tmp.width, tmp.height]; 313 } catch (e) {} 314 } else if (this.board.renderer.type === 'canvas') { 315 this.size = this.crudeSizeEstimate(); 316 } 317 } 318 319 return this; 320 }, 321 322 /** 323 * A very crude estimation of the dimensions of the textbox in case nothing else is available. 324 * @return {Array} 325 */ 326 crudeSizeEstimate: function () { 327 return [parseFloat(this.visProp.fontsize) * this.plaintext.length * 0.45, parseFloat(this.visProp.fontsize) * 0.9]; 328 }, 329 330 /** 331 * Decode unicode entities into characters. 332 * @param {String} string 333 * @returns {String} 334 */ 335 utf8_decode : function (string) { 336 return string.replace(/(\w+);/g, function (m, p1) { 337 return String.fromCharCode(parseInt(p1, 16)); 338 }); 339 }, 340 341 /** 342 * Replace _{} by <sub> 343 * @param {String} te String containing _{}. 344 * @returns {String} Given string with _{} replaced by <sub>. 345 */ 346 replaceSub: function (te) { 347 if (!te.indexOf) { 348 return te; 349 } 350 351 var j, 352 i = te.indexOf('_{'); 353 354 // the regexp in here are not used for filtering but to provide some kind of sugar for label creation, 355 // i.e. replacing _{...} with <sub>...</sub>. What is passed would get out anyway. 356 /*jslint regexp: true*/ 357 358 while (i >= 0) { 359 te = te.substr(0, i) + te.substr(i).replace(/_\{/, '<sub>'); 360 j = te.substr(i).indexOf('}'); 361 if (j >= 0) { 362 te = te.substr(0, j) + te.substr(j).replace(/\}/, '</sub>'); 363 } 364 i = te.indexOf('_{'); 365 } 366 367 i = te.indexOf('_'); 368 while (i >= 0) { 369 te = te.substr(0, i) + te.substr(i).replace(/_(.?)/, '<sub>$1</sub>'); 370 i = te.indexOf('_'); 371 } 372 373 return te; 374 }, 375 376 /** 377 * Replace ^{} by <sup> 378 * @param {String} te String containing ^{}. 379 * @returns {String} Given string with ^{} replaced by <sup>. 380 */ 381 replaceSup: function (te) { 382 if (!te.indexOf) { 383 return te; 384 } 385 386 var j, 387 i = te.indexOf('^{'); 388 389 // the regexp in here are not used for filtering but to provide some kind of sugar for label creation, 390 // i.e. replacing ^{...} with <sup>...</sup>. What is passed would get out anyway. 391 /*jslint regexp: true*/ 392 393 while (i >= 0) { 394 te = te.substr(0, i) + te.substr(i).replace(/\^\{/, '<sup>'); 395 j = te.substr(i).indexOf('}'); 396 if (j >= 0) { 397 te = te.substr(0, j) + te.substr(j).replace(/\}/, '</sup>'); 398 } 399 i = te.indexOf('^{'); 400 } 401 402 i = te.indexOf('^'); 403 while (i >= 0) { 404 te = te.substr(0, i) + te.substr(i).replace(/\^(.?)/, '<sup>$1</sup>'); 405 i = te.indexOf('^'); 406 } 407 408 return te; 409 }, 410 411 /** 412 * Return the width of the text element. 413 * @return {Array} [width, height] in pixel 414 */ 415 getSize: function () { 416 return this.size; 417 }, 418 419 /** 420 * Move the text to new coordinates. 421 * @param {number} x 422 * @param {number} y 423 * @return {object} reference to the text object. 424 */ 425 setCoords: function (x, y) { 426 var coordsAnchor, dx, dy; 427 if (Type.isArray(x) && x.length > 1) { 428 y = x[1]; 429 x = x[0]; 430 } 431 432 if (this.visProp.islabel && Type.exists(this.element)) { 433 coordsAnchor = this.element.getLabelAnchor(); 434 dx = (x - coordsAnchor.usrCoords[1]) * this.board.unitX; 435 dy = -(y - coordsAnchor.usrCoords[2]) * this.board.unitY; 436 437 this.relativeCoords.setCoordinates(Const.COORDS_BY_SCREEN, [dx, dy]); 438 } else { 439 /* 440 this.X = function () { 441 return x; 442 }; 443 444 this.Y = function () { 445 return y; 446 }; 447 */ 448 this.coords.setCoordinates(Const.COORDS_BY_USER, [x, y]); 449 } 450 451 // this should be a local update, otherwise there might be problems 452 // with the tick update routine resulting in orphaned tick labels 453 this.prepareUpdate().update().updateRenderer(); 454 455 return this; 456 }, 457 458 /** 459 * Evaluates the text. 460 * Then, the update function of the renderer 461 * is called. 462 */ 463 update: function (fromParent) { 464 if (!this.needsUpdate) { 465 return this; 466 } 467 468 this.updateCoords(fromParent); 469 this.updateText(); 470 471 if (this.visProp.display === 'internal') { 472 this.plaintext = this.utf8_decode(this.plaintext); 473 } 474 475 this.checkForSizeUpdate(); 476 if (this.needsSizeUpdate) { 477 this.updateSize(); 478 } 479 480 return this; 481 }, 482 483 /** 484 * Used to save updateSize() calls. 485 * Called in JXG.Text.update 486 * That means this.update() has been called. 487 * More tests are in JXG.Renderer.updateTextStyle. The latter tests 488 * are one update off. But this should pose not too many problems, since 489 * it affects fontSize and cssClass changes. 490 * 491 * @private 492 */ 493 checkForSizeUpdate: function () { 494 if (this.board.infobox && this.id === this.board.infobox.id) { 495 this.needsSizeUpdate = false; 496 } else { 497 // For some magic reason it is more efficient on the iPad to 498 // call updateSize() for EVERY text element EVERY time. 499 this.needsSizeUpdate = (this.plaintextOld !== this.plaintext); 500 501 if (this.needsSizeUpdate) { 502 this.plaintextOld = this.plaintext; 503 } 504 } 505 506 }, 507 508 /** 509 * The update function of the renderert 510 * is called. 511 * @private 512 */ 513 updateRenderer: function () { 514 return this.updateRendererGeneric('updateText'); 515 }, 516 517 /** 518 * Converts the GEONExT syntax of the <value> terms into JavaScript. 519 * Also, all Objects whose name appears in the term are searched and 520 * the text is added as child to these objects. 521 * @private 522 * @see JXG.GeonextParser.geonext2JS. 523 */ 524 generateTerm: function (contentStr) { 525 var res, term, i, j, 526 plaintext = '""'; 527 528 // revert possible jc replacement 529 contentStr = contentStr || ''; 530 contentStr = contentStr.replace(/\r/g, ''); 531 contentStr = contentStr.replace(/\n/g, ''); 532 contentStr = contentStr.replace(/"/g, '\''); 533 contentStr = contentStr.replace(/'/g, "\\'"); 534 535 contentStr = contentStr.replace(/&arc;/g, '∠'); 536 contentStr = contentStr.replace(/<arc\s*\/>/g, '∠'); 537 contentStr = contentStr.replace(/<arc\s*\/>/g, '∠'); 538 contentStr = contentStr.replace(/<sqrt\s*\/>/g, '√'); 539 540 contentStr = contentStr.replace(/<value>/g, '<value>'); 541 contentStr = contentStr.replace(/<\/value>/g, '</value>'); 542 543 // Convert GEONExT syntax into JavaScript syntax 544 i = contentStr.indexOf('<value>'); 545 j = contentStr.indexOf('</value>'); 546 if (i >= 0) { 547 while (i >= 0) { 548 plaintext += ' + "' + this.replaceSub(this.replaceSup(contentStr.slice(0, i))) + '"'; 549 term = contentStr.slice(i + 7, j); 550 res = GeonextParser.geonext2JS(term, this.board); 551 res = res.replace(/\\"/g, "'"); 552 res = res.replace(/\\'/g, "'"); 553 554 // GEONExT-Hack: apply rounding once only. 555 if (res.indexOf('toFixed') < 0) { 556 // output of a value tag 557 if (Type.isNumber((Type.bind(this.board.jc.snippet(res, true, '', false), this))())) { 558 // may also be a string 559 plaintext += '+(' + res + ').toFixed(' + (this.visProp.digits) + ')'; 560 } else { 561 plaintext += '+(' + res + ')'; 562 } 563 } else { 564 plaintext += '+(' + res + ')'; 565 } 566 567 contentStr = contentStr.slice(j + 8); 568 i = contentStr.indexOf('<value>'); 569 j = contentStr.indexOf('</value>'); 570 } 571 } 572 573 plaintext += ' + "' + this.replaceSub(this.replaceSup(contentStr)) + '"'; 574 plaintext = this.convertGeonext2CSS(plaintext); 575 576 // This should replace π by π 577 plaintext = plaintext.replace(/&/g, '&'); 578 plaintext = plaintext.replace(/"/g, "'"); 579 580 return plaintext; 581 }, 582 583 /** 584 * Converts the GEONExT tags <overline> and <arrow> to 585 * HTML span tags with proper CSS formating. 586 * @private 587 * @see JXG.Text.generateTerm @see JXG.Text._setText 588 */ 589 convertGeonext2CSS: function (s) { 590 if (typeof s === 'string') { 591 s = s.replace(/<overline>/g, '<span style=text-decoration:overline>'); 592 s = s.replace(/<overline>/g, '<span style=text-decoration:overline>'); 593 s = s.replace(/<\/overline>/g, '</span>'); 594 s = s.replace(/<\/overline>/g, '</span>'); 595 s = s.replace(/<arrow>/g, '<span style=text-decoration:overline>'); 596 s = s.replace(/<arrow>/g, '<span style=text-decoration:overline>'); 597 s = s.replace(/<\/arrow>/g, '</span>'); 598 s = s.replace(/<\/arrow>/g, '</span>'); 599 } 600 601 return s; 602 }, 603 604 /** 605 * Finds dependencies in a given term and notifies the parents by adding the 606 * dependent object to the found objects child elements. 607 * @param {String} content String containing dependencies for the given object. 608 * @private 609 */ 610 notifyParents: function (content) { 611 var search, 612 res = null; 613 614 // revert possible jc replacement 615 content = content.replace(/<value>/g, '<value>'); 616 content = content.replace(/<\/value>/g, '</value>'); 617 618 do { 619 search = /<value>([\w\s\*\/\^\-\+\(\)\[\],<>=!]+)<\/value>/; 620 res = search.exec(content); 621 622 if (res !== null) { 623 GeonextParser.findDependencies(this, res[1], this.board); 624 content = content.substr(res.index); 625 content = content.replace(search, ''); 626 } 627 } while (res !== null); 628 629 return this; 630 }, 631 632 bounds: function () { 633 var c = this.coords.usrCoords; 634 635 return this.visProp.islabel ? [0, 0, 0, 0] : [c[1], c[2] + this.size[1], c[1] + this.size[0], c[2]]; 636 } 637 }); 638 639 /** 640 * @class Construct and handle texts. 641 * 642 * The coordinates can be relative to the coordinates of an element 643 * given in {@link JXG.Options#text.anchor}. 644 * 645 * MathJaX, HTML and GEONExT syntax can be handled. 646 * @pseudo 647 * @description 648 * @name Text 649 * @augments JXG.Text 650 * @constructor 651 * @type JXG.Text 652 * 653 * @param {number,function_number,function_number,function_String,function} z_,x,y,str Parent elements for text elements. 654 * <p> 655 * Parent elements can be two or three elements of type number, a string containing a GEONE<sub>x</sub>T 656 * constraint, or a function which takes no parameter and returns a number. Every parent element determines one coordinate. If a coordinate is 657 * given by a number, the number determines the initial position of a free text. If given by a string or a function that coordinate will be constrained 658 * that means the user won't be able to change the texts's position directly by mouse because it will be calculated automatically depending on the string 659 * or the function's return value. If two parent elements are given the coordinates will be interpreted as 2D affine Euclidean coordinates, if three such 660 * parent elements are given they will be interpreted as homogeneous coordinates. 661 * <p> 662 * The text to display may be given as string or as function returning a string. 663 * 664 * There is the attribute 'display' which takes the values 'html' or 'internal'. In case of 'html' a HTML division tag is created to display 665 * the text. In this case it is also possible to use ASCIIMathML. Incase of 'internal', a SVG or VML text element is used to display the text. 666 * @see JXG.Text 667 * @example 668 * // Create a fixed text at position [0,1]. 669 * var t1 = board.create('text',[0,1,"Hello World"]); 670 * </pre><div id="896013aa-f24e-4e83-ad50-7bc7df23f6b7" style="width: 300px; height: 300px;"></div> 671 * <script type="text/javascript"> 672 * var t1_board = JXG.JSXGraph.initBoard('896013aa-f24e-4e83-ad50-7bc7df23f6b7', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 673 * var t1 = t1_board.create('text',[0,1,"Hello World"]); 674 * </script><pre> 675 * @example 676 * // Create a variable text at a variable position. 677 * var s = board.create('slider',[[0,4],[3,4],[-2,0,2]]); 678 * var graph = board.create('text', 679 * [function(x){ return s.Value();}, 1, 680 * function(){return "The value of s is"+s.Value().toFixed(2);} 681 * ] 682 * ); 683 * </pre><div id="5441da79-a48d-48e8-9e53-75594c384a1c" style="width: 300px; height: 300px;"></div> 684 * <script type="text/javascript"> 685 * var t2_board = JXG.JSXGraph.initBoard('5441da79-a48d-48e8-9e53-75594c384a1c', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 686 * var s = t2_board.create('slider',[[0,4],[3,4],[-2,0,2]]); 687 * var t2 = t2_board.create('text',[function(x){ return s.Value();}, 1, function(){return "The value of s is "+s.Value().toFixed(2);}]); 688 * </script><pre> 689 */ 690 JXG.createText = function (board, parents, attributes) { 691 var t, 692 attr = Type.copyAttributes(attributes, board.options, 'text'), 693 coords = parents.slice(0, -1), 694 content = parents[parents.length - 1]; 695 696 // downwards compatibility 697 attr.anchor = attr.parent || attr.anchor; 698 t = CoordsElement.create(JXG.Text, board, coords, attr, content); 699 700 if (!t) { 701 throw new Error("JSXGraph: Can't create text with parent types '" + 702 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 703 "\nPossible parent types: [x,y], [z,x,y], [element,transformation]"); 704 } 705 706 if (Type.evaluate(attr.rotate) !== 0 && attr.display === 'internal') { 707 t.addRotation(Type.evaluate(attr.rotate)); 708 } 709 710 return t; 711 }; 712 713 JXG.registerElement('text', JXG.createText); 714 715 /** 716 * [[x,y], [w px, h px], [range] 717 */ 718 JXG.createHTMLSlider = function (board, parents, attributes) { 719 var t, par, 720 attr = Type.copyAttributes(attributes, board.options, 'htmlslider'); 721 722 if (parents.length !== 2 || parents[0].length !== 2 || parents[1].length !== 3) { 723 throw new Error("JSXGraph: Can't create htmlslider with parent types '" + 724 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 725 "\nPossible parents are: [[x,y], [min, start, max]]"); 726 } 727 728 // backwards compatibility 729 attr.anchor = attr.parent || attr.anchor; 730 attr.fixed = attr.fixed || true; 731 732 par = [parents[0][0], parents[0][1], 733 '<form style="display:inline">' + 734 '<input type="range" /><span></span><input type="text" />' + 735 '</form>']; 736 737 t = JXG.createText(board, par, attr); 738 t.type = Type.OBJECT_TYPE_HTMLSLIDER; 739 740 t.rendNodeForm = t.rendNode.childNodes[0]; 741 t.rendNodeForm.id = t.rendNode.id + '_form'; 742 743 t.rendNodeRange = t.rendNodeForm.childNodes[0]; 744 t.rendNodeRange.id = t.rendNode.id + '_range'; 745 t.rendNodeRange.min = parents[1][0]; 746 t.rendNodeRange.max = parents[1][2]; 747 t.rendNodeRange.step = attr.step; 748 t.rendNodeRange.value = parents[1][1]; 749 750 t.rendNodeLabel = t.rendNodeForm.childNodes[1]; 751 t.rendNodeLabel.id = t.rendNode.id + '_label'; 752 753 if (attr.withlabel) { 754 t.rendNodeLabel.innerHTML = t.name + '='; 755 } 756 757 t.rendNodeOut = t.rendNodeForm.childNodes[2]; 758 t.rendNodeOut.id = t.rendNode.id + '_out'; 759 t.rendNodeOut.value = parents[1][1]; 760 761 t.rendNodeRange.style.width = attr.widthrange + 'px'; 762 t.rendNodeRange.style.verticalAlign = 'middle'; 763 t.rendNodeOut.style.width = attr.widthout + 'px'; 764 765 t._val = parents[1][1]; 766 767 if (JXG.supportsVML()) { 768 /* 769 * OnChange event is used for IE browsers 770 * The range element is supported since IE10 771 */ 772 Env.addEvent(t.rendNodeForm, 'change', priv.HTMLSliderInputEventHandler, t); 773 } else { 774 /* 775 * OnInput event is used for non-IE browsers 776 */ 777 Env.addEvent(t.rendNodeForm, 'input', priv.HTMLSliderInputEventHandler, t); 778 } 779 780 t.Value = function () { 781 return this._val; 782 }; 783 784 return t; 785 }; 786 787 JXG.registerElement('htmlslider', JXG.createHTMLSlider); 788 789 return { 790 Text: JXG.Text, 791 createText: JXG.createText, 792 createHTMLSlider: JXG.createHTMLSlider 793 }; 794 }); 795