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