Using Dashboard 2.0, created a virtual keyboard similar to 1.0

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!

[
    {
        "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"
        }
    }
]

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

[
    {
        "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"
        }
    }
]
2 Likes

Same thing but using simple-keyboard

[{"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

1 Like

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! :blush:

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.

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.

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

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.

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.