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"]]}]
IMPORTANT EDITORS NOTE: The group description that says "Change the URL to match the base" is now out of date. You now need to take the output of the uib-element
node and send it into a uibuilder node. This is a work-in-progress after all!
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. 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!