1 /* 2 Copyright 2008-2026 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 <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true, window: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview In this file the Text element is defined. 37 */ 38 39 import JXG from "../jxg.js"; 40 import Env from "../utils/env.js"; 41 import Text from "../base/text.js"; 42 import Type from "../utils/type.js"; 43 44 /** 45 * @class 46 * @ignore 47 */ 48 var priv = { 49 /** 50 * @class 51 * @ignore 52 */ 53 54 InputInputEventHandler: function (evt) { 55 this._value = this.rendNodeInput.value; 56 this.board.update(); 57 } 58 }; 59 60 /** 61 * @class This element is used to provide a constructor for special texts containing a 62 * HTML form input element. 63 * For this element, the attribute "display" has to have the value 'html' (which is the default). 64 * 65 * <p><b>Setting a CSS class:</b> The attribute <tt>cssClass</tt> affects the HTML div element that contains the input element. To change the CSS properties of the HTML input element a selector of the form 66 * <tt>.myinput > input { ... }</tt> has to be used. See the analog example for buttons: 67 * {@link Button}. 68 * 69 * <p><b>Access the input element with JavaScript:</b> 70 * The underlying HTML button element can be accessed through the sub-object 'rendNodeInput', e.g. to 71 * add event listeners. 72 * 73 * @pseudo 74 * @name Input 75 * @augments Text 76 * @constructor 77 * @type JXG.Text 78 * 79 * @param {number,function_number,function_String_String,function} x,y,value,label Parent elements for input elements. 80 * <p> 81 * x and y are the coordinates of the lower left corner of the text box. The position of the text is fixed, 82 * x and y are numbers. The position is variable if x or y are functions. 83 * <p> 84 * The default value of the input element must be given as string. 85 * <p> 86 * The label of the input element may be given as string or function. 87 * 88 * @example 89 * // Create an input element at position [1,4]. 90 * var input = board.create('input', [0, 1, 'sin(x)*x', 'f(x)='], {cssStyle: 'width: 100px'}); 91 * var f = board.jc.snippet(input.Value(), true, 'x', false); 92 * var graph = board.create('functiongraph',[f, 93 * function() { 94 * var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[0,0],board); 95 * return c.usrCoords[1]; 96 * }, 97 * function() { 98 * var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[board.canvasWidth,0],board); 99 * return c.usrCoords[1]; 100 * } 101 * ]); 102 * 103 * board.create('text', [1, 3, '<button onclick="updateGraph()">Update graph</button>']); 104 * 105 * var updateGraph = function() { 106 * graph.Y = board.jc.snippet(input.Value(), true, 'x', false); 107 * graph.updateCurve(); 108 * board.update(); 109 * } 110 * </pre><div class="jxgbox" id="JXGc70f55f1-21ba-4719-a37d-a93ae2943faa" style="width: 500px; height: 300px;"></div> 111 * <script type="text/javascript"> 112 * var t1_board = JXG.JSXGraph.initBoard('JXGc70f55f1-21ba-4719-a37d-a93ae2943faa', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false}); 113 * var input = t1_board.create('input', [1, 4, 'sin(x)*x', 'f(x)='], {cssStyle: 'width: 100px'}); 114 * var f = t1_board.jc.snippet(input.Value(), true, 'x', false); 115 * var graph = t1_board.create('functiongraph',[f, 116 * function() { 117 * var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[0,0],t1_board); 118 * return c.usrCoords[1]; 119 * }, 120 * function() { 121 * var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[t1_board.canvasWidth,0],t1_board); 122 * return c.usrCoords[1]; 123 * } 124 * ]); 125 * 126 * t1_board.create('text', [1, 3, '<button onclick="updateGraph()">Update graph</button>']); 127 * 128 * var updateGraph = function() { 129 * graph.Y = t1_board.jc.snippet(input.Value(), true, 'x', false); 130 * graph.updateCurve(); 131 * t1_board.update(); 132 * } 133 * </script><pre> 134 * 135 * @example 136 * // Add the `keyup` event to an input field 137 * var A = board.create('point', [3, -2]); 138 * var i = board.create('input', [-4, -4, "1", "x "]); 139 * 140 * i.rendNodeInput.addEventListener("keyup", ( function () { 141 * var x = parseFloat(this.value); 142 * if (!isNaN(x)) { 143 * A.moveTo([x, 3], 100); 144 * } 145 * })); 146 * 147 * </pre><div id="JXG81c84fa7-3f36-4874-9e0f-d4b9e93e755b" class="jxgbox" style="width: 300px; height: 300px;"></div> 148 * <script type="text/javascript"> 149 * (function() { 150 * var board = JXG.JSXGraph.initBoard('JXG81c84fa7-3f36-4874-9e0f-d4b9e93e755b', 151 * {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}); 152 * var A = board.create('point', [3, -2]); 153 * var i = board.create('input', [-4, -4, "1", "x "]); 154 * 155 * i.rendNodeInput.addEventListener("keyup", ( function () { 156 * var x = parseFloat(this.value); 157 * if (!isNaN(x)) { 158 * A.moveTo([x, 3], 100); 159 * } 160 * })); 161 * 162 * })(); 163 * 164 * </script><pre> 165 * 166 * @example 167 * // Add the `change` event to an input field 168 * var A = board.create('point', [3, -2]); 169 * var i = board.create('input', [-4, -4, "1", "x "]); 170 * 171 * i.rendNodeInput.addEventListener("change", ( function () { 172 * var x = parseFloat(i.Value()); 173 * A.moveTo([x, 2], 100); 174 * })); 175 * 176 * </pre><div id="JXG51c4d78b-a7ad-4c34-a983-b3ddae6192d7" class="jxgbox" style="width: 300px; height: 300px;"></div> 177 * <script type="text/javascript"> 178 * (function() { 179 * var board = JXG.JSXGraph.initBoard('JXG51c4d78b-a7ad-4c34-a983-b3ddae6192d7', 180 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 181 * var A = board.create('point', [3, -2]); 182 * var i = board.create('input', [-4, -4, "1", "x "]); 183 * 184 * i.rendNodeInput.addEventListener("change", ( function () { 185 * var x = parseFloat(i.Value()); 186 * A.moveTo([x, 2], 100); 187 * })); 188 * 189 * })(); 190 * 191 * </script><pre> 192 * 193 * @example 194 * // change the width of an input field 195 * let s = board.create('slider', [[-3, 3], [2, 3], [50, 100, 300]]); 196 * let inp = board.create('input', [-6, 1, 'Math.sin(x)*x', 'f(x)='],{cssStyle:()=>'width:'+s.Value()+'px'}); 197 * 198 * </pre><div id="JXG51c4d78b-a7ad-4c34-a983-b3ddae6192d7-1" class="jxgbox" style="width: 300px; height: 300px;"></div> 199 * <script type="text/javascript"> 200 * (function() { 201 * var board = JXG.JSXGraph.initBoard('JXG51c4d78b-a7ad-4c34-a983-b3ddae6192d7-1', 202 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 203 * let s = board.create('slider', [[-3, 3], [2, 3], [50, 100, 300]]); 204 * let inp = board.create('input', [-6, 1, 'Math.sin(x)*x', 'f(x)='],{cssStyle:()=>'width:'+s.Value()+'px'}); 205 * })(); 206 * 207 * </script><pre> 208 * 209 * @example 210 * Apply CSS classes to label and input tag 211 * <style> 212 * div.JXGtext_inp { 213 * font-weight: bold; 214 * } 215 * 216 * // Label 217 * div.JXGtext_inp > span > span { 218 * padding: 3px; 219 * } 220 * 221 * // Input field 222 * div.JXGtext_inp > span > input { 223 * width: 100px; 224 * border: solid 4px red; 225 * border-radius: 25px; 226 * } 227 * </style> 228 * 229 * var inp = board.create('input', [-6, 1, 'x', 'y'], { 230 * CssClass: 'JXGtext_inp', HighlightCssClass: 'JXGtext_inp' 231 * }); 232 * 233 * </pre> 234 * <style> 235 * div.JXGtext_inp { 236 * font-weight: bold; 237 * } 238 * 239 * div.JXGtext_inp > span > span { 240 * padding: 3px; 241 * } 242 * 243 * div.JXGtext_inp > span > input { 244 * width: 100px; 245 * border: solid 4px red; 246 * border-radius: 25px; 247 * } 248 * </style> 249 * <div id="JXGa3642ebd-a7dc-41ac-beb2-0c9e705ab8b4" class="jxgbox" style="width: 300px; height: 300px;"></div> 250 * <script type="text/javascript"> 251 * (function() { 252 * var board = JXG.JSXGraph.initBoard('JXGa3642ebd-a7dc-41ac-beb2-0c9e705ab8b4', 253 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 254 * var inp = board.create('input', [-6, 1, 'x', 'y'], {CssClass: 'JXGtext_inp', HighlightCssClass: 'JXGtext_inp'}); 255 * 256 * })(); 257 * </script><pre> 258 * 259 */ 260 JXG.createInput = function (board, parents, attributes) { 261 var t, 262 par, 263 setTextBackup, 264 attr = Type.copyAttributes(attributes, board.options, 'input'); 265 266 par = [ 267 parents[0], 268 parents[1], 269 '<span style="display:inline; white-space:nowrap; padding:0px;">' + 270 '<label></label><input type="text" maxlength="' + 271 attr.maxlength + 272 '" style="width:100%" />' + 273 "</span>" 274 ]; 275 276 // Make sure the setText method is the original one. The JessieCode parser changes it during parsing. 277 setTextBackup = Text.prototype.setText; 278 Text.prototype.setText = Text.prototype._setText; 279 280 // 1. Create input element with empty label 281 t = board.create("text", par, attr); 282 t.type = Type.OBJECT_TYPE_INPUT; 283 284 // Restore whichever setText method was set before this contructor was called. 285 Text.prototype.setText = setTextBackup; 286 287 t.rendNodeLabel = t.rendNode.childNodes[0].childNodes[0]; 288 t.rendNodeInput = t.rendNode.childNodes[0].childNodes[1]; 289 t.rendNodeInput.value = parents[2]; 290 t.rendNodeTag = t.rendNodeInput; // Needed for unified treatment in setAttribute 291 t.rendNodeTag.disabled = !!attr.disabled; 292 t.rendNodeLabel.id = t.rendNode.id + "_label"; 293 t.rendNodeInput.id = t.rendNode.id + "_input"; 294 t.rendNodeInput.setAttribute("aria-labelledby", t.rendNodeLabel.id); 295 296 // 2. Set parents[3] (string|function) as label of the input element. 297 // abstract.js selects the correct DOM element for the update 298 t.setText(parents[3]); 299 300 t._value = parents[2]; 301 302 // 3. capture keydown events on the input, and do not let them propagate. The problem is that 303 // elevation controls on view3D use left and right, so editing the input triggers 3D pan. 304 t.rendNodeInput.addEventListener("keydown", (event) => { 305 // only trap left-and-right in case user wants input editing events 306 if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') { 307 event.stopPropagation(); 308 } 309 }); 310 311 312 313 314 /** 315 * @class 316 * @ignore 317 */ 318 t.update = function () { 319 if (this.needsUpdate) { 320 JXG.Text.prototype.update.call(this); 321 this._value = this.rendNodeInput.value; 322 } 323 return this; 324 }; 325 326 /** 327 * Returns the value (content) of the input element 328 * @name Value 329 * @memberOf Input.prototype 330 * @function 331 * @returns {String} content of the input field. 332 */ 333 t.Value = function () { 334 return this._value; 335 }; 336 337 /** 338 * Sets value of the input element. 339 * @name set 340 * @memberOf Input.prototype 341 * @function 342 * 343 * @param {String} val 344 * @returns {JXG.GeometryElement} Reference to the element. 345 * 346 * @example 347 * var i1 = board.create('input', [-3, 4, 'sin(x)', 'f(x)='], {cssStyle: 'width:4em', maxlength: 2}); 348 * var c1 = board.create('checkbox', [-3, 2, 'label 1'], {}); 349 * var b1 = board.create('button', [-3, -1, 'Change texts', function () { 350 * i1.setText('g(x)'); 351 * i1.set('cos(x)'); 352 * c1.setText('label 2'); 353 * b1.setText('Texts are changed'); 354 * }], 355 * {cssStyle: 'width:400px'}); 356 * 357 * </pre><div id="JXG11cac8ff-2354-47e7-9da4-eb298e53de05" class="jxgbox" style="width: 300px; height: 300px;"></div> 358 * <script type="text/javascript"> 359 * (function() { 360 * var board = JXG.JSXGraph.initBoard('JXG11cac8ff-2354-47e7-9da4-eb298e53de05', 361 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 362 * var i1 = board.create('input', [-3, 4, 'sin(x)', 'f(x)='], {cssStyle: 'width:4em', maxlength: 2}); 363 * var c1 = board.create('checkbox', [-3, 2, 'label 1'], {}); 364 * var b1 = board.create('button', [-3, -1, 'Change texts', function () { 365 * i1.setText('g(x)'); 366 * i1.set('cos(x)'); 367 * c1.setText('label 2'); 368 * b1.setText('Texts are changed'); 369 * }], 370 * {cssStyle: 'width:400px'}); 371 * 372 * })(); 373 * 374 * </script><pre> 375 * 376 */ 377 378 /** 379 * @class 380 * @ignore 381 */ 382 383 t.set = function (val) { 384 this._value = val; 385 this.rendNodeInput.value = val; 386 return this; 387 }; 388 389 Env.addEvent(t.rendNodeInput, "input", priv.InputInputEventHandler, t); 390 Env.addEvent( 391 t.rendNodeInput, 392 "mousedown", 393 function (evt) { 394 if (Type.exists(evt.stopPropagation)) { 395 evt.stopPropagation(); 396 } 397 }, 398 t 399 ); 400 Env.addEvent( 401 t.rendNodeInput, 402 "touchstart", 403 function (evt) { 404 if (Type.exists(evt.stopPropagation)) { 405 evt.stopPropagation(); 406 } 407 }, 408 t 409 ); 410 Env.addEvent( 411 t.rendNodeInput, 412 "pointerdown", 413 function (evt) { 414 if (Type.exists(evt.stopPropagation)) { 415 evt.stopPropagation(); 416 } 417 }, 418 t 419 ); 420 421 // This sets the font-size of the input HTML element 422 t.visPropOld.fontsize = '0px'; 423 board.renderer.updateTextStyle(t, false); 424 425 return t; 426 }; 427 428 JXG.registerElement("input", JXG.createInput); 429 430 // export default { 431 // createInput: JXG.createInput 432 // }; 433