Feature Request - Off - Auto - On Toggle

That will be a long story to talk. The best practices are good to know and good to follow but in this (dashboard) case we are kind of end-users. Even when creating ui-custom-widget node you are stepping on the land of the underlying culture. And then there is stuff you allow for user to change and some stuff you try to keep under the widget control so many times the best practice rules just don't fit into the situation.

What I have experienced is that the more open the thing can be, the more likes the product gets. So yes, build it with respect for overrides.
The border-radius - It can't be changed for dashboard as some base value (set once and all elements become rounded) Pretty many different elements needs to be changed. So as dashboard default is not rounded, don't force it if possible. But it will be nice if it behaves correctly when such override comes in play.

For colors, i'd say - same story (with different therms).

Are you on the plans to turn this creation here to the ui-contrib.widget?

Think it would be useful. Was wondering if the border radius had to be an input field on the config screen... That would have been easy to use, but bad from a theme point of view...

Before you dig into codding, if you have an option, do test this solution against browser support on devices. Specially for Iphones Ipads and tablets. This thing here bases on hack solution which may fail in many ways in different solution.

1 Like

If you were creating a contrib node, it would be good to be able to select either 3 or 2 toggles option, so a consistent dashboard theme could be achieved.

(Getting that one in early :wink:)

1 Like

Only 3 ? :wink:

There is also md-nav-bar which is by nature the correct thing to be base of the this thing here.

[{"id":"778d7358.63fecc","type":"ui_template","z":"a05ff4cd.1ae7e8","group":"19f5ed07.08a453","name":"nav-bar","order":12,"width":0,"height":0,"format":"<style>\n._md-nav-bar-list {\n    flex-direction: row;\n    justify-content: flex-start;\n}\nli.md-nav-item {\n    width: 100%;\n}\n.md-nav-item:first-of-type {\n    margin-left: 0px; \n}\nmd-nav-bar > div > nav > ul > li > button{\n    width:98%;\n}\nmd-nav-bar md-nav-ink-bar {\n    color: var(--nr-dashboard-groupTextColor);\n    background: var(--nr-dashboard-groupTextColor);\n}\n</style>\n<div ng-cloak>\n  <md-content class=\"md-padding\">\n    <md-nav-bar\n      md-no-ink-bar=\"disableInkBar\"\n      md-selected-nav-item=\"currentNavItem\"\n      nav-bar-aria-label=\"navigation links\">\n      <md-nav-item md-nav-click=\"goto('page1')\" name=\"page1\">\n        One\n      </md-nav-item>\n      <md-nav-item md-nav-click=\"goto('page2')\" name=\"page2\" ng-disabled=\"secondTabDisabled\">\n        Two\n      </md-nav-item>\n      <md-nav-item md-nav-click=\"goto('page3')\" name=\"page3\">\n        Three\n      </md-nav-item>\n\n    </md-nav-bar>\n    \n\n  </md-content>\n</div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":230,"y":1000,"wires":[[]]},{"id":"19f5ed07.08a453","type":"ui_group","name":"Default","tab":"c71e2691.3704f8","order":1,"disp":true,"width":"6","collapse":false},{"id":"c71e2691.3704f8","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

image

Actually this could have potential to be part of the standard set of dashboard nodes? @dceejay

1 Like

indeed - very happy for that.

PS - I can't help feeling we need a way to collect all the cool snippets you create within templates - almost like a special templates collection. I guess it could just be in flows.nodered.org - but there does seem to be a rich vein here.

1 Like

You are reading my mind...
I proposed to create a UI node, to avoid that people need to start changing the code in order to change the button labels, ... But indeed @hotnipi has so many nice widgets, that it is undoable to create a dedicated UI node for all of them...

1 Like

Isn't that my responsibility to keep the order and put the toys nicely back to shelf if I don't play with them anymore :stuck_out_tongue:

Well, doing many things with slight ideas collected to be one day advised/included to the dashboard-next ...

3 Likes

hotNipi

Thank you for your contribution. I prettied it up a bit to fit my very conservative sizes as I'm running my code on a RaspberryPi4B w/ the Official 800x480 touch screen.... I have the 1x1 widget size as 24x24. I wanted this object to just take up one line on the Dashboard.

image_2021-02-13_104311

Here is the code:

[{"id":"80f561e2.7a935","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"772297c4.f05598","type":"ui_template","z":"80f561e2.7a935","group":"19f5ed07.08a453","name":"3WaySwitch","order":12,"width":"0","height":"0","format":"<div id=\"{{'swc_'+$id}}\" class=\"switchwrapper\">\n  <div class=\"toggle_radio\">\n      \n    <input type=\"radio\" class=\"toggle_option\" id=\"{{'Off_'+$id}}\" name=\"toggle_option\" ng-click=\"send({payload: 'Off'})\" ng-model=\"value\" value='Off'>\n    <input type=\"radio\" checked class=\"toggle_option\" id=\"{{'Auto_'+$id}}\" name=\"toggle_option\" ng-click=\"send({payload: 'Auto'})\" ng-model=\"value\" value='Auto'>\n    <input type=\"radio\" class=\"toggle_option\" id=\"{{'On_'+$id}}\" name=\"toggle_option\" ng-click=\"send({payload: 'On'})\" ng-model=\"value\" value='On'>\n    <div id=\"{{'slider_'+$id}}\" class=\"toggle_option_slider\"></div>\n    <label for=\"{{'Off_'+$id}}\"><p>Off</p></label>\n    <label for=\"{{'Auto_'+$id}}\"><p>Auto</p></label>\n    <label for=\"{{'On_'+$id}}\"><p>On</p></label>\n  </div>\n  \n</div>\n\n<script>\n(function(scope) {\n    // watch msg object from Node-RED\n     scope.$watch('value', function(value) {\n            switch(value){\n                case 'Off':{\n                     $(\"#slider_\"+scope.$id).css(\"left\", \"2px\")\n                    break\n                }\n                case 'Auto':{\n                     $(\"#slider_\"+scope.$id).css(\"left\", \"33%\")\n                    break\n                }\n                case 'On':{\n                    $(\"#slider_\"+scope.$id).css(\"left\", \"66%\")\n                    break\n                }\n            }\n           \n        });\n    scope.$watch('msg', function(msg) {\n        // Select one of the 3 radiobuttons, based on the msg.payload value\n       if(msg){\n        switch(msg.payload) {\n            case 'Off':\n                $(\"#Off_\"+scope.$id).checked = true;\n                break;\n            case 'Auto':\n                $(\"#Auto_\"+scope.$id).checked = true;\n                break;\n            case 'On':\n                $(\"#On_\"+scope.$id).checked = true;\n                break;\n        }   \n       }\n        \n    });\n})(scope);\n</script>\n  \n<style>\n.switchwrapper {\n  margin: auto 0;\n}\n.toggle_radio{\n  position: relative; \n    margin: auto;\n    overflow: hidden;\n    border-radius: 50px;\n    position: relative;\n    height: 19px;\n    border: 1px solid var(--nr-dashboard-groupBorderColor);\n    background: var(--nr-dashboard-widgetBgndColor);\n}\n.toggle_radio > * {\n  float: left;\n}\n.toggle_radio input[type=radio]{\n  display: none;\n}\n.toggle_radio label{\n  display: block;\n  width: 33%;\n  height: 15px;\n  margin: 0;\n  line-height :15px;\n  border-radius: 50px;\n  cursor: pointer;\n  z-index: 1;\n  text-align: center;\n}\n.toggle_radio label >p {\n    background:transparent !important;\n}\n.toggle_option_slider{\n  width: 33%;\n  height: 15px;\n  position: absolute;\n  top: 2px;\n  border-radius: 50px;\n  transition: all .3s ease;\n  z-index:0;\n  left:2px;\n  opacity:0.5;\n  background: var(--nr-dashboard-widgetColor);\n}\n\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":570,"y":260,"wires":[["79fb7910.3efa38"]]},{"id":"79fb7910.3efa38","type":"debug","z":"80f561e2.7a935","name":"Selected option","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":760,"y":280,"wires":[]},{"id":"a4764f08.36e08","type":"inject","z":"80f561e2.7a935","name":"Select Off","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Off","payloadType":"str","x":380,"y":240,"wires":[["772297c4.f05598"]]},{"id":"df57bcc3.02bc4","type":"inject","z":"80f561e2.7a935","name":"Select Auto","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Auto","payloadType":"str","x":370,"y":280,"wires":[["772297c4.f05598"]]},{"id":"80056f30.c386d","type":"inject","z":"80f561e2.7a935","name":"Select On","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"On","payloadType":"str","x":370,"y":320,"wires":[["772297c4.f05598"]]},{"id":"19f5ed07.08a453","type":"ui_group","name":"Default","tab":"c71e2691.3704f8","order":1,"disp":true,"width":"6","collapse":false},{"id":"c71e2691.3704f8","type":"ui_tab","name":"Home","icon":"dashboard","disabled":false,"hidden":false}]

One thing I noticed is that I was unable to simply copy the node... and modify the code to be 2 choices or 4 choices.... as the style settings from one would overwrite the other... So, I'll have to figure that out.

But with the 3way options I'm able to adapt it for the following uses:

Device Mode Off/Auto/ON
Output Mode Auto/Man/Track
Setpoint Mode Local/Remote/Track.

image_2021-02-13_105454

1 Like

Really of topic but just for you I'd suggest to override the base font size for dashboard to be a bit smaller. It helps to maybe get more reasonable amount of text shown so it will be less compromises and it just will look better due the visual balancing of sizes.

BUT only if it does not affect the readability.

If help needed, please start new thread on that topic..

Well that should be figured out correctly. Common styles should be removed completely and added to one ui_template which is targeted to the site head. And if there must be some instance related styles, then may be best is to add them inline.

1 Like

hotNipi,

I considered that.. but I cut my teeth in automation and controls on old DCS systems w/ CGA displays.... I became very skilled at developing clever mneumonics and abbreviations for things on the operator displays. LOL.

So, I lean towards larger text, fewer characters... Although challenging to fit allot of content, the default dashboard font size makes for very readable displays on the Official Raspberry Displays...

2 Likes

This is really good toggle system. I was wondering if I could make it vertical, but didn't really manage to get it working.

I'm still working on it. It is based on a radio button library and uses css properties to control it's appearance. So, I'm having issues when I use multiple ones in the same flow when they have a different number of selections.

If I do a 3 button in one ui group (eg. off/auto/on) and then a 2 button in another ui group (eg. auto/man)... then all toggles become 2 options and drop the third.... as the 2 button was created last and therefore overwrites the css properties.

So, i have to figure out a way to isolate those ccs modifications to just the single node where they are defined...

Looks like some reading/studying is in order for me.

2-way 3-way 4-way
:jigsaw: :upside_down_face:

[{"id":"438a95b9.6a9eac","type":"ui_template","z":"a05ff4cd.1ae7e8","group":"9f2da61.3353758","name":"Dashboard CSS","order":2,"width":12,"height":1,"format":"<style id=\"dashboard-styles-override\">\n.switchwrapper {\n  margin: auto 0;\n}\n.toggle_radio{\n    margin: auto;\n    overflow: hidden;\n    border-radius: 50px;\n    position: relative;\n    height: 26px;\n    border: 1px solid var(--nr-dashboard-groupBorderColor);\n    background: var(--nr-dashboard-widgetBgndColor);\n}\n.toggle_radio > * {\n  float: left;\n}\n.toggle_radio input[type=radio]{\n  display: none;\n}\n.toggle_4{\n     width: 25%;\n}\n.toggle_3{\n     width: 33%;\n}\n.toggle_2{\n     width: 50%;\n}\n.toggle_radio label{\n  display: block;\n  height: 20px;\n  margin: 0;\n  line-height :18px;\n  border-radius: 50px;\n  cursor: pointer;\n  z-index: 1;\n  text-align: center;\n}\n.toggle_radio label >p {\n    background:transparent !important;\n}\n.toggle_option_slider{\n  \n  height: 20px;\n  position: absolute;\n  top: 3px;\n  border-radius: 50px;\n  transition: all .4s ease;\n  z-index:0;\n  left:3px;\n  opacity:0.4;\n  background: var(--nr-dashboard-widgetColor);\n}\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"global","x":460,"y":960,"wires":[[]]},{"id":"c770e530.b74478","type":"ui_template","z":"a05ff4cd.1ae7e8","group":"9f2da61.3353758","name":"3-way switch","order":4,"width":6,"height":1,"format":"<div id=\"{{'swc_'+$id}}\" class=\"switchwrapper\">\n  <div class=\"toggle_radio\">\n    <input type=\"radio\" class=\"toggle_option toggle_3\" id=\"{{'first_'+$id}}\" ng-model=\"value\" value=\"first\">\n    <input type=\"radio\" class=\"toggle_option toggle_3\" id=\"{{'second_'+$id}}\" ng-model=\"value\" value=\"second\">\n    <input type=\"radio\" class=\"toggle_option toggle_3\" id=\"{{'third_'+$id}}\" ng-model=\"value\" value=\"third\">\n    <div id=\"{{'slider_'+$id}}\" class=\"toggle_option_slider toggle_3\"></div>\n    <label class=\" toggle_3\" for=\"{{'first_'+$id}}\"><p>A</p></label>\n    <label class=\" toggle_3\" for=\"{{'second_'+$id}}\"><p>B</p></label>\n    <label class=\" toggle_3\" for=\"{{'third_'+$id}}\"><p>C</p></label>\n  </div>\n  \n</div>\n\n<script>\n(function(scope) {\n    scope.incomingChange = false\n    scope.$watch('value', function(value) {\n        switch(value){\n            case 'first':{\n                $(\"#slider_\"+scope.$id).css(\"left\", \"3px\")\n                break\n            }\n            case 'second':{\n                $(\"#slider_\"+scope.$id).css(\"left\", \"33%\")\n                break\n            }\n            case 'third':{\n                $(\"#slider_\"+scope.$id).css(\"left\", \"66%\")\n                break\n            }\n        }\n        if(scope.incomingChange == true){\n            scope.incomingChange = false\n            return\n        }\n        if(!value){\n            return\n        }\n        scope.send({payload: value})\n       \n    });\n    scope.$watch('msg', function(msg) {\n        if(msg){\n            if(scope.value != msg.payload){\n                scope.incomingChange = true\n                scope.value = msg.payload\n            }\n            \n        }\n    });\n})(scope);\n</script>\n  \n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":450,"y":1200,"wires":[["e5fbeffc.05654"]]},{"id":"e5fbeffc.05654","type":"debug","z":"a05ff4cd.1ae7e8","name":"Selected option","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":660,"y":1200,"wires":[]},{"id":"fffca1ba.ef777","type":"inject","z":"a05ff4cd.1ae7e8","name":"Select first","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"first","payloadType":"str","x":220,"y":1160,"wires":[["c770e530.b74478"]]},{"id":"5d4d028f.3736dc","type":"inject","z":"a05ff4cd.1ae7e8","name":"Select second","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"second","payloadType":"str","x":210,"y":1200,"wires":[["c770e530.b74478"]]},{"id":"32a2f720.f367b8","type":"inject","z":"a05ff4cd.1ae7e8","name":"Select third","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"third","payloadType":"str","x":210,"y":1240,"wires":[["c770e530.b74478"]]},{"id":"de21e3f1.82753","type":"ui_template","z":"a05ff4cd.1ae7e8","group":"9f2da61.3353758","name":"2-way switch","order":4,"width":6,"height":1,"format":"<div id=\"{{'swc_'+$id}}\" class=\"switchwrapper\">\n  <div class=\"toggle_radio\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"{{'first_'+$id}}\" ng-model=\"value\" value=\"first\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"{{'second_'+$id}}\" ng-model=\"value\" value=\"second\">\n    \n    <div id=\"{{'slider_'+$id}}\" class=\"toggle_option_slider toggle_2\"></div>\n    <label class=\" toggle_2\" for=\"{{'first_'+$id}}\"><p>A</p></label>\n    <label class=\" toggle_2\" for=\"{{'second_'+$id}}\"><p>B</p></label>\n\n  </div>\n  \n</div>\n\n<script>\n(function(scope) {\n    scope.incomingChange = false\n    scope.$watch('value', function(value) {\n        switch(value){\n            case 'first':{\n                $(\"#slider_\"+scope.$id).css(\"left\", \"3px\")\n                break\n            }\n            case 'second':{\n                $(\"#slider_\"+scope.$id).css(\"left\", \"49%\")\n                break\n            }\n\n        }\n        if(scope.incomingChange == true){\n            scope.incomingChange = false\n            return\n        }\n        if(!value){\n            return\n        }\n        scope.send({payload: value})\n       \n    });\n    scope.$watch('msg', function(msg) {\n        if(msg){\n            if(scope.value != msg.payload){\n                scope.incomingChange = true\n                scope.value = msg.payload\n            }\n            \n        }\n    });\n})(scope);\n</script>\n  \n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":450,"y":1060,"wires":[["56b0f8.831e0f08"]]},{"id":"56b0f8.831e0f08","type":"debug","z":"a05ff4cd.1ae7e8","name":"Selected option","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":660,"y":1060,"wires":[]},{"id":"96d99491.c677b8","type":"inject","z":"a05ff4cd.1ae7e8","name":"Select first","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"first","payloadType":"str","x":220,"y":1020,"wires":[["de21e3f1.82753"]]},{"id":"a07e0e6e.4244c","type":"inject","z":"a05ff4cd.1ae7e8","name":"Select second","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"second","payloadType":"str","x":210,"y":1060,"wires":[["de21e3f1.82753"]]},{"id":"5ec61ac2.9a01c4","type":"ui_template","z":"a05ff4cd.1ae7e8","group":"9f2da61.3353758","name":"4-way switch","order":4,"width":6,"height":1,"format":"<div id=\"{{'swc_'+$id}}\" class=\"switchwrapper\">\n  <div class=\"toggle_radio\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"{{'first_'+$id}}\" ng-model=\"value\" value=\"first\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"{{'second_'+$id}}\" ng-model=\"value\" value=\"second\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"{{'third_'+$id}}\" ng-model=\"value\" value=\"third\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"{{'fourth_'+$id}}\" ng-model=\"value\" value=\"fourth\">\n    <div id=\"{{'slider_'+$id}}\" class=\"toggle_option_slider toggle_4\"></div>\n    <label class=\" toggle_4\" for=\"{{'first_'+$id}}\"><p>A</p></label>\n    <label class=\" toggle_4\" for=\"{{'second_'+$id}}\"><p>B</p></label>\n    <label class=\" toggle_4\" for=\"{{'third_'+$id}}\"><p>C</p></label>\n    <label class=\" toggle_4\" for=\"{{'fourth_'+$id}}\"><p>D</p></label>\n  </div>\n  \n</div>\n\n<script>\n(function(scope) {\n    scope.incomingChange = false\n    scope.$watch('value', function(value) {\n        switch(value){\n            case 'first':{\n                $(\"#slider_\"+scope.$id).css(\"left\", \"3px\")\n                break\n            }\n            case 'second':{\n                $(\"#slider_\"+scope.$id).css(\"left\", \"25%\")\n                break\n            }\n            case 'third':{\n                $(\"#slider_\"+scope.$id).css(\"left\", \"50%\")\n                break\n            }\n            case 'fourth':{\n                $(\"#slider_\"+scope.$id).css(\"left\", \"74%\")\n                break\n            }\n        }\n        if(scope.incomingChange == true){\n            scope.incomingChange = false\n            return\n        }\n        if(!value){\n            return\n        }\n        scope.send({payload: value})\n       \n    });\n    scope.$watch('msg', function(msg) {\n        if(msg){\n            if(scope.value != msg.payload){\n                scope.incomingChange = true\n                scope.value = msg.payload\n            }\n            \n        }\n    });\n})(scope);\n</script>\n  \n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":430,"y":1340,"wires":[["51fb29aa.c34508"]]},{"id":"51fb29aa.c34508","type":"debug","z":"a05ff4cd.1ae7e8","name":"Selected option","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":640,"y":1340,"wires":[]},{"id":"32d36a7c.a9e9c6","type":"inject","z":"a05ff4cd.1ae7e8","name":"Select first","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"first","payloadType":"str","x":200,"y":1300,"wires":[["5ec61ac2.9a01c4"]]},{"id":"8878f3d7.188a4","type":"inject","z":"a05ff4cd.1ae7e8","name":"Select second","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"second","payloadType":"str","x":190,"y":1340,"wires":[["5ec61ac2.9a01c4"]]},{"id":"3824b305.e6cafc","type":"inject","z":"a05ff4cd.1ae7e8","name":"Select third","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"third","payloadType":"str","x":190,"y":1380,"wires":[["5ec61ac2.9a01c4"]]},{"id":"a71786e7.e950d8","type":"inject","z":"a05ff4cd.1ae7e8","name":"Select fourth","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"fourth","payloadType":"str","x":190,"y":1420,"wires":[["5ec61ac2.9a01c4"]]},{"id":"9f2da61.3353758","type":"ui_group","name":"Flight","tab":"d6d3c358.5fb46","order":1,"disp":true,"width":"12","collapse":false},{"id":"d6d3c358.5fb46","type":"ui_tab","name":"Home","icon":"dashboard","order":1,"disabled":false,"hidden":false}]
3 Likes

Man!!! You are so much faster than me at this stuff... I tried in-line and I was struggling. I was just starting down the path of creating different classes to configure with CCS... (only I was going the long way and putting that code in each of the toggle objects). Suddenly, I see a NodeRed Forum Notice and you've beat me to it with a much cleaner solution!

I appreciate your efforts in my and others behalf. I'm going to meld this code with my mine for those who need them to be quite small and really like them a bit more predefined for control purposes.. In fact, I will publish it as a Flow for folks.... and will give you due credit.

Again thank you so much!

1 Like

No I'm not fast. I'v seen fast codding. I'm slow as the snail.
But there is other therms in coding like reliability, compatibility, openness, future-proof, reason-ability, readability, learnability etc. Those make much more sense than speed. And I fail most of the time with most of the therms I used here :smiley:
But the perfect world is still too far to reach so let's keep going.

Thank you :slight_smile:

1 Like

I created a flow featuring these nodes. It's here.

Multi-State Toggle Examples using UI-Template and CCS Overrides

Full credit is given to user hotNipi.

image_2021-02-15_133534

1 Like

Next challenge - vertical 2 column layout for 8-way with option to disable couple of them with incoming msg