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