Here is a complete flow that tests out the front-end router introduced in v6.7. It mostly uses front-end code but it uses the recently introduced uib-save
node to provide a small flow that sets up all of the front-end files.
Instructions:
- Import the flow
- Set the uibuilder node url
- deploy
- Change the selection of uibuilder instance in the
uib-save
node - you might have to close and re-open to get the actual list - I've already noted that as a bug for a future fix. - Re-deploy.
- Open the page and play with the routes.
Expect this to appear as a new example in the library in the next release.
[{"id":"858a25ec633e15aa","type":"tab","label":"router test","disabled":false,"info":"","env":[]},{"id":"f21209a191164954","type":"group","z":"858a25ec633e15aa","name":"Base setup","style":{"fill":"#ffffbf","fill-opacity":"0.12","label":true,"color":"#000000"},"nodes":["91d0333d46f8e7f4","980cfa1b7c1b3a5f","6cc5ccb9c91f801d","13bb356399138c24","59925a9e660a9e95","43e63c95d634a048","eb268d3e884b4b1c","09151d9dbab7fce8","e610deb0d3b69094"],"x":54,"y":119,"w":852,"h":162},{"id":"877f1fc905614aab","type":"group","z":"858a25ec633e15aa","name":"Front-end Code - Run after the base node has been deployed. Sets up FE code and routes. \\n ","style":{"label":true,"stroke":"#a4a4a4","fill-opacity":"0.33","color":"#000000","fill":"#ffffff"},"nodes":["8fe9cca36746933e","b90be49846f3f6ba","48f120153d22462f","95521b5e3f43c779","458a4019ee805475","0b263d2d25010d6d","8af6244dc82f99f2","631bd40ef94fd12c","d975894b5c20ed71","5d7e4ff16bfbb57f"],"x":54,"y":323,"w":852,"h":218},{"id":"e610deb0d3b69094","type":"junction","z":"858a25ec633e15aa","g":"f21209a191164954","x":440,"y":160,"wires":[["980cfa1b7c1b3a5f"]]},{"id":"91d0333d46f8e7f4","type":"debug","z":"858a25ec633e15aa","g":"f21209a191164954","name":"uib Std Output","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"","statusType":"counter","x":755,"y":160,"wires":[],"l":false},{"id":"980cfa1b7c1b3a5f","type":"uibuilder","z":"858a25ec633e15aa","g":"f21209a191164954","name":"","topic":"","url":"uib-router-eg","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"templateFolder":"blank","extTemplate":"","showfolder":false,"reload":true,"sourceFolder":"src","deployedVersion":"6.6.0","showMsgUib":true,"title":"","descr":"","x":600,"y":200,"wires":[["91d0333d46f8e7f4"],["6cc5ccb9c91f801d","eb268d3e884b4b1c"]]},{"id":"6cc5ccb9c91f801d","type":"debug","z":"858a25ec633e15aa","g":"f21209a191164954","name":"uib Control Output","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"","statusType":"counter","x":845,"y":220,"wires":[],"l":false},{"id":"13bb356399138c24","type":"link in","z":"858a25ec633e15aa","g":"f21209a191164954","name":"uib-upd-egs - no cache","links":["226aef3812fcb00b"],"x":315,"y":160,"wires":[["e610deb0d3b69094"]]},{"id":"59925a9e660a9e95","type":"link in","z":"858a25ec633e15aa","g":"f21209a191164954","name":"uib-upd-egs - cached","links":["eb268d3e884b4b1c"],"x":225,"y":240,"wires":[["43e63c95d634a048"]]},{"id":"43e63c95d634a048","type":"uib-cache","z":"858a25ec633e15aa","g":"f21209a191164954","cacheall":false,"cacheKey":"topic","newcache":true,"num":1,"storeName":"default","name":"Cache (by topic)","storeContext":"context","varName":"uib_cache","x":360,"y":200,"wires":[["980cfa1b7c1b3a5f"]]},{"id":"eb268d3e884b4b1c","type":"link out","z":"858a25ec633e15aa","g":"f21209a191164954","name":"link out 68","mode":"link","links":["59925a9e660a9e95"],"x":755,"y":240,"wires":[]},{"id":"09151d9dbab7fce8","type":"inject","z":"858a25ec633e15aa","g":"f21209a191164954","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":170,"y":200,"wires":[["43e63c95d634a048"]]},{"id":"8fe9cca36746933e","type":"inject","z":"858a25ec633e15aa","g":"877f1fc905614aab","name":"","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"setup all FE files","x":115,"y":380,"wires":[["8af6244dc82f99f2","631bd40ef94fd12c","d975894b5c20ed71","5d7e4ff16bfbb57f"]],"l":false},{"id":"b90be49846f3f6ba","type":"template","z":"858a25ec633e15aa","g":"877f1fc905614aab","name":"index.html","field":"payload","fieldType":"msg","format":"html","syntax":"plain","template":"<!doctype html>\n<html lang=\"en\">\n\n<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\n <!-- #region Supporting Scripts. These MUST be in the right order. Note no leading / -->\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\">\n /* OPTIONAL: Put your custom code in that */\n </script>\n <!-- #endregion -->\n\n <template id=\"route01\">\n <h2>This comes from an internal <code class=\"r01style\"><template></code> tag</h2>\n <div>\n Route 1\n </div>\n <script>\n console.log('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><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><template></code> tag</h2>\n <div class=\"extraclass\">\n Route 6\n </div>\n </template>\n\n</head>\n\n<body class=\"uib\">\n <script>\n console.log('a script at the start of body')\n </script>\n\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.</div>\n\n <div id=\"more\">\n <!-- '#more' is used as a parent for dynamic HTML content in examples -->\n </div>\n\n <ul id=\"routemenu\">Route Menu\n <!-- <li><a href=\"#route01\" onclick=\"router.doRoute(event)\">#1</a></li> -->\n <li><a href=\"#route01\">#1 (Internal template)</a></li>\n <li><a href=\"#route02\">#2 (Internal template)</a></li>\n <li><a href=\"#route03?doh=rei\">#3 (External template)</a></li>\n <li><a href=\"#route04\">#4 (fails as the external route template doesn't exist)</a></li>\n <li><a href=\"#route05\">#5 (External template)</a></li>\n <li><a href=\"#route06\">#6 (Internal template)</a></li>\n </ul>\n <div href=\"#route01\" onclick=\"router.doRoute(event)\">#1 via event handler</div>\n\n</body>\n\n</html>","output":"str","x":550,"y":380,"wires":[["48f120153d22462f"]]},{"id":"48f120153d22462f","type":"uib-save","z":"858a25ec633e15aa","g":"877f1fc905614aab","url":"uib-router-eg","uibId":"980cfa1b7c1b3a5f","folder":"src","fname":"","createFolder":true,"reload":true,"usePageName":false,"encoding":"utf8","mode":438,"name":"","topic":"","x":810,"y":380,"wires":[]},{"id":"95521b5e3f43c779","type":"template","z":"858a25ec633e15aa","g":"877f1fc905614aab","name":"index.js","field":"payload","fieldType":"msg","format":"javascript","syntax":"plain","template":"/*globals UibRouter, uibuilder */\n\nconst routerConfig = {\n // Router templates created inside the routeContainer, specify an CSS selector\n // If not provided, default div with ID uibroutecontainer is added as the last element of the body\n routeContainer: '#routecontainer',\n\n // Optionally, chose a default route id to be displayed on load\n defaultRoute: 'route03',\n\n hide: true,\n // unload: 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 {id: 'route01', src: '#route01'},\n {id: 'route02', src: '#route02'},\n {id: 'route03', src: './fe-routes/route03.html', type: 'url'},\n // Doesn't exist. Tests load error\n {id: 'route04', src: './fe-routes/dummy.html', type: 'url'},\n ],\n}\n// @ts-ignore\nconst router = new UibRouter(routerConfig)\n\n// Example of dynamically adding additional routes\nconst extraRoutes = [\n { id: 'route05', src: './fe-routes/route05.html', type: 'url' },\n { id: 'route06', src: '#route06' },\n]\nrouter.addRoutes(extraRoutes)\n\n// @ts-ignore - Optionally send a msg back to Node-RED when the route changes\nuibuilder.watchUrlHash()\n\n// Example of changing route from code (after 5 seconds):\nsetTimeout(() => {\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 */\n//\n// uibuilder.onChange('uibrouter', uibrouter => {\n// if (uibrouter === 'loaded') {\n// // Do stuff\n// }\n// })\n/** Alternatively, use this custom event handler (no dependency on uibuilder) */\n// document.addEventListener('uibrouter:loaded', () => {\n// console.log('The router is fully loaded - including all external templates')\n// // Do stuff\n// })\n","output":"str","x":560,"y":420,"wires":[["48f120153d22462f"]]},{"id":"458a4019ee805475","type":"template","z":"858a25ec633e15aa","g":"877f1fc905614aab","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 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 in a route')\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</div>\n<div class=\"coolclass\">\n Temperature: <span id=\"mytemp\">...</span>℃<br>\n Humidity: <span id=\"myhumid\">...</span>%\n</div>\n","output":"str","x":550,"y":460,"wires":[["48f120153d22462f"]]},{"id":"0b263d2d25010d6d","type":"template","z":"858a25ec633e15aa","g":"877f1fc905614aab","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":550,"y":500,"wires":[["48f120153d22462f"]]},{"id":"8af6244dc82f99f2","type":"change","z":"858a25ec633e15aa","g":"877f1fc905614aab","name":"index.html","rules":[{"t":"set","p":"fname","pt":"msg","to":"index.html","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":380,"wires":[["b90be49846f3f6ba"]]},{"id":"631bd40ef94fd12c","type":"change","z":"858a25ec633e15aa","g":"877f1fc905614aab","name":"index.js","rules":[{"t":"set","p":"fname","pt":"msg","to":"index.js","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":300,"y":420,"wires":[["95521b5e3f43c779"]]},{"id":"d975894b5c20ed71","type":"change","z":"858a25ec633e15aa","g":"877f1fc905614aab","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":340,"y":460,"wires":[["458a4019ee805475"]]},{"id":"5d7e4ff16bfbb57f","type":"change","z":"858a25ec633e15aa","g":"877f1fc905614aab","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":340,"y":500,"wires":[["0b263d2d25010d6d"]]}]