Hi @Yannick1 !
Here's a modified virtual keyboard flow that should work for decimals, let me know if that works for you and I'll update the flow at https://flows.nodered.org/flow/7fb5bc5ae66e6bc1b1c1b8e800bdef51:
{
"id": "b973810a.e5c0f",
"type": "debug",
"z": "5993de68022e11bb",
"name": "",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"x": 730,
"y": 400,
"wires": []
},
{
"id": "c5db1e72.3ef58",
"type": "ui_template",
"z": "5993de68022e11bb",
"group": "6a4d510d.5de65",
"name": "Virtual Keyboard",
"order": 1,
"width": 0,
"height": 0,
"format": "<div id=\"empty\"></div>\n<button class=\"VK\">V-KeyBoard On</button>\n\n<!-- The Modal -->\n<div id=\"myModal\" class=\"modal\">\n\n <!-- Modal content -->\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <span class=\"close\" onclick=\"closeModal()\">×</span>\n <h2 id=\"vkeyname\" style=\"background-color: aliceblue !important; color: black !important; text-align: center; min-height: 30px;\">V-Keyboard</h2>\n </div>\n <div class=\"modal-body\">\n <div id=\"keyboard\"></div>\n <div>\n </div>\n </div>\n </div>\n</div>\n\n<style>\n.VK{\n position: fixed;\n top: 60px;\n right: 20px;\n height: 30px;\n}\n</style>\n\n<script>\n\nvar clickState = 1;\nvar btn = document.querySelector('.VK');\n\nbtn.addEventListener('click', function(){\n\n if (clickState == 0) {\n this.textContent = 'V-KeyBoard On';\n modal = document.getElementById('myModal');\n clickState = 1;\n } else {\n this.textContent = 'V-KeyBoard Off';\n modal = document.getElementById('empty');\n clickState = 0;\n }\n\n});\n</script>\n\n<script>\n // Get the modal\nvar modal = document.getElementById('myModal');\nvar inputTags;\nvar inputType;\nvar inputTarget;\n\nvar getinputs = function() {\n inputTags = document.getElementsByTagName(\"input\");\n console.log(inputTags)\n for (var i = 0; i < inputTags.length; i++) {\n inputTags[i].addEventListener('click', openModal, false)\n }\n}\n\nsetTimeout(function(){ getinputs(); }, 1000);\n\nvar openModal = function() {\n inputType = event.target.type\n inputTarget = event.target\n var layoutName;\n if (inputType == \"number\"){\n //inputTarget.type = \"number\" //hack because chrome doesn't allow setselection in number inputs\n //inputTarget.value = \"\"\n layoutName = \"numbers_only\"\n }else{\n layoutName = \"english\"\n }\n $('#vkeyname').text(event.target.value)\n $('#keyboard').unbind().removeData();\n modal.style.display = \"block\";\n $('#keyboard').jkeyboard({\n layout: layoutName,\n input: $('#'+$(this).attr('id'))\n });\n}\n\n\n// Get the <span> element that closes the modal\nvar span = document.getElementsByClassName(\"close\")[0];\n\n// When the user clicks anywhere outside of the modal, close it\nwindow.onclick = function(event) {\n var source = event.target;\n if (source == modal || source == span) {\n closeModal(source)\n }\n};\n\nvar closeModal = function(source){\n //console.log(\"closing\")\n modal.style.display = \"none\";\n \n if (inputType == \"number\"){\n inputTarget.type = \"number\" //hack because chrome doesn't allow selectionstart on number inputs\n }\n}\n\n \n// the semi-colon before function invocation is a safety net against concatenated\n// scripts and/or other plugins which may not be closed properly.\n; (function ($, window, document, undefined) {\n\n // undefined is used here as the undefined global variable in ECMAScript 3 is\n // mutable (ie. it can be changed by someone else). undefined isn't really being\n // passed in so we can ensure the value of it is truly undefined. In ES5, undefined\n // can no longer be modified.\n\n // window and document are passed through as local variable rather than global\n // as this (slightly) quickens the resolution process and can be more efficiently\n // minified (especially when both are regularly referenced in your plugin).\n\n // Create the defaults once\n var pluginName = \"jkeyboard\",\n defaults = {\n layout: \"english\",\n input: $('#input'),\n customLayouts: {\n selectable: []\n },\n };\n\n\n var function_keys = {\n backspace: {\n text: 'DEL',\n },\n return: {\n text: 'Enter'\n },\n shift: {\n text: 'Shift'\n },\n space: {\n text: 'Space'\n },\n numeric_switch: {\n text: '123',\n command: function () {\n this.createKeyboard('numeric');\n this.events();\n }\n },\n layout_switch: {\n text: '<i class=\"fa fa-keyboard-o\" aria-hidden=\"true\"></i>',\n command: function () {\n var l = this.toggleLayout();\n this.createKeyboard(l);\n this.events();\n }\n },\n character_switch: {\n text: 'ABC',\n command: function () {\n this.createKeyboard(layout);\n this.events();\n }\n },\n symbol_switch: {\n text: '#+=',\n command: function () {\n this.createKeyboard('symbolic');\n this.events();\n }\n }\n };\n\n\n var layouts = {\n selectable: ['english', 'russian','french', 'emoji'],\n english: [\n ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',],\n ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l',],\n ['shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm', 'backspace'],\n ['numeric_switch', 'layout_switch', 'space', 'return']\n ],\n russian: [\n ['й', 'ц', 'у', 'к', 'е', 'н', 'г', 'ш', 'щ', 'з', 'х'],\n ['ф', 'ы', 'в', 'а', 'п', 'р', 'о', 'л', 'д', 'ж', 'э'],\n ['shift', 'я', 'ч', 'с', 'м', 'и', 'т', 'ь', 'б', 'ю', 'backspace'],\n ['numeric_switch', 'layout_switch', 'space', 'return']\n ],\n french: [\n ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p',],\n ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l','à','ç'],\n ['shift', 'z', 'x', 'c', 'v', 'b', 'n', 'm','é','è', 'backspace'],\n ['numeric_switch', 'layout_switch', 'space', 'return']\n ],\n emoji: [\n ['😀', '😁', '😂', '🤣', '😃', '😄', '😅', '😆', '😉', '😊',],\n ['😋', '😎', '😍', '😘', '❤️', '🙏', '🔥', '✨', '👍','👌','💯'],\n ['🤷', '🎉', '👏', '🤦', '🙌', '🎶', '💥', '🌈','✅','⭐', '👀'],\n ['numeric_switch', 'layout_switch', 'space', 'return']\n ], \n numeric: [\n ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],\n ['-', '/', ':', ';', '(', ')', '$', '&', '@', '\"'],\n ['symbol_switch', '.', ',', '?', '!', \"'\", 'backspace'],\n ['character_switch', 'layout_switch', 'space', 'return'],\n ],\n numbers_only: [\n ['1', '2', '3',],\n ['4', '5', '6',],\n ['7', '8', '9',],\n ['0', '.', 'backspace', 'return'],\n ],\n symbolic: [\n ['[', ']', '{', '}', '#', '%', '^', '*', '+', '='],\n ['_', '\\\\', '|', '~', '<', '>'],\n ['numeric_switch', '.', ',', '?', '!', \"'\", 'backspace'],\n ['character_switch', 'layout_switch', 'space', 'return'],\n\n ]\n }\n\n var shift = false, capslock = false, layout = 'english', layout_id = 0;\n\n let decimal;\n\n // The actual plugin constructor\n function Plugin(element, options) {\n this.element = element;\n // jQuery has an extend method which merges the contents of two or\n // more objects, storing the result in the first object. The first object\n // is generally empty as we don't want to alter the default options for\n // future instances of the plugin\n this.settings = $.extend({}, defaults, options);\n // Extend & Merge the cusom layouts\n layouts = $.extend(true, {}, this.settings.customLayouts, layouts);\n if (Array.isArray(this.settings.customLayouts.selectable)) {\n $.merge(layouts.selectable, this.settings.customLayouts.selectable);\n }\n this._defaults = defaults;\n this._name = pluginName;\n this.init();\n }\n\n Plugin.prototype = {\n init: function () {\n layout = this.settings.layout;\n this.createKeyboard(layout);\n this.events();\n },\n\n setInput: function (newInputField) {\n this.settings.input = newInputField;\n },\n\n createKeyboard: function (layout) {\n shift = false;\n capslock = false;\n\n var keyboard_container = $('<ul/>').addClass('jkeyboard'),\n me = this;\n\n layouts[layout].forEach(function (line, index) {\n var line_container = $('<li/>').addClass('jline');\n line_container.append(me.createLine(line));\n keyboard_container.append(line_container);\n });\n\n $(this.element).html('').append(keyboard_container);\n },\n\n createLine: function (line) {\n var line_container = $('<ul/>');\n\n line.forEach(function (key, index) {\n var key_container = $('<li/>').addClass('jkey').data('command', key);\n\n if (function_keys[key]) {\n key_container.addClass(key).html(function_keys[key].text);\n }\n else {\n key_container.addClass('letter').html(key);\n }\n\n line_container.append(key_container);\n })\n\n return line_container;\n },\n\n events: function () {\n var letters = $(this.element).find('.letter'),\n shift_key = $(this.element).find('.shift'),\n space_key = $(this.element).find('.space'),\n backspace_key = $(this.element).find('.backspace'),\n return_key = $(this.element).find('.return'),\n\n me = this,\n fkeys = Object.keys(function_keys).map(function (k) {\n return '.' + k;\n }).join(',');\n\n letters.on('click', function () {\n me.type((shift || capslock) ? $(this).text().toUpperCase() : $(this).text());\n });\n\n space_key.on('click', function () {\n me.type(' ');\n });\n\n return_key.on('click', function () {\n me.enter();\n });\n\n backspace_key.on('click', function () {\n me.backspace();\n });\n\n shift_key.on('click', function () {\n if (capslock) {\n me.toggleShiftOff();\n capslock = false;\n } else {\n me.toggleShiftOn();\n }\n }).on('dblclick', function () {\n capslock = true;\n });\n\n\n $(fkeys).on('click', function () {\n var command = function_keys[$(this).data('command')].command;\n if (!command) return;\n\n command.call(me);\n });\n },\n\n type: function (key) {\n var input = this.settings.input,\n val = input.val(),\n input_node = input.get(0),\n start = input_node.selectionStart,\n end = input_node.selectionEnd;\n\n var max_length = $(input).attr(\"maxlength\");\n if (start == end && end == val.length) {\n if (!max_length || val.length < max_length) {\n input.val(val + key);\n input.change()\n $('#vkeyname').text(val + key)\n }\n } else {\n if (input_node.type == \"text\"){\n var new_string = this.insertToString(start, end, val, key);\n input.val(new_string);\n start++;\n end = start;\n input_node.setSelectionRange(start, end);\n input.change()\n }else if (input_node.type == \"number\"){\n if (key == \".\"){\n decimal = val + \".\";\n }else{\n if (decimal){\n input.val(decimal + + key);\n decimal = false\n }else{\n input.val(val + key);\n decimal = false\n }\n input.change()\n input.focus()\n }\n }else{\n input.val(val + key);\n input.change()\n input.focus()\n }\n $('#vkeyname').text(val + key)\n \n }\n input.trigger('focus');\n\n if (shift && !capslock) {\n this.toggleShiftOff();\n }\n },\n \n enter: function () {\n var input = this.settings.input,\n val = input.val();\n input_node = input.get(0),\n start = input_node.selectionStart,\n end = input_node.selectionEnd;\n console.log(input)\n if (input_node.type == \"text\"){\n val = val + \"\\n\";\n $('#vkeyname').text(val)\n }\n input.change()\n input_node.focus()\n input_node.blur()\n if (input_node.type == \"number\"){\n modal.style.display = \"none\";\n }\n },\n\n backspace: function () {\n var input = this.settings.input,\n val = input.val();\n input_node = input.get(0),\n start = input_node.selectionStart,\n end = input_node.selectionEnd;\n if (input.type == \"text\"){\n input.val(val.slice(0, start-1) + val.slice(start))\n input_node.setSelectionRange(start-1, start-1);\n //console.log(val)\n $('#vkeyname').text(val)\n }else{\n input.val(val.slice(0,-1))\n $('#vkeyname').text(val.slice(0,-1))\n }\n //input.change()\n //input.focus()\n },\n\n toggleShiftOn: function () {\n var letters = $(this.element).find('.letter'),\n shift_key = $(this.element).find('.shift');\n\n letters.addClass('uppercase');\n shift_key.addClass('active')\n shift = true;\n },\n\n toggleShiftOff: function () {\n var letters = $(this.element).find('.letter'),\n shift_key = $(this.element).find('.shift');\n\n letters.removeClass('uppercase');\n shift_key.removeClass('active');\n shift = false;\n },\n\n toggleLayout: function () {\n layout_id = layout_id || 0;\n var plain_layouts = layouts.selectable;\n layout_id++;\n\n var current_id = layout_id % plain_layouts.length;\n var SelectedLayoutName = plain_layouts[current_id];\n $('#vkeyname').text('V-Keyboard ' + SelectedLayoutName )\n return plain_layouts[current_id];\n },\n\n insertToString: function (start, end, string, insert_string) {\n return string.substring(0, start) + insert_string + string.substring(end, string.length);\n }\n };\n\n /*\n\t\t// A really lightweight plugin wrapper around the constructor,\n\t\t// preventing against multiple instantiations\n\t\t$.fn[ pluginName ] = function ( options ) {\n\t\t\t\treturn this.each(function() {\n\t\t\t\t\t\tif ( !$.data( this, \"plugin_\" + pluginName ) ) {\n\t\t\t\t\t\t\t\t$.data( this, \"plugin_\" + pluginName, new Plugin( this, options ) );\n\t\t\t\t\t\t}\n\t\t\t\t});\n\t\t};\n */\n var methods = {\n init: function(options) {\n if (!this.data(\"plugin_\" + pluginName)) {\n this.data(\"plugin_\" + pluginName, new Plugin(this, options));\n }\n },\n\t\t\tsetInput: function(content) {\n\t\t\t\tthis.data(\"plugin_\" + pluginName).setInput($(content));\n },\n setLayout: function(layoutname) {\n // change layout if it is not match current\n object = this.data(\"plugin_\" + pluginName);\n if (typeof(layouts[layoutname]) !== 'undefined' && object.settings.layout != layoutname) {\n object.settings.layout = layoutname;\n object.createKeyboard(layoutname);\n object.events();\n };\n },\n };\n\n\t\t$.fn[pluginName] = function (methodOrOptions) {\n if (methods[methodOrOptions]) {\n return methods[methodOrOptions].apply(this.first(), Array.prototype.slice.call( arguments, 1));\n } else if (typeof methodOrOptions === 'object' || ! methodOrOptions) {\n // Default to \"init\"\n return methods.init.apply(this.first(), arguments);\n } else {\n $.error('Method ' + methodOrOptions + ' does not exist on jQuery.tooltip');\n }\n };\n\n})(jQuery, window, document);\n</script>\n\n<style>\nbody {font-family: Arial, Helvetica, sans-serif;}\n\n.nr-dashboard-theme .nr-dashboard-template .md-button:not(:first-of-type) {\n margin-top: 0px;\n}\n\n/* The Modal (background) */\n.modal {\n display: none; /* Hidden by default */\n position: fixed; /* Stay in place */\n opacity:0.99;\n z-index: 100; /* Sit on top */\n left: 0;\n top: 0;\n width: 100%; /* Full width */\n height: 100%; /* Full height */\n overflow: auto; /* Enable scroll if needed */\n background-color: rgb(0,0,0); /* Fallback color */\n background-color: rgba(0,0,0,0.4); /* Black w/ opacity */\n}\n\n/* Modal Content */\n.modal-content {\n position: fixed;\n background-color: #fefefe;\n margin: auto;\n padding: 0;\n bottom: 0%;\n left: 50%;\n transform: translate(-50%, 0%);\n border: 1px solid #888;\n width: fit-content;\n max-width: 100%;\n max-height: 100%;\n box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);\n -webkit-animation-name: animate;\n -webkit-animation-duration: 0.4s;\n animation-name: animate;\n animation-duration: 0.4s\n}\n\n/* Add Animation */\n@-webkit-keyframes animate {\n from {bottom:100%; opacity:0} \n to {bottom:0%; opacity:1}\n}\n\n@keyframes animate {\n from {bottom:100%; opacity:0}\n to {bottom:0%; opacity:1}\n}\n\n/* The Close Button */\n.close {\n color: black;\n float: right;\n font-size: 28px;\n font-weight: bold;\n}\n\n.close:hover,\n.close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n\n.modal-header {\n padding: 2px 16px;\n background-color: aliceblue;\n color: white;\n}\n\n.modal-body {padding: 2px 16px;}\n\n.modal-footer {\n padding: 2px 16px;\n background-color: #5cb85c;\n color: white;\n}\n\n.jkeyboard {\n display: inline-block;\n}\n.jkeyboard, .jkeyboard .jline, .jkeyboard .jline ul {\n display: block;\n margin: 0;\n padding: 0;\n}\n.jkeyboard .jline {\n text-align: center;\n margin-left: -14px;\n}\n.jkeyboard .jline ul li {\n font-family: arial, sans-serif;\n font-size: 20px;\n display: inline-block;\n border: 1px solid #468db3;\n -webkit-box-shadow: 0 0 3px #468db3;\n -webkit-box-shadow: inset 0 0 3px #468db3;\n margin: 5px 0 1px 6px;\n color: #000000;\n border-radius: 5px;\n width: 52px;\n height: 52px;\n box-sizing: border-box;\n text-align: center;\n line-height: 52px;\n overflow: hidden;\n cursor: pointer;\n -webkit-touch-callout: none;\n -webkit-user-select: none;\n -khtml-user-select: none;\n -moz-user-select: -moz-none;\n -ms-user-select: none;\n user-select: none;\n}\n.jkeyboard .jline ul li.uppercase {\n text-transform: uppercase;\n}\n.jkeyboard .jline ul li:hover, .jkeyboard .jline ul li:active {\n background-color: #185a82;\n}\n.jkeyboard .jline .return {\n width: 80px;\n}\n.jkeyboard .jline .space {\n width: 366px;\n}\n.jkeyboard .jline .numeric_switch {\n width: 65px;\n}\n.jkeyboard .jline .layout_switch {\n}\n.jkeyboard .jline .shift {\n width: 60px;\n}\n.jkeyboard .jline .backspace {\n width: 69px;\n}\n</style>",
"storeOutMessages": false,
"fwdInMessages": false,
"resendOnRefresh": false,
"templateScope": "local",
"className": "",
"x": 560,
"y": 360,
"wires": [
[]
]
},
{
"id": "d1b0fa5f.bfb838",
"type": "ui_text_input",
"z": "5993de68022e11bb",
"name": "",
"label": "",
"tooltip": "",
"group": "6a4d510d.5de65",
"order": 5,
"width": 0,
"height": 0,
"passthru": true,
"mode": "number",
"delay": "0",
"topic": "",
"sendOnBlur": true,
"className": "",
"topicType": "str",
"x": 550,
"y": 400,
"wires": [
[
"b973810a.e5c0f"
]
]
},
{
"id": "6a4d510d.5de65",
"type": "ui_group",
"name": "Users",
"tab": "56715436.c2591c",
"order": 2,
"disp": true,
"width": "9",
"collapse": false
},
{
"id": "56715436.c2591c",
"type": "ui_tab",
"name": "Home Tab",
"icon": "dashboard",
"order": 3
}
]