# Using Dashboard 2.0, created a virtual keyboard similar to 1.0

**URL:** https://discourse.nodered.org/t/using-dashboard-2-0-created-a-virtual-keyboard-similar-to-1-0/95966
**Category:** Dashboard
**Tags:** dashboard-2
**Created:** [13 March 2025 02:40 UTC](https://discourse.nodered.org/t/using-dashboard-2-0-created-a-virtual-keyboard-similar-to-1-0/95966 "2025-03-13T02:40:40Z")
**Posts on this page:** 10
**Page:** 1

<div class="post-metadata">

### Author: ![hauier](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hauier/32/98583_2.png) [@hauier](https://discourse.nodered.org/u/hauier)
#### Post date: [13 March 2025 02:40 UTC](https://discourse.nodered.org/t/using-dashboard-2-0-created-a-virtual-keyboard-similar-to-1-0/95966/1 "2025-03-13T02:40:40Z")

</div>

I was using Dashboard 1.0, where the virtual keyboard would appear when I pressed the text input. However, after switching to Dashboard 2.0, I was no longer able to use this feature. I have implemented a similar solution and am sharing it in case anyone else needs it.

The entire functionality works as follows: when a text input is clicked, a pre-created dialog window appears. Inside the dialog, a keyboard is displayed, and when a key is clicked, the corresponding data is added to a globally stored variable and passed on.

Currently, it can only be used with a single UI text node, but I plan to explore alternative methods to improve it further. I would appreciate any ideas or additional suggestions from others!

```auto
[
    {
        "id": "4f81f8796c7be6db",
        "type": "ui-text-input",
        "z": "4f4a93bcc4e0c4f4",
        "group": "f845855078ca3080",
        "name": "",
        "label": "text",
        "order": 1,
        "width": 0,
        "height": 0,
        "topic": "topic",
        "topicType": "msg",
        "mode": "text",
        "tooltip": "",
        "delay": 300,
        "passthru": true,
        "sendOnDelay": false,
        "sendOnBlur": true,
        "sendOnEnter": true,
        "className": "",
        "clearable": false,
        "sendOnClear": false,
        "icon": "",
        "iconPosition": "left",
        "iconInnerPosition": "inside",
        "x": 890,
        "y": 520,
        "wires": [
            [
                "e5861f0fb8a66626"
            ]
        ]
    },
    {
        "id": "d7c252ad7e82d0b3",
        "type": "ui-template",
        "z": "4f4a93bcc4e0c4f4",
        "group": "f845855078ca3080",
        "page": "",
        "ui": "",
        "name": "text click event",
        "order": 2,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<script>\nexport default {\n mounted() {\n this.addInputListeners();\n },\n methods: {\n addInputListeners() {\n const inputTags = document.getElementsByTagName(\"input\");\n console.log(inputTags);\n for (let i = 0; i < inputTags.length; i++) {\n inputTags[i].addEventListener(\"click\", this.openModal, false);\n }\n },\n openModal(event) {\n const msg = { payload: \"keyboar\" };\n this.send(msg);\n }\n }\n};\n</script>\n\n<style scoped>\ninput {\n margin: 0.5em;\n padding: 0.5em;\n}\n</style>\n",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 520,
        "y": 440,
        "wires": [
            [
                "91681223e14df927"
            ]
        ]
    },
    {
        "id": "d0c0129bc857d04e",
        "type": "debug",
        "z": "4f4a93bcc4e0c4f4",
        "name": "debug 2673",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 930,
        "y": 440,
        "wires": []
    },
    {
        "id": "91681223e14df927",
        "type": "change",
        "z": "4f4a93bcc4e0c4f4",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{\"groups\":{\"show\":[\"Active Development:keyboard\"]}}",
                "tot": "json"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 720,
        "y": 440,
        "wires": [
            [
                "d0c0129bc857d04e",
                "2c4e5b8bba08b7a4"
            ]
        ]
    },
    {
        "id": "2c4e5b8bba08b7a4",
        "type": "ui-control",
        "z": "4f4a93bcc4e0c4f4",
        "name": "",
        "ui": "35e7e3e70a04a217",
        "events": "all",
        "x": 920,
        "y": 380,
        "wires": [
            []
        ]
    },
    {
        "id": "c1bd36d069e5fc3c",
        "type": "ui-template",
        "z": "4f4a93bcc4e0c4f4",
        "group": "2a906d93a4506197",
        "page": "",
        "ui": "",
        "name": "",
        "order": 1,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>\n <div class=\"keyboard-container\">\n <div class=\"keyboard\">\n <div class=\"keyboard-line\" v-for=\"(line, lineIndex) in currentLayout\" :key=\"lineIndex\">\n <button class=\"keyboard-key\" v-for=\"(key, keyIndex) in line\" :key=\"keyIndex\" @click=\"pressKey(key)\">\n {{ displayKey(key) }}\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<script>\nexport default {\n name: 'VirtualKeyboard',\n props: {\n layout: {\n type: String,\n default: 'english'\n }\n },\n data() {\n return {\n shift: false,\n capslock: false,\n englishLayout: [\n ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],\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', 'Del'],\n ['-', '/','space', 'return']\n ],\n numericLayout: [\n ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'],\n ['Del']\n ]\n }\n },\n computed: {\n currentLayout() {\n return this.layout === 'numeric' ? this.numericLayout : this.englishLayout;\n }\n },\n methods: {\n pressKey(key) {\n if (key === 'shift') {\n this.shift = !this.shift;\n //this.sendMessage({ payload: 'shift' });\n return;\n }\n if (key === 'Del') {\n this.$emit('key-press', 'backspace');\n this.sendMessage({ payload: 'backspace' });\n return;\n }\n if (key === 'space') {\n this.$emit('key-press', ' ');\n this.sendMessage({ payload: ' ' });\n return;\n }\n if (key === 'return') {\n this.$emit('key-press', '\\n');\n this.sendMessage({ payload: '\\n' });\n return;\n }\n let output = key;\n if (this.shift || this.capslock) {\n output = key.toUpperCase();\n if (this.shift) {\n this.shift = false;\n }\n }\n this.$emit('key-press', output);\n this.sendMessage({ payload: output });\n },\n displayKey(key) {\n if (key.length === 1 && /[a-zA-Z]/.test(key)) {\n return (this.shift || this.capslock) ? key.toUpperCase() : key.toLowerCase();\n }\n return key;\n },\n sendMessage(payload) {\n if (typeof this.send === 'function') {\n this.send(payload);\n } else {\n this.$emit('input', payload);\n }\n }\n }\n}\n</script>\n\n<style scoped>\n.keyboard-container {\n display: inline-block;\n border: 1px solid #ccc;\n padding: 10px;\n background: #f9f9f9;\n}\n.keyboard {\n list-style: none;\n padding: 0;\n margin: 0;\n}\n.keyboard-line {\n display: flex;\n margin-bottom: 5px;\n}\n.keyboard-key {\n flex: 1;\n margin: 2px;\n padding: 10px;\n font-size: 16px;\n cursor: pointer;\n border: 1px solid #468db3;\n border-radius: 5px;\n background: #fff;\n transition: background 0.2s;\n}\n.keyboard-key:hover {\n background: #e0f0ff;\n}\n</style>\n",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 520,
        "y": 520,
        "wires": [
            [
                "940966306cf78ad2"
            ]
        ]
    },
    {
        "id": "940966306cf78ad2",
        "type": "function",
        "z": "4f4a93bcc4e0c4f4",
        "name": "function 89",
        "func": "//< *****************************************************************>//\n// @ts-ignore\nvar context = this.context.global;\n//<***************************************************************** >//\nvar buffer;\n\nif(buffer != \"\")\n{\n if (msg.payload === \"backspace\") \n {\n buffer = buffer.slice(0, -1);\n } else \n {\n buffer += msg.payload;\n }\n}else\n{\n buffer += msg.payload;\n}\n\nmsg.payload = buffer;\n\nreturn msg;\n",
        "outputs": 1,
        "timeout": 0,
        "noerr": 0,
        "initialize": "",
        "finalize": "",
        "libs": [],
        "x": 710,
        "y": 520,
        "wires": [
            [
                "4f81f8796c7be6db",
                "3ac979ede4029ac7"
            ]
        ]
    },
    {
        "id": "e5861f0fb8a66626",
        "type": "debug",
        "z": "4f4a93bcc4e0c4f4",
        "name": "debug 2674",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 1050,
        "y": 520,
        "wires": []
    },
    {
        "id": "3ac979ede4029ac7",
        "type": "debug",
        "z": "4f4a93bcc4e0c4f4",
        "name": "debug 2675",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": false,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "auto",
        "x": 870,
        "y": 600,
        "wires": []
    },
    {
        "id": "f845855078ca3080",
        "type": "ui-group",
        "name": "adminsetting",
        "page": "8f7a46dc69023904",
        "width": "12",
        "height": "1",
        "order": 1,
        "showTitle": true,
        "className": "",
        "visible": "true",
        "disabled": "false",
        "groupType": "default"
    },
    {
        "id": "35e7e3e70a04a217",
        "type": "ui-base",
        "name": "keyboard",
        "path": "/dashboard",
        "appIcon": "",
        "includeClientData": true,
        "acceptsClientConfig": [
            "ui-notification",
            "ui-gauge"
        ],
        "showPathInSidebar": false,
        "showPageTitle": true,
        "navigationStyle": "icon",
        "titleBarStyle": "fixed",
        "showReconnectNotification": false,
        "notificationDisplayTime": "1",
        "showDisconnectNotification": false
    },
    {
        "id": "2a906d93a4506197",
        "type": "ui-group",
        "name": "keyboard",
        "page": "8f7a46dc69023904",
        "width": "12",
        "height": "1",
        "order": 2,
        "showTitle": true,
        "className": "",
        "visible": "true",
        "disabled": "false",
        "groupType": "dialog"
    },
    {
        "id": "8f7a46dc69023904",
        "type": "ui-page",
        "name": "user",
        "ui": "35e7e3e70a04a217",
        "path": "/page9",
        "icon": "account",
        "layout": "grid",
        "theme": "0d92c765bfad87e6",
        "breakpoints": [
            {
                "name": "Default",
                "px": "0",
                "cols": "3"
            },
            {
                "name": "Tablet",
                "px": "576",
                "cols": "6"
            },
            {
                "name": "Small Desktop",
                "px": "768",
                "cols": "9"
            },
            {
                "name": "Desktop",
                "px": "1024",
                "cols": "12"
            }
        ],
        "order": 1,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "0d92c765bfad87e6",
        "type": "ui-theme",
        "name": "Basic Blue Theme",
        "colors": {
            "surface": "#14549e",
            "primary": "#336ba9",
            "bgPage": "#eeeeee",
            "groupBg": "#ffffff",
            "groupOutline": "#cccccc"
        },
        "sizes": {
            "pagePadding": "12px",
            "groupGap": "12px",
            "groupBorderRadius": "4px",
            "widgetGap": "12px",
            "density": "default"
        }
    }
]

```

---

<div class="post-metadata">

### Author: ![hauier](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hauier/32/98583_2.png) [@hauier](https://discourse.nodered.org/u/hauier)
#### Post date: [13 March 2025 06:58 UTC](https://discourse.nodered.org/t/using-dashboard-2-0-created-a-virtual-keyboard-similar-to-1-0/95966/2 "2025-03-13T06:58:17Z")

</div>

I have created an entirely new code from scratch, and it works perfectly! I’m very satisfied with the results.

```auto
[
    {
        "id": "9c90b5400d6ac0eb",
        "type": "ui-template",
        "z": "a7164eb2fc7a385e",
        "group": "f845855078ca3080",
        "page": "",
        "ui": "",
        "name": "click event",
        "order": 4,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>\n <div>\n\n </div>\n</template>\n\n<script>\nexport default {\n mounted() {\n this.addInputListeners();\n },\n methods: {\n addInputListeners() {\n const inputs = document.getElementsByTagName(\"input\");\n console.log(\"Monitoring input elements:\", inputs);\n for (let i = 0; i < inputs.length; i++) {\n inputs[i].addEventListener(\"click\", this.handleInputClick, false);\n }\n },\n handleInputClick(event) {\n const inputEl = event.target;\n const msg = { payload: { id: inputEl.id || \"undefined\" } };\n console.log(\"Input clicked. Sending message:\", msg);\n this.sendMessage(msg);\n },\n sendMessage(msg) {\n if (typeof this.send === \"function\") {\n this.send(msg);\n }\n this.$emit(\"input\", msg);\n }\n }\n};\n</script>\n\n<style scoped>\n</style>\n",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 670,
        "y": 180,
        "wires": [
            [
                "91681223e14df927",
                "c1bd36d069e5fc3c"
            ]
        ]
    },
    {
        "id": "91681223e14df927",
        "type": "change",
        "z": "a7164eb2fc7a385e",
        "name": "",
        "rules": [
            {
                "t": "set",
                "p": "payload",
                "pt": "msg",
                "to": "{\"groups\":{\"show\":[\"Active Development:keyboard\"]}}",
                "tot": "json"
            }
        ],
        "action": "",
        "property": "",
        "from": "",
        "to": "",
        "reg": false,
        "x": 900,
        "y": 180,
        "wires": [
            [
                "c1f02c6b662e7f31"
            ]
        ]
    },
    {
        "id": "c1bd36d069e5fc3c",
        "type": "ui-template",
        "z": "a7164eb2fc7a385e",
        "group": "2a906d93a4506197",
        "page": "",
        "ui": "",
        "name": "",
        "order": 1,
        "width": 0,
        "height": 0,
        "head": "",
        "format": "<template>\n <div class=\"keyboard-container\">\n <div class=\"keyboard\">\n <div class=\"keyboard-line\" v-for=\"(line, lineIndex) in currentLayout\" :key=\"lineIndex\">\n <button class=\"keyboard-key\" v-for=\"(key, keyIndex) in line\" :key=\"keyIndex\" @click=\"pressKey(key)\">\n {{ displayKey(key) }}\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<script>\nexport default {\n name: 'VirtualKeyboard',\n props: {\n targetId: {\n type: String,\n default: \"\" \n },\n layout: {\n type: String,\n default: 'english'\n }\n },\n data() {\n return {\n shift: false,\n capslock: false,\n englishLayout: [\n ['1','2','3','4','5','6','7','8','9','0'],\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','Del'],\n ['-', '/','space','return']\n ],\n numericLayout: [\n ['1','2','3','4','5','6','7','8','9','0'],\n ['Del']\n ]\n }\n },\n computed: {\n currentLayout() {\n return this.layout === 'numeric' ? this.numericLayout : this.englishLayout;\n },\n targetInputId() {\n if (this.msg && this.msg.payload && this.msg.payload.id) {\n return this.msg.payload.id;\n }\n return this.targetId;\n }\n },\n methods: {\n pressKey(key) {\n let output;\n if (key === 'shift') {\n this.shift = !this.shift;\n return;\n }\n if (key === 'Del') {\n output = 'backspace';\n } else if (key === 'space') {\n output = ' ';\n } else if (key === 'return') {\n output = '\\n';\n } else {\n output = key;\n if (this.shift || this.capslock) {\n output = key.toUpperCase();\n if (this.shift) this.shift = false;\n }\n }\n const targetId = this.targetInputId;\n if (targetId) {\n const targetInput = document.getElementById(targetId);\n if (targetInput) {\n if (output === 'backspace') {\n targetInput.value = targetInput.value.slice(0, -1);\n } else {\n targetInput.value += output;\n }\n targetInput.dispatchEvent(new Event(\"input\"));\n } else {\n console.warn(\"Target input not found for id:\", targetId);\n }\n } else {\n console.warn(\"No target input id specified.\");\n }\n this.$emit('key-press', output);\n this.sendMessage({ payload: output });\n },\n displayKey(key) {\n if (key.length === 1 && /[a-zA-Z]/.test(key)) {\n return (this.shift || this.capslock) ? key.toUpperCase() : key.toLowerCase();\n }\n return key;\n },\n sendMessage(payload) {\n if (typeof this.send === 'function') {\n this.send(payload);\n } else {\n this.$emit(\"input\", payload);\n }\n }\n }\n};\n</script>\n\n<style scoped>\n.keyboard-container {\n display: inline-block;\n border: 1px solid #ccc;\n padding: 10px;\n background: #f9f9f9;\n}\n.keyboard-line {\n display: flex;\n margin-bottom: 5px;\n}\n.keyboard-key {\n flex: 1;\n margin: 2px;\n padding: 10px;\n font-size: 16px;\n cursor: pointer;\n border: 1px solid #468db3;\n border-radius: 5px;\n background: #fff;\n transition: background 0.2s;\n}\n.keyboard-key:hover {\n background: #e0f0ff;\n}\n</style>\n",
        "storeOutMessages": true,
        "passthru": true,
        "resendOnRefresh": true,
        "templateScope": "local",
        "className": "",
        "x": 880,
        "y": 240,
        "wires": [
            []
        ]
    },
    {
        "id": "4f81f8796c7be6db",
        "type": "ui-text-input",
        "z": "a7164eb2fc7a385e",
        "group": "f845855078ca3080",
        "name": "",
        "label": "text",
        "order": 1,
        "width": 0,
        "height": 0,
        "topic": "topic",
        "topicType": "msg",
        "mode": "text",
        "tooltip": "",
        "delay": 300,
        "passthru": true,
        "sendOnDelay": false,
        "sendOnBlur": true,
        "sendOnEnter": true,
        "className": "",
        "clearable": false,
        "sendOnClear": false,
        "icon": "",
        "iconPosition": "left",
        "iconInnerPosition": "inside",
        "x": 890,
        "y": 400,
        "wires": [
            []
        ]
    },
    {
        "id": "493f7e59c233fe8f",
        "type": "ui-text-input",
        "z": "a7164eb2fc7a385e",
        "group": "f845855078ca3080",
        "name": "",
        "label": "text1",
        "order": 2,
        "width": 0,
        "height": 0,
        "topic": "topic",
        "topicType": "msg",
        "mode": "text",
        "tooltip": "",
        "delay": 300,
        "passthru": true,
        "sendOnDelay": false,
        "sendOnBlur": true,
        "sendOnEnter": true,
        "className": "",
        "clearable": false,
        "sendOnClear": false,
        "icon": "",
        "iconPosition": "left",
        "iconInnerPosition": "inside",
        "x": 890,
        "y": 340,
        "wires": [
            []
        ]
    },
    {
        "id": "03d917a7deb5b254",
        "type": "ui-text-input",
        "z": "a7164eb2fc7a385e",
        "group": "f845855078ca3080",
        "name": "",
        "label": "text",
        "order": 3,
        "width": 0,
        "height": 0,
        "topic": "topic",
        "topicType": "msg",
        "mode": "text",
        "tooltip": "",
        "delay": 300,
        "passthru": true,
        "sendOnDelay": false,
        "sendOnBlur": true,
        "sendOnEnter": true,
        "className": "",
        "clearable": false,
        "sendOnClear": false,
        "icon": "",
        "iconPosition": "left",
        "iconInnerPosition": "inside",
        "x": 890,
        "y": 460,
        "wires": [
            []
        ]
    },
    {
        "id": "c1f02c6b662e7f31",
        "type": "ui-control",
        "z": "a7164eb2fc7a385e",
        "name": "",
        "ui": "35e7e3e70a04a217",
        "events": "all",
        "x": 1120,
        "y": 180,
        "wires": [
            []
        ]
    },
    {
        "id": "f845855078ca3080",
        "type": "ui-group",
        "name": "usersetting",
        "page": "8f7a46dc69023904",
        "width": "12",
        "height": "1",
        "order": 1,
        "showTitle": true,
        "className": "",
        "visible": "true",
        "disabled": "false",
        "groupType": "default"
    },
    {
        "id": "2a906d93a4506197",
        "type": "ui-group",
        "name": "keyboard",
        "page": "8f7a46dc69023904",
        "width": "12",
        "height": "1",
        "order": 2,
        "showTitle": true,
        "className": "",
        "visible": "true",
        "disabled": "false",
        "groupType": "dialog"
    },
    {
        "id": "35e7e3e70a04a217",
        "type": "ui-base",
        "name": "스마트진단기",
        "path": "/dashboard",
        "appIcon": "",
        "includeClientData": true,
        "acceptsClientConfig": [
            "ui-notification",
            "ui-gauge"
        ],
        "showPathInSidebar": false,
        "showPageTitle": true,
        "navigationStyle": "icon",
        "titleBarStyle": "fixed",
        "showReconnectNotification": false,
        "notificationDisplayTime": "1",
        "showDisconnectNotification": false
    },
    {
        "id": "8f7a46dc69023904",
        "type": "ui-page",
        "name": "users",
        "ui": "35e7e3e70a04a217",
        "path": "/page9",
        "icon": "account",
        "layout": "grid",
        "theme": "0d92c765bfad87e6",
        "breakpoints": [
            {
                "name": "Default",
                "px": "0",
                "cols": "3"
            },
            {
                "name": "Tablet",
                "px": "576",
                "cols": "6"
            },
            {
                "name": "Small Desktop",
                "px": "768",
                "cols": "9"
            },
            {
                "name": "Desktop",
                "px": "1024",
                "cols": "12"
            }
        ],
        "order": 1,
        "className": "",
        "visible": "true",
        "disabled": "false"
    },
    {
        "id": "0d92c765bfad87e6",
        "type": "ui-theme",
        "name": "Basic Blue Theme",
        "colors": {
            "surface": "#14549e",
            "primary": "#336ba9",
            "bgPage": "#eeeeee",
            "groupBg": "#ffffff",
            "groupOutline": "#cccccc"
        },
        "sizes": {
            "pagePadding": "12px",
            "groupGap": "12px",
            "groupBorderRadius": "4px",
            "widgetGap": "12px",
            "density": "default"
        }
    }
]

```

---

<div class="post-metadata">

### Author: ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)
#### Post date: [13 March 2025 08:40 UTC](https://discourse.nodered.org/t/using-dashboard-2-0-created-a-virtual-keyboard-similar-to-1-0/95966/3 "2025-03-13T08:40:38Z")

</div>

Same thing but using [simple-keyboard](https://hodgef.com/simple-keyboard/)

```auto
[{"id":"6b472dd07145836b","type":"ui-template","z":"9ab81d258b54a577","group":"","page":"","ui":"cf21e7dda9e29be4","name":"load scripts","order":0,"width":0,"height":0,"head":"","format":"\n<script src=\"https://cdn.jsdelivr.net/npm/simple-keyboard@latest/build/index.js\"></script>\n<link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/simple-keyboard@latest/build/css/index.css\">","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"widget:ui","className":"","x":410,"y":2460,"wires":[[]]},{"id":"dabf83cc262c9fe1","type":"ui-template","z":"9ab81d258b54a577","group":"40d9b986076be70a","page":"","ui":"","name":"","order":1,"width":0,"height":0,"head":"","format":"<template>\n <div class=\"simple-keyboard\"></div> \n</template>\n\n<script>\n export default { \n data() {\n return {\n keyboard:null,\n selectedInput:undefined\n }\n },\n computed: {\n \n \n },\n mounted() {\n this.$socket.on('msg-input:' + this.id, this.onInput) \n let interval = setInterval(() => {\n if (window.SimpleKeyboard) { \n clearInterval(interval)\n this.draw()\n }\n }, 40)\n },\n methods: {\n \n draw () \n {\n const Keyboard = window.SimpleKeyboard.default;\n const myKeyboard = new Keyboard({\n onChange: input => onChange(input),\n onKeyPress: button => onKeyPress(button)\n });\n const onChange = (input) => { \n const target = document.querySelector(this.selectedInput)\n if(!target){\n return\n }\n target.value = input;\n target.dispatchEvent(new Event(\"input\"));\n console.log(\"Input changed\", input);\n }\n\n const onKeyPress = (button) => {\n console.log(\"Button pressed\", button);\n }\n this.keyboard = myKeyboard\n },\n \n onInput (msg) { \n if(!this.keyboard){\n return\n } \n this.selectedInput = \"#\"+msg.payload.id \n this.keyboard.setOptions({\n inputName: \"#\"+msg.payload.id\n });\n }\n }\n }\n</script>\n\n<style>\n .simple-keyboard {\n color:black;\n }\n .input{\n color:white;\n }\n \n \n</style>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":680,"y":2620,"wires":[[]]},{"id":"5b468309595d3755","type":"ui-template","z":"9ab81d258b54a577","group":"0636247ce7239e6a","page":"","ui":"","name":"click event","order":4,"width":0,"height":0,"head":"","format":"<template>\n <div>\n\n </div>\n</template>\n\n<script>\nexport default {\n mounted() {\n this.addInputListeners();\n },\n methods: {\n addInputListeners() {\n const inputs = document.getElementsByTagName(\"input\");\n console.log(\"Monitoring input elements:\", inputs);\n for (let i = 0; i < inputs.length; i++) {\n inputs[i].addEventListener(\"click\", this.handleInputClick, false);\n }\n },\n handleInputClick(event) {\n const inputEl = event.target;\n const msg = { payload: { id: inputEl.id || \"undefined\" } };\n console.log(\"Input clicked. Sending message:\", msg);\n this.sendMessage(msg);\n },\n sendMessage(msg) {\n if (typeof this.send === \"function\") {\n this.send(msg);\n }\n this.$emit(\"input\", msg);\n }\n }\n};\n</script>\n\n<style scoped>\n</style>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":410,"y":2500,"wires":[["bec64a21fb9fe05b","2fc81b39d4611f09","58491604f7d43889"]]},{"id":"bec64a21fb9fe05b","type":"change","z":"9ab81d258b54a577","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"groups\":{\"show\":[\"Active Development:keyboard\"]}}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":680,"y":2500,"wires":[["6c5d85628ec11ede"]]},{"id":"2fc81b39d4611f09","type":"ui-template","z":"9ab81d258b54a577","d":true,"group":"40d9b986076be70a","page":"","ui":"","name":"","order":2,"width":0,"height":0,"head":"","format":"<template>\n <div class=\"keyboard-container\">\n <div class=\"keyboard\">\n <div class=\"keyboard-line\" v-for=\"(line, lineIndex) in currentLayout\" :key=\"lineIndex\">\n <button class=\"keyboard-key\" v-for=\"(key, keyIndex) in line\" :key=\"keyIndex\" @click=\"pressKey(key)\">\n {{ displayKey(key) }}\n </button>\n </div>\n </div>\n </div>\n</template>\n\n<script>\nexport default {\n name: 'VirtualKeyboard',\n props: {\n targetId: {\n type: String,\n default: \"\" \n },\n layout: {\n type: String,\n default: 'english'\n }\n },\n data() {\n return {\n shift: false,\n capslock: false,\n englishLayout: [\n ['1','2','3','4','5','6','7','8','9','0'],\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','Del'],\n ['-', '/','space','return']\n ],\n numericLayout: [\n ['1','2','3','4','5','6','7','8','9','0'],\n ['Del']\n ]\n }\n },\n computed: {\n currentLayout() {\n return this.layout === 'numeric' ? this.numericLayout : this.englishLayout;\n },\n targetInputId() {\n if (this.msg && this.msg.payload && this.msg.payload.id) {\n return this.msg.payload.id;\n }\n return this.targetId;\n }\n },\n methods: {\n pressKey(key) {\n let output;\n if (key === 'shift') {\n this.shift = !this.shift;\n return;\n }\n if (key === 'Del') {\n output = 'backspace';\n } else if (key === 'space') {\n output = ' ';\n } else if (key === 'return') {\n output = '\\n';\n } else {\n output = key;\n if (this.shift || this.capslock) {\n output = key.toUpperCase();\n if (this.shift) this.shift = false;\n }\n }\n const targetId = this.targetInputId;\n if (targetId) {\n const targetInput = document.getElementById(targetId);\n if (targetInput) {\n if (output === 'backspace') {\n targetInput.value = targetInput.value.slice(0, -1);\n } else {\n targetInput.value += output;\n }\n targetInput.dispatchEvent(new Event(\"input\"));\n } else {\n console.warn(\"Target input not found for id:\", targetId);\n }\n } else {\n console.warn(\"No target input id specified.\");\n }\n this.$emit('key-press', output);\n this.sendMessage({ payload: output });\n },\n displayKey(key) {\n if (key.length === 1 && /[a-zA-Z]/.test(key)) {\n return (this.shift || this.capslock) ? key.toUpperCase() : key.toLowerCase();\n }\n return key;\n },\n sendMessage(payload) {\n if (typeof this.send === 'function') {\n this.send(payload);\n } else {\n this.$emit(\"input\", payload);\n }\n }\n }\n};\n</script>\n\n<style scoped>\n.keyboard-container {\n display: inline-block;\n border: 1px solid #ccc;\n padding: 10px;\n background: #f9f9f9;\n}\n.keyboard-line {\n display: flex;\n margin-bottom: 5px;\n}\n.keyboard-key {\n flex: 1;\n margin: 2px;\n padding: 10px;\n font-size: 16px;\n cursor: pointer;\n border: 1px solid #468db3;\n border-radius: 5px;\n background: #fff;\n transition: background 0.2s;\n}\n.keyboard-key:hover {\n background: #e0f0ff;\n}\n</style>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":680,"y":2580,"wires":[[]]},{"id":"908bc43a4f30391b","type":"ui-text-input","z":"9ab81d258b54a577","group":"0636247ce7239e6a","name":"","label":"text","order":1,"width":0,"height":0,"topic":"topic","topicType":"msg","mode":"text","tooltip":"","delay":300,"passthru":true,"sendOnDelay":false,"sendOnBlur":true,"sendOnEnter":true,"className":"","clearable":false,"sendOnClear":false,"icon":"","iconPosition":"left","iconInnerPosition":"inside","x":670,"y":2720,"wires":[[]]},{"id":"f14e8308a33abea7","type":"ui-text-input","z":"9ab81d258b54a577","group":"0636247ce7239e6a","name":"","label":"text1","order":2,"width":0,"height":0,"topic":"topic","topicType":"msg","mode":"text","tooltip":"","delay":300,"passthru":true,"sendOnDelay":false,"sendOnBlur":true,"sendOnEnter":true,"className":"","clearable":false,"sendOnClear":false,"icon":"","iconPosition":"left","iconInnerPosition":"inside","x":670,"y":2660,"wires":[[]]},{"id":"d219d6de47048a5a","type":"ui-text-input","z":"9ab81d258b54a577","group":"0636247ce7239e6a","name":"","label":"text","order":3,"width":0,"height":0,"topic":"topic","topicType":"msg","mode":"text","tooltip":"","delay":300,"passthru":true,"sendOnDelay":false,"sendOnBlur":true,"sendOnEnter":true,"className":"","clearable":false,"sendOnClear":false,"icon":"","iconPosition":"left","iconInnerPosition":"inside","x":670,"y":2780,"wires":[[]]},{"id":"6c5d85628ec11ede","type":"ui-control","z":"9ab81d258b54a577","name":"","ui":"cf21e7dda9e29be4","events":"all","x":860,"y":2500,"wires":[[]]},{"id":"58491604f7d43889","type":"delay","z":"9ab81d258b54a577","name":"","pauseType":"delay","timeout":"250","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":510,"y":2620,"wires":[["dabf83cc262c9fe1"]]},{"id":"cf21e7dda9e29be4","type":"ui-base","name":"My Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"navigationStyle":"default","titleBarStyle":"default","showReconnectNotification":true,"notificationDisplayTime":1,"showDisconnectNotification":true},{"id":"40d9b986076be70a","type":"ui-group","name":"keyboard","page":"8f7a46dc69023904","width":"12","height":"1","order":2,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"dialog"},{"id":"0636247ce7239e6a","type":"ui-group","name":"usersetting","page":"8f7a46dc69023904","width":"12","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"8f7a46dc69023904","type":"ui-page","name":"user","ui":"cf21e7dda9e29be4","path":"/page9","icon":"account","layout":"grid","theme":"0d92c765bfad87e6","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":2,"className":"","visible":"true","disabled":"false"},{"id":"0d92c765bfad87e6","type":"ui-theme","name":"Basic Blue Theme","colors":{"surface":"#14549e","primary":"#336ba9","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px","density":"default"}}]

```

Edit: forgot scripts loading

---

<div class="post-metadata">

### Author: ![hauier](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hauier/32/98583_2.png) [@hauier](https://discourse.nodered.org/u/hauier)
#### Post date: [13 March 2025 09:03 UTC](https://discourse.nodered.org/t/using-dashboard-2-0-created-a-virtual-keyboard-similar-to-1-0/95966/4 "2025-03-13T09:03:45Z")

</div>

Actually, I knew about **simple-keyboard** , but I didn't know how to use it...  
I downloaded the ZIP file and registered the module in **setting.js** , but it didn't work, so I gave up.  
I would appreciate it if you could show me how to make it work! 😊

---

<div class="post-metadata">

### Author: ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)
#### Post date: [13 March 2025 09:10 UTC](https://discourse.nodered.org/t/using-dashboard-2-0-created-a-virtual-keyboard-similar-to-1-0/95966/5 "2025-03-13T09:10:10Z")

</div>

Well it is working example I just fist forgot to add template for scripts to load.  
You cant use it as module Id quess. For that kind of usage you'll need to build a widget.

---

<div class="post-metadata">

### Author: ![j2002](https://avatars.discourse-cdn.com/v4/letter/j/eb8c5e/32.png) [@j2002](https://discourse.nodered.org/u/j2002)
#### Post date: [13 March 2025 09:31 UTC](https://discourse.nodered.org/t/using-dashboard-2-0-created-a-virtual-keyboard-similar-to-1-0/95966/6 "2025-03-13T09:31:22Z")

</div>

Thanks. I have been looking for this for months. I will try to adapt it for a form node, because I don't see how to send what is entered in the text nodes. When I click on them, it first sends null  
P.S: I cannot made work your example in the second post.

---

<div class="post-metadata">

### Author: ![j2002](https://avatars.discourse-cdn.com/v4/letter/j/eb8c5e/32.png) [@j2002](https://discourse.nodered.org/u/j2002)
#### Post date: [13 March 2025 16:55 UTC](https://discourse.nodered.org/t/using-dashboard-2-0-created-a-virtual-keyboard-similar-to-1-0/95966/7 "2025-03-13T16:55:56Z")

</div>

How can I made it work in all tabs. Because it seems changing temaplate node click event to Widget (UI\_Scoped) is not enough

---

<div class="post-metadata">

### Author: ![hotNipi](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hotnipi/32/383_2.png) [@hotNipi](https://discourse.nodered.org/u/hotNipi)
#### Post date: [13 March 2025 17:37 UTC](https://discourse.nodered.org/t/using-dashboard-2-0-created-a-virtual-keyboard-similar-to-1-0/95966/8 "2025-03-13T17:37:46Z")

</div>

I think there's no zero-code solution to have one ui\_template for keyboard to appear on every page. Group type Dialog must be binded to some page.

---

<div class="post-metadata">

### Author: ![hauier](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/hauier/32/98583_2.png) [@hauier](https://discourse.nodered.org/u/hauier)
#### Post date: [14 March 2025 00:10 UTC](https://discourse.nodered.org/t/using-dashboard-2-0-created-a-virtual-keyboard-similar-to-1-0/95966/9 "2025-03-14T00:10:32Z")

</div>

If you want to receive feedback through a text node, you can set it up like this. After that, you can use the functionality to store the global variable behind the text node.

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/f/3/f397a07128255404fc665a929d8d6fb5156deae4.png)

---

<div class="post-metadata">

### Author: ![system](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/1X/d073cd938eafa2e558d7c2cd59003b3ef4963033.png) [@system](https://discourse.nodered.org/u/system)
#### Post date: [16 April 2025 23:34 UTC](https://discourse.nodered.org/t/using-dashboard-2-0-created-a-virtual-keyboard-similar-to-1-0/95966/10 "2025-04-16T23:34:46Z")

</div>

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.
