UNPKG

node-red-contrib-uibuilder

Version:

Easily create data-driven web UI's for Node-RED. Single- & Multi-page. Multiple UI's. Work with existing web development workflows or mix and match with no-code/low-code features.

1,375 lines 51.7 kB
[ { "id": "7a8938150991c681", "type": "tab", "label": "FE SPA/Router Test", "disabled": false, "info": "", "env": [] }, { "id": "640ceb641391f9cc", "type": "group", "z": "7a8938150991c681", "name": "Base setup", "style": { "fill": "#ffffbf", "fill-opacity": "0.12", "label": true, "color": "#000000" }, "nodes": [ "e4cdac0e218a5e1e", "3e65e04e8f3565e3", "8bc0962b2dba2520", "3546ce64892a333a", "0e11e8e769a9219a", "8e2f1374a9a76d76", "bbb0e25cef901c1c", "22ccc24e5166142a", "63da533e2908bf83", "98e4278794cb35ef", "5c473c47668f0ce0" ], "x": 34, "y": 119, "w": 922, "h": 182 }, { "id": "c3a232d06aa06594", "type": "group", "z": "7a8938150991c681", "name": "Front-end Code - Run after the base node has been deployed. Sets up FE code and routes. \\n See index.js for more example code and why one route deliberately errors.", "style": { "label": true, "stroke": "#a4a4a4", "fill-opacity": "0.33", "color": "#000000", "fill": "#ffffff" }, "nodes": [ "5869b4d24b2c0911", "90b798197324da6a", "46417436f605c0c1", "c1b0b0183d20aaf9", "927ba7dbd20d5964", "2199d9454364159f", "a56111d557982b5b", "85854171f0f3dcbf", "577186271aeb2259", "3e135abd73d70a3f", "d9ed791379c57b6e", "6e38486309e2b5f9", "2eab563ede910399", "50479c9c9196e354", "f641946a7fadb7ec", "f4d6aa5a43904b61", "cd7e2260acfb2837", "b69447560b4922e3", "befedea2cb12ae53", "daa0f17f58f3ff3b", "9569263e1438c8a3", "6b763b023881184b", "6d37f292df6114b7" ], "x": 34, "y": 323, "w": 802, "h": 538 }, { "id": "9a2093133d807e0e", "type": "group", "z": "7a8938150991c681", "name": "Dynamically add a new route template & route config (NB: Config update currently only possible in front-end code, see index.js) \\n Note 2 separate msgs because 2nd needs to use the msg.payload. Each must have a separate topic and the msg._ui must be deleted for the 2nd msg.", "style": { "fill": "#e3f3d3", "fill-opacity": "0.21", "label": true, "color": "#000000" }, "nodes": [ "8864796a3917418e", "5f3d38f35edbf22f", "222db5ecca3f3715", "b308646d2c1a0c5c", "f72f5d130732030e" ], "x": 34, "y": 943, "w": 952, "h": 138 }, { "id": "e86a98882ea76964", "type": "group", "z": "7a8938150991c681", "name": "Example of changing route from Node-RED using UIBUILDER remote navigation command", "style": { "fill": "#ffffff", "fill-opacity": "0.23", "label": true, "color": "#000000" }, "nodes": [ "3b39bf87cb0e7184", "cc4dd43f7629a00f" ], "x": 34, "y": 1119, "w": 652, "h": 82 }, { "id": "6e612779617645b9", "type": "group", "z": "7a8938150991c681", "name": "Example of using a `uib-topic` attribute in HTML and sending dynamic changes from Node-RED", "style": { "fill": "#ffefbf", "fill-opacity": "0.21", "label": true, "color": "#000000" }, "nodes": [ "cc214421ca8412c3", "1fa0ac9f91b8c662" ], "x": 34, "y": 1219, "w": 652, "h": 82 }, { "id": "a2da759a22a95865", "type": "group", "z": "7a8938150991c681", "name": "While you CAN load libraries from Node-RED, it isn't recommended as it may not be loaded soon enough", "style": { "fill": "#ff0000", "fill-opacity": "0.17", "label": true, "color": "#000000" }, "nodes": [ "2bfaf3b2a46e3887", "8860abb566c62ba7", "2053864ec810956e" ], "x": 34, "y": 1359, "w": 660, "h": 122 }, { "id": "8cac0890a5253216", "type": "group", "z": "7a8938150991c681", "name": "Page controls", "style": { "fill": "#d1d1d1", "fill-opacity": "0.4", "label": true }, "nodes": [ "4729218738877835", "f3e097ba5e4f1ef8", "2088df1aa0646328", "e98b72181d4d4dd2", "1514154742f4d159", "894d5cb803a3d468", "34cf6a2788658ef9" ], "x": 874, "y": 319, "w": 432, "h": 262 }, { "id": "22ccc24e5166142a", "type": "junction", "z": "7a8938150991c681", "g": "640ceb641391f9cc", "x": 420, "y": 160, "wires": [ [ "3e65e04e8f3565e3" ] ] }, { "id": "f4d6aa5a43904b61", "type": "junction", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "x": 680, "y": 820, "wires": [ [ "3e135abd73d70a3f" ] ] }, { "id": "cd7e2260acfb2837", "type": "junction", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "x": 640, "y": 500, "wires": [ [ "3e135abd73d70a3f" ] ] }, { "id": "b69447560b4922e3", "type": "junction", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "x": 640, "y": 380, "wires": [ [ "3e135abd73d70a3f" ] ] }, { "id": "9569263e1438c8a3", "type": "junction", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "x": 660, "y": 680, "wires": [ [ "3e135abd73d70a3f" ] ] }, { "id": "e4cdac0e218a5e1e", "type": "debug", "z": "7a8938150991c681", "g": "640ceb641391f9cc", "name": "standard output", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "counter", "x": 800, "y": 160, "wires": [] }, { "id": "3e65e04e8f3565e3", "type": "uibuilder", "z": "7a8938150991c681", "g": "640ceb641391f9cc", "name": "", "topic": "", "url": "spa", "okToGo": true, "fwdInMessages": false, "allowScripts": false, "allowStyles": false, "copyIndex": true, "templateFolder": "blank", "extTemplate": "", "showfolder": false, "reload": true, "sourceFolder": "src", "deployedVersion": "7.5.0", "showMsgUib": false, "title": "", "descr": "", "editurl": "vscode://file/src/uibRoot/spa/?windowId=_blank", "x": 570, "y": 200, "wires": [ [ "e4cdac0e218a5e1e" ], [ "63da533e2908bf83" ] ] }, { "id": "8bc0962b2dba2520", "type": "link in", "z": "7a8938150991c681", "g": "640ceb641391f9cc", "name": "uib-upd-egs - no cache", "links": [ "cc4dd43f7629a00f", "cc214421ca8412c3" ], "x": 295, "y": 160, "wires": [ [ "22ccc24e5166142a" ] ] }, { "id": "3546ce64892a333a", "type": "link in", "z": "7a8938150991c681", "g": "640ceb641391f9cc", "name": "uib-upd-egs - cached", "links": [ "8e2f1374a9a76d76", "e98b72181d4d4dd2", "2bfaf3b2a46e3887", "b308646d2c1a0c5c" ], "x": 205, "y": 240, "wires": [ [ "0e11e8e769a9219a" ] ] }, { "id": "0e11e8e769a9219a", "type": "uib-cache", "z": "7a8938150991c681", "g": "640ceb641391f9cc", "cacheall": false, "cacheKey": "topic", "newcache": true, "num": 1, "storeName": "default", "name": "Cache (by topic)", "storeContext": "context", "varName": "uib_cache", "x": 340, "y": 200, "wires": [ [ "3e65e04e8f3565e3" ] ] }, { "id": "8e2f1374a9a76d76", "type": "link out", "z": "7a8938150991c681", "g": "640ceb641391f9cc", "name": "link out 68", "mode": "link", "links": [ "3546ce64892a333a" ], "x": 765, "y": 220, "wires": [] }, { "id": "bbb0e25cef901c1c", "type": "inject", "z": "7a8938150991c681", "g": "640ceb641391f9cc", "name": "Clear Cache", "props": [ { "p": "uibuilderCtrl", "v": "clear cache", "vt": "str" }, { "p": "cacheControl", "v": "CLEAR", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 150, "y": 200, "wires": [ [ "0e11e8e769a9219a" ] ] }, { "id": "5869b4d24b2c0911", "type": "inject", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "", "props": [ { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "setup all FE files", "x": 95, "y": 380, "wires": [ [ "2199d9454364159f", "a56111d557982b5b", "85854171f0f3dcbf", "577186271aeb2259", "d9ed791379c57b6e", "befedea2cb12ae53", "6b763b023881184b" ] ], "l": false }, { "id": "90b798197324da6a", "type": "template", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "index.html", "field": "payload", "fieldType": "msg", "format": "html", "syntax": "plain", "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 <title>FE Router - Node-RED uibuilder</title>\n <meta name=\"description\" content=\"Node-RED uibuilder - FE Router\">\n\n <!-- Your own CSS (defaults to loading uibuilders css)-->\n <link type=\"text/css\" rel=\"stylesheet\" href=\"./index.css\" media=\"all\">\n <!-- Optional CSS for Markdown code highlighting -->\n <link type=\"text/css\" rel=\"stylesheet\"\n href=\"https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@latest/build/styles/hybrid.min.css\">\n \n <!-- #region Supporting Scripts. These MUST be in the right order. Note no leading / -->\n <!-- Optional library for highlighting Markdown code blocks -->\n <script defer src=\"https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@latest/build/highlight.min.js\"></script>\n <!-- Optional Markdown-IT library - needed for Markdown content routes -->\n <script defer src=\"https://cdn.jsdelivr.net/npm/markdown-it@latest/dist/markdown-it.min.js\"></script>\n <script defer src=\"../uibuilder/uibuilder.iife.min.js\"></script>\n <script defer src=\"../uibuilder/utils/uibrouter.iife.min.js\"></script>\n <script defer src=\"./index.js\"></script>\n <!-- #endregion -->\n\n <template id=\"route01\">\n <h2>This comes from an internal <code class=\"r01style\">&lt;template></code> tag</h2>\n <div>\n Route 1\n </div>\n <script>\n console.info('I was produced by a script in Route 1')\n </script>\n <style>\n .r01style {\n background-color: yellow;\n color: blue;\n font-weight: 900;\n }\n </style>\n </template>\n <template id=\"route02\">\n <h2>This also comes from an internal <code>&lt;template></code> tag</h2>\n <div class=\"extraclass\">\n Route 2\n </div>\n </template>\n <template id=\"route06\">\n <h2>This also comes from an internal <code>&lt;template></code> tag</h2>\n <div class=\"extraclass\">\n Route 6\n </div>\n </template>\n <!-- NB: Markdown cannot be defined in a template, must be external -->\n\n</head><body>\n <script>\n console.info('This log is produced by script at the start of body, just an example to show the order of things.')\n </script>\n\n <header>\n <h1 class=\"with-subtitle\">An example of a framework-less front-end router</h1>\n <div role=\"doc-subtitle\">Using the UIBUILDER IIFE library for Node-RED.</div>\n\n <div id=\"menu1\"><!-- Used by the router's auto-menu --></div>\n\n <!-- Using internal router variables to show the current route's title and id -->\n <div style=\"background-color: dimgrey;\">\n <h2 class=\"with-subtitle\">Current Route Title: <uib-var variable=\"uibrouter_CurrentTitle\"></uib-var></h2>\n <div role=\"doc-subtitle\"><uib-var variable=\"uibrouter_CurrentDescription\"></uib-var></div>\n </div>\n </header>\n\n <main>\n <!-- We can manually load a route using this if you want to\n But you may prefer a menu (see config and menu1 id in header) -->\n <!-- <div href=\"#route01\" onclick=\"router.doRoute(event)\" class=\"border\" style=\"cursor: pointer;\">Goto route #1 via click event handler</div> -->\n\n <div id=\"more\"><!-- '#more' is used as a parent for dynamic HTML content in examples --></div>\n \n <div id=\"uibroutecontainer\"><!-- router content will appear here --></div>\n </main>\n\n <section id=\"common-content\"><!-- from external content --></section>\n\n <footer uib-topic=\"footer\" style=\"border: 1px dashed silver;\">\n <!-- Set dynamically from Node-RED -->\n Pre-defined footer\n </footer>\n\n</body></html>", "output": "str", "x": 530, "y": 380, "wires": [ [ "b69447560b4922e3" ] ] }, { "id": "46417436f605c0c1", "type": "template", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "index.js", "field": "payload", "fieldType": "msg", "format": "javascript", "syntax": "plain", "template": "// @ts-nocheck\n/*globals UibRouter, uibuilder */\n\nconst routerConfig = {\n // Router templates created inside the routeContainer, specify an CSS selector\n // If not provided, defaults to '#uibdefaultroutecontainer' which is added as the last element of the body.\n // If provided but does not exist, will error and the router won't work\n routeContainer: '#uibroutecontainer',\n\n // Optionally, chose a default route id to be displayed on load\n // If not given, the first defined route is used.\n defaultRoute: 'route03',\n\n // Optional, default=false. False unloads route content when going to a new route.\n // Set to true to hide/unhide rather than unload/load the actual route elements.\n // When false:\n // - Scripts in the templates execute every visit to the route.\n // - Template content completely removed from browser when leaving the route.\n // - Ensures latest template content is loaded on each visit.\n // When true, scripts only run the first time. Content is retained & template not updated\n hide: false,\n\n // Optional, default=false. False loads external templates on first use.\n // Set to `true` to pre-load all external templates\n // templateLoadAll: true,\n\n // Optional, default=true. If true, templates are unloaded after use.\n // Only set to false if your network is slow to load the templates.\n // templateUnload: true,\n\n // Define the possible routes type=url for externals\n // Can be an object or an array but each entry must be an object containing {id,src,type}\n // type can be anything but only `url` will be treated as an external template file.\n // src is either a CSS selector for a <template> or a URL of an HTML file.\n // id must match the href=\"#routeid\" in any menu/link. and `<template id=\"routeid\">` on any loaded template\n // must be unique on the page\n routes: [\n {\n id: 'route01', src: '#route01',\n title: 'Route 1', description: 'My first route',\n }, {\n id: 'route02', src: '#route02',\n title: 'Route 2', description: 'My second route',\n }, {\n id: 'route03', src: './fe-routes/route03.html', type: 'url',\n title: 'Route 3', description: 'My third route',\n },\n // Doesn't exist. Tests load error\n {id: 'route04', src: './fe-routes/dummy.html', type: 'url'},\n // Testing Markdown templates - Markdown-IT Library must be loaded\n {\n id: 'route-md-01', src: './fe-routes/route-md-01.md', type: 'url',\n title: 'Markdown Route 1', format: 'markdown',\n description: 'A route defined using markdown #1',\n }, {\n id: 'route-md-02', src: './fe-routes/route-md-02.md', type: 'url',\n title: 'Markdown Route 2', format: 'markdown',\n description: 'A route defined using markdown #2',\n },\n ],\n\n // Auto-navigation menu - create an element menu1 to act as parent\n routeMenus: [\n {\n id: 'menu1',\n menuType: 'horizontal',\n label: 'Main Menu',\n },\n ],\n\n // OPTIONAL. If present, external html is loaded direct to DOM\n // Templates that fail to load are ignored\n otherLoad: [\n {\n id: 'external-content',\n src: './fe-routes/other-content.html',\n container: '#common-content', // CSS Selector for parent\n }\n ],\n}\nconst router = new UibRouter(routerConfig)\n\nconsole.info('Expect to see errors in the console if trying to access \"route04\" and \"dead-duck2\" as they deliberately do not exist so you can see what happens.')\n\n// Example of dynamically adding additional routes -> must be external or have existing templates\n// Note that this isn't a particularly good idea since an externally loaded route, if pre-selected\n// on page load will not be able to find the route. The default route will be used instead and an\n// error is printed to the console.\n// So if wanting to dynamically add routes, it is best to also use the `defaultRouteOnLoad` option.\nsetTimeout(() => { // run after 2 secs so we can see it happen, you probably wouldn't bother normally\n const extraRoutes = [\n { id: 'route05', src: './fe-routes/route05.html', type: 'url', title: 'Route 5' },\n // Test late loading a non-existant external template\n { id: 'dead-duck2', src: './fe-routes/dummy.html', type: 'url' },\n { id: 'route06', src: '#route06' /* NB: No title specified */ },\n ]\n // note that this also updates the menu\n router.addRoutes(extraRoutes)\n}, 2000)\n\n// Currently no way to dynamically add new routes from Node-RED\n// So we need to do it using a normal uibuilder msg handler\nuibuilder.onTopic('addRoute', (msg) => {\n console.log(`Adding new route from Node-RED. Route id: \"${msg.payload.id}\"`)\n // note that this also updates the menu\n router.addRoutes(msg.payload)\n})\n\n// Load something other than a route - maybe a side menu, ToC or something else\n// router.loadOther({\n// id: 'fred',\n// src: './fe-routes/xxxxx.html',\n// container: '#more',\n// })\n\n// Example of changing route from code (after 5 seconds):\n// setTimeout(() => {\n// router.doRoute('route01')\n// }, 5000)\n\n/** If you need to be certain that all external route templates\n * have loaded before doing something, this is how. */\n// uibuilder.onChange('uibrouter', uibrouter => {\n// if (uibrouter === 'loaded') {\n// // Do stuff\n// }\n// })\n\n// note that, unlike uibuilder.watchUrlHash(), these also report the initial load route as well.\n/** Monitor route changes in code */\n// uibuilder.onChange('uibrouter_CurrentRoute', (routeId) => {\n// console.log(`ROUTE CHANGED. New Route: ${routeId}`)\n// // To get the previous route, use: router.previousRouteId\n// // To get the current route's config, use: router.currentRoute()\n// // To manually notify Node-RED: uibuilder.send({topic: 'route-change', payload: {newRoute: routeId, oldRoute: router.previousRouteId}})\n// uibuilder.send({topic: 'route-change', payload: { newRoute: routeId, oldRoute: router.previousRouteId }})\n// })\n/** Monitor route changes in code and get the new route config */\n// uibuilder.onChange('uibrouter_CurrentDetails', (routeConfig) => {\n// console.log('ROUTE CHANGED. New Route Details: ', routeConfig)\n// // To manually notify Node-RED: uibuilder.send(topic: 'route-change-details', payload: routeConfig)\n// uibuilder.send({ topic: 'route-change', payload: routeConfig })\n// })\n", "output": "str", "x": 540, "y": 420, "wires": [ [ "b69447560b4922e3" ] ] }, { "id": "c1b0b0183d20aaf9", "type": "template", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "route03.html", "field": "payload", "fieldType": "msg", "format": "html", "syntax": "plain", "template": "<style>\n /* Note that these only exist when this route is showing */\n .extraclass {\n color: green;\n border: 2px solid var(--warning);\n padding: 1em;\n }\n .coolclass {\n background-color: var(--surface4);\n border: 2px solid var(--info-intense);\n padding: 1em;\n margin: .5em 0;\n }\n</style>\n<script>\n // ! WARNING: This will be re-run EVERY time the route is shown - use with caution.\n // Here we use a simple global variable to ensure we only ever run once.\n // Also note that deleting a route that has previously displayed will not \n // remove any global values set here including even handlers.\n \n console.log('route03 external: built-in script running ...')\n\n if (!window['mysensor']) window['mysensor'] = {temperature: 'N/A', humdity: 'N/A' }\n else {\n let e = $('#mytemp') // make sure the element exists\n if (e) e.innerText = window['mysensor'].temperature // update if it exists\n e = $('#myhumid') // make sure the element exists\n if (e) e.innerText = window['mysensor'].humidity // update if it exists\n }\n\n // We will wrap this in a check to make sure it only ever runs\n // once per page load.\n if (!window['route3Run']) { // make sure this is unique across routes\n\n // A function to use in <a href=\"#routeid\" onclick=\"doClick1(event)\">\n // function doClick1(event) {\n // console.log('You did a click1 (onclick=\"router.doRoute(event)\")', event)\n // }\n\n // Alternative to using onclick is to add the handler via code\n // const r03d02 = $('#r03d02')\n // if (r03d02) r03d02.addEventListener('click', (event) => {\n // $('#r03d02').addEventListener('click', (event) => {\n // console.log('You did a different click (addEventListener)', event)\n // })\n // or even:\n // r03d02.addEventListener('click', doClick1)\n\n // Example of updating the UI direct from a Node-RED message\n uibuilder.onChange('msg', (msg) => {\n console.log('msg from Node-RED handled inside route03')\n if(msg.topic === 'mysensor') {\n let e = $('#mytemp') // make sure the element exists\n if (e) e.innerText = msg.payload.temperature // update if it exists\n window['mysensor'].temperature = msg.payload.temperature\n e = $('#myhumid') // make sure the element exists\n if (e) e.innerText = msg.payload.humidity // update if it exists\n window['mysensor'].humidity = msg.payload.humidity\n }\n })\n // You could instead use the `uib-update` node to avoid this code.\n }\n // Make sure we only run the above once.\n route3Run = true\n</script>\n\n<h2>This comes from an external file</h2>\n<div id=\"r03d01\" class=\"extraclass\">\n Route 3 part 1\n</div>\n<div id=\"r03d02\" class=\"extraclass\">\n Route 3 part 2\n <button type=\"button\" onclick=\"uibuilder.eventSend(event)\">Msg to Node-RED</button>\n</div>\n<div class=\"coolclass\">\n Temperature: <span id=\"mytemp\">...</span>℃<br>\n Humidity: <span id=\"myhumid\">...</span>%\n</div>\n", "output": "str", "x": 530, "y": 500, "wires": [ [ "cd7e2260acfb2837" ] ] }, { "id": "927ba7dbd20d5964", "type": "template", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "route05.html", "field": "payload", "fieldType": "msg", "format": "html", "syntax": "plain", "template": "<h2>This comes from an external file - <code>./fe-routes/route05.html</code></h2>\n<div id=\"r04d01\" class=\"extraclass\">\n Route 5 part 1\n</div>\n<div id=\"r04d02\" class=\"extraclass\">\n Route 5 part 2\n</div>\n", "output": "str", "x": 530, "y": 540, "wires": [ [ "cd7e2260acfb2837" ] ] }, { "id": "2199d9454364159f", "type": "change", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "index.html", "rules": [ { "t": "set", "p": "fname", "pt": "msg", "to": "index.html", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 290, "y": 380, "wires": [ [ "90b798197324da6a" ] ] }, { "id": "a56111d557982b5b", "type": "change", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "index.js", "rules": [ { "t": "set", "p": "fname", "pt": "msg", "to": "index.js", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 280, "y": 420, "wires": [ [ "46417436f605c0c1" ] ] }, { "id": "85854171f0f3dcbf", "type": "change", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "fe-routes/route03.html", "rules": [ { "t": "set", "p": "fname", "pt": "msg", "to": "fe-routes/route03.html", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 320, "y": 500, "wires": [ [ "c1b0b0183d20aaf9" ] ] }, { "id": "577186271aeb2259", "type": "change", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "fe-routes/route05.html", "rules": [ { "t": "set", "p": "fname", "pt": "msg", "to": "fe-routes/route05.html", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 320, "y": 540, "wires": [ [ "927ba7dbd20d5964" ] ] }, { "id": "8864796a3917418e", "type": "inject", "z": "7a8938150991c681", "g": "9a2093133d807e0e", "name": "", "props": [ { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "setup all FE files", "x": 95, "y": 1000, "wires": [ [ "5f3d38f35edbf22f" ] ], "l": false }, { "id": "5f3d38f35edbf22f", "type": "template", "z": "7a8938150991c681", "g": "9a2093133d807e0e", "name": "route07 template", "field": "payload", "fieldType": "msg", "format": "html", "syntax": "plain", "template": "<div>\n <h2>dynamically loaded from Node-RED</h2>\n <p>\n A template created in Node-RED and sent to\n clients using a uib-tag node to create the template\n via a uib-cache node. So (re)-loaded as needed.\n </p>\n <div id=\"r07d01\" class=\"extraclass\">\n Route 7 part 1\n </div>\n <div id=\"r07d02\" class=\"extraclass\">\n Route 7 part 2\n </div>\n</div>\n\n<script>\n window.jk = () => {\n console.log('hello')\n }\n console.log('This log produced from loading template route07 (which came from Node-RED)')\n</script>", "output": "str", "x": 220, "y": 1000, "wires": [ [ "222db5ecca3f3715" ] ] }, { "id": "222db5ecca3f3715", "type": "uib-tag", "z": "7a8938150991c681", "g": "9a2093133d807e0e", "name": "", "topic": "", "tag": "template", "tagSource": "", "tagSourceType": "str", "parent": "head", "parentSource": "", "parentSourceType": "str", "elementId": "route07", "elementIdSourceType": "str", "position": "last", "positionSourceType": "str", "slotSourceProp": "payload", "slotSourcePropType": "msg", "attribsSource": "", "attribsSourceType": "msg", "slotPropMarkdown": false, "x": 440, "y": 1000, "wires": [ [ "f72f5d130732030e", "b308646d2c1a0c5c" ] ] }, { "id": "b308646d2c1a0c5c", "type": "link out", "z": "7a8938150991c681", "g": "9a2093133d807e0e", "name": "cache", "mode": "link", "links": [ "3546ce64892a333a" ], "x": 910, "y": 1000, "wires": [], "l": true }, { "id": "f72f5d130732030e", "type": "change", "z": "7a8938150991c681", "g": "9a2093133d807e0e", "name": "Add route to route list var", "rules": [ { "t": "delete", "p": "_ui", "pt": "msg" }, { "t": "set", "p": "payload", "pt": "msg", "to": "{\"id\":\"route07\",\"src\":\"#route07\",\"title\":\"Route #7\",\"description\":\"Yet another route, #7\"}", "tot": "json" }, { "t": "set", "p": "topic", "pt": "msg", "to": "addRoute", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 690, "y": 1040, "wires": [ [ "b308646d2c1a0c5c" ] ] }, { "id": "3b39bf87cb0e7184", "type": "inject", "z": "7a8938150991c681", "g": "e86a98882ea76964", "name": "Navigate to #route07 using uibuilder external command", "props": [ { "p": "_uib", "v": "{\"command\":\"navigate\",\"prop\":\"#route07\"}", "vt": "json" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "uib-command-route-change", "x": 280, "y": 1160, "wires": [ [ "cc4dd43f7629a00f" ] ], "info": "Change the \"prop\" value to a CSS Selector.\r\n\r\nThe display will appear as the last child of\r\nthat selected element.\r\n\r\ne.g. `body` or `#more`." }, { "id": "cc4dd43f7629a00f", "type": "link out", "z": "7a8938150991c681", "g": "e86a98882ea76964", "name": "no-cache", "mode": "link", "links": [ "8bc0962b2dba2520" ], "x": 600, "y": 1160, "wires": [], "l": true }, { "id": "3449ec7ba8779fd3", "type": "comment", "z": "7a8938150991c681", "name": "Front-end router library tests and examples. \\n After import, (1) change the URL in uibuilder node then deploy, (2) change the selection in the uib-save node and deploy again, \\n (3) Run the Front-end Code flow. Example is then ready to use.", "info": "", "x": 460, "y": 60, "wires": [] }, { "id": "4729218738877835", "type": "inject", "z": "7a8938150991c681", "g": "8cac0890a5253216", "name": "Toggle Visible Msgs", "props": [ { "p": "_uib", "v": "{\"command\":\"showMsg\", \"pageName\": \"index.html\"}", "vt": "json" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 1010, "y": 360, "wires": [ [ "e98b72181d4d4dd2" ] ] }, { "id": "f3e097ba5e4f1ef8", "type": "inject", "z": "7a8938150991c681", "g": "8cac0890a5253216", "name": "Log Lvl 5", "props": [ { "p": "_uib", "v": "{\"command\":\"set\",\"prop\":\"logLevel\",\"value\":5}", "vt": "json" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "Set-log-level", "x": 1040, "y": 400, "wires": [ [ "e98b72181d4d4dd2" ] ] }, { "id": "2088df1aa0646328", "type": "inject", "z": "7a8938150991c681", "g": "8cac0890a5253216", "name": "Log Lvl 0", "props": [ { "p": "_uib", "v": "{\"command\":\"set\",\"prop\":\"logLevel\",\"value\":0}", "vt": "json" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "Set-log-level", "x": 1040, "y": 440, "wires": [ [ "e98b72181d4d4dd2" ] ] }, { "id": "e98b72181d4d4dd2", "type": "link out", "z": "7a8938150991c681", "g": "8cac0890a5253216", "name": "cached", "mode": "link", "links": [ "3546ce64892a333a" ], "x": 1220, "y": 400, "wires": [], "l": true }, { "id": "aea8f8d0d90b6b31", "type": "comment", "z": "7a8938150991c681", "name": "Example updated: 2025-08-17 \\n (MAJOR UPDATE in v7.5.0)", "info": "", "x": 1020, "y": 60, "wires": [] }, { "id": "3e135abd73d70a3f", "type": "uib-save", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "url": "spa", "uibId": "3e65e04e8f3565e3", "folder": "src", "fname": "", "createFolder": true, "reload": true, "usePageName": false, "encoding": "utf8", "mode": 438, "name": "", "topic": "", "x": 760, "y": 380, "wires": [] }, { "id": "63da533e2908bf83", "type": "switch", "z": "7a8938150991c681", "g": "640ceb641391f9cc", "name": "Route different control msgs", "property": "uibuilderCtrl", "propertyType": "msg", "rules": [ { "t": "regex", "v": "client (dis)?connect", "vt": "str", "case": false }, { "t": "eq", "v": "route change", "vt": "str" }, { "t": "eq", "v": "visibility", "vt": "str" }, { "t": "else" } ], "checkall": "true", "repair": false, "outputs": 4, "x": 705, "y": 240, "wires": [ [ "8e2f1374a9a76d76", "98e4278794cb35ef" ], [ "5c473c47668f0ce0" ], [], [] ], "outputLabels": [ "Network (socket) client connect/disconnect", "uibrouter route changes", "page visibility", "Anything else" ], "l": false }, { "id": "98e4278794cb35ef", "type": "debug", "z": "7a8938150991c681", "d": true, "g": "640ceb641391f9cc", "name": "uib client connect/disconnect Output", "active": true, "tosidebar": true, "console": false, "tostatus": true, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "counter", "x": 815, "y": 220, "wires": [], "l": false }, { "id": "5c473c47668f0ce0", "type": "debug", "z": "7a8938150991c681", "g": "640ceb641391f9cc", "name": "route changes", "active": false, "tosidebar": true, "console": false, "tostatus": true, "complete": "true", "targetType": "full", "statusVal": "", "statusType": "counter", "x": 830, "y": 260, "wires": [] }, { "id": "cc214421ca8412c3", "type": "link out", "z": "7a8938150991c681", "g": "6e612779617645b9", "name": "no-cache", "mode": "link", "links": [ "8bc0962b2dba2520" ], "x": 600, "y": 1260, "wires": [], "l": true }, { "id": "1fa0ac9f91b8c662", "type": "inject", "z": "7a8938150991c681", "g": "6e612779617645b9", "name": "Change Footer HTML & attributes via uib-topic attribute in html", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" }, { "p": "attributes", "v": "{\t \"style\":\"background-color: blueviolet; border: 2px solid hsl(\" & $random()*360 & \" 100% 50%);\"\t}", "vt": "jsonata" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "footer", "payload": "\"<div>This is an HTML footer sent from Node-RED. Random Number: \" & \t$formatInteger($random() * 100, \"0\") &\t\"</div>\"", "payloadType": "jsonata", "x": 300, "y": 1260, "wires": [ [ "cc214421ca8412c3" ] ], "info": "Change the \"prop\" value to a CSS Selector.\r\n\r\nThe display will appear as the last child of\r\nthat selected element.\r\n\r\ne.g. `body` or `#more`." }, { "id": "d9ed791379c57b6e", "type": "change", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "fe-routes/route-md-01.md", "rules": [ { "t": "set", "p": "fname", "pt": "msg", "to": "fe-routes/route-md-01.md", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 330, "y": 680, "wires": [ [ "6e38486309e2b5f9" ] ] }, { "id": "6e38486309e2b5f9", "type": "template", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "route-md-01.md", "field": "payload", "fieldType": "msg", "format": "markdown", "syntax": "plain", "template": "## This is Markdown Route 01\n\nIt is rendered only if the Markdown-IT library is loaded.\n\n1. Num list a\n1. Num list b\n\n> [!TIP]\n> Markdown routes have to be external files because they\n> may contain characters that HTML uses for itself.\n\n### Can we use router variables?\n\nYes, absolutely. We can use\n`<uib-var variable=\"uibrouter_CurrentTitle\"></uib-var>`\nin our Markdown just as we can in HTML.\n\n#### Current route title\n\n<uib-var variable=\"uibrouter_CurrentTitle\"></uib-var>\n\n#### Current route description\n<uib-var variable=\"uibrouter_CurrentDescription\"></uib-var>\n", "output": "str", "x": 540, "y": 680, "wires": [ [ "9569263e1438c8a3" ] ] }, { "id": "8860abb566c62ba7", "type": "inject", "z": "7a8938150991c681", "g": "a2da759a22a95865", "name": "Dynamic load MarkdownIT from CDN", "props": [ { "p": "_ui", "v": "{\"method\":\"load\",\"srcScripts\":[\"https://cdn.jsdelivr.net/npm/markdown-it@13.0.1/dist/markdown-it.min.js\"]}", "vt": "json" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "load-markdownit-cdn", "x": 230, "y": 1400, "wires": [ [ "2bfaf3b2a46e3887" ] ] }, { "id": "2053864ec810956e", "type": "inject", "z": "7a8938150991c681", "g": "a2da759a22a95865", "name": "Remove MarkdownIT", "props": [ { "p": "_ui", "v": "[{\"method\":\"removeAll\",\"components\":[\"head > script[src*='markdown-it']\"]}]", "vt": "json" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "", "x": 280, "y": 1440, "wires": [ [ "2bfaf3b2a46e3887" ] ] }, { "id": "2bfaf3b2a46e3887", "type": "link out", "z": "7a8938150991c681", "g": "a2da759a22a95865", "name": "cached", "mode": "link", "links": [ "3546ce64892a333a" ], "x": 600, "y": 1400, "wires": [], "l": true }, { "id": "2eab563ede910399", "type": "comment", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "Use loadOther to load common content from external files", "info": "", "x": 430, "y": 780, "wires": [] }, { "id": "50479c9c9196e354", "type": "comment", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "External Markdown Templates. Load HighlightJS \\n & Markdown-IT libraries to see them correctly rendered \\n NB: Markdown routes have to be external due to HTML limitations", "info": "", "x": 449.99999237060547, "y": 610.0000152587891, "wires": [] }, { "id": "f641946a7fadb7ec", "type": "comment", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "External Route Templates", "info": "", "x": 330, "y": 460, "wires": [] }, { "id": "1514154742f4d159", "type": "inject", "z": "7a8938150991c681", "g": "8cac0890a5253216", "name": "Light", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "mark-light", "payload": "{\"class\":\"light\"}", "payloadType": "json", "x": 970, "y": 500, "wires": [ [ "894d5cb803a3d468" ] ] }, { "id": "894d5cb803a3d468", "type": "uib-update", "z": "7a8938150991c681", "g": "8cac0890a5253216", "name": "Set Light/Dark mode", "topic": "", "mode": "update", "modeSourceType": "modeType", "cssSelector": "html", "cssSelectorType": "str", "slotSourceProp": "", "slotSourcePropType": "msg", "attribsSource": "payload", "attribsSourceType": "msg", "slotPropMarkdown": false, "x": 1160, "y": 500, "wires": [ [ "e98b72181d4d4dd2" ] ] }, { "id": "34cf6a2788658ef9", "type": "inject", "z": "7a8938150991c681", "g": "8cac0890a5253216", "name": "Dark", "props": [ { "p": "payload" }, { "p": "topic", "vt": "str" } ], "repeat": "", "crontab": "", "once": false, "onceDelay": 0.1, "topic": "mark-dark", "payload": "{\"class\":\"dark\"}", "payloadType": "json", "x": 970, "y": 540, "wires": [ [ "894d5cb803a3d468" ] ] }, { "id": "befedea2cb12ae53", "type": "change", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "fe-routes/route-md-02.md", "rules": [ { "t": "set", "p": "fname", "pt": "msg", "to": "fe-routes/route-md-02.md", "tot": "str" } ], "action": "", "property": "", "from": "", "to": "", "reg": false, "x": 330, "y": 720, "wires": [ [ "daa0f17f58f3ff3b" ] ] }, { "id": "daa0f17f58f3ff3b", "type": "template", "z": "7a8938150991c681", "g": "c3a232d06aa06594", "name": "route-md-02.md", "field": "payload", "fieldType": "msg", "format": "markdown", "syntax": "plain", "template": "## This is Markdown Route 2\n\nRendered by Markdown-IT.\n\n* List 1\n* List 2\n\n```js\n// Some example JavaScript in a code block\nconst myvar = 42\nif (myvar > 40) {\n console.log('Phew! Old')\n}\n```\n\n```html\n<!-- Some example HTML in a code block -->\n<ol class=\"border\">\n <li>List Item #1</li>\n <li>List Item #2</li>\n <li>List Item #3</li>\n</ol>\n```\n\n> Note:\n>\n> The HighlightJS library and CSS must be pre-loaded\n> for the code blocks to render nicely.\n", "output": "str", "x": 540, "y": 720, "wires": [ [ "9569263e1438c8a3"