Form builder with custom HTML

Hello,
I am still learning how to use uibuilder and I have been using the uib-element node, part of uibuilder.

So in short, what I want to-do is add some HTML to uib-element node and get an output in node-red.

As the the uib-element form does not support dropdowns at this point I have to input custom HTML.

I am inputting this HTML.

<label for="client-names">Select a client:</label>
<select name="client-names" id="client-names" onchange="this.dataset.newValue = this.value" onfocus="this.dataset.oldValue = this.value">
  <option value="blank"></option>
  <option value=0>Client</option>
  <option value=1>Test</option>
</select>

I want to connect this to the output and when some selectes an option it will send an output to node-red.
I inspected some example code and added the onfocus and onchange to see if that would pickup the value, but it didn't.

I also tried adding this button that is used in the example, and I get an output to node-red. But the array is empty.

<button id="r6" type="button" label="Send to Node-RED" value="Buttons can have values as well" onclick="uibuilder.eventSend(event)" name="r6" title="Type: button">Send to Node-RED</button>

I also tried to add a normal text input copied from the example code used and still nothing.

<input id="r1" type="text" required="false" label="Text Input:" value="Foo" onchange="this.dataset.newValue = this.value" onfocus="this.dataset.oldValue = this.value" name="r1" title="Type: text" data-old-value="Foo">

I am using this example code. uibuilder - Video 003 - Simple Form (flow) - Node-RED
and injecting in HTML.

I have also been looking in the documentation and found a section that looks like it should be picking up onChange request, but I am sure there is somthing I am missing.

const msgChgEvt = uibuilder.onChange('msg', (msg) => {
    // Dump the msg as text to the html element with an id of "msg"
    const eMsg = $('#msg')
    if (eMsg) eMsg.innerHTML = uibuilder.syntaxHighlight(msg)
})

Any pointers would be great.

I think I worked it out, I need to change my onchange event to this and then I started getting outputs.

onchange="uibuilder.eventSend(event)

Well, that's what I've been working on recently - as well as textarea inputs. I've also been reworking the uibuilder CSS (which you don't have to use of course if you don't want to) to simplify the HTML output from uib-element while making the layout more robust using a CSS Grid layout.

I could possibly cut short the development of v6.5 and finish off those enhancements if you wanted? Would take a few days as this week has been unbelievably manic.

Looks good except that you should currently wrap that in a div. In the next release, you won't need the extra div.

If you have made sure that the select is within the form, the send to node-red button will include the data automatically. You wouldn't need to do anything else.

If your select is not inside a form tag, you would need to change the button from the default click handler to your own custom one that picks up the form data. Alternatively, you could add an attribute to the select tag that tells the browser what form it belongs to. That way, you can have - lets say - just the submit button inside a form tag and the select could be put somewhere else.

The Input (Form Input) element - HTML: HyperText Markup Language | MDN (mozilla.org)

The form tag is the key though.

Thanks for the detailed reply, this has helped me get some insight.

Oh you are close to releasing these items? Only release them once it is ready, but having these items would mean I can stick to the low code option :smiley:

How long would you be to finish off the release v6.5?

My original solution work for little bit, but now I am getting this error every time I try to inject the HTML.

As a note this is being run in a docker instance

2023-04-26 19:55:57 26 Apr 11:55:57 - [error] TypeError: Cannot create property 'onchange' on string '
2023-04-26 19:55:57 <select name="client-names" id="client-names" onchange="uibuilder.eventSend(event)">
2023-04-26 19:55:57   <option value="blank"></option>
2023-04-26 19:55:57   <option value=0>Client</option>
2023-04-26 19:55:57   <option value=1>Test</option>
2023-04-26 19:55:57 </select>
2023-04-26 19:55:57 '
2023-04-26 19:55:57     at /data/node_modules/node-red-contrib-uibuilder/nodes/uib-element/customNode.js:550:29
2023-04-26 19:55:57     at Array.forEach (<anonymous>)
2023-04-26 19:55:57     at buildSForm (/data/node_modules/node-red-contrib-uibuilder/nodes/uib-element/customNode.js:540:22)
2023-04-26 19:55:57     at buildUi (/data/node_modules/node-red-contrib-uibuilder/nodes/uib-element/customNode.js:870:19)
2023-04-26 19:55:57     at async nodeInstance.inputMsgHandler [as _inputCallback] (/data/node_modules/node-red-contrib-uibuilder/nodes/uib-element/customNode.js:89:5)

Not going to promise exactly. I need to make a few changes and as long as I can discipline myself not to get distracted, that shouldn't take too long, a few days. One of the advantages of having a standardised data schema for output.

You are trying to insert a string into the form rather than creating the low-code output structure which is why you are getting the error. Inserting an HTML string

Do you realise that you can do that post the uib-element output? Because the output is a standard schema, you can easily make your own amendments too. If I get a few minutes, I'll work up an example - you can use that as a workaround until the new release is ready.

To be honest I am not sure what I am doing I am just trying things to get it to work the way I want it. I do not really understand JavaScript but I am trying to lol.'

This is the flow I am using and I did not know I could inject code straight into the uibuild node.

An example would be perfect, then I can see what I am doing wrong and replicate in other areas I may want to use. Thanks for your help!

[
    {
        "id": "75aeb431c2cbdb1a",
        "type": "comment",
        "z": "54e00243c63a0667",
        "name": "Example Form Data",
        "info": "```json\n[\n    {\n        \"id\": \"r1\",\n        \"type\": \"text\",\n        \"required\": false,\n        \"label\": \"Text Input:\",\n        \"value\": \"Foo\"\n    },\n    {\n        \"id\": \"r2\",\n        \"type\": \"color\",\n        \"required\": false,\n        \"label\": \"Colour:\",\n        \"value\": \"#427798\"\n    },\n    {\n        \"id\": \"r3\",\n        \"type\": \"date\",\n        \"required\": true,\n        \"label\": \"Date:\"\n    },\n    {\n        \"id\": \"r4\",\n        \"type\": \"range\",\n        \"required\": false,\n        \"label\": \"Range (0-100):\",\n        \"value\": \"20\",\n        \"min\": 0,\n        \"max\": 100\n    },\n    {\n        \"id\": \"r5\",\n        \"type\": \"button\",\n        \"label\": \"Send to Node-RED\",\n        \"value\": \"Buttons can have values as well\"\n    }\n]\n```",
        "x": 150,
        "y": 320,
        "wires": []
    },
    {
        "id": "458fa38ac86fcae4",
        "type": "uib-element",
        "z": "54e00243c63a0667",
        "name": "",
        "topic": "",
        "elementtype": "sform",
        "parent": "#more",
        "parentSource": "",
        "parentSourceType": "str",
        "elementid": "myform1",
        "elementId": "",
        "elementIdSourceType": "str",
        "heading": "Our Simple Form",
        "headingSourceType": "str",
        "headingLevel": "h2",
        "position": "last",
        "positionSourceType": "str",
        "confData": {},
        "x": 470,
        "y": 420,
        "wires": [
            [
                "976a7e804093ea33"
            ]
        ]
    },
    {
        "id": "976a7e804093ea33",
        "type": "link out",
        "z": "54e00243c63a0667",
        "name": "link out 1",
        "mode": "link",
        "links": [
            "472ff48730d1b698"
        ],
        "x": 675,
        "y": 440,
        "wires": []
    },
    {
        "id": "f1954da8724cab66",
        "type": "inject",
        "z": "54e00243c63a0667",
        "name": "",
        "props": [
            {
                "p": "payload"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "form",
        "payload": "[{\"id\":\"r1\",\"type\":\"text\",\"required\":false,\"label\":\"Text Input:\",\"value\":\"Foo\"},{\"id\":\"r2\",\"type\":\"color\",\"required\":false,\"label\":\"Colour:\",\"value\":\"#427798\"},{\"id\":\"r3\",\"type\":\"date\",\"required\":true,\"label\":\"Date:\"},{\"id\":\"r4\",\"type\":\"range\",\"required\":false,\"label\":\"Range (0-100):\",\"value\":\"20\",\"min\":0,\"max\":100},{\"id\":\"r5\",\"type\":\"button\",\"label\":\"Send to Node-RED\",\"value\":\"Buttons can have values as well\"}]",
        "payloadType": "json",
        "x": 110,
        "y": 420,
        "wires": [
            [
                "458fa38ac86fcae4"
            ]
        ]
    },
    {
        "id": "8a1bae4c574b3ed5",
        "type": "template",
        "z": "54e00243c63a0667",
        "name": "",
        "field": "payload",
        "fieldType": "msg",
        "format": "handlebars",
        "syntax": "mustache",
        "template": "\n<select name=\"client-names\" id=\"client-names\" onchange=\"uibuilder.eventSend(event)\">\n  <option value=\"blank\"></option>\n  <option value=0>Client</option>\n  <option value=1>Test</option>\n</select>\n",
        "output": "str",
        "x": 260,
        "y": 520,
        "wires": [
            [
                "8ded0f524aed181e"
            ]
        ]
    },
    {
        "id": "f0031b98af699a49",
        "type": "inject",
        "z": "54e00243c63a0667",
        "name": "",
        "props": [
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "form",
        "x": 110,
        "y": 520,
        "wires": [
            [
                "8a1bae4c574b3ed5"
            ]
        ]
    },
    {
        "id": "8ded0f524aed181e",
        "type": "uib-element",
        "z": "54e00243c63a0667",
        "name": "",
        "topic": "",
        "elementtype": "html",
        "parent": "#more",
        "parentSource": "",
        "parentSourceType": "str",
        "elementid": "client_select",
        "elementId": "",
        "elementIdSourceType": "str",
        "heading": "Our Simple Form",
        "headingSourceType": "str",
        "headingLevel": "h2",
        "position": "last",
        "positionSourceType": "str",
        "confData": {},
        "x": 470,
        "y": 520,
        "wires": [
            [
                "976a7e804093ea33"
            ]
        ]
    },
    {
        "id": "5f79bf4a4f4f3f36",
        "type": "group",
        "z": "54e00243c63a0667",
        "name": "uibuilder simple form example \\n ",
        "style": {
            "fill": "#dbcbe7",
            "fill-opacity": "0.2",
            "label": true,
            "color": "#000000"
        },
        "nodes": [
            "7c8adbf5b52fc611",
            "501e49abf6e5a607",
            "618c5774773d9086",
            "dea4924479704c6e",
            "75e04dbce19bc0e1",
            "fd14560e7aa0f275",
            "472ff48730d1b698",
            "32f6056e316798bf",
            "363f0c47148e997e",
            "3b043396fab746f0",
            "65dffaaedb01010f"
        ],
        "x": 54,
        "y": 43,
        "w": 792,
        "h": 238
    },
    {
        "id": "7c8adbf5b52fc611",
        "type": "uibuilder",
        "z": "54e00243c63a0667",
        "g": "5f79bf4a4f4f3f36",
        "name": "",
        "topic": "",
        "url": "demo",
        "fwdInMessages": false,
        "allowScripts": false,
        "allowStyles": false,
        "copyIndex": true,
        "templateFolder": "blank",
        "extTemplate": "",
        "showfolder": false,
        "reload": true,
        "sourceFolder": "src",
        "deployedVersion": "6.4.1",
        "showMsgUib": true,
        "credentials": {},
        "x": 520,
        "y": 160,
        "wires": [
            [
                "501e49abf6e5a607"
            ],
            [
                "32f6056e316798bf",
                "363f0c47148e997e"
            ]
        ],
        "info": "This example uses a blank template with\r\nthe IIFE build of the front-end client.\r\n\r\nIt does not use any front-end framework, just\r\npure HTML, CSS and JavaScript.\r\n\r\nThe IIFE build should be included using a link\r\ntag in your HTML."
    },
    {
        "id": "501e49abf6e5a607",
        "type": "debug",
        "z": "54e00243c63a0667",
        "g": "5f79bf4a4f4f3f36",
        "name": "uibuilder standard output",
        "active": true,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "counter",
        "x": 715,
        "y": 120,
        "wires": [],
        "l": false,
        "info": "This shows the data coming out of the\r\nuibuilder node's Port #1 (top) which is\r\nthe standard output.\r\n\r\nHere you will see any standard msg sent from\r\nyour front-end code."
    },
    {
        "id": "618c5774773d9086",
        "type": "debug",
        "z": "54e00243c63a0667",
        "g": "5f79bf4a4f4f3f36",
        "name": "uibuilder control output",
        "active": false,
        "tosidebar": true,
        "console": false,
        "tostatus": true,
        "complete": "true",
        "targetType": "full",
        "statusVal": "",
        "statusType": "counter",
        "x": 785,
        "y": 200,
        "wires": [],
        "l": false,
        "info": "This shows the data coming out of the\r\nuibuilder node's Port #2 (bottom) which is\r\nthe control output.\r\n\r\nHere you will see any control msg either sent\r\nby the node itself or from the front-end library.\r\n\r\nFor example the \"client disconnect\" and\r\n\"client connect\" messages. Or the \"visibility\"\r\nmessages from the client.\r\n\r\nLoop the \"client connect\", \"cache replay\" and\r\n\"cache clear\" messages back to a `uib-cache`\r\nnode before the input to uibuilder in order\r\nto control the output of the cache."
    },
    {
        "id": "dea4924479704c6e",
        "type": "link in",
        "z": "54e00243c63a0667",
        "g": "5f79bf4a4f4f3f36",
        "name": "uib-client-tests-uncached",
        "links": [
            "16010315c365626a",
            "1d1f5b0d55f6a646",
            "4ce1fc8c40ccbf7d",
            "81158a3cb7a12342",
            "b7f66bb3aee49f18",
            "1c433aa390e1bc8c",
            "278d2031ac38b84a",
            "756993c325791499",
            "cd89e56d548536d4",
            "948afbc2b3902c35"
        ],
        "x": 225,
        "y": 140,
        "wires": [
            [
                "75e04dbce19bc0e1"
            ]
        ]
    },
    {
        "id": "75e04dbce19bc0e1",
        "type": "junction",
        "z": "54e00243c63a0667",
        "g": "5f79bf4a4f4f3f36",
        "x": 380,
        "y": 140,
        "wires": [
            [
                "7c8adbf5b52fc611"
            ]
        ]
    },
    {
        "id": "fd14560e7aa0f275",
        "type": "uib-cache",
        "z": "54e00243c63a0667",
        "g": "5f79bf4a4f4f3f36",
        "cacheall": false,
        "cacheKey": "topic",
        "newcache": true,
        "num": 1,
        "storeName": "memory",
        "name": "Cache",
        "storeContext": "context",
        "varName": "uib_cache",
        "x": 330,
        "y": 220,
        "wires": [
            [
                "7c8adbf5b52fc611"
            ]
        ],
        "info": "Cache inputs based on the msg.topic.\r\n\r\nJust the last msg is kept for each\r\ntopic.\r\n\r\nDefault context store is used in this\r\nexample since not everyone will have\r\ndefined a file-based store.\r\n\r\nThat means that if you want an initial\r\nzero-code layout, you need to trigger\r\nits sending from a trigger node that\r\nfires when Node-RED (re)starts."
    },
    {
        "id": "472ff48730d1b698",
        "type": "link in",
        "z": "54e00243c63a0667",
        "g": "5f79bf4a4f4f3f36",
        "name": "uib-client-tests-cached",
        "links": [
            "32f6056e316798bf",
            "16cdac4094403b45",
            "1c433aa390e1bc8c",
            "976a7e804093ea33"
        ],
        "x": 225,
        "y": 240,
        "wires": [
            [
                "fd14560e7aa0f275"
            ]
        ]
    },
    {
        "id": "32f6056e316798bf",
        "type": "link out",
        "z": "54e00243c63a0667",
        "g": "5f79bf4a4f4f3f36",
        "name": "client-tests-control-outputs",
        "mode": "link",
        "links": [
            "472ff48730d1b698"
        ],
        "x": 715,
        "y": 200,
        "wires": [],
        "info": "Sends control messages back to the cache input\r\n\r\nThis ensures that when new clients (browser\r\ntabs) connect or if a page is reloaded, \r\nthe browser gets the cached data automatically.\r\n\r\nYou could filter this output to only include\r\n\"client connect\" messages if you wanted to \r\nimprove efficiency when you expect lots of \r\nclients to connect."
    },
    {
        "id": "363f0c47148e997e",
        "type": "junction",
        "z": "54e00243c63a0667",
        "g": "5f79bf4a4f4f3f36",
        "x": 740,
        "y": 180,
        "wires": [
            [
                "618c5774773d9086"
            ]
        ]
    },
    {
        "id": "3b043396fab746f0",
        "type": "inject",
        "z": "54e00243c63a0667",
        "g": "5f79bf4a4f4f3f36",
        "name": "Clear Cache",
        "props": [
            {
                "p": "uibuilderCtrl",
                "v": "clear cache",
                "vt": "str"
            },
            {
                "p": "cacheControl",
                "v": "CLEAR",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "",
        "x": 170,
        "y": 200,
        "wires": [
            [
                "fd14560e7aa0f275"
            ]
        ]
    },
    {
        "id": "65dffaaedb01010f",
        "type": "inject",
        "z": "54e00243c63a0667",
        "g": "5f79bf4a4f4f3f36",
        "name": "Reload",
        "props": [
            {
                "p": "_ui",
                "v": "{\"method\":\"reload\"}",
                "vt": "json"
            },
            {
                "p": "topic",
                "vt": "str"
            }
        ],
        "repeat": "",
        "crontab": "",
        "once": false,
        "onceDelay": 0.1,
        "topic": "reload",
        "x": 190,
        "y": 100,
        "wires": [
            [
                "75e04dbce19bc0e1"
            ]
        ],
        "info": "Sends a pre-formatted msg to the front-end that\r\ncauses the page to reload itself."
    }
]

Apologies, I've just found a slight bug in the output that uib-element with the form type generates - it puts the row containing the button inside the last form div instead of inside the form. That will all be fixed in the upcoming release which I will certainly now accelerate.

The way I've done your work-around is to use the core template node to easily put the HTML string onto the payload (you could of course do that several other ways too). Then I've added another uib-element node set to use HTML type.

This is the easiest way because you don't need to even manually add any low-code config.

However, due to the bug mentioned, if you want to add the dropdown as the last entry in the form before the button, you need to configure the uib-element node slightly oddly.

It is easier if you can add it earlier though. Here is the config for adding the dropdown as the first entry in the form.

Which is obviously a simpler CSS Selector.

From the resulting page, pressing the button having selected a client from the dropdown, this is what you get in the payload:

image

And the expanded data in the msg._ui:

image

Where you can see that the form was invalid because nothing was entered into the required fields but you still got back the selected data including the "old" previous value.

1 Like

Thank you, this really helps me get my forms built! I will look forward to the new release once it is ready.

1 Like

You might also wish to remember that, since the output is "just HTML", once you've output a form, you could grab the HTML and put it into the index.html file as fixed HTML. This is good if you need to improve efficiency.

1 Like

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