๐ŸŽ† New Year fun with uibuilder - no-code web pages

For those happy to install a development version of uibuilder, here is a set of flows that demonstrate how to turn simple input data into an HTML UI with no code. Similar to using the core Dashboard but using pure HTML.

It is a new node - uib-element - that lets you select between several types of output (a table and various list types at the moment). Your simple array or object input will be turned into the appropriate element in your front-end "dashboard". This node will replace the experimental uib-list node which will start to be deprecated in the next release.

Obviously this is not (yet) as simple as using Dashboard since that also has a full layout helper as well. With this node, you do need to know a little bit about being able to identify HTML elements using CSS Selectors. (Hint: Use your browser's developer tools - the Elements tab lets you copy a selector for any element). Eventually, I'll build up a more complete set of possibilities and examples.

I still need to add a bunch of optional configuration options to each type, update documentation, etc.

There is also a new set of features due to land in the new front-end uibuilder client that will make it easy to update attributes and slot content on existing HTML elements.

The important thing to note is that anything you may want to be able to update/remove/etc HAS to have an HTML ID e.g. <div id="somethingunique"> The node requires you to set an id but cannot check that it is unique.

The 2nd thing to note is that "all" the new node does is to create msg._ui configuration objects that the uibuilder front-end library translates into HTML and inserts to the DOM (the thing you see in the browser). You can tell the node to just output that code rather than sending it direct to a uibuilder node. That not only lets you see how the config is formatted so you can expand on it yourself and build your own custom interfaces, it also lets you chain nodes together. An example is in the flows below.

To install:

cd ~/.node-red
npm install totallyinformation/node-red-contrib-uibuilder#v6.1.0

Updates are ongoing but I try to keep the GitHub branch mostly working.

Example flows:

[{"id":"3badb0a6906eef7f","type":"tab","label":"uib Element node tests","disabled":false,"info":"","env":[]},{"id":"3cd8b7c5aae9d40b","type":"group","z":"3badb0a6906eef7f","name":"Base node. Change ALL URL's to match.","style":{"label":true,"fill":"#e3f3d3","fill-opacity":"0.28","color":"#3f3f3f"},"nodes":["454da28ef00e68b6","90794d03f65a40d4","26fbd32ea1d00ff2","a48c0beb68e76845","c492d5d86eb0cfcb","f0f4f6c5a20a1823","195a61f93a912086"],"x":34,"y":19,"w":582,"h":142},{"id":"e1901bb3da85dd78","type":"group","z":"3badb0a6906eef7f","name":"Create a Unordered or Ordered List from a simple input. Change the URL to match the base.","style":{"fill":"#bfdbef","fill-opacity":"0.29","label":true,"color":"#3f3f3f"},"nodes":["51db7182c8c27901","0fe568807e7bd394","496418a44a66cc5e","0f1d399c3f14e113"],"x":34,"y":179,"w":652,"h":162},{"id":"0f5c35ef5c4bf8f5","type":"group","z":"3badb0a6906eef7f","name":"Create a Definition List from a simple input. Change the URL to match the base","style":{"label":true,"fill":"#bfdbef","fill-opacity":"0.4","color":"#3f3f3f"},"nodes":["af9ee48ee4da8b11","168cfe507c55796a","03979555908332c4","d08a2076e8158929","8fc4bf76f457ae68"],"x":714,"y":179,"w":692,"h":202},{"id":"0f3fbb9e84943e6b","type":"group","z":"3badb0a6906eef7f","name":"Create a table from simple input. Change the URL to match the base","style":{"fill":"#dbcbe7","fill-opacity":"0.3","label":true,"color":"#3f3f3f"},"nodes":["e30d349d9b3399c8","d58b7efe68a2da4d","d52bbc4c5096d4ba","4ce558165663c95b","900f97b2b079f1bf"],"x":34,"y":407,"w":852,"h":174},{"id":"8049618125109f32","type":"group","z":"3badb0a6906eef7f","name":"Chaining test. Change the URL's to match the base","style":{"fill":"#ffefbf","fill-opacity":"0.29","label":true,"color":"#000000"},"nodes":["14ded4d1dc70efbf","d3cda6078ae9ca73","d3a95abaddc5c013","ec046fe7e55e944c","cef57eb67e055c56"],"x":34,"y":619,"w":1192,"h":122},{"id":"195a61f93a912086","type":"junction","z":"3badb0a6906eef7f","g":"3cd8b7c5aae9d40b","x":270,"y":100,"wires":[["90794d03f65a40d4"]]},{"id":"454da28ef00e68b6","type":"inject","z":"3badb0a6906eef7f","g":"3cd8b7c5aae9d40b","name":"Send a msg","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"A Message From Node-RED","payload":"","payloadType":"date","x":150,"y":80,"wires":[["195a61f93a912086"]],"info":"Send a simply msg to the front-end.\r\n\r\nThe default front-end template code will display the msg\r\nusing HTML formatting, no coding required."},{"id":"90794d03f65a40d4","type":"uibuilder","z":"3badb0a6906eef7f","g":"3cd8b7c5aae9d40b","name":"","topic":"","url":"uib-element-test","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"templateFolder":"esm-blank-client","extTemplate":"","showfolder":false,"reload":true,"sourceFolder":"src","deployedVersion":"6.1.0","showMsgUib":true,"x":410,"y":100,"wires":[["26fbd32ea1d00ff2"],["a48c0beb68e76845"]],"info":"This example uses the default blank template.\r\n\r\nIt does not use any front-end framework, just\r\npure HTML, CSS and JavaScript."},{"id":"26fbd32ea1d00ff2","type":"debug","z":"3badb0a6906eef7f","g":"3cd8b7c5aae9d40b","name":"uibuilder standard output","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"","statusType":"counter","x":555,"y":60,"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":"a48c0beb68e76845","type":"debug","z":"3badb0a6906eef7f","g":"3cd8b7c5aae9d40b","name":"uibuilder control output","active":false,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"","statusType":"counter","x":555,"y":120,"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":"c492d5d86eb0cfcb","type":"inject","z":"3badb0a6906eef7f","g":"3cd8b7c5aae9d40b","name":"Reload","props":[{"p":"_ui","v":"{\"method\":\"reload\"}","vt":"json"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"reload","x":130,"y":120,"wires":[["195a61f93a912086"]],"info":"Sends a pre-formatted msg to the front-end that\r\ncauses the page to reload itself."},{"id":"f0f4f6c5a20a1823","type":"comment","z":"3badb0a6906eef7f","g":"3cd8b7c5aae9d40b","name":"Chk Description in each node","info":"","x":380,"y":60,"wires":[]},{"id":"51db7182c8c27901","type":"inject","z":"3badb0a6906eef7f","g":"e1901bb3da85dd78","name":"Plain UL/OL List (Array)","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"auto-create-list","payload":"[\"LI One\",\"LI Two\",\"LI Three\",\"LI Four\"]","payloadType":"json","x":200,"y":220,"wires":[["496418a44a66cc5e"]]},{"id":"0fe568807e7bd394","type":"inject","z":"3badb0a6906eef7f","g":"e1901bb3da85dd78","name":"Remove","props":[{"p":"mode","v":"remove","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":240,"y":300,"wires":[["496418a44a66cc5e"]]},{"id":"e30d349d9b3399c8","type":"inject","z":"3badb0a6906eef7f","g":"0f3fbb9e84943e6b","name":"Plain Table (Object)","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"auto-create-table-from-object","payload":"{\"ROW1\":{\"COL1\":\"R1C1\",\"COL2\":\"R1C2\"},\"ROW2\":{\"COL1\":\"R2C1\",\"COL2\":\"R2C2\"}}","payloadType":"json","x":170,"y":460,"wires":[["d52bbc4c5096d4ba"]]},{"id":"d58b7efe68a2da4d","type":"inject","z":"3badb0a6906eef7f","g":"0f3fbb9e84943e6b","name":"Plain Table (Array)","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"auto-create-table-from-array","payload":"[{\"COL1\":\"R1C1\",\"COL2\":\"R1C2\"},{\"COL1\":\"R2C1\",\"COL2\":\"R2C2\"}]","payloadType":"json","x":170,"y":500,"wires":[["d52bbc4c5096d4ba"]]},{"id":"14ded4d1dc70efbf","type":"change","z":"3badb0a6906eef7f","g":"8049618125109f32","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"{\"ROW1\":{\"COL1\":\"R1C1\",\"COL2\":\"R1C2\"},\"ROW2\":{\"COL1\":\"R2C1\",\"COL2\":\"R2C2\"}}","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":640,"y":680,"wires":[["d3a95abaddc5c013"]]},{"id":"d3cda6078ae9ca73","type":"inject","z":"3badb0a6906eef7f","g":"8049618125109f32","name":"Plain List","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"Chain 1 & 2","payload":"[\"LI One\",\"LI Two\",\"LI Three\",\"LI Four\"]","payloadType":"json","x":140,"y":660,"wires":[["ec046fe7e55e944c"]]},{"id":"d52bbc4c5096d4ba","type":"uib-element","z":"3badb0a6906eef7f","g":"0f3fbb9e84943e6b","url":"uib-element-test","elementid":"eltest-tbl","elementtype":"table","parent":"#more","passthrough":false,"outputs":0,"name":"","confData":{},"cacheOn":true,"storeName":"default","storeContext":"context","varName":"uib_el","newcache":true,"x":480,"y":520,"wires":[]},{"id":"d3a95abaddc5c013","type":"uib-element","z":"3badb0a6906eef7f","g":"8049618125109f32","url":"uib-element-test","elementid":"chain2","elementtype":"table","parent":"#chain1 *[data-row-index=\"3\"]","passthrough":false,"outputs":0,"name":"","confData":{},"cacheOn":true,"storeName":"default","storeContext":"context","varName":"uib_el","newcache":true,"x":970,"y":680,"wires":[]},{"id":"496418a44a66cc5e","type":"uib-element","z":"3badb0a6906eef7f","g":"e1901bb3da85dd78","url":"uib-element-test","elementid":"eltest-ul-ol","elementtype":"ul","parent":"#more","passthrough":false,"outputs":0,"name":"","confData":{},"cacheOn":true,"storeName":"default","storeContext":"context","varName":"uib_el","newcache":true,"x":500,"y":260,"wires":[]},{"id":"ec046fe7e55e944c","type":"uib-element","z":"3badb0a6906eef7f","g":"8049618125109f32","url":"uib-element-test","elementid":"chain1","elementtype":"ul","parent":"#more","passthrough":true,"outputs":1,"name":"","confData":{},"cacheOn":true,"storeName":"default","storeContext":"context","varName":"uib_el","newcache":true,"x":390,"y":680,"wires":[["14ded4d1dc70efbf"]]},{"id":"4ce558165663c95b","type":"inject","z":"3badb0a6906eef7f","g":"0f3fbb9e84943e6b","name":"Remove","props":[{"p":"mode","v":"remove","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":200,"y":540,"wires":[["d52bbc4c5096d4ba"]]},{"id":"0f1d399c3f14e113","type":"inject","z":"3badb0a6906eef7f","g":"e1901bb3da85dd78","name":"Plain UL/OL List (Object)","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"auto-create-list","payload":"{\"L1\":\"LI One\",\"L2\":\"LI Two\",\"L3\":\"LI Three\",\"L4\":\"LI Four\"}","payloadType":"json","x":190,"y":260,"wires":[["496418a44a66cc5e"]]},{"id":"af9ee48ee4da8b11","type":"inject","z":"3badb0a6906eef7f","g":"0f5c35ef5c4bf8f5","name":"Plain DL List (Array)","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"auto-create-dl-list","payload":"[[\"Entry One\",\"Definition One\"],[\"Entry Two\",\"Definition Two\"],[\"Entry Three\",\"Definition Three\"],[\"Entry Four\",\"Definition Four\"]]","payloadType":"json","x":930,"y":220,"wires":[["03979555908332c4"]]},{"id":"168cfe507c55796a","type":"inject","z":"3badb0a6906eef7f","g":"0f5c35ef5c4bf8f5","name":"Remove","props":[{"p":"mode","v":"remove","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":960,"y":340,"wires":[["03979555908332c4"]]},{"id":"03979555908332c4","type":"uib-element","z":"3badb0a6906eef7f","g":"0f5c35ef5c4bf8f5","url":"uib-element-test","elementid":"eltest-dl","elementtype":"dl","parent":"#more","passthrough":false,"outputs":0,"name":"","confData":{},"cacheOn":true,"storeName":"default","storeContext":"context","varName":"uib_el","newcache":true,"x":1230,"y":280,"wires":[]},{"id":"d08a2076e8158929","type":"inject","z":"3badb0a6906eef7f","g":"0f5c35ef5c4bf8f5","name":"Plain DL List (List of Objects)","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"auto-create-dl-list","payload":"[{\"Entry 1\":\"Definition 1\"},{\"Entry 2\":\"Definition 2\"},{\"Entry 3\":\"Definition 3\"},{\"Entry 4\":\"Definition 4\"}]","payloadType":"json","x":900,"y":260,"wires":[["03979555908332c4"]]},{"id":"8fc4bf76f457ae68","type":"inject","z":"3badb0a6906eef7f","g":"0f5c35ef5c4bf8f5","name":"Plain DL List (Object of Objects)","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"auto-create-dl-list","payload":"{\"ONE\":{\"A\":\"LI One A\"},\"TWO\":{\"B\":[\"LI Two B1\",\"LI Two B2\"]},\"THREE\":{\"C\":{\"C1\":\"LI Three C1\",\"C2\":\"LI Three C2\"}}}","payloadType":"json","x":890,"y":300,"wires":[["03979555908332c4"]]},{"id":"900f97b2b079f1bf","type":"comment","z":"3badb0a6906eef7f","g":"0f3fbb9e84943e6b","name":"Send the UL/OL list and change the Parent to `#eltest-ul-ol *[data-row-index=\"3\"]` \\n (without the quotes) to insert the table into the list entry #3","info":"","x":580,"y":460,"wires":[]},{"id":"cef57eb67e055c56","type":"inject","z":"3badb0a6906eef7f","g":"8049618125109f32","name":"Remove","props":[{"p":"mode","v":"remove","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":140,"y":700,"wires":[["ec046fe7e55e944c"]]}]

PS: For those who understand the behind-the-scenes working of this, you will note that this is not the most efficient approach since we are sending lots of data from node-red to the front-end which then has to convert it to HTML. Don't fret though, I have a plan for that too. :wink: Which will involve picking back up some work I started previously on some pure HMTL components.

The whole idea behind all of this, of course, is to gradually reduce the complexity of creating data-driven web UI's to a level that anyone can do it. But without having to rely on front-end frameworks like REACT, Vue or Angular which have a tendency to get outdated and so have to go through major, breaking changes.

As always, please read the CHANGELOG for all of the other changes coming to uibuilder v6.1.0. Have fun! :grin: :man_mage:

3 Likes

Julian,

thank you for this.
Is there any "breaking-change" in v.6.1 vs v. 5.1.1? I mean, after update will the 5.1.1. code still work with no changes?

Fabio

Hi, I try my best to use semver versioning so yes, there were some breaking changes between v5.1.1 and v6.0.0

However, the biggest was a switch to node.js v14 (from 12) and node-red v3 as the minimum dependencies.

This is the full list:

  • Minimum Node-RED version is now v3

  • Minimum Node.js version is now v14 LTS (in line with Node-RED v3) - note that the minimum minor version changes to the latest v14 LTS version whenever uibuilder is updated.

  • Not sure if this is really breaking. However, uib-cache nodes were not properly handling cases where, when processing incoming msgs, the chosen "Cache by" msg property was an empty string in the input msg. Previously handling of that case was dependent on the store and type being used. It is now ignored. The common case is where the setting is msg.topic and using the default trigger node which has msg.topic set to an empty string. Previously that was sometimes recorded and sometimes not. Now it is never recorded.

So as you can see, as long as you are reasonably current with node.js and node-red, it is doubtful you will have any issues.

Of course, the v6.1.0 branch IS currently a DEV version so expect some instability. v6.0.0 has had no reported issues however and should be solid - it is the currently released version. There will be an announcement as always when 6.1 goes live.

I am on nodejs v. 16.18.1, node-red 3.0.2 and not using uib-cache, so I should be OK.

Thanks!

1 Like

In reference to the post, here is the "chained" example (two uib-element nodes chained together) showing a list with a table embedded into the content of bullet #3:

This data generated the list:

[
    "LI One",
    "LI Two",
    "LI Three",
    "LI Four"
]

and this data generated the table:

{
    "ROW1": {
        "COL1": "R1C1",
        "COL2": "R1C2"
    },
    "ROW2": {
        "COL1": "R2C1",
        "COL2": "R2C2"
    }
}

and here is the flow that did it:

The buttons in the web page are from the standard example and show how easy it is to send data from the web back to node-red.


For those who understand HTML or like the details, the HTML code generated by the node looks like the following. Note the use of data-* attributes that ensure that you can select specific elements throughout the generated code. That lets you, for example, just update specific parts of the UI from further data. I'll follow up with an example of that soon. This example simply would replace the entire list or table each time you sent data to the node. That is absolutely fine for many cases but obviously wouldn't be so good for very large data or very fast-changing data.

<ul id="chain1">
    <li data-row-index="1" class="odd">
        LI One
    </li>
    <li data-row-index="2" class="even">
        LI Two
    </li>
    <li data-row-index="3" class="odd">LI Three<table id="chain2">
            <thead>
                <tr>
                    <th data-hdr-row-index="1" data-col-index="1" data-col-name="COL1">
                        COL1
                    </th>
                    <th data-hdr-row-index="1" data-col-index="2" data-col-name="COL2">
                        COL2
                    </th>
                </tr>
            </thead>
            <tbody>
                <tr data-row-index="1" class="odd" data-row-name="ROW1">
                    <td data-row-index="1" data-col-index="1" data-col-name="COL1">
                        R1C1
                    </td>
                    <td data-row-index="1" data-col-index="2" data-col-name="COL2">
                        R1C2
                    </td>
                </tr>
                <tr data-row-index="2" class="even" data-row-name="ROW2">
                    <td data-row-index="2" data-col-index="1" data-col-name="COL1">
                        R2C1
                    </td>
                    <td data-row-index="2" data-col-index="2" data-col-name="COL2">
                        R2C2
                    </td>
                </tr>
            </tbody>
        </table>
    </li>
    <li data-row-index="4" class="even">
        LI Four
    </li>
</ul>

Because the table was generated from an object (rather than an array which would also work), each row of the table has a name.

If I now wanted to change the data in the table for row 2, column 2, the CSS selector would be #chain2 > tbody > tr.even > td:nth-child(1) (copied from the browsers dev tools) or, more simply #chain2 *[data-row-index="2"][data-col-index="2"] or indeed #chain2 *[data-row-index="2"][data-col-name="COL2"] or #chain2 *[data-row-name="ROW2"] *[data-col-name="COL2"] - I'm sure some of the web experts watching could find even better selector examples but hopefully this gets the idea across.

I haven't yet quite decided whether to add an HTML ID attribute to everything below the parent (#chain1 in this case). On the one hand, this would make selection and update of individual elements even simpler. But on the other hand, because they have to be unique on the page, they will become quite long (e.g. chain2-ROW2-COL2) - let me know if you have an opinion on the matter.


And finally, for those who again, like the details. This is the message that is actually sent to the front end. The uibuilder front-end library automatically processes this, there is no need for any front-end code. This illustrates that you can create these data structures yourself and so create any kind of web UI direct from Node-RED.

{
    "_msgid": "2fe8506ed4e075c3",
    "topic": "Chain 1 & 2",
    "mode": "update",
    "_uib": {
        "originator": "d3a95abaddc5c013"
    },
    "_ui": [
        {
            "method": "remove",
            "components": [
                "#chain1"
            ]
        },
        {
            "method": "add",
            "components": [
                {
                    "type": "ul",
                    "id": "chain1",
                    "attributes": {},
                    "components": [
                        {
                            "type": "li",
                            "attributes": {
                                "data-row-index": 1,
                                "class": "odd"
                            },
                            "slot": "LI One"
                        },
                        {
                            "type": "li",
                            "attributes": {
                                "data-row-index": 2,
                                "class": "even"
                            },
                            "slot": "LI Two"
                        },
                        {
                            "type": "li",
                            "attributes": {
                                "data-row-index": 3,
                                "class": "odd"
                            },
                            "slot": "LI Three"
                        },
                        {
                            "type": "li",
                            "attributes": {
                                "data-row-index": 4,
                                "class": "even"
                            },
                            "slot": "LI Four"
                        }
                    ],
                    "parent": "#more"
                }
            ]
        },
        {
            "method": "remove",
            "components": [
                "#chain2"
            ]
        },
        {
            "method": "add",
            "components": [
                {
                    "type": "table",
                    "id": "chain2",
                    "attributes": {},
                    "components": [
                        {
                            "type": "thead",
                            "components": [
                                {
                                    "type": "tr",
                                    "components": [
                                        {
                                            "type": "th",
                                            "attributes": {
                                                "data-hdr-row-index": 1,
                                                "data-col-index": 1,
                                                "data-col-name": "COL1"
                                            },
                                            "slot": "COL1"
                                        },
                                        {
                                            "type": "th",
                                            "attributes": {
                                                "data-hdr-row-index": 1,
                                                "data-col-index": 2,
                                                "data-col-name": "COL2"
                                            },
                                            "slot": "COL2"
                                        }
                                    ]
                                }
                            ]
                        },
                        {
                            "type": "tbody",
                            "components": [
                                {
                                    "type": "tr",
                                    "components": [
                                        {
                                            "type": "td",
                                            "attributes": {
                                                "data-row-index": 1,
                                                "data-col-index": 1,
                                                "data-col-name": "COL1"
                                            },
                                            "slot": "R1C1"
                                        },
                                        {
                                            "type": "td",
                                            "attributes": {
                                                "data-row-index": 1,
                                                "data-col-index": 2,
                                                "data-col-name": "COL2"
                                            },
                                            "slot": "R1C2"
                                        }
                                    ],
                                    "attributes": {
                                        "data-row-index": 1,
                                        "class": "odd",
                                        "data-row-name": "ROW1"
                                    }
                                },
                                {
                                    "type": "tr",
                                    "components": [
                                        {
                                            "type": "td",
                                            "attributes": {
                                                "data-row-index": 2,
                                                "data-col-index": 1,
                                                "data-col-name": "COL1"
                                            },
                                            "slot": "R2C1"
                                        },
                                        {
                                            "type": "td",
                                            "attributes": {
                                                "data-row-index": 2,
                                                "data-col-index": 2,
                                                "data-col-name": "COL2"
                                            },
                                            "slot": "R2C2"
                                        }
                                    ],
                                    "attributes": {
                                        "data-row-index": 2,
                                        "class": "even",
                                        "data-row-name": "ROW2"
                                    }
                                }
                            ]
                        }
                    ],
                    "parent": "#chain1 *[data-row-index=\"3\"]"
                }
            ]
        }
    ]
}
1 Like

.. doesn't install as part of #v6.1.0 installation. And can't be found any other way. What I'm missing?

Did you reload the Editor? Also, manual node installs need to restart node-red then reload the editor.

Many times + clear cache + restart ...

weird. Let me do a fresh install test.

It worked for me. But I deleted the uibuilder folder first.

I didn't have uibuilder folder before.

Sorry I misread your message. The installation of 6.1.0 worked (even with the folder being there).

Just did a fresh node-red install and it has worked.

nrinstall -f nr3
cd nr3/data
npm install totallyinformation/node-red-contrib-uibuilder#v6.1.0
cd ..
npm start

image

Will try on my "live" system which is running v6.0.0

What do you get at the startup of Node-RED? This is from my live system after I installed 6.1

Welcome to Node-RED
===================
4 Jan 11:52:24 - [info] Node-RED version: v3.0.2
4 Jan 11:52:24 - [info] Node.js  version: v16.18.1
4 Jan 11:52:24 - [info] Linux 4.19.0-22-amd64 x64 LE
4 Jan 11:52:24 - [info] Loading palette nodes
4 Jan 11:52:46 - [info] [wiser:wiser-class.js:setup] Setup fn complete
4 Jan 11:52:56 - [info] Dashboard version 3.2.3 started at /ui
4 Jan 11:52:57 - [info] Settings file  : /home/home/nrmain/data/settings.js
4 Jan 11:52:57 - [info] HTTP Static    : /home/home/nrmain/data/public > /
4 Jan 11:52:57 - [info] Context store  : 'default' [module=memory]
4 Jan 11:52:57 - [info] Context store  : 'file' [module=localfilesystem]
4 Jan 11:52:57 - [info] User directory : /home/home/nrmain/data
4 Jan 11:52:57 - [warn] Projects disabled : editorTheme.projects.enabled=false
4 Jan 11:52:57 - [info] Flows file     : /home/home/nrmain/data/nrmain_flows.json
4 Jan 11:52:57 - [info] +-----------------------------------------------------
4 Jan 11:52:57 - [info] | uibuilder v6.1.0 initialised
4 Jan 11:52:57 - [info] | root folder: /home/home/nrmain/data/uibuilder
4 Jan 11:52:57 - [info] | Using Node-RED's webserver at:
4 Jan 11:52:57 - [info] |   https://0.0.0.0:1880/
4 Jan 11:52:57 - [info] | Installed packages:
4 Jan 11:52:57 - [info] |   bootstrap, bootstrap-vue, http-vue-loader, jquery
4 Jan 11:52:57 - [info] |   lodash, tabulator-tables, video.js, vue
4 Jan 11:52:57 - [info] |   vue-speedometer, vue-tabulator, vuetify
4 Jan 11:52:57 - [info] +-----------------------------------------------------
4 Jan 11:52:58 - [info] Server now running at https://127.0.0.1:1880/red/
4 Jan 11:52:58 - [info] Starting flows

It is working there as well.

image

I have something bigger going on. Nothing installs currently. Hands dirty moments ...

1 Like

With the previous example - to change one of the table cells from node-red, add these 4 lines to the onChange function of your index.js:

    if (msg.topic === 'elChange') {
        const el = $('#chain2 *[data-row-name="ROW2"] *[data-col-name="COL2"]')
        el.replaceChildren(msg.payload)
    }

Then send a message with the appropriate topic to the main uibuilder node (after sending the list/table chain of course). Noting that in this case, msg.payload has to be a string. Try this for example:

I'll be putting together another new node that will make changing of attributes and slots a lot easier - that will have a matching function in the front-end client so that you can do easy changes from front-end code as well as via no-code from node-red. :grin:


PS: the $() is not jQuery! It is the helper function that the uibuilder client adds for you (as long as nothing else has already added it). It simply takes a CSS selector and tries to return a reference to the appropriate DOM element.

And for even more fun, why not send new HTML - this not only changes the random number in the text but also the colour!

Change the inject's payload to "Random: <span style='color:rgb(calc(100 + " & $formatInteger($random()*100, "0") & "), calc(100 + " & $formatInteger($random()*100, "0") & "), calc(100 + " & $formatInteger($random()*100, "0") & "))'>" & $formatInteger($random()*100, "0") & "</span>"

and the index.js code to:

    if (msg.topic === 'elChange') {
        const el = $('#chain2 *[data-row-name="ROW2"] *[data-col-name="COL2"]')
        if (msg.contentType !== 'TEXT') {
            el.innerHTML = msg.payload
        } else {
            el.innerText = msg.payload
        }
    }