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