\n This is a UIBUILDER example showing how easy it is to create a dynamic view of IoT devices in a building\n using SVG images both for the background (floor-plan) and device indicators.\n
\n\n \n\n
My House, Ground Floor
\n
\n \n
\n",
+ "output": "str",
+ "x": 860,
+ "y": 1200,
+ "wires": [
+ [
+ "3bc7cd6653cb521c"
+ ]
+ ]
+ },
+ {
+ "id": "3bc7cd6653cb521c",
+ "type": "uib-save",
+ "z": "ff9704678e3a4b61",
+ "g": "5eb220cde9614fee",
+ "url": "uib-dynamic-svg-eg",
+ "uibId": "18b02b8e78a54427",
+ "folder": "src",
+ "fname": "",
+ "createFolder": false,
+ "reload": false,
+ "usePageName": false,
+ "encoding": "utf8",
+ "mode": 438,
+ "name": "",
+ "topic": "",
+ "x": 1090,
+ "y": 1200,
+ "wires": []
+ },
+ {
+ "id": "5a1578bc66134fda",
+ "type": "change",
+ "z": "ff9704678e3a4b61",
+ "g": "5eb220cde9614fee",
+ "name": "index.html",
+ "rules": [
+ {
+ "t": "set",
+ "p": "fname",
+ "pt": "msg",
+ "to": "index.html",
+ "tot": "str"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 710,
+ "y": 1200,
+ "wires": [
+ [
+ "2ae5e4c81a6d9d60"
+ ]
+ ]
+ },
+ {
+ "id": "5003f0c412d3fccb",
+ "type": "change",
+ "z": "ff9704678e3a4b61",
+ "g": "5eb220cde9614fee",
+ "name": "index.js",
+ "rules": [
+ {
+ "t": "set",
+ "p": "fname",
+ "pt": "msg",
+ "to": "index.js",
+ "tot": "str"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 700,
+ "y": 1240,
+ "wires": [
+ [
+ "e0ae3e10d4c8573e"
+ ]
+ ]
+ },
+ {
+ "id": "e0ae3e10d4c8573e",
+ "type": "template",
+ "z": "ff9704678e3a4b61",
+ "g": "5eb220cde9614fee",
+ "name": "",
+ "field": "payload",
+ "fieldType": "msg",
+ "format": "javascript",
+ "syntax": "mustache",
+ "template": "// @ts-nocheck\n/** Dynamic SVG example */\n\n// uibuilder.logLevel = 1\n\n// The Template tag in index.html contains a template \"bulb\" SVG image\n// Here, we clone that multiple times and set some properties.\n// Note that `htmlClone` is a function that will land in the uibuilder client in v6.2\n// Also, the $ function is improved in v6.2 so a copy of that is included here for convenience.\n//\n// We track state and position class on data-* attributes so that it is much easier to process\n// click events in Node-RED without having to create a custom click handler, we can just use the standard eventSend.\n// CSS classes do all the clever stuff 😁\n\nuibuilder.applyTemplate('bulb-template', 'floorplan', {\n attributes: {\n id: 'bulb1',\n // Apply base bulb class and a positioning class\n class: 'bulb posn1',\n // Track the on/off state separately - makes processing in Node-RED easier\n 'data-state': 'off',\n // Track the position class separately - makes processing in Node-RED easier\n 'data-posn': 'posn1',\n // We have to add event handlers after a clone, they cannot be included in the template\n onclick: 'uibuilder.eventSend(event)'\n }\n})\nuibuilder.applyTemplate('bulb-template', 'floorplan', {\n attributes: {\n id: 'bulb2',\n // Apply base bulb class and a positioning class\n class: 'bulb posn2',\n // Track the on/off state separately - makes processing in Node-RED easier\n 'data-state': 'off',\n // Track the position class separately - makes processing in Node-RED easier\n 'data-posn': 'posn2',\n // We have to add event handlers after a clone, they cannot be included in the template\n onclick: 'uibuilder.eventSend(event)'\n }\n})\nuibuilder.applyTemplate('bulb-template', 'floorplan', {\n attributes: {\n id: 'bulb3',\n // Apply base bulb class and a positioning class\n class: 'bulb posn3',\n // Track the on/off state separately - makes processing in Node-RED easier\n 'data-state': 'off',\n // Track the position class separately - makes processing in Node-RED easier\n 'data-posn': 'posn3',\n // We have to add event handlers after a clone, they cannot be included in the template\n onclick: 'uibuilder.eventSend(event)'\n }\n})\nuibuilder.applyTemplate('bulb-template', 'floorplan', {\n attributes: {\n id: 'bulb4',\n // Apply base bulb class and a positioning class\n class: 'bulb posn4',\n // Track the on/off state separately - makes processing in Node-RED easier\n 'data-state': 'off',\n // Track the position class separately - makes processing in Node-RED easier\n 'data-posn': 'posn4',\n // We have to add event handlers after a clone, they cannot be included in the template\n onclick: 'uibuilder.eventSend(event)'\n }\n})\n\n// htmlClone($('#bulb-template'), $('#floorplan'), {\n// // As always, we set a unique ID for every created element so it can be updated easily later\n// id: 'bulb1',\n// // You only need this if you want to choose 'first' or a position number,\n// // the clone will be added at the specified child position of the parent.\n// // position: 'last', \n// attributes: {\n// // Apply base bulb class and a positioning class\n// class: 'bulb posn1',\n// // Track the on/off state separately - makes processing in Node-RED easier\n// 'data-state': 'off',\n// // Track the position class separately - makes processing in Node-RED easier\n// 'data-posn': 'posn1'\n// },\n// // We have to add event handlers after a clone, they cannot be included in the template\n// events: {\n// click: 'uibuilder.eventSend'\n// }\n// })\n\n// htmlClone(mySelector('#bulb-template'), mySelector('#floorplan'), {\n// id: 'bulb2',\n// attributes: {\n// class: 'bulb posn2',\n// 'data-state': 'off',\n// 'data-posn': 'posn2'\n// },\n// events: {\n// click: 'uibuilder.eventSend'\n// }\n// })\n\n// htmlClone(mySelector('#bulb-template'), mySelector('#floorplan'), {\n// id: 'bulb3',\n// attributes: {\n// class: 'bulb posn3',\n// 'data-state': 'off',\n// 'data-posn': 'posn3'\n// },\n// events: {\n// click: 'uibuilder.eventSend'\n// }\n// })\n\n// htmlClone(mySelector('#bulb-template'), mySelector('#floorplan'), {\n// id: 'bulb4',\n// attributes: {\n// class: 'bulb posn4',\n// 'data-state': 'off',\n// 'data-posn': 'posn4'\n// },\n// events: {\n// click: 'uibuilder.eventSend'\n// },\n// })\n",
+ "output": "str",
+ "x": 860,
+ "y": 1240,
+ "wires": [
+ [
+ "3bc7cd6653cb521c"
+ ]
+ ]
+ },
+ {
+ "id": "d52529f420959f1d",
+ "type": "change",
+ "z": "ff9704678e3a4b61",
+ "g": "5eb220cde9614fee",
+ "name": "index.css",
+ "rules": [
+ {
+ "t": "set",
+ "p": "fname",
+ "pt": "msg",
+ "to": "index.css",
+ "tot": "str"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 700,
+ "y": 1280,
+ "wires": [
+ [
+ "6174f2357fd373d1"
+ ]
+ ]
+ },
+ {
+ "id": "6174f2357fd373d1",
+ "type": "template",
+ "z": "ff9704678e3a4b61",
+ "g": "5eb220cde9614fee",
+ "name": "",
+ "field": "payload",
+ "fieldType": "msg",
+ "format": "css",
+ "syntax": "mustache",
+ "template": "/* Load defaults from `/node_modules/node-red-contrib-uibuilder/front-end/uib-brand.css`\n * This version auto-adjusts for light/dark browser settings but might not be as complete.\n */\n@import url(\"../uibuilder/uib-brand.css\");\n\n/* These variables build on existing variables in uib-brand.css\n * They will be incorporated into that file in uibuilder v6.2\n */\n:root {\n --warning-intense: hsl(var(--warning-hue) 100% 50%);\n --failure-intense: hsl(var(--failure-hue) 100% 50%);\n --surface5: hsl(\n /* additional background shade */\n var(--brand-hue) calc(100% * var(--surfaces-saturation)) calc(100% * (var(--surfaces-lightness) - (var(--surfaces-factor) * .20) + (var(--surfaces-factor) * var(--surfaces-bias)))));\n}\n\n\n/* Bulb classes control look, colour and position */\n\n.bulb {\n /* Default \"off\" class plus standard style */\n z-index: 9999 !important;\n /* Bulbs HAVE to be in the top z-layer */\n cursor: pointer;\n position: absolute;\n /* allows exact positioning within the parent div */\n transition: filter 2s ease-in-out 0s;\n background-color: rgba(0, 0, 0, 0.001);\n /* transparent background */\n filter: url(\"#shadow\");\n /* selects the shadow filter */\n}\n\n.bulb path {\n fill: grey;\n}\n\n.bulb-warn {\n /* Standard \"on\" class */\n filter: url('#glow');\n /* selects the glow filter instead of shadow */\n}\n\n.bulb-warn path {\n fill: var(--warning-intense);\n}\n\n.bulb-fail {\n /* Alternative \"on\" class with different colour */\n filter: url('#glow');\n}\n\n.bulb-fail path {\n fill: var(--failure-intense);\n}\n\n/* Bulb position classes, change as needed\n * Positions are relative to the parent floorplan div\n */\n.posn1 {\n top: 100px;\n left: 100px;\n}\n\n.posn2 {\n top: 120px;\n left: 270px;\n}\n\n.posn3 {\n top: 120px;\n left: 650px;\n}\n\n.posn4 {\n top: 270px;\n left: 250px;\n}\n\n/* floorplan div class change anything\n * except the position:relative.\n * The background image location is relative\n * to your uibuilder front-end files.\n */\n.plan {\n position: relative;\n width: 99%;\n height: 30rem;\n background-color: var(--surface5);\n background-repeat: no-repeat;\n background-image: url(\"./background.svg\");\n /* Sadly, can't use a GIST directly here. Copy this file to the local folder instead: */\n /* background: url(\"https://gist.github.com/TotallyInformation/02eb3716157db586f3f5b8a85c241009#file-background-svg\"); */\n}\n",
+ "output": "str",
+ "x": 860,
+ "y": 1280,
+ "wires": [
+ [
+ "3bc7cd6653cb521c"
+ ]
+ ]
+ },
+ {
+ "id": "768c8273347df9fa",
+ "type": "change",
+ "z": "ff9704678e3a4b61",
+ "g": "5eb220cde9614fee",
+ "name": "background.svg",
+ "rules": [
+ {
+ "t": "set",
+ "p": "fname",
+ "pt": "msg",
+ "to": "background.svg",
+ "tot": "str"
+ }
+ ],
+ "action": "",
+ "property": "",
+ "from": "",
+ "to": "",
+ "reg": false,
+ "x": 720,
+ "y": 1320,
+ "wires": [
+ []
+ ]
+ },
+ {
+ "id": "5dce296a96d3f95e",
+ "type": "comment",
+ "z": "ff9704678e3a4b61",
+ "g": "5eb220cde9614fee",
+ "name": "background.svg",
+ "info": "Can't include this directly here otherwise\nthe flow cannot be posted to the forum.\n\nThe example background file can be obtained\nhere:\n\nhttps://gist.github.com/TotallyInformation/02eb3716157db586f3f5b8a85c241009#file-background-svg\n\nCopy the text out of it and paste into a new \nfile, `background1.svg` in the same location \nas your `index.html` file.\n",
+ "x": 900,
+ "y": 1320,
+ "wires": []
+ },
+ {
+ "id": "8653c6e164389a3a",
+ "type": "junction",
+ "z": "ff9704678e3a4b61",
+ "g": "1678f3c7ab967e39",
+ "x": 520,
+ "y": 1040,
+ "wires": [
+ [
+ "18b02b8e78a54427"
+ ]
+ ]
+ },
+ {
+ "id": "9ca3a3da680721e7",
+ "type": "inject",
+ "z": "ff9704678e3a4b61",
+ "g": "1678f3c7ab967e39",
+ "name": "Toggle Visible Msgs",
+ "props": [
+ {
+ "p": "_uib",
+ "v": "{\"command\":\"showMsg\"}",
+ "vt": "json"
+ }
+ ],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": 0.1,
+ "topic": "",
+ "x": 230,
+ "y": 1320,
+ "wires": [
+ [
+ "bf47991b0cd95827"
+ ]
+ ]
+ },
+ {
+ "id": "bf47991b0cd95827",
+ "type": "link out",
+ "z": "ff9704678e3a4b61",
+ "g": "1678f3c7ab967e39",
+ "name": "Send to uibuilder node",
+ "mode": "link",
+ "links": [
+ "8ce7dd9a5d97b83b"
+ ],
+ "x": 395,
+ "y": 1320,
+ "wires": []
+ }
+]
+
diff --git a/examples/Low-code/low-code-report-builder.json b/examples/Low-code/low-code-report-builder.json
index 70da878c..353df1a6 100644
--- a/examples/Low-code/low-code-report-builder.json
+++ b/examples/Low-code/low-code-report-builder.json
@@ -3,7 +3,7 @@
"id": "56af672c3ae066b8",
"type": "group",
"z": "ff9704678e3a4b61",
- "name": "uibuilder report builder example \\n ",
+ "name": "UIBUILDER report builder low-code example. (Updated 2024-07-01) \\n ",
"style": {
"fill": "#ffffff",
"fill-opacity": "0.2",
@@ -12,17 +12,15 @@
},
"nodes": [
"bdd00bc65ececfb2",
- "b77a8ab550aef26a",
"4b423eb7794241f7",
"48bca724c94ff6ad",
"d0b0802620db38cd",
- "59718b678565eb7c",
- "bf4b37633885a3a1"
+ "59718b678565eb7c"
],
"x": 44,
- "y": 19,
+ "y": 39,
"w": 672,
- "h": 282
+ "h": 262
},
{
"id": "bdd00bc65ececfb2",
@@ -32,6 +30,7 @@
"name": "",
"topic": "",
"url": "uib-report-example",
+ "okToGo": true,
"fwdInMessages": false,
"allowScripts": false,
"allowStyles": false,
@@ -43,7 +42,10 @@
"sourceFolder": "src",
"deployedVersion": "6.1.0-beta",
"showMsgUib": true,
- "x": 400,
+ "title": "",
+ "descr": "",
+ "editurl": "vscode://file/src/uibRoot/uib-report-example/?windowId=_blank",
+ "x": 380,
"y": 220,
"wires": [
[
@@ -55,44 +57,14 @@
],
"info": "This example uses a blank template with\r\nthe IIFE build of the front-end client.\r\n\r\nIt does not use any front-end framework, just\r\npure HTML, CSS and JavaScript.\r\n"
},
- {
- "id": "b77a8ab550aef26a",
- "type": "inject",
- "z": "ff9704678e3a4b61",
- "g": "56af672c3ae066b8",
- "name": "Reload",
- "props": [
- {
- "p": "_ui",
- "v": "{\"method\":\"reload\"}",
- "vt": "json"
- },
- {
- "p": "topic",
- "vt": "str"
- }
- ],
- "repeat": "",
- "crontab": "",
- "once": false,
- "onceDelay": 0.1,
- "topic": "reload",
- "x": 160,
- "y": 240,
- "wires": [
- [
- "bf4b37633885a3a1"
- ]
- ],
- "info": "Sends a pre-formatted msg to the front-end that\r\ncauses the page to reload itself."
- },
{
"id": "4b423eb7794241f7",
"type": "debug",
"z": "ff9704678e3a4b61",
+ "d": true,
"g": "56af672c3ae066b8",
"name": "uibuilder standard output",
- "active": false,
+ "active": true,
"tosidebar": true,
"console": false,
"tostatus": true,
@@ -100,8 +72,8 @@
"targetType": "full",
"statusVal": "",
"statusType": "counter",
- "x": 555,
- "y": 180,
+ "x": 535,
+ "y": 200,
"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."
@@ -110,9 +82,10 @@
"id": "48bca724c94ff6ad",
"type": "debug",
"z": "ff9704678e3a4b61",
+ "d": true,
"g": "56af672c3ae066b8",
"name": "uibuilder control output",
- "active": false,
+ "active": true,
"tosidebar": true,
"console": false,
"tostatus": true,
@@ -120,7 +93,7 @@
"targetType": "full",
"statusVal": "",
"statusType": "counter",
- "x": 555,
+ "x": 535,
"y": 260,
"wires": [],
"l": false,
@@ -131,10 +104,10 @@
"type": "comment",
"z": "ff9704678e3a4b61",
"g": "56af672c3ae066b8",
- "name": "Low-code report building with the help of uibuilder \\n Chk Description in each node & in here \\n You need to add a line to the HTML so that the Markdown output works correctly, see within",
- "info": "To get the Markdown outputs to display,\nplease add the following line to the html\nfile before the reference to uibuilder's\nlibrary:\n\n```\n\n```\n\n---\n\nThis example starts with the standard IIFE \nuibuilder template. You will note, however,\nthat you can remove all of the body HTML\nand it will still work.\n\nThis is because all of the important text,\ntables and layout are created dynamically\nusing a standardised configuration schema.\n\nThe schema used by uibuilder is actually\nquite similar, at least in concept, to that\nused by pdfmaker which was the inspiration\nfor this example. I didn't know about pdfmaker\nwhen I designed the config-driven aspects of\nuibuilder and it is comforting to know that I\ncame up with a similar solution to a similar\nproblem.\n\nOutput to HTML is far more flexible than\noutput to PDF via pdfmaker though and it will\ncontinue to grow and improve as HMTL and CSS do.\n\nYou can still output the resulting page to\nPDF very easily though.\n\n---\n\nFull documentation for uibuilder's low-code,\nconfiguration-driven UI creation capabilities\nis available in the documentation:\n\nhttps://totallyinformation.github.io/node-red-contrib-uibuilder/#/uibuilder.module?id=dynamic-data-driven-html-content-1",
+ "name": "Low-code report building with the help of uibuilder. No HTML or JS needed. \\n Check the descriptions in each node & in this comment node. \\n Uses Markdown in this case & automatically loads the Markdown library for you from CDN.",
+ "info": "To get the Markdown outputs to display,\nplease add the following line to the html\nfile before the reference to uibuilder's\nlibrary:\n\n```\n\n```\n\n---\n\nThis example starts with the standard IIFE \nuibuilder template. You will note, however,\nthat you can remove all of the body HTML\nand it will still work.\n\nThis is because all of the important text,\ntables and layout are created dynamically\nusing a standardised configuration schema.\n\nThe schema used by UIBUILDER is actually\nquite similar, at least in concept, to that\nused by pdfmaker which was the inspiration\nfor this example. I didn't know about pdfmaker\nwhen I designed the config-driven aspects of\nuibuilder and it is comforting to know that I\ncame up with a similar solution to a similar\nproblem.\n\nOutput to HTML is far more flexible than\noutput to PDF via pdfmaker though and it will\ncontinue to grow and improve as HMTL and CSS do.\n\nYou can still output the resulting page to\nPDF very easily though.\n\n---\n\nFull documentation for uibuilder's low-code,\nconfiguration-driven UI creation capabilities\nis available in the documentation:\n\nhttps://totallyinformation.github.io/node-red-contrib-uibuilder/#/uibuilder.module?id=dynamic-data-driven-html-content-1",
"x": 380,
- "y": 100,
+ "y": 120,
"wires": []
},
{
@@ -142,11 +115,11 @@
"type": "inject",
"z": "ff9704678e3a4b61",
"g": "56af672c3ae066b8",
- "name": "",
+ "name": "REPORT!",
"props": [
{
"p": "_ui",
- "v": "[{\"method\":\"remove\",\"components\":[\"#ui-test\"]},{\"method\":\"add\",\"components\":[{\"type\":\"main\",\"id\":\"ui-test\",\"parent\":\"#more\",\"components\":[{\"type\":\"h1\",\"slot\":\"uibuilder Low-Code Tests: Tables\"},{\"type\":\"p\",\"slot\":\"This is an example of using uibuilder's low-code, config-driven page builder. It is based on the TABLES example from pdfmake. This is partly to demonstrate that pdfmake and uibuilder use related principals for similar outcomes.\"},{\"type\":\"p\",\"slot\":\"In addition, it demonstrates how to create a complex HTML report layout dynamically direct from Node-RED using a standardised configuration and data-driven, low-code methodology.\"},{\"type\":\"p\",\"slot\":\"It would be easy to directly print this to paper or print/save to PDF.\"},{\"type\":\"p\",\"slot\":\"Of course, much of the output configuration could also be dynamcially created from other data rather than being specified manually as in this example. Change the input from JSON to Expression for example in order to use JSONata to include data from an input msg.\"},{\"type\":\"p\",\"slot\":\"The input in this example is set up so that re-sending the data, removes and re-add's the entire thing. This should not be noticable in the browser. Try changing some data and resending to see the effect.\"},{\"type\":\"p\",\"slot\":\"Except where mentioned in the tables themselves, the majority of the styling comes from the pre-loaded uibuilder stylesheet. Of course, you can replace this with your own.\"},{\"type\":\"article\",\"components\":[{\"type\":\"h2\",\"slot\":\"A simple table (no style overrides)\"},{\"type\":\"p\",\"slot\":\"Nothing more than a couple of unstyled rows and columns. No headings.\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Column 1\"},{\"type\":\"td\",\"slot\":\"Column 2\"},{\"type\":\"td\",\"slot\":\"Column 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"One value goes here\"},{\"type\":\"td\",\"slot\":\"Another one here\"},{\"type\":\"td\",\"slot\":\"OK\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"h2\",\"slot\":\"A simple table with nested elements\"},{\"type\":\"p\",\"slot\":\"It is of course possible to nest any other type of nodes available in pdfmake uibuilder/HTML inside table cells.\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Column 1\"},{\"type\":\"td\",\"slot\":\"Column 2\"},{\"type\":\"td\",\"slot\":\"Column 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Let's try an unordered list\",\"components\":[{\"type\":\"ul\",\"components\":[{\"type\":\"li\",\"slot\":\"Item 1\"},{\"type\":\"li\",\"slot\":\"Item 2\"}]}]},{\"type\":\"td\",\"slot\":\"or a nested table\",\"components\":[{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"th\",\"slot\":\"Col 1\"},{\"type\":\"th\",\"slot\":\"Col 2\"},{\"type\":\"th\",\"slot\":\"Col 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"R1C1\"},{\"type\":\"td\",\"slot\":\"R1C2\"},{\"type\":\"td\",\"slot\":\"R1C3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"R2C1\"},{\"type\":\"td\",\"slot\":\"R2C2\"},{\"type\":\"td\",\"slot\":\"R2C3\"}]}]}]},{\"type\":\"td\",\"slotMarkdown\":\"Inlines can be _styled_ easily as everywhere else. Even using Markdown!\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"h2\",\"slot\":\"Defining column widths\"},{\"type\":\"p\",\"slotMarkdown\":\"~~Tables support the same width definitions as standard columns~~ HTML is different to pdfmaker here since styling is done using CSS.\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"width:100em;\"},\"slot\":\"width:100em\"},{\"type\":\"td\",\"slot\":\"Unsized\"},{\"type\":\"td\",\"attributes\":{\"style\":\"width:25%;\"},\"slot\":\"width:25%\"},{\"type\":\"td\",\"slot\":\"Unsized\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"fixed-width cells have exactly the specified width\"},{\"type\":\"td\",\"slotMarkdown\":\"_nothing interesting here_\"},{\"type\":\"td\",\"slotMarkdown\":\"_nothing interesting here_\"},{\"type\":\"td\",\"slotMarkdown\":\"_nothing interesting here_\"}]}]},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"width:90%;\"},\"slotMarkdown\":\"This is a ~~star-sized~~ fixed % size column. The next column over, an auto-sized column, will wrap to accomodate all the text in this cell.\"},{\"type\":\"td\",\"slot\":\"I am auto sized.\"}]}]},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slotMarkdown\":\"This is ~~a star-sized~~ an unsized column. The next column over, also auto-sized, will not wrap to accomodate all the text in this cell, because it has been given the noWrap style.\"},{\"type\":\"td\",\"attributes\":{\"style\":\"white-space: nowrap;\"},\"slot\":\"I am no-wrap auto sized.\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"h2\",\"slot\":\"Defining row heights\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"height:2em;\"},\"slot\":\"row 1 with height 2em\"},{\"type\":\"td\",\"slot\":\"Column B\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"height:5em;\"},\"slot\":\"row 2 with height 5em\"},{\"type\":\"td\",\"slot\":\"Column B\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"height:7em;\"},\"slot\":\"row 3 with height 7em\"},{\"type\":\"td\",\"slot\":\"Column B\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"h2\",\"slot\":\"Column/row spans\"},{\"type\":\"p\",\"slot\":\"Each cell-element can set a rowSpan or colSpan\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"th\",\"attributes\":{\"colspan\":\"2\",\"alignment\":\"center\"},\"slot\":\"Header with Colspan = 2, centered\"},{\"type\":\"th\",\"slot\":\"Header 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"th\",\"slot\":\"Header 1\"},{\"type\":\"th\",\"slot\":\"Header 2\"},{\"type\":\"th\",\"slot\":\"Header 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Sample value 1\"},{\"type\":\"td\",\"slot\":\"Sample value 2\"},{\"type\":\"td\",\"slot\":\"Sample value 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"rowspan\":3},\"slot\":\"rowSpan set to 3 Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\"},{\"type\":\"td\",\"slot\":\"Sample value 2\"},{\"type\":\"td\",\"slot\":\"Sample value 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Sample value 2\"},{\"type\":\"td\",\"slot\":\"Sample value 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Sample value 2\"},{\"type\":\"td\",\"slot\":\"Sample value 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Sample value 1\"},{\"type\":\"td\",\"attributes\":{\"rowspan\":2,\"colspan\":2},\"slot\":\"Both: rowSpan and colSpan can be defined at the same time\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Sample value 1\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"p\",\"slot\":\"The remainder of the pdfmaker example deals with formatting. For an HTML report, this is done via CSS which can easily reproduce everything pdfmaker can do and a lot more.\"}]}]}]}]",
+ "v": "[{\"method\":\"load\",\"srcScripts\":[\"https://cdn.jsdelivr.net/npm/markdown-it/dist/markdown-it.min.js\"]},{\"method\":\"remove\",\"components\":[\"#ui-test\"]},{\"method\":\"add\",\"components\":[{\"type\":\"main\",\"id\":\"ui-test\",\"parent\":\"#more\",\"components\":[{\"type\":\"h1\",\"slot\":\"uibuilder Low-Code Tests: Tables\"},{\"type\":\"p\",\"slot\":\"This is an example of using uibuilder's low-code, config-driven page builder. It is based on the TABLES example from pdfmake. This is partly to demonstrate that pdfmake and uibuilder use related principals for similar outcomes.\"},{\"type\":\"p\",\"slot\":\"In addition, it demonstrates how to create a complex HTML report layout dynamically direct from Node-RED using a standardised configuration and data-driven, low-code methodology.\"},{\"type\":\"p\",\"slot\":\"It would be easy to directly print this to paper or print/save to PDF.\"},{\"type\":\"p\",\"slot\":\"Of course, much of the output configuration could also be dynamcially created from other data rather than being specified manually as in this example. Change the input from JSON to Expression for example in order to use JSONata to include data from an input msg.\"},{\"type\":\"p\",\"slot\":\"The input in this example is set up so that re-sending the data, removes and re-add's the entire thing. This should not be noticable in the browser. Try changing some data and resending to see the effect.\"},{\"type\":\"p\",\"slot\":\"Except where mentioned in the tables themselves, the majority of the styling comes from the pre-loaded uibuilder stylesheet. Of course, you can replace this with your own.\"},{\"type\":\"article\",\"components\":[{\"type\":\"h2\",\"slot\":\"A simple table (no style overrides)\"},{\"type\":\"p\",\"slot\":\"Nothing more than a couple of unstyled rows and columns. No headings.\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Column 1\"},{\"type\":\"td\",\"slot\":\"Column 2\"},{\"type\":\"td\",\"slot\":\"Column 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"One value goes here\"},{\"type\":\"td\",\"slot\":\"Another one here\"},{\"type\":\"td\",\"slot\":\"OK\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"h2\",\"slot\":\"A simple table with nested elements\"},{\"type\":\"p\",\"slot\":\"It is of course possible to nest any other type of nodes available in pdfmake uibuilder/HTML inside table cells.\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Column 1\"},{\"type\":\"td\",\"slot\":\"Column 2\"},{\"type\":\"td\",\"slot\":\"Column 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Let's try an unordered list\",\"components\":[{\"type\":\"ul\",\"components\":[{\"type\":\"li\",\"slot\":\"Item 1\"},{\"type\":\"li\",\"slot\":\"Item 2\"}]}]},{\"type\":\"td\",\"slot\":\"or a nested table\",\"components\":[{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"th\",\"slot\":\"Col 1\"},{\"type\":\"th\",\"slot\":\"Col 2\"},{\"type\":\"th\",\"slot\":\"Col 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"R1C1\"},{\"type\":\"td\",\"slot\":\"R1C2\"},{\"type\":\"td\",\"slot\":\"R1C3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"R2C1\"},{\"type\":\"td\",\"slot\":\"R2C2\"},{\"type\":\"td\",\"slot\":\"R2C3\"}]}]}]},{\"type\":\"td\",\"slotMarkdown\":\"Inlines can be _styled_ easily as everywhere else. Even using Markdown!\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"h2\",\"slot\":\"Defining column widths\"},{\"type\":\"p\",\"slotMarkdown\":\"~~Tables support the same width definitions as standard columns~~ HTML is different to pdfmaker here since styling is done using CSS.\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"width:100em;\"},\"slot\":\"width:100em\"},{\"type\":\"td\",\"slot\":\"Unsized\"},{\"type\":\"td\",\"attributes\":{\"style\":\"width:25%;\"},\"slot\":\"width:25%\"},{\"type\":\"td\",\"slot\":\"Unsized\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"fixed-width cells have exactly the specified width\"},{\"type\":\"td\",\"slotMarkdown\":\"_nothing interesting here_\"},{\"type\":\"td\",\"slotMarkdown\":\"_nothing interesting here_\"},{\"type\":\"td\",\"slotMarkdown\":\"_nothing interesting here_\"}]}]},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"width:90%;\"},\"slotMarkdown\":\"This is a ~~star-sized~~ fixed % size column. The next column over, an auto-sized column, will wrap to accomodate all the text in this cell.\"},{\"type\":\"td\",\"slot\":\"I am auto sized.\"}]}]},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slotMarkdown\":\"This is ~~a star-sized~~ an unsized column. The next column over, also auto-sized, will not wrap to accomodate all the text in this cell, because it has been given the noWrap style.\"},{\"type\":\"td\",\"attributes\":{\"style\":\"white-space: nowrap;\"},\"slot\":\"I am no-wrap auto sized.\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"h2\",\"slot\":\"Defining row heights\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"height:2em;\"},\"slot\":\"row 1 with height 2em\"},{\"type\":\"td\",\"slot\":\"Column B\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"height:5em;\"},\"slot\":\"row 2 with height 5em\"},{\"type\":\"td\",\"slot\":\"Column B\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"style\":\"height:7em;\"},\"slot\":\"row 3 with height 7em\"},{\"type\":\"td\",\"slot\":\"Column B\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"h2\",\"slot\":\"Column/row spans\"},{\"type\":\"p\",\"slot\":\"Each cell-element can set a rowSpan or colSpan\"},{\"type\":\"table\",\"components\":[{\"type\":\"tr\",\"components\":[{\"type\":\"th\",\"attributes\":{\"colspan\":\"2\",\"alignment\":\"center\"},\"slot\":\"Header with Colspan = 2, centered\"},{\"type\":\"th\",\"slot\":\"Header 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"th\",\"slot\":\"Header 1\"},{\"type\":\"th\",\"slot\":\"Header 2\"},{\"type\":\"th\",\"slot\":\"Header 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Sample value 1\"},{\"type\":\"td\",\"slot\":\"Sample value 2\"},{\"type\":\"td\",\"slot\":\"Sample value 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"attributes\":{\"rowspan\":3},\"slot\":\"rowSpan set to 3 Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor\"},{\"type\":\"td\",\"slot\":\"Sample value 2\"},{\"type\":\"td\",\"slot\":\"Sample value 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Sample value 2\"},{\"type\":\"td\",\"slot\":\"Sample value 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Sample value 2\"},{\"type\":\"td\",\"slot\":\"Sample value 3\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Sample value 1\"},{\"type\":\"td\",\"attributes\":{\"rowspan\":2,\"colspan\":2},\"slot\":\"Both: rowSpan and colSpan can be defined at the same time\"}]},{\"type\":\"tr\",\"components\":[{\"type\":\"td\",\"slot\":\"Sample value 1\"}]}]}]},{\"type\":\"article\",\"components\":[{\"type\":\"p\",\"slot\":\"The remainder of the pdfmaker example deals with formatting. For an HTML report, this is done via CSS which can easily reproduce everything pdfmaker can do and a lot more.\"}]}]}]}]",
"vt": "json"
},
{
@@ -160,25 +133,12 @@
"onceDelay": 0.1,
"topic": "tables",
"x": 160,
- "y": 200,
- "wires": [
- [
- "bf4b37633885a3a1"
- ]
- ],
- "info": "This example sends a bunch of TABLEs to\r\nthe front-end.\r\n\r\nIt is taken from the [pdfmake]() tables example\r\nand adapted for uibuilder.\r\n\r\nIt demonstrates how complex reports can be created\r\nas easily in uibuilder as in pdfmake. With the \r\ndifference being that uibuilder outputs vanilla\r\nHTML which can be copied/pasted into many other tools.\r\nOr printed to PDF or a printer."
- },
- {
- "id": "bf4b37633885a3a1",
- "type": "junction",
- "z": "ff9704678e3a4b61",
- "g": "56af672c3ae066b8",
- "x": 270,
"y": 220,
"wires": [
[
"bdd00bc65ececfb2"
]
- ]
+ ],
+ "info": "This example sends a bunch of TABLEs to\r\nthe front-end.\r\n\r\nIt is taken from the [pdfmake]() tables example\r\nand adapted for uibuilder.\r\n\r\nIt demonstrates how complex reports can be created\r\nas easily in uibuilder as in pdfmake. With the \r\ndifference being that uibuilder outputs vanilla\r\nHTML which can be copied/pasted into many other tools.\r\nOr printed to PDF or a printer.\r\n\r\nThough the input uses Markdown (which requires an\r\nexternal library to render), UIBUILDER automatically\r\nloads the library for you! All defined in the low-code\r\nJSON in this inject node."
}
]
diff --git a/examples/Simple Flow.json b/examples/Simple Flow.json
index 167cbd39..fa424628 100644
--- a/examples/Simple Flow.json
+++ b/examples/Simple Flow.json
@@ -3,7 +3,7 @@
"id": "2d368cb9c96bdff7",
"type": "group",
"z": "ff9704678e3a4b61",
- "name": "uibuilder \"simple\" example",
+ "name": "UIBUILDER \"simple\" example (Updated: 2024-067-01)",
"style": {
"fill": "#ffffff",
"fill-opacity": "0.21",
@@ -12,68 +12,30 @@
},
"nodes": [
"116a78405410289c",
- "8ecb7d35e9edab87",
- "312d0d91e2a9b622",
- "a9a02535264dd9ed",
"fa042ed296e9de80",
"bb3b15c09b085da2",
"7845085b17f1686b",
"054d34fec0aa457b",
"cef55ea2491d8024",
- "3e4c1bb1d26f20ce"
+ "3e4c1bb1d26f20ce",
+ "5a5653fbb5559e32"
],
"x": 54,
"y": 339,
"w": 972,
- "h": 202
+ "h": 388
},
{
"id": "116a78405410289c",
"type": "comment",
"z": "ff9704678e3a4b61",
"g": "2d368cb9c96bdff7",
- "name": "Quote of the Day example. (Read me)",
- "info": "This flow gets a \"quote of the day\" from the Internet and passes it\nto uibuilder. It caches the result so that if you reload the page,\nyou get the last result back. The quote is updated every 30 minutes\nduring the day and evening.\n\n\"Simple\" refers to the front-end code. While the flow looks a little\ncomplex, it really isn't. On the Node-RED server side: A trigger, an Internet request,\nand uibuilder. On the client browser side: A well-formatted HTML blockquote, a simple\nfunction that updates the UI and a function that listens to messages from Node-RED,\nand finally some really simple CSS for formatting.\n\n## Configuration\n\nUpdate the files:\n\n* `index.html`\n* `index.js`\n* `index.css`\n\nAccording to the example(s) in the 3 other comment nodes in this example.\n\nPress the button on the trigger to start the flow.",
- "x": 230,
+ "name": "Quote of the Day example. (READ ME FIRST!)",
+ "info": "This flow gets a \"quote of the day\" from the Internet and passes it\nto uibuilder. It caches the result so that if you reload the page,\nyou get the last result back. The quote is updated every 30 minutes\nduring the day and evening.\n\n\"Simple\" refers to the front-end code. While the flow looks a little\ncomplex, it really isn't. On the Node-RED server side: A trigger, an Internet request,\nand uibuilder. On the client browser side: A well-formatted HTML blockquote, a simple\nfunction that updates the UI and a function that listens to messages from Node-RED,\nand finally some really simple CSS for formatting.\n\n## Configuration\n\n1. Give the uibuilder node a url name.\n2. Deploy.\n3. Change the selected uibuilder node in the uib-save node.\n4. Deploy again.\n5. Update the front-end code files by running the sub-group. This updates index.(html|js|css).\n6. Deploy again.\n\nPress the button on the trigger named \"getQuote\" to start the flow.",
+ "x": 260,
"y": 380,
"wires": []
},
- {
- "id": "8ecb7d35e9edab87",
- "type": "comment",
- "z": "ff9704678e3a4b61",
- "g": "2d368cb9c96bdff7",
- "name": "index.html",
- "info": "\n\n\n \n \n\n Quote of the day - Node-RED uibuilder\n \n \n\n \n \n\n \n \n \n \n\n\n \n
uibuilder example: Get and view a Quote of the Day
\n
Using the uibuilder IIFE library. No framework used.
\n \n\n \n\n
\n No quote received yet.\n \n
\n \n\n",
- "x": 640,
- "y": 380,
- "wires": [],
- "icon": "node-red/parser-html.svg"
- },
- {
- "id": "312d0d91e2a9b622",
- "type": "comment",
- "z": "ff9704678e3a4b61",
- "g": "2d368cb9c96bdff7",
- "name": "index.js",
- "info": "// @ts-nocheck\n\n/** Example of using the IIFE build of the uibuilder client library\n * Note that uibuilder.start() should no longer be needed.\n * See the documentation if the client doesn't start on its own.\n */\n'use strict'\n\n// Update the quote\nfunction updQuote(quote) {\n // Update the quote\n $('#quoteText').innerText = quote.body\n // and the author\n $('#quoteAuthor').innerText = quote.author\n // And the overall quote citation\n $('#quote').setAttribute('cite', quote.url) \n}\n\n// Listen for incoming messages from Node-RED\nuibuilder.onChange('msg', function(msg) {\n // Make sure we only process quotes and ignore other messages.\n // Generally sensible to filter like this.\n if (msg.topic === 'getQuote') updQuote(msg.payload.quote)\n})\n",
- "x": 770,
- "y": 380,
- "wires": [],
- "icon": "font-awesome/fa-code"
- },
- {
- "id": "a9a02535264dd9ed",
- "type": "comment",
- "z": "ff9704678e3a4b61",
- "g": "2d368cb9c96bdff7",
- "name": "index.css",
- "info": "/* Load defaults from `/node_modules/node-red-contrib-uibuilder/front-end/uib-brand.css`\n * This version auto-adjusts for light/dark browser settings but might not be as complete.\n */\n@import url(\"./uib-brand.css\");\n\n/* OR, load the defaults from the older `/node_modules/node-red-contrib-uibuilder/front-end/uib-styles.css` */\n/* @import url(\"./uib-styles.css\"); */\n\n/* Formate the quote of the day */\n#quote {\n background-color: beige;\n color: black;\n padding: 1rem;\n}\n\n#quoteText {\n font-style:italic;\n font-size: larger;\n}\n\n#quoteAuthor {\n display:block;\n font-style:normal\n}\n",
- "x": 900,
- "y": 380,
- "wires": [],
- "icon": "node-red/hash.svg"
- },
{
"id": "fa042ed296e9de80",
"type": "debug",
@@ -98,6 +60,7 @@
"name": "Simple Example",
"topic": "",
"url": "uib-simple-example",
+ "okToGo": true,
"fwdInMessages": false,
"allowScripts": false,
"allowStyles": false,
@@ -109,7 +72,6 @@
"sourceFolder": "src",
"deployedVersion": "6.1.0-beta",
"showMsgUib": true,
- "credentials": {},
"x": 630,
"y": 440,
"wires": [
@@ -166,6 +128,7 @@
"id": "cef55ea2491d8024",
"type": "debug",
"z": "ff9704678e3a4b61",
+ "d": true,
"g": "2d368cb9c96bdff7",
"name": "qotd-debug",
"active": false,
@@ -202,5 +165,219 @@
"cef55ea2491d8024"
]
]
+ },
+ {
+ "id": "5a5653fbb5559e32",
+ "type": "group",
+ "z": "ff9704678e3a4b61",
+ "g": "2d368cb9c96bdff7",
+ "name": "Run this to update the front-end code files (after setting the uibuilder url name)",
+ "style": {
+ "fill": "#ffffff",
+ "fill-opacity": "0.31",
+ "label": true,
+ "color": "#000000"
+ },
+ "nodes": [
+ "55abd225ad81d743",
+ "490d3439c60c1b7a",
+ "c1d742d7201e9392",
+ "73dc5646a81ea558",
+ "46757650f470b5bd",
+ "85c41b050ea3a3de",
+ "e26eb52f5db65966",
+ "d7be075fdea36b47"
+ ],
+ "x": 84,
+ "y": 539,
+ "w": 652,
+ "h": 162
+ },
+ {
+ "id": "55abd225ad81d743",
+ "type": "inject",
+ "z": "ff9704678e3a4b61",
+ "g": "5a5653fbb5559e32",
+ "name": "",
+ "props": [],
+ "repeat": "",
+ "crontab": "",
+ "once": false,
+ "onceDelay": 0.1,
+ "topic": "",
+ "x": 145,
+ "y": 620,
+ "wires": [
+ [
+ "73dc5646a81ea558",
+ "46757650f470b5bd",
+ "e26eb52f5db65966"
+ ]
+ ],
+ "l": false
+ },
+ {
+ "id": "490d3439c60c1b7a",
+ "type": "template",
+ "z": "ff9704678e3a4b61",
+ "g": "5a5653fbb5559e32",
+ "name": "",
+ "field": "payload",
+ "fieldType": "msg",
+ "format": "html",
+ "syntax": "mustache",
+ "template": "\n\n \n \n\n Quote of the day - Node-RED UIBUILDER\n \n \n\n \n \n\n \n \n \n \n\n
UIBUILDER example: Get and view a Quote of the Day
\n
Using the UIBUILDER IIFE library. No framework used.