[uibuilder] Towards zero-code web UI's

OK, I'll admit, a slightly click-baity title :grin: but following on from my previous post.

But as I'm working towards v6.1 of uibuilder, I've been revisiting my assumptions and thinking about how we can move closer to no-code with uibuilder.

v5 introduced the new, re-vamped client library builds which come with the capability to translate standard configuration data into live display elements. That was the first phase towards low-/no-code with uibuilder.

v6.1 will extend on that to provide a couple of Node-RED nodes, likely to be called uib-element and uib-update.

uib-element will build the configuration data for some standard types of display such as lists and tables from simple input data then optionally send it direct to your front-end page or output the data so that you can further add to it or amend it. uib-update will make it easy to send updated data to a specific UI element.

So, as an example. You will be able to send an array of objects to a uib-element node and it will dynamically build a table on your web page. Then you will be able to use uib-update to send a payload to replace the content of a single cell on that table.

NONE OF THIS NEEDS A FRONT-END FRAMEWORK! So no need for Vue, REACT, Angular, etc. This is pure, standards-based HTML.

Anyway, I wanted to provide an early look at some of this. While the nodes are not quite ready, you can install the v6.1.0 branch of uibuilder if you want to play along. But I also wanted to give you an example that you can play with even without installing a beta version.

So here are a series of inject nodes that you can connect to a uibuilder node running the default "blank" template. Hopefully will give you simple but good taste of what can be done without any fancy frameworks and without any code.

Just click on each inject from top-to-bottom.

[{"id":"62d58bb53910ec63","type":"inject","z":"225a6af5f8bc4cc2","name":"Add List (_ui)","props":[{"p":"_ui","v":"[{\"method\":\"remove\",\"components\":[\"#eltest-upd\"]},{\"method\":\"add\",\"components\":[{\"type\":\"ul\",\"id\":\"eltest-upd\",\"attributes\":{\"name\":\"testy-list\",\"style\":\"list-style: \\\"❌\\\";\"},\"components\":[{\"type\":\"li\",\"id\":\"eltest-upd-1\",\"attributes\":{\"data-row-index\":1,\"class\":\"odd\"},\"slot\":\"1) To do <span name=\\\"when\\\">now</span>\"},{\"type\":\"li\",\"attributes\":{\"data-row-index\":2,\"class\":\"even\",\"id\":\"eltest-upd-2\"},\"slot\":\"2) To do <span name=\\\"when\\\">tomorrow</span>\"},{\"type\":\"li\",\"id\":\"eltest-upd-3\",\"attributes\":{\"data-row-index\":3,\"class\":\"odd\"},\"slot\":\"3) To do <span name=\\\"when\\\">this week</span>\"},{\"type\":\"li\",\"id\":\"eltest-upd-4\",\"attributes\":{\"data-row-index\":4,\"class\":\"even\"},\"slot\":\"4) To do <span name=\\\"when\\\">sometime</span>\"}],\"parent\":\"#more\"}]}]","vt":"json"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":280,"wires":[["f3d45472d250833a"]]},{"id":"6b17d81ec2209b1d","type":"inject","z":"225a6af5f8bc4cc2","name":"Ambulance All (_ui)","props":[{"p":"_ui","v":"[{\"method\":\"update\",\"components\":[{\"x-id\":\"eltest-upd\",\"name\":\"testy-list\",\"x-type\":\"ul\",\"x-parent\":\"#more\",\"attributes\":{\"style\":\"list-style: \\\"🚑\\\";\"}}]}]","vt":"json"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":150,"y":560,"wires":[["f3d45472d250833a"]]},{"id":"6b4d8a4a4ca96607","type":"inject","z":"225a6af5f8bc4cc2","name":"Overdue 1 (_ui)","props":[{"p":"_ui","v":"[{\"method\":\"update\",\"components\":[{\"selector\":\"#eltest-upd *[data-row-index=\\\"1\\\"]:not([data-done=\\\"Y\\\"])\",\"attributes\":{\"style\":\"background-color: red; color: yellow; font-weight: bolder;\"}}]}]","vt":"json"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":140,"y":320,"wires":[["f3d45472d250833a"]]},{"id":"c47e58de8dc519b7","type":"inject","z":"225a6af5f8bc4cc2","name":"Tick off 2 (_ui)","props":[{"p":"_ui","v":"[{\"method\":\"update\",\"components\":[{\"selector\":\"#eltest-upd > li:nth-child(2)\",\"# selector\":\"#eltest-upd *[data-row-index=\\\"2\\\"]\",\"# id\":\"eltest-upd-2\",\"attributes\":{\"style\":\"list-style: \\\"✅\\\";\",\"data-done\":\"Y\"}}]}]","vt":"json"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":400,"wires":[["f3d45472d250833a"]]},{"id":"7689357d95808fab","type":"inject","z":"225a6af5f8bc4cc2","name":"Tick off 1 (_ui)","props":[{"p":"_ui","v":"[{\"method\":\"update\",\"components\":[{\"selector\":\"#eltest-upd > li:nth-child(1)\",\"# selector\":\"#eltest-upd *[data-row-index=\\\"1\\\"]\",\"# id\":\"eltest-upd-1\",\"attributes\":{\"style\":\"list-style: \\\"✅\\\";\",\"data-done\":\"Y\"}}]}]","vt":"json"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":360,"wires":[["f3d45472d250833a"]]},{"id":"cabe1f260b988cc2","type":"inject","z":"225a6af5f8bc4cc2","name":"Un-tick 1 (_ui)","props":[{"p":"_ui","v":"[{\"method\":\"update\",\"components\":[{\"x-id\":\"eltest-upd-1\",\"selector\":\"#eltest-upd > li:nth-child(1)\",\"# selector\":\"#eltest-upd *[data-row-index=\\\"1\\\"]\",\"# id\":\"eltest-upd-2\",\"attributes\":{\"style\":\"list-style: revert;\",\"data-done\":\"N\"}}]}]","vt":"json"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":440,"wires":[["f3d45472d250833a"]]},{"id":"56364793494b9db6","type":"inject","z":"225a6af5f8bc4cc2","name":"Chg Due 1 (_ui)","props":[{"p":"_ui","v":"[{\"method\":\"update\",\"components\":[{\"selector\":\"#eltest-upd-1 > [name=\\\"when\\\"]\",\"slot\":\"soon\"}]}]","vt":"json"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":140,"y":480,"wires":[["f3d45472d250833a"]]},{"id":"050cc93892c1d90c","type":"inject","z":"225a6af5f8bc4cc2","name":"Chg Due all (_ui)","props":[{"p":"_ui","v":"[{\"method\":\"update\",\"components\":[{\"selector\":\"#eltest-upd *[name=\\\"when\\\"]\",\"# name\":\"when\",\"slot\":\"soonish\"}]}]","vt":"json"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":140,"y":520,"wires":[["f3d45472d250833a"]]}]

You might want to add this to your index.html file so that the output appears in the middle of the page (or wherever you want it):

<div id="more"></div>

That will be part of the updated standard templates and examples when v6.1.0 is released.

The new nodes will make this kind of thing far easier still since you won't need to manually create the configuration objects.


Have fun! And please let me have feedback on your thoughts and needs.


Oh, and I forgot. People will rightly raise the question about a page designer :grin:

I'm eying up a couple at the moment:

Either of these will output web pages that uibuilder can use. I'm sure there are more options out there. Let me know if you know of any, especially if you've used them.

I am really looking forward to playing with this!

1 Like

Apologies - the flow I shared above has a minor flaw, as written it only works on uibuilder v6.1.0 which is the current development branch on GitHub. However, you can make it work on v5/6 if you change the property called "selector" in the input JSON to be "type".

As a bonus to make up for that though, try sending this msg to uibuilder and watch the browser tab change its title string!

[{"id":"61567ed0d7f4502d","type":"inject","z":"56443195ea782ac2","name":"Upd title","props":[{"p":"topic","vt":"str"},{"p":"payload"},{"p":"_ui","v":"{\t   \"method\": \"update\",\t   \"type\": \"title\"\t}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"uibuilder-dynamic-titles","payload":"\"(\" & $formatInteger($random()*100, \"00\") & \") Dynamic uibuilder!\"","payloadType":"jsonata","x":200,"y":260,"wires":[["7d274212fa8b1c65"]]}]

It is just a pre-formatted inject node:

Note that the _ui property only sets the mode and selects the element to be updated (the HTML title in this case). The uibuilder client library automatically takes the msg.payload and applies it.

Still continuing to improve the uib-element node and the corresponding features in the modern uibuilder client builds. Sorry, had hoped to upload the updates tonight but didn't quite make it. Hopefully a new code drop tomorrow.

A reminder that this addition to uibuilder sends instructions to the client library to dynamically build UI, on-page elements from simple data inputs. For example, automatically showing input arrays as a list or an object as a table.

You might be interested to note that the uib-element node already supports the following:

  • Raw HTML (e.g. from a node-red template node)
  • Updates to the page title, meta description and first h1 tag
  • Simple, well-formed tables
  • Simple, well-formed lists (unordered/bullet, ordered/numbered, description lists)
  • Articles - a simple card-like structure with optional headings wrapped in an <article> tag.

Lots more to do but they might have to wait for a v6.1.1 release.

Each dynamic element is wrapped in a containing <div> and all key parts of the element have unique id's and other identifying attributes to make further selection as easy as possible. Lists and tables have odd/even row classes added as well.

Also new is the first stage of a position property. For now it lets you add new elements either at the first or last child position of the parent element. So dynamically adding a heading at the start of the page is now possible for example.

All of the elements are designed to be (fairly) accessible. Rows and columns for example being clearly identified and named where possible. I'm trying to follow at least the basic ARIA rules for accessibility.

And, of course, the uib-element node outputs uibuilder's low-code configuration-driven JSON data which means that you can further amend the configuration before sending and you can chain together as many elements as you like before sending to uibuilder. You can also use the uib-element nodes as a primer for creating dynamic UI's using uibuilder's low-code method. Should the no-code method no longer quite meet your needs.

Of note to anyone who has been trying out the features as I've been developing them in the v6.1.0 branch is that I've ditched the node's build-in caching and internal uibuilder links in favour of a simpler approach where you simply send the output - either directly, or via a link node. This actually makes caching easier as long as you keep a unique identifier on the flows as you can have a single uib-cache node before the uibuilder node and everything is replayed in the right order.

Once again - NO FRAMEWORKS are needed for all of this, it is all vanilla HTML and JavaScript. But if you want or need to use a framework, you still can.