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