Flows from "Writing Custom Templates - FlowFuse Dashboard" Tutorial

These flows are those built when following the "Writing Custom Templates - FlowFuse Dashboard" YouTube tutorial, published by FlowFuse.

It guides you on how to build your own widgets for FlowFuse Dashboard (also known as Node-RED Dashboard 2.0).

Dependencies

The following, additional, packages will need to be installed via the Node-RED Palette Manager, or npm, in order for the flows to run.

  • @flowfuse/node-red-dashboard (link)
  • node-red-contrib-web-worldmap (link)
injecthttp requestworldmapdebug 1function 1Raw Web TechIn Built Stuffdebug 2iframeUpdate Locationui-templateFormdebug 3ui-templatechange
Flow 1
[{"id":"51a8e2eaa201f97a","type":"inject","z":"391328aec32f274c","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":80,"wires":[["bb2d08de5dfd6486"]]},{"id":"bb2d08de5dfd6486","type":"http request","z":"391328aec32f274c","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"http://api.open-notify.org/iss-now.json","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":330,"y":100,"wires":[["f15545c51c86ff28","99e772cb2d69f37a"]]},{"id":"136e4b6bee7e277c","type":"worldmap","z":"391328aec32f274c","name":"","lat":"","lon":"","zoom":"","layer":"","cluster":"","maxage":"","usermenu":"show","layers":"show","panit":"false","panlock":"false","zoomlock":"false","hiderightclick":"false","coords":"false","showgrid":"false","showruler":"false","allowFileDrop":"false","path":"/worldmap","overlist":"DR,CO,RA,DN","maplist":"OSMG,OSMC,EsriC,EsriS,UKOS","mapname":"","mapurl":"","mapopt":"","mapwms":false,"x":760,"y":60,"wires":[]},{"id":"f15545c51c86ff28","type":"debug","z":"391328aec32f274c","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":500,"y":100,"wires":[]},{"id":"99e772cb2d69f37a","type":"function","z":"391328aec32f274c","name":"function 1","func":"msg = {\n    payload: {\n        name: \"ISS\",\n        lat: msg.payload.iss_position.latitude,\n        lon: msg.payload.iss_position.longitude,\n        icon: \"iss\"\n    }\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":60,"wires":[["136e4b6bee7e277c","05c44b3a70226a37","2ac7883786d1dac0","076fbd504c3a834f"]]},{"id":"3cff02cbe32e88fb","type":"ui-template","z":"391328aec32f274c","group":"fde680bc13eac73c","page":"","ui":"","name":"Raw Web Tech","order":1,"width":0,"height":0,"head":"","format":"<template>\n    <h1 id=\"my-header\">Hello World</h1>\n</template>\n\n<script>\ndocument.getElementById(\"my-header\").innerHTML = \"Updated Header\"\n</script>\n\n<style>\n    h1 {\n        color: red;\n    }\n</style>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":780,"y":160,"wires":[[]]},{"id":"05c44b3a70226a37","type":"ui-template","z":"391328aec32f274c","group":"36e086084db4d144","page":"","ui":"","name":"In Built Stuff","order":1,"width":0,"height":0,"head":"","format":"<template>\n    <div>\n        {{ msg.payload }}\n    </div>\n    <button onclick=\"onClick()\">Click Me!</button>\n</template>\n\n<script>\n    window.onClick = () => {\n        this.send({\n            payload: 'Button Clicked'\n        })\n    }\n</script>\n\n<style>\nbutton {\n    background-color: black;\n    color: white;\n    padding: 6px;\n}\n</style>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":770,"y":120,"wires":[["d60878d3a283ab52"]]},{"id":"d60878d3a283ab52","type":"debug","z":"391328aec32f274c","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":920,"y":120,"wires":[]},{"id":"a3661c376af7f109","type":"ui-template","z":"391328aec32f274c","group":"e6ef4e2f832b5560","page":"","ui":"","name":"iframe","order":1,"width":"6","height":"8","head":"","format":"<template>\n    <iframe src=\"/worldmap\"></iframe>\n</template>\n\n<style>\niframe {\n    width: 100%;\n    height: 100%;\n}\n</style>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":750,"y":200,"wires":[[]]},{"id":"02e27045e9f73272","type":"ui-button","z":"391328aec32f274c","group":"ac686ff4425de150","name":"","label":"Update Location","order":1,"width":0,"height":0,"emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"","payloadType":"date","topic":"topic","topicType":"msg","buttonColor":"","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":130,"y":120,"wires":[["bb2d08de5dfd6486"]]},{"id":"2ac7883786d1dac0","type":"ui-template","z":"391328aec32f274c","group":"ac686ff4425de150","page":"","ui":"","name":"","order":2,"width":0,"height":0,"head":"","format":"<template>\n    <div class=\"location\" :class=\"hemisphere.toLowerCase()\">\n        Location: {{ msg.payload }}\n    </div>\n    <div>\n        <label>Hemisphere:</label>\n        <span>{{ hemisphere }}</span>\n    </div>\n    <!-- Conditional Logic -->\n    <label v-if=\"hemisphere === 'Southern'\">You're in the South!</label>\n    <label v-else>You're not in the South!</label>\n    <!-- List Rendering -->\n    <ul>\n        <li v-for=\"location in locations\">{{ location }}</li>\n    </ul>\n</template>\n\n<script>\n    export default {\n        data() {\n            // define variables available component-wide\n            // (in <template> and component functions)\n            return {\n                locations: [\n                    'London',\n                    'Paris',\n                    'Tokyo'\n                ]\n            }\n        },\n        computed: {\n            hemisphere: function () {\n                if (this.msg?.payload?.lat > 0) {\n                    return \"Northern\"\n                } else if (this.msg?.payload?.lat < 0) {\n                    return \"Southern\"\n                } else {\n                    return \"None\"\n                }\n            }\n        },\n        methods: { },\n        mounted() {\n            // code here when the component is first loaded\n\n        },\n        unmounted() {\n            // code here when the component is removed from the Dashboard\n            // i.e. when the user navigates away from the page\n\n        }\n    }\n</script>\n<style>\n    /* define any styles here - supports raw CSS */\n    .location {\n        font-weight: bold;\n    }\n    .location.southern {\n        color: blue;\n    }\n    .location.northern {\n        color: red;\n    }\n</style>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":760,"y":20,"wires":[[]]},{"id":"effbd7be410b0cab","type":"ui-template","z":"391328aec32f274c","group":"6958c0d43e4af796","page":"","ui":"","name":"Form","order":1,"width":0,"height":0,"head":"","format":"<template>\n    <v-form @submit.prevent=\"calculate\">\n        <div>\n            <v-text-field v-model=\"lat\" label=\"Latitude\"></v-text-field>\n        </div>\n        <div>\n            <v-text-field v-model=\"lon\" label=\"Longitude\"></v-text-field>\n        </div>\n        <v-btn type=\"submit\" block>Send</v-btn>\n    </form>\n</template>\n\n<script>\n    export default {\n        data() {\n            // define variables available component-wide\n            // (in <template> and component functions)\n            return {\n                lat: 0,\n                lon: 0\n            }\n        },\n        methods: {\n            calculate: function () {\n                this.send({\n                    payload: {\n                        name: 'Your Location',\n                        lat: this.lat,\n                        lon: this.lon\n                    }\n                })\n            }\n        }\n    }\n</script>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":330,"y":260,"wires":[["136e4b6bee7e277c","19fd32a9462d7848","0e921f9a56c10863"]]},{"id":"19fd32a9462d7848","type":"debug","z":"391328aec32f274c","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":500,"y":260,"wires":[]},{"id":"076fbd504c3a834f","type":"ui-template","z":"391328aec32f274c","group":"b4294af8315736a3","page":"","ui":"","name":"","order":1,"width":0,"height":0,"head":"","format":"<template>\n    <v-card v-for=\"location in locations\">\n        <v-img :src=\"location.img\" class=\"align-end\" gradient=\"to bottom, rgba(0,0,0,.1), rgba(0,0,0,.5)\" height=\"200px\" cover>\n            <v-card-title class=\"text-white\" v-text=\"location.name\"></v-card-title>\n        </v-img>\n    \n        <v-card-actions>\n            <v-spacer></v-spacer>\n            {{ calculateDistance(location.coords.lat, location.coords.lon) }} km\n        </v-card-actions>\n    </v-card>\n    <v-card >\n        <v-img :src=\"\" class=\"align-end\" gradient=\"to bottom, rgba(0,0,0,.1), rgba(0,0,0,.5)\" height=\"200px\"\n            cover>\n            <v-card-title class=\"text-white\">Your Location</v-card-title>\n        </v-img>\n    \n        <v-card-actions>\n            <v-spacer></v-spacer>\n            {{ calculateDistance(yourLocation.lat, yourLocation.lon) }} km\n        </v-card-actions>\n    </v-card>\n</template>\n\n<script>\n    export default {\n        data() {\n            // define variables available component-wide\n            // (in <template> and component functions)\n            return {\n                locations: [{\n                    name: 'London',\n                    img:\n                    'https://upload.wikimedia.org/wikipedia/commons/thumb/e/e4/Palace_of_Westminster_from_the_dome_on_Methodist_Central_Hall_%28cropped%29.jpg/900px-Palace_of_Westminster_from_the_dome_on_Methodist_Central_Hall_%28cropped%29.jpg',\n                    coords: {\n                        lat: 51.32,\n                        lon: 0.5\n                    }\n                }, {\n                    name: 'Paris',\n                    img:\n                    'https://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Seine_and_Eiffel_Tower_from_Tour_Saint_Jacques_2013-08.JPG/1200px-Seine_and_Eiffel_Tower_from_Tour_Saint_Jacques_2013-08.JPG',\n                    coords: {\n                        lat: 48.48,\n                        lon: 2.2\n                    }\n                }, {\n                    name: 'Tokyo',\n                    img: 'https://upload.wikimedia.org/wikipedia/commons/b/b2/Skyscrapers_of_Shinjuku_2009_January.jpg',\n                    coords: {\n                        lat: 35.4,\n                        lon: 139.45\n                    }\n                }],\n                iss: {\n                    lat: 0,\n                    lon: 0\n                },\n                yourLocation: {\n                    lat: 0,\n                    lon: 0\n                }\n            }\n        },\n        computed: {\n\n        },\n        methods: {\n            // expose a method to our <template> and Vue Application\n            calculateDistance: function (cityLat, cityLon) {\n                const iss = this.iss\n                var distance = 'Unknown'\n                if (iss) {\n                    const issLat = this.deg2rad(iss.lat)\n                    const issLon = this.deg2rad(iss.lon)\n                    \n                    cityLat = this.deg2rad(cityLat)\n                    cityLon = this.deg2rad(cityLon)\n                    \n                    const partA = Math.sin(issLat) * Math.sin(cityLat)\n                    const partB = Math.cos(issLat) * Math.cos(cityLat) * Math.cos(cityLon - issLon)\n                    distance = 6371 * Math.acos(partA + partB)\n                    \n                    // round distance to 2 dp\n                    distance = Math.round(distance * 100) / 100\n                }\n                return distance\n            },\n            deg2rad: function (deg) {\n                return deg * (Math.PI / 180)\n            }\n        },\n        mounted () {\n            this.$socket.on('msg-input:' + this.id, (msg) => {\n                if (msg.form) {\n                    // store form data\n                    this.yourLocation = msg.form\n                }\n                if (msg.payload) {\n                    // store the ISS data\n                    this.iss = msg.payload\n                }\n            })\n        }\n    }\n</script>","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":720,"y":300,"wires":[[]]},{"id":"0e921f9a56c10863","type":"change","z":"391328aec32f274c","name":"","rules":[{"t":"set","p":"form","pt":"msg","to":"payload","tot":"msg"},{"t":"delete","p":"payload","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":520,"y":300,"wires":[["076fbd504c3a834f"]]},{"id":"fde680bc13eac73c","type":"ui-group","name":"Starting Example","page":"9c6bbb686c561118","width":"6","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"36e086084db4d144","type":"ui-group","name":"In Built Stuff","page":"9c6bbb686c561118","width":"6","height":"1","order":2,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"e6ef4e2f832b5560","type":"ui-group","name":"IFRAME","page":"9c6bbb686c561118","width":"6","height":"1","order":3,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"ac686ff4425de150","type":"ui-group","name":"ISS Tracker","page":"9c6bbb686c561118","width":"6","height":"1","order":4,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"6958c0d43e4af796","type":"ui-group","name":"Your Location","page":"9c6bbb686c561118","width":"6","height":"1","order":5,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"b4294af8315736a3","type":"ui-group","name":"Locations","page":"9c6bbb686c561118","width":"6","height":"1","order":6,"showTitle":true,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"9c6bbb686c561118","type":"ui-page","name":"Page 1","ui":"53046a510b1f819d","path":"/page1","icon":"home","layout":"notebook","theme":"bafff2709d88aaef","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":1,"className":"","visible":"true","disabled":"false"},{"id":"53046a510b1f819d","type":"ui-base","name":"My Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"default","titleBarStyle":"default"},{"id":"bafff2709d88aaef","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094CE","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"density":"default","pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

Flow Info

Created 3 weeks, 3 days ago
Rating: not yet rated

Owner

Actions

Node Types

Core
  • change (x1)
  • debug (x3)
  • function (x1)
  • http request (x1)
  • inject (x1)
Other

Tags

  • dashboard-2
  • dashboard2
  • flowfuse-dashboard
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option