Help with UIBuilder

Urm, no it wouldn't. D1 is a highly engineered AngularJS integration to Node-RED. D2 is a highly engineered VueJS/Vuetify integration to Node-RED.

UIBUILDER is a library that creates links between Node-RED (server) and your connected browsers, it is an enhancement to comms and web services. It also has a front-end helper library that not only helps glue things together but also provides quite a few helper functions to even out some of the oddities of working with HTML and front-end JavaScript.

But it is a fundamentally different approach than that of D1 or D2. Giving you complete flexibility to do what you want and providing a thin layer of progressive enhancement over HTML/CSS/JS. So that whatever you learn as you are using UIBUILDER, you will find applicable to any front-end development.

Because D1, D2, and UIBUILDER are all web services at heart though, it IS possible to access visuals between them (though not always easy) but less possible to have them as integrated apps.

I am sure, it will all make sense to me one day. :slight_smile:
I even watched a complete basic html/css course video.
When something works, I think I got it. unfortunately no where near it. all i can do is fiddle with a working sketch and change color say from red to green.
i was in a lot of dilemma to chose between D2 and UIB, and chose UIB becuase first few things I tried it worked, (of course with 99% your help).
I am still very comfortable with D1, I can make things relatively easily. but it took me 3 years to get here, may be i will give some more time to self.

I did try, and in D1, i could easily recreate guages

i
wonder what type of charts are suitable to make a state-change graph like this.

image

i would also require 'clickable' chart so that i can further process it.

Jadwal  |  Charts  |  Google for Developers seem pretty similar. May work. Not tried.

1 Like

This one might be interesting to try:

Remember, any front-end library or framework should work fine with UIBUILDER.

Here is another possibility:

Indeed, looks exactly like I want..

few things to tweak, axis labels are not appearing at the CHANGE point, but after fixed interval, i can live with it for time being, the tool tip is giving what i need anyway, including the duration!

i want to interact with it now, the doc says something regarding this, have to study to make sense and where to put the code..

image

trying it now...

1 Like

this is what i got to so far... hard coded the example in index.js.
now, how do i pass on dynamic data into the flow ?

I tried with template, but it doesn't work.

[{"id":"df1e18d31f27e540","type":"group","z":"0293de8750223951","name":"Load HTML/CSS/JS Files","style":{"stroke":"#9363b7","fill":"#dbcbe7","label":true,"color":"#6f2fa0"},"nodes":["a4b12724eb473f10","7d8fc759a9471b62","7795f8d0869c925e","c98cab456b72075f","6903100ffb4db4dc","abf4ed6710c598ca","24c77c0563358957","a1085a5db2393e9b","2626e724a98754e5","122d8fbf4a888bcf","512f5338ba4c64a6","d7d16d255e48fb69","5677ccf3a5bcbeb0","a4a8e42540004ddf","0860dc9bbffaee2d"],"x":114,"y":39,"w":752,"h":202},{"id":"a4b12724eb473f10","type":"change","z":"0293de8750223951","g":"df1e18d31f27e540","name":"index.css","rules":[{"t":"set","p":"fname","pt":"msg","to":"index.css","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":120,"wires":[["7d8fc759a9471b62"]]},{"id":"7d8fc759a9471b62","type":"template","z":"0293de8750223951","g":"df1e18d31f27e540","name":"CSS","field":"payload","fieldType":"msg","format":"css","syntax":"mustache","template":":ROOT, :ROOT.LIGHT{COLOR-SCHEME:DARK;}\n  .overflow-hidden {overflow: hidden;}\n  .livedisplay {font-size: 2.5em;text-align: center;}\n\n.zero {border-radius: 10px;font-size: 2.8em;text-align: center;background-color:#2D2D2D;}\n\n.large-font-r {border-radius: 10px;font-size:2.8em;background:red;color: white;font-family: \"Trebuchet MS\";text-align: center;}\n.large-font-b {border-radius: 10px;font-size:2.8em;background:blue;color: white;font-family: \"Trebuchet MS\";text-align: center;}\n.large-font-g {border-radius: 10px;font-size:2.8em;background:green;color: white;font-family: \"Trebuchet MS\";text-align: center;}\n\n.another-large {color:white; font-size: 2em; vertical-align:\"middle\" ; text-align: center;}\n.medium-font {font-size: 1.3em; text-align: center;}\n.smallfont {font-family: \"arial narrow\";font-size:10px;color: cyan;text-align: center;font-weight:100;}\n.raw-input {font-size:1.3em;color: white;text-align: center;font-weight:200;}\n\n.mf {font-size:17px;color:yellow;font-family: \"arial narrow\";text-align: right;}\n.lf {font-size:35px;color: cyan;font-family: \"trebuchet ms\";text-align: center;}\n\n.oee {background:#101010; font-size: 0.9em; text-align: center;}\n.oee1 {background:#202020 ;color:yellow; font-size: 0.9em; text-align: center;}\n.oeehead {background:black ;color:white; font-size: 1.3em; text-align: center;font-weight: 200;}\n.oee2 {background:#505050 ;color:yellow; font-size: 1.3em; text-align: center; font-weight: 600;}\n.oeedn {background:#121212 ;color:pink      ; font-size: 0.9em; text-align: center;}\n.oeeup {background:#121212 ;color:lightgreen; font-size: 0.9em; text-align: center;}\n.oee_grey {background:#525252 ;color:#020202; font-size: 0.9em; text-align: center;}\n.oee_grey {background:#525252 ;color:#020202; font-size: 0.9em; text-align: center;}\n.oee_green {background:green ;color:#020202; font-size: 1.3em; text-align: center;}\n.oee_pink {background:pink ;color:#020202; font-size: 1.3em; text-align: center;}\n\n.ltm {font-size: 0.9em; text-align: center;}\n\n.red {font-size:20px;color: pink;font-family: \"courier new\";text-align: center;}\n.sum {font-size:20px;color: yellow;background:#202020;font-family: \"courier new\";text-align: center;}\n.orange {font-size:20px;color: orange;font-family: \"courier new\";text-align: center;}\n.green {font-size:20px;color: #55ff55;font-family: \"courier new\";text-align: center;}\n.shifts {font-size:20px;color: white;font-family: \"arial narrow\";text-align: right;}\n.mcselect {font-size:17px;color: #FBFF00;background:#505050;height:24px;font-family: \"courier\";text-align: center;}\n\n.hour0 {font-size:20px;color: white;background:darkgreen;height:30px;font-family: \"courier\";text-align: right;}\n.hour1 {font-size:18px;color: #FBFF00;background:#808080;height:25px;font-family: \"courier\";text-align: right;}\n.hour2 {font-size:18px;color: #FBFF00;background:#707070;height:25px;font-family: \"courier\";text-align: right;}\n.hour3 {font-size:18px;color: #FBFF00;background:#606060;height:25px;font-family: \"courier\";text-align: right;}\n.hour4 {font-size:18px;color: #FBFF00;background:#505050;height:25px;font-family: \"courier\";text-align: right;}\n.hour5 {font-size:18px;color: #FBFF00;background:#404040;height:25px;font-family: \"courier\";text-align: right;}\n.hour6 {font-size:18px;color: #FBFF00;background:#303030;height:25px;font-family: \"courier\";text-align: right;}\n.hour7 {font-size:18px;color: #FBFF00;background:#202020;height:25px;font-family: \"courier\";text-align: right;}\n\n\n.smallfont1 {font-size: 12px; color:yellow ; font-family: \"verdana\" ; text-align: center;}\n.f12 {font-size: 1em;background:red; color:yellow ; text-align: center;}\n\n.smallfont2 {color:grey ; font-family: \"verdana\" ; text-align: center;}\n.another-large1 {font-size: 20px; color: blue ;text-align: right;}\n\n.container {display: flex;flex-direction: column;align-items: center;}\n\n.time {font-size:0.5em;color: yellow;font-family: \"courier narrow\";text-align: center;}\n.rotate {  font-size:12px;  text-align: center;  background:#404040;  white-space: nowrap;  vertical-align: middle;  width: 1.0em;}\n.rotate div {\n  -moz-transform: rotate(-90.0deg);  /* FF3.5+ */       \n  -o-transform: rotate(-90.0deg);  /* Opera 10.5 */  \n  -webkit-transform: rotate(-90.0deg);  /* Saf3.1+, Chrome */\n  filter:  progid:DXImageTransform.Microsoft.BasicImage(rotation=0.083);  /* IE6,IE7 */\n  -ms-filter: \"progid:DXImageTransform.Microsoft.BasicImage(rotation=0.083)\"; /* IE8 */\n  margin-left: -10em;\n  margin-right: -10em;}\n.m_on {font-size:17px;color: green;font-family: \"courier\";text-align: center;}\n.m_off {font-size:17px;color: brown;font-family: \"courier\";text-align: center;}\n.m_1 {font-size:15px;color: green;font-family: \"courier\";text-align: center;}\n.m_0 {font-size:15px;color: red;font-family: \"courier\";text-align: center;}\n\n    .cap {font-size:20px;color: yellow;font-family: \"courier new\";text-align: left;}\n    .zero1 {font-size:1em;color: #606060; font-family: \"courier new\";text-align: center;}\n    .zero2 {font-size:1em;color: white;   font-family: \"courier new\";text-align: center;}\n    .zero3 {font-size:1em;color: black;   font-family: \"courier new\";text-align: center;}\n    .zero4 {font-size:1em;color: white;   font-family: \"courier new\";text-align: center;}\n    .zero5 {font-size:1em;color: yellow;  font-family: \"courier new\";text-align: center;}\n\n.my-button {border: 2;color: white;align:center;padding: 1px;border-radius: 50px;transition: 0.4s ease;}\n.my-button:hover {background: black;cursor: pointer;}\n\n  .btn-group .header {background-color: #198754;background-image: radial-gradient(circle, #198754 5%, #199754 15%, #198754 60%);border: none;border-radius: 30px;color: white;font-weight:700;font-family: AvenirNextCondensed-Bold;padding: 8px 20px;text-align: center;\n   text-shadow: 2px 2px 4px #000000;  display: inline-block;height:40px;font-size: 1.2em;cursor: default;float: left;border: 3px solid yellow;}\n\n  .btn-group .button0 {background-color: grey;box-shadow:5px 5px 5px #0078d0;border: none;border-radius: 50px;color: white;padding: 8px 20px;text-align: center;\n  display: inline-block;height:40px;font-size: 1.6em;cursor: default;float: left;border: 3px solid black;}\n\n  .btn-group .time_display {font-size:0.9em;background-color: green;box-sizing:border-box;border-radius: 50px;color: white;padding: 2px 20px;text-align: center;\n  display: flexbox;height:40px;cursor: default;float: right;border: 3px solid black;}\n\n  .btn-group .button1 {background-color: #0078d0;border-radius: 50px;color: white;padding: 8px 20px;text-align: center;\n   display: inline-block;height:40px;font-size: 1em;cursor: pointer;float: left;border: 3px solid black;}\n\n  .btn-group .button2 {background-color: #0078d0;border: none;border-radius: 50px;color: white;padding: 8px 20px;text-align: center;\n  display: inline-block;height:60px;font-size: 1.6em;cursor: pointer;float: left;border: 3px solid black;}\n\n  .btn-group .button3 {background-color: #0078d0;border: none;border-radius: 50px;color: white;padding: 2px 20px;text-align: center;\n  display: inline-block;height:60px;font-size: 0.8em;cursor: pointer;float: left;border: 1px solid black;  }\n\n  .btn-group .button4 {background-color: #0078d0;border: none;border-radius: 50px;color: white; padding: 2px 5px;text-align: center;\n      display: inline-block;height:60px;font-size: 0.7em;font-weight: 100;cursor: pointer;float: left;border: 1px solid black;  }\n\n  .btn-group .button0:hover {background-color: grey;}\n  .btn-group .button1:hover {background-color: red;transform: translateY(-2px);box-shadow: 0 5px #0078d0;}\n  .btn-group .button2:hover {background-color: red;transform: translateY(-2px);box-shadow: 0 5px #0078d0;}\n  .btn-group .button3:hover {background-color: red;transform: translateY(-2px);box-shadow: 0 5px #0078d0;}\n  .btn-group .button4:hover {background-color: green;transform: translateY(-2px);box-shadow: 0 5px #0078d0;}\n\n  .bigtext {font-size: 1.5em;}\n  .button:active {background-color: magenta;box-shadow: 0 5px #666;transform: translateY(4px);}\n  .button1:active {background-color: magenta;box-shadow: 0 5px #666;transform: translateY(4px);}\n  .button2:active {background-color: magenta;box-shadow: 0 5px #666;transform: translateY(4px);}\n  .button3:active {background-color: magenta;box-shadow: 0 5px #666;transform: translateY(4px);}\n  .button4:active {background-color: magenta;box-shadow: 0 5px #666;transform: translateY(4px);}\n\n  .marquee {height: 30px;overflow: hidden;position: relative;background: black;color: lightgreen;font-weight: 200;border: 1px solid pink;}\n  .marquee p {position: absolute;width: 100%;height: 100%;margin: 0;line-height: 30px;text-align: center;moz-transform: translateX(100%);\n    -webkit-transform: translateX(100%);transform: translateX(100%);moz-animation: scroll-left 30s linear infinite;webkit-animation: scroll-left 30s linear infinite;\n    animation: scroll-left 30s linear infinite;}\n    \n    @-moz-keyframes scroll-left {0% {moz-transform: translateX(100%);}100% {-moz-transform: translateX(-100%);}    }\n    @-webkit-keyframes scroll-left {0% {-webkit-transform: translateX(100%);}100% {-webkit-transform: translateX(-100%);}    }\n    @keyframes scroll-left {0% {-moz-transform: translateX(100%);-webkit-transform: translateX(100%); transform: translateX(100%);}100% {\n    -moz-transform: translateX(-100%);-webkit-transform: translateX(-100%); transform: translateX(-100%);}}\n\n","output":"str","x":630,"y":120,"wires":[["7795f8d0869c925e"]]},{"id":"7795f8d0869c925e","type":"uib-save","z":"0293de8750223951","g":"df1e18d31f27e540","url":"dt","uibId":"9fd42f1d69b31f36","folder":"src","fname":"","createFolder":false,"reload":false,"usePageName":false,"encoding":"utf8","mode":438,"name":"","topic":"","x":790,"y":120,"wires":[]},{"id":"c98cab456b72075f","type":"change","z":"0293de8750223951","g":"df1e18d31f27e540","name":"index.js","rules":[{"t":"set","p":"fname","pt":"msg","to":"index.js","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":460,"y":160,"wires":[["a4a8e42540004ddf"]]},{"id":"6903100ffb4db4dc","type":"uib-save","z":"0293de8750223951","g":"df1e18d31f27e540","url":"dt","uibId":"9fd42f1d69b31f36","folder":"src","fname":"","createFolder":false,"reload":false,"usePageName":false,"encoding":"utf8","mode":438,"name":"","topic":"","x":790,"y":160,"wires":[]},{"id":"abf4ed6710c598ca","type":"change","z":"0293de8750223951","g":"df1e18d31f27e540","name":"index.html","rules":[{"t":"set","p":"fname","pt":"msg","to":"index.html","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":470,"y":80,"wires":[["a1085a5db2393e9b"]]},{"id":"24c77c0563358957","type":"uib-save","z":"0293de8750223951","g":"df1e18d31f27e540","url":"dt","uibId":"9fd42f1d69b31f36","folder":"src","fname":"","createFolder":false,"reload":false,"usePageName":false,"encoding":"utf8","mode":438,"name":"","topic":"","x":790,"y":80,"wires":[]},{"id":"a1085a5db2393e9b","type":"template","z":"0293de8750223951","g":"df1e18d31f27e540","name":"HTML","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<!doctype html>\n<html lang=\"en\"><head>\n\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n    <link rel=\"icon\" href=\"../uibuilder/images/node-blue.ico\">\n\n    <!-- Your own CSS (defaults to loading uibuilders css)-->\n    <link type=\"text/css\" rel=\"stylesheet\" href=\"./index.css\" media=\"all\">\n\n    <!-- #region Supporting Scripts. These MUST be in the right order. Note no leading / -->\n    <script src=\"https://cdn.jsdelivr.net/npm/apexcharts\"></script> \n    <script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>\n    <script defer src=\"../uibuilder/uibuilder.iife.min.js\"></script>\n    <script defer src=\"./index.js\">/* OPTIONAL: Put your custom code in that */</script>\n    <!-- #endregion -->\n\n</head><body class=\"uib\">\n    \n<h4 class=\"with-subtitle\"> \n</h4>\n\n\n<div class=\"btn-group\">\n    <img src=\"\\bbbdlogo1.gif\" width=\"60\" height=\"40\"  border-radius=\"30px\" style= \"margin: 5px 5px 25px 25px\"; >\n    \n    <button id=\"b01\" class=\"header\" onclick=\"uibuilder.eventSend(event)\">MyCompany Name</button>\n <!--   <button id=\"b02\" class=\"button1\" onclick=\"window.location.href = 'http://10.180.18.18:1881/dash1'\" >Live</button>\n -->\n    <button id=\"m01\" class=\"button1\" onclick=\"uibuilder.eventSend(event)\">M-1</button>\n    <button id=\"m02\" class=\"button1\" onclick=\"uibuilder.eventSend(event)\">M-2</button>\n    <button id=\"m03\" class=\"button1\" onclick=\"uibuilder.eventSend(event)\">M-3</button>\n    <button id=\"m04\" class=\"button1\" onclick=\"uibuilder.eventSend(event)\">M-4</button>\n    <button id=\"m05\" class=\"button1\" onclick=\"uibuilder.eventSend(event)\">M-5</button>\n    <button id=\"m06\" class=\"button1\" onclick=\"uibuilder.eventSend(event)\">M-6</button>\n    <button id=\"m07\" class=\"button1\" onclick=\"uibuilder.eventSend(event)\">M-7</button>\n    <button id=\"m08\" class=\"button1\" onclick=\"uibuilder.eventSend(event)\">M-8</button>\n    <button id=\"b10\" class=\"time_display\" >Time</button>\n\n<div id=\"sform1\">\n    <form><div title=\"Required. Type: Date\" class=\"required\"><label for=\"r3-date-req\">Select Date :</label>\n    <input type=\"Date\" label=\"Select Date :\" id=\"r3-date-req\" value=\"{{msg.curdate}}\" required=\"true\" onchange=\"this.dataset.newValue = this.value\"\n     onfocus=\"this.dataset.oldValue = this.value\" name=\"r3-date-req\" title=\"Required. Type: Date\">\n\n<button type=\"button\" title=\"Send the form data back to Node-RED\" onclick=\"uibuilder.eventSend(event)\" id=\"sform1-btn-send\">Send</button>\n<button type=\"button\" title=\"Reset the form\" onclick=\"uibuilder.eventSend(event)\" id=\"sform1-btn-reset\">Reset</button>\n\n</form>\n\n<div uib-topic=\"btnreport\"></div>\n\n</div>\n<div id=\"timeline\" style=\"height: 180px\"></div>\n<div id=\"more\"></div>\n   \n</html>\n\n    \n","output":"str","x":630,"y":80,"wires":[["24c77c0563358957"]]},{"id":"2626e724a98754e5","type":"inject","z":"0293de8750223951","g":"df1e18d31f27e540","name":"Load","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":210,"y":140,"wires":[["5677ccf3a5bcbeb0"]]},{"id":"122d8fbf4a888bcf","type":"delay","z":"0293de8750223951","g":"df1e18d31f27e540","name":"","pauseType":"delay","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":460,"y":200,"wires":[["512f5338ba4c64a6"]]},{"id":"512f5338ba4c64a6","type":"change","z":"0293de8750223951","g":"df1e18d31f27e540","name":"Reload","rules":[{"t":"set","p":"_ui","pt":"msg","to":"{\"method\":\"reload\"}","tot":"json"},{"t":"set","p":"topic","pt":"msg","to":"reload","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":640,"y":200,"wires":[["d7d16d255e48fb69"]]},{"id":"d7d16d255e48fb69","type":"link out","z":"0293de8750223951","g":"df1e18d31f27e540","name":"link out 4","mode":"link","links":["b9266ac33032ca79"],"x":755,"y":200,"wires":[]},{"id":"5677ccf3a5bcbeb0","type":"junction","z":"0293de8750223951","g":"df1e18d31f27e540","x":320,"y":140,"wires":[["abf4ed6710c598ca","a4b12724eb473f10","c98cab456b72075f","122d8fbf4a888bcf"]]},{"id":"a4a8e42540004ddf","type":"template","z":"0293de8750223951","g":"df1e18d31f27e540","name":"JS","field":"payload","fieldType":"msg","format":"javascript","syntax":"mustache","template":"google.charts.load('current', { 'packages': ['timeline'] });\ngoogle.charts.setOnLoadCallback(drawChart);\nfunction drawChart() {\n    var container = document.getElementById('timeline');\n    var chart = new google.visualization.Timeline(container);\n    var dataTable = new google.visualization.DataTable();\n\n    dataTable.addColumn({ type: 'string', id: 'Machine' });\n    dataTable.addColumn({ type: 'string', id: 'Name' });\n    dataTable.addColumn({ type: 'date', id: 'Start' });\n    dataTable.addColumn({ type: 'date', id: 'End' });\n    dataTable.addRows([\n        ['Machine-1', 'ON', new Date(2024, 10, 27, 6, 00), new Date(2024, 10, 27, 06, 10)],\n        ['Machine-1', 'OFF', new Date(2024, 10, 27, 06, 10), new Date(2024, 10, 27, 06, 45)],\n        ['Machine-1', 'ON', new Date(2024, 10, 27, 06, 45), new Date(2024, 10, 27, 06, 59)],\n        ['Machine-1', 'OFF', new Date(2024, 10, 27, 06, 59), new Date(2024, 10, 27, 07, 45)],\n        ['Machine-1', 'ON', new Date(2024, 10, 27, 07, 45), new Date(2024, 10, 27, 08, 19)],\n        ['Machine-1', 'OFF', new Date(2024, 10, 27, 08, 19), new Date(2024, 10, 27, 09, 45)],\n        ['Machine-1', 'ON', new Date(2024, 10, 27, 09, 45), new Date(2024, 10, 27, 10, 29)]]\n        );\n\n    chart.draw(dataTable);\n}","output":"str","x":630,"y":160,"wires":[["6903100ffb4db4dc"]]},{"id":"0860dc9bbffaee2d","type":"template","z":"0293de8750223951","g":"df1e18d31f27e540","name":"JS","field":"payload","fieldType":"msg","format":"javascript","syntax":"mustache","template":"let charttitle = 'Machine Operating Trend' \n\n let ymin1 = 0\n let ymax1 = 60\n\nlet axistitle1 = 'PPM'\n\nlet data = [\n    {\n        \"name\": \"PPM\",\n        \"type\": \"line\",\n        \"data\": [\n        ]\n    }\n]\n\nconst options = {\n    series: data,\n    // series: [],\n    noData: {\n        text: 'Loading...'\n    },\n    chart: {\n        type: 'line', stacked: false, height: 200, background: '#000', zoom:\n            { type: 'x', enabled: true, autoScaleYaxis: true },\n        toolbar: { autoSelected: 'zoom' }\n    },\n    dataLabels: { enabled: false },\n    markers: { size: 0, },\n    theme: { mode: 'dark' },\n    colors: ['#FA2E2E'],\n\n    title: {\n        text: charttitle,\n        align: 'center',\n        margin: 10, offsetX: 0, offsetY: 0, floating: false,\n        style: {\n            fontSize: 30, fontWeight: 'bold', fontFamily: undefined, color: 'yellow'\n        },\n    },\n    stroke: { width: [2], curve: 'smooth' },\n    yaxis: [\n        { labels: { formatter: function (val) { return val.toFixed(1); }, }, min: ymin1, max: ymax1, title: { text: axistitle1, style: { color: \"#FA2E2E\" } }, },\n     ],\n\n    xaxis: { lines: { show: false, }, type: 'datetime', datetimeUTC: false, labels: { format: 'dd/MMM HH:mm', }, },\n    tooltip: {\n        enabled: true, style: { fontSize: '12px', fontFamily: undefined }, x: { show: true, format: 'dd/MMM HH:mm', }, row: { colors: undefined, opacity: 0.5 },\n        column: { colors: undefined, opacity: 0.5 },\n        padding: { top: 0, right: 0, bottom: 0, left: 0 },\n    },\n    grid: {\n        show: true, borderColor: '#ffffff', strokeDashArray: 0, position: 'back',\n        xaxis: { lines: { show: false } },\n        yaxis: { lines: { show: false } },\n        row: { colors: undefined, opacity: 0.5 },\n        column: { colors: undefined, opacity: 0.5 },\n        padding: { top: 0, right: 0, bottom: 0, left: 0 },\n    }\n}\n\nconst chart = new ApexCharts(document.querySelector(\"#chart\"), options)\nchart.render()\n\nuibuilder.onChange('msg', (msg) => {\n\n    if (msg.charttitle) charttitle = msg.charttitle\n    if (msg.axistitle1) axistitle1 = msg.axistitle1\n\n    if (!msg.ymin1) ymin1 = msg.ymin1\n    if (!msg.ymax1) ymax1 = msg.ymax1\n\n    chart.updateOptions(options)\n\n    chart.updateSeries(msg.payload)\n})\n","output":"str","x":190,"y":180,"wires":[[]]},{"id":"37775c10f1670aa3","type":"group","z":"0293de8750223951","name":"UI-BUILDER","style":{"stroke":"#ffcf3f","fill":"#ffefbf","label":true,"color":"#ffC000"},"nodes":["9fd42f1d69b31f36","ed061d1aaea36b27","38b4106e9e8e908b","3a8ac55b6b4ae99e","c33b322ffefdc55e","b66d56cce110e894","b9266ac33032ca79","64963083f31f0a8d"],"x":114,"y":379,"w":542,"h":175.33333333333337},{"id":"9fd42f1d69b31f36","type":"uibuilder","z":"0293de8750223951","g":"37775c10f1670aa3","name":"","topic":"","url":"dt","okToGo":true,"fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"templateFolder":"blank","extTemplate":"","showfolder":false,"reload":false,"sourceFolder":"src","deployedVersion":"7.0.4","showMsgUib":false,"title":"","descr":"","editurl":"vscode://vscode-remote/ssh-remote+10.180.18.18\\Users\\OEEHo\\.node-red\\uibuilder/dt/?windowId=_blank","x":430,"y":493.33333333333337,"wires":[["c33b322ffefdc55e","b66d56cce110e894"],[]]},{"id":"ed061d1aaea36b27","type":"uib-cache","z":"0293de8750223951","g":"37775c10f1670aa3","cacheall":true,"cacheKey":"topic","newcache":true,"num":"10","storeName":"memory","name":"Cache","storeContext":"context","varName":"uib_cache","x":250,"y":493.33333333333337,"wires":[["9fd42f1d69b31f36"]]},{"id":"38b4106e9e8e908b","type":"link in","z":"0293de8750223951","g":"37775c10f1670aa3","name":"link in 1","links":["bb6abffbdf462b7a","e893b72a78545d65","d84c15b43a28ccc2","470e8ae8230ed45d","bc1bee8f1ce78407","074f90a5ea8b0345","5baac438f8920839"],"x":155,"y":493.33333333333337,"wires":[["ed061d1aaea36b27"]]},{"id":"3a8ac55b6b4ae99e","type":"inject","z":"0293de8750223951","g":"37775c10f1670aa3","name":"Reload","props":[{"p":"_ui","v":"{\"method\":\"reload\"}","vt":"json"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"reload","x":230,"y":453.33333333333337,"wires":[["64963083f31f0a8d"]],"info":"Sends a pre-formatted msg to the front-end that\r\ncauses the page to reload itself."},{"id":"c33b322ffefdc55e","type":"debug","z":"0293de8750223951","g":"37775c10f1670aa3","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":595,"y":513.3333333333334,"wires":[],"l":false},{"id":"b66d56cce110e894","type":"link out","z":"0293de8750223951","g":"37775c10f1670aa3","name":"link out 3","mode":"link","links":["b08870e9dec516af","a46bbdb548f199f9"],"x":595,"y":433.33333333333337,"wires":[]},{"id":"b9266ac33032ca79","type":"link in","z":"0293de8750223951","g":"37775c10f1670aa3","name":"link in 3","links":["d7d16d255e48fb69"],"x":235,"y":420,"wires":[["64963083f31f0a8d"]]},{"id":"64963083f31f0a8d","type":"junction","z":"0293de8750223951","g":"37775c10f1670aa3","x":320,"y":413.33333333333337,"wires":[["9fd42f1d69b31f36"]]}]

EDIT:

I am now re-writing the entire index.js file when data changes, and reloading the page to get what i want, may be not ideal, but i am getting there.

Not got a lot of time at the mo. However, you've hard-coded the data into your JavaScript. The reason for having JavaScript in the first place is so that you don't have to hard-code things. :wink:

All you need is a uibuilder.onChange('msg', (msg) => { ... } handler in the JavaScript - the default template comes with example code for that. Or you can look in the docs.

Then you can simply send just the data for a new entry and inside the onChange callback, you can call the dataTable.addRows function with the data from the msg.


Incidentally, I really do recommend that you take time to run through the Getting Started guide. It doesn't take long but it should cement in place the concepts you need.

There is also a video series if you prefer.

1 Like

I actually have gone though this video series. But since i am still learning my alphabets i am afraid doesn't make sense to me much , now. But i will go through again as this time may be it may make sense to me since i have made my hands dirty now quite a bit.

1 Like

It will come together I've no doubt. You are making steady progress.

Thanks, appreciate the motivation :slight_smile: . but i keep needing 'quick fixes' so that i keep moving ahead, will keep discussing that in the other thread.

I have successfully created a dynamic table using the example provided in the uibuilder.
Now, I need to make it a clickable table, where upon clicking any row, I get back the information in the table in that row. I do it succesfully with ui_table node in dashboard 1.0.
there is an example in uibuilder about getting back information from front end back to node-red. but i cant adapt it to the table.
a little help please..

table

OK, nice. So you will need a bit of javascript for this. Basically, you will need to attach an event listener to every row where the callback grabs the data and sends it back.

Let me work up a quick example.

So lets take the simplest case first because there is a complexity that will need to be either dealt with or worked around.

In this case, I assume that you generate an initial table using a uib-element node but then save the resulting HTML back to the index.html file. The reason for this is that it means that the core table element will be permanent and this makes life a LOT easier. You can still use no-code nodes to add/remove table rows in this case but the table itself needs to stay in place in the static page file.

With that assumption, this example outputs a message to Node-RED with the cell text contents of every cell in the clicked row. Even if you change the

Here is the JavaScript in the index.js file:

function addClickListenerToTableRows(tblId) {
    const table = document.getElementById(tblId)

    if (!table) {
        console.warn(`Table with ID "${tblId}" not found`)
        return
    }

    // Add event listener to the table's <tbody> to capture clicks on any <tr> within it
    table.querySelector('tbody').addEventListener('click', event => {
        const clickedRow = event.target.closest('tr')
        // console.log('row', clickedRow)

        if (clickedRow) {
            // Initialize an empty object to store cell data
            const rowData = {}

            // Loop through each <td> in the clicked row
            clickedRow.querySelectorAll('td').forEach(cell => {
                const colName = cell.getAttribute('data-col-name')

                // Only add cells that have a `data-col-name` attribute
                if (colName) {
                    rowData[colName] = cell.textContent.trim()
                }
            })

            console.log('Row data:', rowData)
            
            const rowIndex = clickedRow.rowIndex

            uibuilder.send({
                topic: `${tblId} row $(rowIndex) clicked`,
                rowIndex: rowIndex,
                payload: rowData,
            })
        }
    })
}

// Call the function to activate the event listener
addClickListenerToTableRows('eltest-tbl-table')

And here is the index.html file I used for testing:

<!doctype html>
<html lang="en"><head>

    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" href="../uibuilder/images/node-blue.ico">

    <title>UIBUILDER Table Response Example</title>
    <meta name="description" content="UIBUILDER Table Response Example">

    <!-- Your own CSS (defaults to loading uibuilders css)-->
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">

    <!-- #region Supporting Scripts. These MUST be in the right order. Note no leading / -->
    <script defer src="../uibuilder/uibuilder.iife.min.js"></script>
    <script defer src="./index.js"></script>
    <!-- #endregion -->

</head><body>

    <h1 class="with-subtitle">UIBUILDER Table Response Example</h1>
    <div role="doc-subtitle">Using the uibuilder IIFE library.</div>

    <div id="more">
        <div aria-labelledby="eltest-tbl-heading" id="eltest-tbl">
            <h4 id="eltest-tbl-heading">My Table Heading (h4)</h4>
            <table id="eltest-tbl-table">
                <thead>
                    <tr>
                        <th data-col-index="1" data-col-name="COL1">COL1</th>
                        <th data-col-index="2" data-col-name="COL2">COL2</th>
                        <th data-col-index="3" data-col-name="COL3">COL3</th>
                    </tr>
                </thead>
                <tbody>
                    <tr class="r1">
                        <td data-col-index="1" data-col-name="COL1" class="r1 c1">R1C1</td>
                        <td data-col-index="2" data-col-name="COL2" class="r1 c2">R1C2</td>
                        <td data-col-index="3" data-col-name="COL3" class="r1 c3">R1C3</td>
                    </tr>
                    <tr class="r2">
                        <td data-col-index="1" data-col-name="COL1" class="r2 c1">R2C1</td>
                        <td data-col-index="2" data-col-name="COL2" class="r2 c2">R2C2</td>
                        <td data-col-index="3" data-col-name="COL3" class="r2 c3">R2C</td>
                    </tr>
                    <tr class="r3">
                        <td data-col-index="1" data-col-name="COL1" class="r3 c1">R3C1</td>
                        <td data-col-index="2" data-col-name="COL2" class="r3 c2">R3C2</td>
                        <td data-col-index="3" data-col-name="COL3" class="r3 c3">R3C3</td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>

</body></html>

That HTML is what the table example for the uib-element node produces (except that I've updated the page title/heading).

You may also like to look at what I used to be able to produce a working example in minutes. Basically, I used ChatGPT. :sunglasses: You can see the query I used and the results it gave me here:

1 Like

Thanks for the quick response!
I copied your example and it works perfectly.

Now I need to modify my flow creating the table so that it does the same.
Will post soon.

Edit: So this example works for only fixed number of rows ?

No, this works no matter what you add/remove from the table rows. Only the table itself needs to be fixed - strictly speaking, the table with its tbody tag.

Adding/removing tbody rows is catered for in the script.

I also now have a more comprehensive tblAddClickListener function that has lots more options. This will probably make its way into the uibuilder client library. Eventually will become an option on the no-code table output.

Let me know if you need any help dynamically adding/removing or replacing table rows.


PS: This also copes with adding/removing columns. :smile:

You really prompted me to work on expanding table handling for the uibuilder client library.

I'm glad to report that I have some new library functions in the works that will make it much easier to add/remove rows (and eventually columns as well) and change cell content.

I'll be adding these to the library and I will also make them available as remote commands so you don't have to dip into front-end JavaScript to manipulate tables.

The extended version of tblAddClickListener has already been added to the library in the v7.1 development branch for the next release. :smile:


BTW, as this thread has grown rather. I've changed the title slightly in case people are looking for uibuilder help in the future.