Orchestrating Dashboard page flow via 'ui_control'


#1

There have been several questions lately about how to switch dashboard pages, or trigger actions when a certain page becomes active. Although the Dashboard has its own built-in Models, Views, and Controllers (MVC), it’s nice to be able to have more “control” over the UI page interactions. What follows here is a pattern that has worked well for me in the past…

In the first Editor tab (flow), I like to put a single ui_control node with input and output links connected to all my other tabs (the application pages):

The input links allow events on one page to switch to another dashboard page – for instance, clicking on a row in the “Supplier Table” page switches to the “Purchase Order” list for the selected supplier. This way, the user does not have to lookup a Supplier name, open the Nav Menu, select the Purchase Orders page, and then execute a search based on that name.

The output links go to a switch node that directs the control msg based on the targeted tab’s name. Each output port corresponds to one application page, using a link out/in pair to carry the msg object. The goal is to detect when a new page is activated, and to kick off a flow that queries a database for the results to be shown on that page (e.g. a list of Materials, or a graph of Sensor readings). The switch node is configured to lookup the activated tab by name (using the msg.name property), since the order of the tabs can change over time:

Of course, this means that you will have to give each tab a unique name – and if you want to rename the tab, you’ll need to keep the switch rules in sync. Note also that you should only have 1 ui_control node across all of your flows. “Bad Stuff” can happen if you cross the streams, Ray… bad stuff. (well, at least odd stuff)

So now we have pages that switch to each other, passing along information as the user selects it, and only querying for data as its needed to render the target page. Nice – and also useful for some “simple” user/role based access.

Since each page gets notified when it becomes active, your flow can check for things like global context variables, or the outputs of other flows, and when not found it routes the user back to the expected input page. Although the Dashboard itself is designed for a single-user, you can redirect all pages back to a “login” or “access” page before allowing the user to see certain data – certainly not high-security, but still a valid technique when you want to present different slices of data to some users, or require a valid Pin Pad entry before changing the thermostat settings, for instance.


Login and Password in dashboard (localhost:1880/ui)?!
Three different Dashboard users
Access a property of the dashboard within a Express Application
#2

Steve, do yo have a small demo flow so people (me) could take a look at and see this in action?


#3

I was going to include one, but it was too intrinsically tied into my application – I’ll have to work on a stripped down version that I can post later. But the heart of the idea is represented above…


Make a button link to another page HTML !?
#4

Hi Steve, thank you very much for the awesome explanation !! There was very little info about this node (even in the old forum). My only reference was the flow posted by Dave (link below), which clearly explains how to use the node but you expanded a lot the available knowledge.

Simple example of hiding groups


#5

Hey Steve,
i just stumbled upon your post. I am still very interested to see an example in action. It's not fully clear to me what happens after the switch node is passed...

Thanks!


#6

second this...

I'm looking for a way to protect some of the tabs (settings pages) with a simple PIN input.
I have found a PIN input panel, it's not clear to me how to create a button on a page which requires a PIN input before switching to the page. Ideally the PIN can be defined with the page to be accessed...

Here's the PIN Input:

[{"id":"4ec43274.4d990c","type":"ui_template","z":"6880e39e.295cac","group":"e50f9fb2.c6557","name":"Key 1","order":1,"width":"2","height":"2","format":"<md-button class=\"vibrate filled touched bigfont rounded\" style=\"background-color:#333333\" ng-click=\"send({payload: '1' })\"> \n<svg width=\"105px\" height=\"105px\" version=\"1.1\" viewBox=\"0 0 200 200\">\n <g id=\"Key-1\">\n  <rect fill=\"#4D4D4D\" width=\"200\" height=\"200\" rx=\"12\" ry=\"12\"/>\n  <path fill=\"none\" stroke=\"#B3B3B3\" stroke-width=\"7.99957\" d=\"M6 194c-1,-1 -2,-3 -2,-6l0 -176c0,-4 4,-8 8,-8l176 0c2,0 4,1 6,2\"/>\n  <path fill=\"none\" stroke=\"#1A1A1A\" stroke-width=\"7.99957\" d=\"M194 6c1,1 2,3 2,6l0 176c0,4 -4,8 -8,8l-176 0c-2,0 -4,-1 -6,-2\"/>\n  <text x=\"59\" y=\"153\" style=\"fill: #E6E6E6; font-weight: normal; font-size: 150px; font-family: Arial;\">1</text>\n </g>\n</svg>\n</md-button>\n","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":710,"y":420,"wires":[["12c6ceca.ad5f99"]]},{"id":"622a1965.9c012","type":"ui_template","z":"6880e39e.295cac","group":"e50f9fb2.c6557","name":"Key 6","order":6,"width":"2","height":"2","format":"<md-button class=\"vibrate filled touched bigfont rounded\" style=\"background-color:#333333\" ng-click=\"send({payload: '6'})\"> \n<svg width=\"105px\" height=\"105px\" version=\"1.1\" viewBox=\"0 0 200 200\">\n <g id=\"Key-6\">\n  <rect fill=\"#4D4D4D\" width=\"200\" height=\"200\" rx=\"12\" ry=\"12\"/>\n  <path fill=\"none\" stroke=\"#B3B3B3\" stroke-width=\"7.99957\" d=\"M6 194c-1,-1 -2,-3 -2,-6l0 -176c0,-4 4,-8 8,-8l176 0c2,0 4,1 6,2\"/>\n  <path fill=\"none\" stroke=\"#1A1A1A\" stroke-width=\"7.99957\" d=\"M194 6c1,1 2,3 2,6l0 176c0,4 -4,8 -8,8l-176 0c-2,0 -4,-1 -6,-2\"/>\n  <text x=\"59\" y=\"153\" style=\"fill: #E6E6E6; font-weight: normal; font-size: 150px; font-family: Arial;\">6</text>\n </g>\n</svg>\n</md-button>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":990,"y":460,"wires":[["12c6ceca.ad5f99"]]},{"id":"c877b09a.f22a28","type":"ui_template","z":"6880e39e.295cac","group":"e50f9fb2.c6557","name":"Key 3","order":3,"width":"2","height":"2","format":"<md-button class=\"vibrate filled touched bigfont rounded\" style=\"background-color:#333333\" ng-click=\"send({payload: '3' })\"> \n<svg width=\"105px\" height=\"105px\" version=\"1.1\" viewBox=\"0 0 200 200\">\n <g id=\"Key-3\">\n  <rect fill=\"#4D4D4D\" width=\"200\" height=\"200\" rx=\"12\" ry=\"12\"/>\n  <path fill=\"none\" stroke=\"#B3B3B3\" stroke-width=\"7.99957\" d=\"M6 194c-1,-1 -2,-3 -2,-6l0 -176c0,-4 4,-8 8,-8l176 0c2,0 4,1 6,2\"/>\n  <path fill=\"none\" stroke=\"#1A1A1A\" stroke-width=\"7.99957\" d=\"M194 6c1,1 2,3 2,6l0 176c0,4 -4,8 -8,8l-176 0c-2,0 -4,-1 -6,-2\"/>\n  <text x=\"59\" y=\"153\" style=\"fill: #E6E6E6; font-weight: normal; font-size: 150px; font-family: Arial;\">3</text>\n </g>\n</svg>\n</md-button>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":990,"y":420,"wires":[["12c6ceca.ad5f99"]]},{"id":"7979b9.87747e48","type":"ui_template","z":"6880e39e.295cac","group":"e50f9fb2.c6557","name":"Key 4","order":4,"width":"2","height":"2","format":"<md-button class=\"vibrate filled touched bigfont rounded\" style=\"background-color:#333333\" ng-click=\"send({payload: '4'})\"> \n<svg width=\"105px\" height=\"105px\" version=\"1.1\" viewBox=\"0 0 200 200\">\n <g id=\"Key-4\">\n  <rect fill=\"#4D4D4D\" width=\"200\" height=\"200\" rx=\"12\" ry=\"12\"/>\n  <path fill=\"none\" stroke=\"#B3B3B3\" stroke-width=\"7.99957\" d=\"M6 194c-1,-1 -2,-3 -2,-6l0 -176c0,-4 4,-8 8,-8l176 0c2,0 4,1 6,2\"/>\n  <path fill=\"none\" stroke=\"#1A1A1A\" stroke-width=\"7.99957\" d=\"M194 6c1,1 2,3 2,6l0 176c0,4 -4,8 -8,8l-176 0c-2,0 -4,-1 -6,-2\"/>\n  <text x=\"59\" y=\"153\" style=\"fill: #E6E6E6; font-weight: normal; font-size: 150px; font-family: Arial;\">4</text>\n </g>\n</svg>\n</md-button>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":710,"y":460,"wires":[["12c6ceca.ad5f99"]]},{"id":"ea362834.bc9cf8","type":"ui_template","z":"6880e39e.295cac","group":"e50f9fb2.c6557","name":"Key 5","order":5,"width":"2","height":"2","format":"<md-button class=\"vibrate filled touched bigfont rounded\" style=\"background-color:#333333\" ng-click=\"send({payload: '5'})\"> \n<svg width=\"105px\" height=\"105px\" version=\"1.1\" viewBox=\"0 0 200 200\">\n <g id=\"Key-5\">\n  <rect fill=\"#4D4D4D\" width=\"200\" height=\"200\" rx=\"12\" ry=\"12\"/>\n  <path fill=\"none\" stroke=\"#B3B3B3\" stroke-width=\"7.99957\" d=\"M6 194c-1,-1 -2,-3 -2,-6l0 -176c0,-4 4,-8 8,-8l176 0c2,0 4,1 6,2\"/>\n  <path fill=\"none\" stroke=\"#1A1A1A\" stroke-width=\"7.99957\" d=\"M194 6c1,1 2,3 2,6l0 176c0,4 -4,8 -8,8l-176 0c-2,0 -4,-1 -6,-2\"/>\n  <text x=\"59\" y=\"153\" style=\"fill: #E6E6E6; font-weight: normal; font-size: 150px; font-family: Arial;\">5</text>\n </g>\n</svg>\n</md-button>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":850,"y":460,"wires":[["12c6ceca.ad5f99"]]},{"id":"ccfa88ba.1d96d","type":"ui_template","z":"6880e39e.295cac","group":"e50f9fb2.c6557","name":"Key 7","order":7,"width":"2","height":"2","format":"<md-button class=\"vibrate filled touched bigfont rounded\" style=\"background-color:#333333\" ng-click=\"send({payload: '7'})\"> \n<svg width=\"105px\" height=\"105px\" version=\"1.1\" viewBox=\"0 0 200 200\">\n <g id=\"Key-7\">\n  <rect fill=\"#4D4D4D\" width=\"200\" height=\"200\" rx=\"12\" ry=\"12\"/>\n  <path fill=\"none\" stroke=\"#B3B3B3\" stroke-width=\"7.99957\" d=\"M6 194c-1,-1 -2,-3 -2,-6l0 -176c0,-4 4,-8 8,-8l176 0c2,0 4,1 6,2\"/>\n  <path fill=\"none\" stroke=\"#1A1A1A\" stroke-width=\"7.99957\" d=\"M194 6c1,1 2,3 2,6l0 176c0,4 -4,8 -8,8l-176 0c-2,0 -4,-1 -6,-2\"/>\n  <text x=\"59\" y=\"153\" style=\"fill: #E6E6E6; font-weight: normal; font-size: 150px; font-family: Arial;\">7</text>\n </g>\n</svg>\n</md-button>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":710,"y":500,"wires":[["12c6ceca.ad5f99"]]},{"id":"3d0356e8.3564f2","type":"ui_template","z":"6880e39e.295cac","group":"e50f9fb2.c6557","name":"Key 2","order":2,"width":"2","height":"2","format":"<md-button class=\"vibrate filled touched bigfont rounded\" style=\"background-color:#333333\" ng-click=\"send({payload: '2' })\"> \n<svg width=\"105px\" height=\"105px\" version=\"1.1\" viewBox=\"0 0 200 200\">\n <g id=\"Key-2\">\n  <rect fill=\"#4D4D4D\" width=\"200\" height=\"200\" rx=\"12\" ry=\"12\"/>\n  <path fill=\"none\" stroke=\"#B3B3B3\" stroke-width=\"7.99957\" d=\"M6 194c-1,-1 -2,-3 -2,-6l0 -176c0,-4 4,-8 8,-8l176 0c2,0 4,1 6,2\"/>\n  <path fill=\"none\" stroke=\"#1A1A1A\" stroke-width=\"7.99957\" d=\"M194 6c1,1 2,3 2,6l0 176c0,4 -4,8 -8,8l-176 0c-2,0 -4,-1 -6,-2\"/>\n  <text x=\"59\" y=\"153\" style=\"fill: #E6E6E6; font-weight: normal; font-size: 150px; font-family: Arial;\">2</text>\n </g>\n</svg>\n</md-button>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":850,"y":420,"wires":[["12c6ceca.ad5f99"]]},{"id":"c7330a2.ac1d8f8","type":"ui_template","z":"6880e39e.295cac","group":"e50f9fb2.c6557","name":"Key 8","order":8,"width":"2","height":"2","format":"<md-button class=\"vibrate filled touched bigfont rounded\" style=\"background-color:#333333\" ng-click=\"send({payload: '8'})\"> \n<svg width=\"105px\" height=\"105px\" version=\"1.1\" viewBox=\"0 0 200 200\">\n <g id=\"Key-8\">\n  <rect fill=\"#4D4D4D\" width=\"200\" height=\"200\" rx=\"12\" ry=\"12\"/>\n  <path fill=\"none\" stroke=\"#B3B3B3\" stroke-width=\"7.99957\" d=\"M6 194c-1,-1 -2,-3 -2,-6l0 -176c0,-4 4,-8 8,-8l176 0c2,0 4,1 6,2\"/>\n  <path fill=\"none\" stroke=\"#1A1A1A\" stroke-width=\"7.99957\" d=\"M194 6c1,1 2,3 2,6l0 176c0,4 -4,8 -8,8l-176 0c-2,0 -4,-1 -6,-2\"/>\n  <text x=\"59\" y=\"153\" style=\"fill: #E6E6E6; font-weight: normal; font-size: 150px; font-family: Arial;\">8</text>\n </g>\n</svg>\n</md-button>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":850,"y":500,"wires":[["12c6ceca.ad5f99"]]},{"id":"8610d7d3.f19168","type":"ui_template","z":"6880e39e.295cac","group":"e50f9fb2.c6557","name":"Key 9","order":9,"width":"2","height":"2","format":"<md-button class=\"vibrate filled touched bigfont rounded\" style=\"background-color:#333333\" ng-click=\"send({payload: '9'})\"> \n<svg width=\"105px\" height=\"105px\" version=\"1.1\" viewBox=\"0 0 200 200\">\n <g id=\"Key-9\">\n  <rect fill=\"#4D4D4D\" width=\"200\" height=\"200\" rx=\"12\" ry=\"12\"/>\n  <path fill=\"none\" stroke=\"#B3B3B3\" stroke-width=\"7.99957\" d=\"M6 194c-1,-1 -2,-3 -2,-6l0 -176c0,-4 4,-8 8,-8l176 0c2,0 4,1 6,2\"/>\n  <path fill=\"none\" stroke=\"#1A1A1A\" stroke-width=\"7.99957\" d=\"M194 6c1,1 2,3 2,6l0 176c0,4 -4,8 -8,8l-176 0c-2,0 -4,-1 -6,-2\"/>\n  <text x=\"59\" y=\"153\" style=\"fill: #E6E6E6; font-weight: normal; font-size: 150px; font-family: Arial;\">9</text>\n </g>\n</svg>\n</md-button>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":990,"y":500,"wires":[["12c6ceca.ad5f99"]]},{"id":"978a7747.08ba6","type":"ui_template","z":"6880e39e.295cac","group":"e50f9fb2.c6557","name":"Key *","order":10,"width":"2","height":"2","format":"<md-button class=\"vibrate filled touched bigfont rounded\" style=\"background-color:#333333\" ng-click=\"send({payload: '*'})\"> \n<svg width=\"105px\" height=\"105px\" version=\"1.1\" viewBox=\"0 0 200 200\">\n <g id=\"Key-Reset\">\n  <rect fill=\"#4D4D4D\" width=\"200\" height=\"200\" rx=\"12\" ry=\"12\">\n    <title>Reset</title>\n  </rect>\n  <path fill=\"none\" stroke=\"#B3B3B3\" stroke-width=\"7.99957\" d=\"M6 194c-1,-1 -2,-3 -2,-6l0 -176c0,-4 4,-8 8,-8l176 0c2,0 4,1 6,2\"/>\n  <path fill=\"none\" stroke=\"#1A1A1A\" stroke-width=\"7.99957\" d=\"M194 6c1,1 2,3 2,6l0 176c0,4 -4,8 -8,8l-176 0c-2,0 -4,-1 -6,-2\"/>\n  <text x=\"50\" y=\"250\" style=\"fill: #E6E6E6; font-weight: normal; font-size: 250px; font-family: Arial;\">*</text>\n </g>\n</svg>\n</md-button>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":710,"y":540,"wires":[["12c6ceca.ad5f99"]]},{"id":"95d0781b.02f34","type":"ui_template","z":"6880e39e.295cac","group":"e50f9fb2.c6557","name":"Key 0","order":11,"width":"2","height":"2","format":"<md-button class=\"vibrate filled touched bigfont rounded\" style=\"background-color:#333333\" ng-click=\"send({payload: '0'})\"> \n<svg width=\"105px\" height=\"105px\" version=\"1.1\" viewBox=\"0 0 200 200\">\n <g id=\"Key-0\">\n  <rect fill=\"#4D4D4D\" width=\"200\" height=\"200\" rx=\"12\" ry=\"12\"/>\n  <path fill=\"none\" stroke=\"#B3B3B3\" stroke-width=\"7.99957\" d=\"M6 194c-1,-1 -2,-3 -2,-6l0 -176c0,-4 4,-8 8,-8l176 0c2,0 4,1 6,2\"/>\n  <path fill=\"none\" stroke=\"#1A1A1A\" stroke-width=\"7.99957\" d=\"M194 6c1,1 2,3 2,6l0 176c0,4 -4,8 -8,8l-176 0c-2,0 -4,-1 -6,-2\"/>\n  <text x=\"59\" y=\"153\" style=\"fill: #E6E6E6; font-weight: normal; font-size: 150px; font-family: Arial;\">0</text>\n </g>\n</svg>\n</md-button>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":850,"y":540,"wires":[["12c6ceca.ad5f99"]]},{"id":"a452985a.1afa7","type":"ui_template","z":"6880e39e.295cac","group":"e50f9fb2.c6557","name":"Key #","order":12,"width":"2","height":"2","format":"<md-button class=\"vibrate filled touched bigfont rounded\" style=\"background-color:#333333\" ng-click=\"send({payload: '#'})\"> \n<svg width=\"105px\" height=\"105px\" version=\"1.1\" viewBox=\"0 0 200 200\">\n <g id=\"Key-Enter\">\n  <rect fill=\"#4D4D4D\" width=\"200\" height=\"200\" rx=\"12\" ry=\"12\">\n    <title>Enter</title>\n  </rect>\n  <path fill=\"none\" stroke=\"#B3B3B3\" stroke-width=\"7.99957\" d=\"M6 194c-1,-1 -2,-3 -2,-6l0 -176c0,-4 4,-8 8,-8l176 0c2,0 4,1 6,2\"/>\n  <path fill=\"none\" stroke=\"#1A1A1A\" stroke-width=\"7.99957\" d=\"M194 6c1,1 2,3 2,6l0 176c0,4 -4,8 -8,8l-176 0c-2,0 -4,-1 -6,-2\"/>\n  <text x=\"59\" y=\"153\" style=\"fill: #E6E6E6; font-weight: normal; font-size: 150px; font-family: Arial;\">#</text>\n </g>\n</svg>\n</md-button>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":990,"y":540,"wires":[["12c6ceca.ad5f99"]]},{"id":"cadeafc6.552e4","type":"ui_text","z":"6880e39e.295cac","group":"e50f9fb2.c6557","order":13,"width":0,"height":0,"name":"keys pressed","label":"Number:","format":"{{msg.payload}}","layout":"row-spread","x":1210,"y":420,"wires":[]},{"id":"12c6ceca.ad5f99","type":"function","z":"6880e39e.295cac","name":"code entry","func":"var key = msg.payload;\nvar out = context.get(\"code\") || \"\";\n\nif (key === \"#\") {\n    key = \"\"; // send code\n}\nelse if (key === \"*\") {\n    key = \"\"; // reset code\n    out = \"\";\n}\nelse if (!isNaN(+key)) {\n    out += key; // append key\n}\ncontext.set(\"code\", key ? out : undefined);\n\n// output #1: keys entered\n// output #2: completed code\nmsg.payload = out;\nreturn [\n    key ? msg : {payload: \"\"},\n    out ? msg : null\n];","outputs":2,"noerr":0,"x":1170,"y":480,"wires":[["cadeafc6.552e4"],["6f371256.0e1004"]],"outputLabels":["last key number","completed code"]},{"id":"6f371256.0e1004","type":"debug","z":"6880e39e.295cac","name":"code entered","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","x":1210,"y":540,"wires":[]},{"id":"e50f9fb2.c6557","type":"ui_group","z":"","name":"Keypad","tab":"84110940.4950e8","order":5,"disp":true,"width":"6"},{"id":"84110940.4950e8","type":"ui_tab","z":"","name":"Button Panel","icon":"dashboard"}]