Feature Request - Off - Auto - On Toggle

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

Years ago... I was doing some CompactLogix PLC programming and WonderWare Intouch Development for a Waste Water Treatment facility in a factory. I had one vessel, called the reactor, where we had 7 phases (actions is a better word) defined to perform batch processing or alternative routing... Literally... Collect, Transfer, Treat, Recirc, Divert, Discharge, Standby. (some of these phases were elaborate multi-step processes too.... Treat could have been broken up into additional Phases)...

Actually much better challenge
image
As I was lazy enough the sliding element position is percentage driven. (Of course you can fine-tune it)
But as you can see it starts to fail for a) wider layouts, b) when count of choices rises.

So it can have better solution. :slight_smile:

Hey guys,
I have announced here a first beta version.

We can do that in the near future, but I will be some time away from Node-RED soon, so will have to wait some time...

I thought I'd show you all my first implementation of the Multi-State UI-Template based Toggle Switch in a hobby project that I have. (Sonoran Desert Fish Nook YAAK - yet another aquarium controller). The NodeRED project is running in Kiosk Mode on the small 7" touch display. More than adequate to touch the toggle and move it accurately..

(ignore the fact that my Conductivity is reading zero... probe was accidently dropped and fully submerged in water...)

I also experimented with a bit more CSS overriding with the UITemplets to control Font Size and Header Spacing on the Groups (known as cards). I made this a global change on the project, so that I get a better fit for the content on the Raspberry Pi Official 800x480 LCD Touch Display this way...

5 Likes

I understand. There are a few dashboard nodes that don't play nice and do require me to either use AUTO or a 2 height grid. I can live with that. It's actually a very small number of dashboard nodes that give me grief. Some things like Tab/Group/Menu font sizes, padding, spacing, etc.. I control with a Dashboard Override script so that it plays very nice on smaller displays.

I switched to 48x48 for the 1x1 widget size... No way.... Can't get any amount of content on the screen.

I'm able to fit yours on 1 line if I mess with the height CCS property in the UI template used for the rounded corners... just the .wrapper.. 18 is about right. although I wish the Option Text would center better...

.multistate-switch-wrapper{ border-radius:15px; height:18px; } .multistate-switch-slider{ border-radius:15px; }

NewMultiStateNode

@SonoraTechnical I've moved the above post here to keep the subject matter together and provide context to your post.
Your issue is an edge case, as you are trying to use dashboard elements, whether it be the ui-template or the contrib node, in a very small widget size, whilst the other topic is about developing a new node.

Please lets confine this discussion here if possible.
Thanks.