Feature Request - Off - Auto - On Toggle

Folks,

Doing a fair amount of automation and control using Node-Red. I have a few devices that could benefit from a UI object that allows Users to set the Device ON, OFF or in AUTO .. AUto would allow other logic to take over.

I realize I could do this with multiple Switches... One being Auto/Man ... and the other On/Off... I'd need some way to dynamically indicate On/Off selections aren't available when in Auto mode...

However, a simple 3 mode selector switch would suffice. Another use did one in html on the dashboard and it turned out like this.... which is good. I even like they way he did his simple 2-state switch as well.

Is there a UI object that already does this?

1 Like

Only a suggestion:

You could use the button node and do a bit of magic around it and have it change the text displayed to cycle through the three options each time it is pressed.

I'll see if I can make something up for you now.

Not prefect, but for the sake of the idea:

[{"id":"177964a6.57d263","type":"ui_button","z":"8bb4de19.f72c88","name":"","group":"53792891.774238","order":19,"width":"3","height":"1","passthru":false,"label":"{{msg.text}}","tooltip":"","color":"","bgcolor":"","icon":"","payload":"x","payloadType":"str","topic":"","x":310,"y":1410,"wires":[["6daf649.31bf79c"]]},{"id":"6daf649.31bf79c","type":"function","z":"8bb4de19.f72c88","name":"cycle","func":"msg1 = {};\nlet c = context.get(\"counter\") || 0;\nif (c == 0)\n{\n    //  0\n    msg1.text = \"Option 1\";\n    msg.payload = \"1\";\n} else\nif (c == 1)\n{\n    //  1\n    msg1.text = \"Option 2\";\n    msg.payload = \"2\";\n} else\nif ( c == 2)\n{\n    //  2\n    msg1.text = \"Option 3\";\n    msg.payload = \"3\";\n}\nc = (c+1)%3;\ncontext.set(\"counter\",c);\n\nreturn [msg,msg1];","outputs":2,"noerr":0,"initialize":"","finalize":"","x":460,"y":1410,"wires":[["1d6b69fa.2e1a16"],["177964a6.57d263"]]},{"id":"1d6b69fa.2e1a16","type":"debug","z":"8bb4de19.f72c88","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":640,"y":1410,"wires":[]},{"id":"46e4d14b.8a3fd","type":"inject","z":"8bb4de19.f72c88","name":"Setup","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":310,"y":1370,"wires":[["6daf649.31bf79c"]]},{"id":"53792891.774238","type":"ui_group","name":"Group 7","tab":"de5134a7.f0a0d","order":4,"disp":true,"width":6},{"id":"de5134a7.f0a0d","type":"ui_tab","name":"Tab 6","icon":"dashboard","order":8}]

You can sproose up the working, but adding colour to the message going back to the button node to have it showing different colours for different modes.

1 Like

You can replicate the functionality of a three way switch using standard nodes, but I'm sure there would be better solutions :wink:

buttons

[{"id":"4a059b03.66e274","type":"ui_button","z":"c9c4eeb8.bad8e","name":"","group":"ddd690d2.0351d","order":3,"width":2,"height":1,"passthru":false,"label":"ON","tooltip":"","color":"{{text}}","bgcolor":"{{background}}","icon":"","payload":"ON","payloadType":"str","topic":"","x":180,"y":2150,"wires":[["80b32f8.0de55d","f4ad2e40.9b8b1","3eaa0333.dfbb8c"]]},{"id":"aabe418b.41968","type":"ui_button","z":"c9c4eeb8.bad8e","name":"","group":"ddd690d2.0351d","order":4,"width":2,"height":1,"passthru":false,"label":"OFF","tooltip":"","color":"{{text}}","bgcolor":"{{background}}","icon":"","payload":"OFF","payloadType":"str","topic":"","x":410,"y":2150,"wires":[["e96f62b1.9659f","e8f988c7.50a8f8","3eaa0333.dfbb8c"]]},{"id":"a3a375f7.131f78","type":"ui_button","z":"c9c4eeb8.bad8e","name":"","group":"ddd690d2.0351d","order":5,"width":2,"height":1,"passthru":false,"label":"AUTO","tooltip":"","color":"{{text}}","bgcolor":"{{background}}","icon":"","payload":"AUTO","payloadType":"str","topic":"","x":630,"y":2150,"wires":[["75e9bcf4.272a74","4fdceabf.5fa4d4","3eaa0333.dfbb8c"]]},{"id":"80b32f8.0de55d","type":"change","z":"c9c4eeb8.bad8e","name":"","rules":[{"t":"set","p":"background","pt":"msg","to":"#d4d4d4","tot":"str"},{"t":"set","p":"text","pt":"msg","to":"#ff0000","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":255,"y":2210,"wires":[["aabe418b.41968","a3a375f7.131f78"]],"l":false},{"id":"e96f62b1.9659f","type":"change","z":"c9c4eeb8.bad8e","name":"","rules":[{"t":"set","p":"background","pt":"msg","to":"#d4d4d4","tot":"str"},{"t":"set","p":"text","pt":"msg","to":"#ff0000","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":405,"y":2210,"wires":[["4a059b03.66e274","a3a375f7.131f78"]],"l":false},{"id":"75e9bcf4.272a74","type":"change","z":"c9c4eeb8.bad8e","name":"","rules":[{"t":"set","p":"background","pt":"msg","to":"#d4d4d4","tot":"str"},{"t":"set","p":"text","pt":"msg","to":"#ff0000","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":545,"y":2210,"wires":[["aabe418b.41968","4a059b03.66e274"]],"l":false},{"id":"f4ad2e40.9b8b1","type":"change","z":"c9c4eeb8.bad8e","name":"","rules":[{"t":"set","p":"background","pt":"msg","to":"#ff0000","tot":"str"},{"t":"set","p":"text","pt":"msg","to":"#000000","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":85,"y":2100,"wires":[["4a059b03.66e274"]],"l":false},{"id":"e8f988c7.50a8f8","type":"change","z":"c9c4eeb8.bad8e","name":"","rules":[{"t":"set","p":"background","pt":"msg","to":"#ff0000","tot":"str"},{"t":"set","p":"text","pt":"msg","to":"#000000","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":325,"y":2100,"wires":[["aabe418b.41968"]],"l":false},{"id":"4fdceabf.5fa4d4","type":"change","z":"c9c4eeb8.bad8e","name":"","rules":[{"t":"set","p":"background","pt":"msg","to":"#ff0000","tot":"str"},{"t":"set","p":"text","pt":"msg","to":"#000000","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":545,"y":2100,"wires":[["a3a375f7.131f78"]],"l":false},{"id":"3eaa0333.dfbb8c","type":"ui_text_input","z":"c9c4eeb8.bad8e","name":"","label":"Output","tooltip":"","group":"ddd690d2.0351d","order":6,"width":2,"height":1,"passthru":false,"mode":"text","delay":300,"topic":"","x":410,"y":2270,"wires":[[]]},{"id":"ddd690d2.0351d","type":"ui_group","name":"weather-icons-lite","tab":"117b6717.6166b9","order":1,"disp":true,"width":18,"collapse":false},{"id":"117b6717.6166b9","type":"ui_tab","name":"Example","icon":"dashboard","order":10,"disabled":false,"hidden":false}]
2 Likes

Trying_To_Learn and Paul-Reed, I was probably overthinking it. You have both suggested a sufficient solution.... In Auto Mode, I'll just need a way to read in the actual State from whatever command the device receives remotely...

Hi Peter,
Although I like the proposals from Andrew and Paul, I must admit that I also like your screenshot...
I found an example, which is a simple radiobutton styled with CSS:

3-state switch

I quickly tried it in a template node, but unfortunately I don't have the necessary CSS knowledge to get a nice look and feel in the Node-RED dashboard...
So hopefully somebody else can join your discussion and pimp it a little bit, because it would look nice also on my personal dashboard :wink:
Bart

2 Likes

No guarantees what so ever :stuck_out_tongue:

[{"id":"772297c4.f05598","type":"ui_template","z":"a05ff4cd.1ae7e8","group":"19f5ed07.08a453","name":"3-way switch","order":12,"width":"6","height":"1","format":"<div class=\"switchwrapper\">\n  <div class=\"toggle_radio\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"first_toggle\" name=\"toggle_option\">\n    <input type=\"radio\" checked class=\"toggle_option\" id=\"second_toggle\" name=\"toggle_option\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"third_toggle\" name=\"toggle_option\">\n    <label for=\"first_toggle\"><p>First</p></label>\n    <label for=\"second_toggle\"><p>Second</p></label>\n    <label for=\"third_toggle\"><p>Third</p></label>\n    <div class=\"toggle_option_slider\">\n    </div>\n</div>\n  \n<style>\n.switchwrapper {\n  margin: 0;\n}\n.toggle_radio{\n  position: relative; \n    margin: auto;\n    overflow: hidden;\n    border-radius: 50px;\n    position: relative;\n    height: 26px;\n    border: 1px solid gray;\n}\n.toggle_radio > * {\n  float: left;\n}\n.toggle_radio input[type=radio]{\n  display: none;\n}\n.toggle_radio label{\n  z-index: 0;\n  display: block;\n  width: 33%;\n  height: 20px;\n  margin: 0;\n  border-radius: 50px;\n  cursor: pointer;\n  z-index: 1;\n  text-align: center;\n}\n.toggle_option_slider{\n  width: 33%;\n  height: 20px;\n  position: absolute;\n  top: 3px;\n  border-radius: 50px;\n  transition: all .4s ease;\n}\n\n#first_toggle:checked ~ .toggle_option_slider{\n  background: rgba(255,255,255,.3);\n  left: 3px;\n}\n#second_toggle:checked ~ .toggle_option_slider{\n  background: rgba(255,255,255,.3);\n  left: 33%\n}\n#third_toggle:checked ~ .toggle_option_slider{\n  background: rgba(255,255,255,.3);\n  left: 66%;\n}\n</style>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":240,"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}]
1 Like

Wow - thats really close in such a short amount of time !!

I really like the OPs original HTML mockup

I think this has given me the motivation to get into some serious css and html - i will take what you have done in your example and start playing with it i think

Craig

Don't Think Twice, It's All Right
-- Bob Dylan

The .toggle_radio label misses the line-height: 18px to get text vertically aligned

Fixed version

[{"id":"772297c4.f05598","type":"ui_template","z":"a05ff4cd.1ae7e8","group":"19f5ed07.08a453","name":"3-way switch","order":12,"width":"6","height":"1","format":"<div class=\"switchwrapper\">\n  <div class=\"toggle_radio\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"first_toggle\" name=\"toggle_option\">\n    <input type=\"radio\" checked class=\"toggle_option\" id=\"second_toggle\" name=\"toggle_option\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"third_toggle\" name=\"toggle_option\">\n    <label for=\"first_toggle\"><p>First</p></label>\n    <label for=\"second_toggle\"><p>Second</p></label>\n    <label for=\"third_toggle\"><p>Third</p></label>\n    <div class=\"toggle_option_slider\">\n    </div>\n</div>\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: 26px;\n    border: 1px solid gray;\n    background: #1111116b;\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: 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;\n}\n.toggle_option_slider{\n  width: 33%;\n  height: 20px;\n  position: absolute;\n  top: 3px;\n  border-radius: 50px;\n  transition: all .4s ease;\n}\n\n#first_toggle:checked ~ .toggle_option_slider{\n  background: rgba(255,255,255,.3);\n  left: 3px;\n}\n#second_toggle:checked ~ .toggle_option_slider{\n  background: rgba(255,255,255,.3);\n  left: 33%\n}\n#third_toggle:checked ~ .toggle_option_slider{\n  background: rgba(255,255,255,.3);\n  left: 66%;\n}\n</style>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":240,"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}]

Looks like this for me
image

3 Likes

Morning @hotNipi,

I didn't want to mention you explicit, but I was hoping: please god, let hotnipi read this discussion :rofl:

That looks very nice... Thank you so much !!!
This is ideally e.g. to set the room temperature to ON/OFF/AUTO...

I have taken the liberty to add some stuff to your flow:

  • There was a div element not closed (also not in the original cssdeck article):

    image

  • I have added a click event handler to every radio button, to send an output message when a button is clicked.

  • I have added a watch for incoming messages, to be able to set the initial value of the switch.

The updated flow:

image

[{"id":"772297c4.f05598","type":"ui_template","z":"2b6f5d19.202242","group":"19f5ed07.08a453","name":"3-way switch","order":12,"width":"6","height":"1","format":"<div class=\"switchwrapper\">\n  <div class=\"toggle_radio\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"first_toggle\" name=\"toggle_option\" ng-click=\"send({payload: 'first'})\">\n    <input type=\"radio\" checked class=\"toggle_option\" id=\"second_toggle\" name=\"toggle_option\" ng-click=\"send({payload: 'second'})\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"third_toggle\" name=\"toggle_option\" ng-click=\"send({payload: 'third'})\">\n    <label for=\"first_toggle\"><p>First</p></label>\n    <label for=\"second_toggle\"><p>Second</p></label>\n    <label for=\"third_toggle\"><p>Third</p></label>\n    <div class=\"toggle_option_slider\">\n    </div>\n  </div>\n</div>\n\n<script>\n(function(scope) {\n    // watch msg object from Node-RED\n    scope.$watch('msg', function(msg) {\n        // Select one of the 3 radiobuttons, based on the msg.payload value\n        switch(msg.payload) {\n            case \"first\":\n                document.getElementById(\"first_toggle\").checked = true;\n                break;\n            case \"second\":\n                document.getElementById(\"second_toggle\").checked = true;\n                break;\n            case \"third\":\n                document.getElementById(\"third_toggle\").checked = true;\n                break;\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: 26px;\n    border: 1px solid gray;\n    background: #1111116b;\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: 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;\n}\n.toggle_option_slider{\n  width: 33%;\n  height: 20px;\n  position: absolute;\n  top: 3px;\n  border-radius: 50px;\n  transition: all .4s ease;\n}\n\n#first_toggle:checked ~ .toggle_option_slider{\n  background: rgba(255,255,255,.3);\n  left: 3px;\n}\n#second_toggle:checked ~ .toggle_option_slider{\n  background: rgba(255,255,255,.3);\n  left: 33%\n}\n#third_toggle:checked ~ .toggle_option_slider{\n  background: rgba(255,255,255,.3);\n  left: 66%;\n}\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":610,"y":1920,"wires":[["79fb7910.3efa38"]]},{"id":"79fb7910.3efa38","type":"debug","z":"2b6f5d19.202242","name":"Selected option","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":800,"y":1920,"wires":[]},{"id":"a4764f08.36e08","type":"inject","z":"2b6f5d19.202242","name":"Select first","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"first","payloadType":"str","x":420,"y":1880,"wires":[["772297c4.f05598"]]},{"id":"df57bcc3.02bc4","type":"inject","z":"2b6f5d19.202242","name":"Select second","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"second","payloadType":"str","x":410,"y":1920,"wires":[["772297c4.f05598"]]},{"id":"80056f30.c386d","type":"inject","z":"2b6f5d19.202242","name":"Select third","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"third","payloadType":"str","x":410,"y":1960,"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}]

P.S. I have not tested this yet, but does something like this work for all Node-RED themes or is it fixed colors?

2 Likes

What needs to be changed to allow 2 toggles in the same workspace?

3way

CSS stuff for theme support. Not all can be done with quick changes ....

<div class="switchwrapper">
  <div class="toggle_radio">
      
    <input type="radio" class="toggle_option" id="first_toggle" name="toggle_option" ng-click="send({payload: 'first'})">
    <input type="radio" checked class="toggle_option" id="second_toggle" name="toggle_option" ng-click="send({payload: 'second'})">
    <input type="radio" class="toggle_option" id="third_toggle" name="toggle_option" ng-click="send({payload: 'third'})">
    <div class="toggle_option_slider"></div>
    <label for="first_toggle"><p>First</p></label>
    <label for="second_toggle"><p>Second</p></label>
    <label for="third_toggle"><p>Third</p></label>
    
    
    
  </div>
  
</div>

<script>
(function(scope) {
    // watch msg object from Node-RED
    scope.$watch('msg', function(msg) {
        // Select one of the 3 radiobuttons, based on the msg.payload value
        switch(msg.payload) {
            case "first":
                document.getElementById("first_toggle").checked = true;
                break;
            case "second":
                document.getElementById("second_toggle").checked = true;
                break;
            case "third":
                document.getElementById("third_toggle").checked = true;
                break;
        }
    });
})(scope);
</script>
  
<style>
.switchwrapper {
  margin: auto 0;
}
.toggle_radio{
  position: relative; 
    margin: auto;
    overflow: hidden;
    border-radius: 50px;
    position: relative;
    height: 26px;
    border: 1px solid var(--nr-dashboard-groupBorderColor);
    background: var(--nr-dashboard-widgetBgndColor);
}
.toggle_radio > * {
  float: left;
}
.toggle_radio input[type=radio]{
  display: none;
}
.toggle_radio label{
  display: block;
  width: 33%;
  height: 20px;
  margin: 0;
  line-height :18px;
  border-radius: 50px;
  cursor: pointer;
  z-index: 1;
  text-align: center;
}
.toggle_radio label >p {
    background:transparent !important;
}
.toggle_option_slider{
  width: 33%;
  height: 20px;
  position: absolute;
  top: 3px;
  border-radius: 50px;
  transition: all .4s ease;
  z-index:0;
}

#first_toggle:checked ~ .toggle_option_slider{
  background: var(--nr-dashboard-widgetColor);
  opacity:0.4;
  left: 3px;
}
#second_toggle:checked ~ .toggle_option_slider{
  background: var(--nr-dashboard-widgetColor);
  opacity:0.4;
  left: 33%
}
#third_toggle:checked ~ .toggle_option_slider{
  background: var(--nr-dashboard-widgetColor);
  opacity:0.4;
  left: 66%;
}
</style>
1 Like

To support multiple of them, yeah, elements must be created to have unique ids for scope and the state change operation should probably be moved int scope watcher where css changes then applied via javascript call.

1 Like

Multiple support.

[{"id":"772297c4.f05598","type":"ui_template","z":"a05ff4cd.1ae7e8","group":"19f5ed07.08a453","name":"3-way switch","order":12,"width":"6","height":"1","format":"<div id=\"{{'swc_'+$id}}\" class=\"switchwrapper\">\n  <div class=\"toggle_radio\">\n      \n    <input type=\"radio\" class=\"toggle_option\" id=\"{{'first_'+$id}}\" name=\"toggle_option\" ng-click=\"send({payload: 'first'})\" ng-model=\"value\" value=\"first\">\n    <input type=\"radio\" checked class=\"toggle_option\" id=\"{{'second_'+$id}}\" name=\"toggle_option\" ng-click=\"send({payload: 'second'})\" ng-model=\"value\" value=\"second\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"{{'third_'+$id}}\" name=\"toggle_option\" ng-click=\"send({payload: 'third'})\" ng-model=\"value\" value=\"third\">\n    <div id=\"{{'slider_'+$id}}\" class=\"toggle_option_slider\"></div>\n    <label for=\"{{'first_'+$id}}\"><p>First</p></label>\n    <label for=\"{{'second_'+$id}}\"><p>Second</p></label>\n    <label for=\"{{'third_'+$id}}\"><p>Third</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 '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           \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 \"first\":\n                $(\"#first_\"+scope.$id).checked = true;\n                break;\n            case \"second\":\n                $(\"#second_\"+scope.$id).checked = true;\n                break;\n            case \"third\":\n                $(\"#third_\"+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: 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_radio label{\n  display: block;\n  width: 33%;\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  width: 33%;\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\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":390,"y":920,"wires":[["79fb7910.3efa38"]]},{"id":"79fb7910.3efa38","type":"debug","z":"a05ff4cd.1ae7e8","name":"Selected option","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":580,"y":940,"wires":[]},{"id":"a4764f08.36e08","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":900,"wires":[["772297c4.f05598"]]},{"id":"df57bcc3.02bc4","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":940,"wires":[["772297c4.f05598"]]},{"id":"80056f30.c386d","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":980,"wires":[["772297c4.f05598"]]},{"id":"afa57baa.600738","type":"ui_template","z":"a05ff4cd.1ae7e8","group":"19f5ed07.08a453","name":"3-way switch","order":12,"width":"6","height":"1","format":"<div id=\"{{'swc_'+$id}}\" class=\"switchwrapper\">\n  <div class=\"toggle_radio\">\n      \n    <input type=\"radio\" class=\"toggle_option\" id=\"{{'first_'+$id}}\" name=\"toggle_option\" ng-click=\"send({payload: 'first'})\" ng-model=\"value\" value=\"first\">\n    <input type=\"radio\" checked class=\"toggle_option\" id=\"{{'second_'+$id}}\" name=\"toggle_option\" ng-click=\"send({payload: 'second'})\" ng-model=\"value\" value=\"second\">\n    <input type=\"radio\" class=\"toggle_option\" id=\"{{'third_'+$id}}\" name=\"toggle_option\" ng-click=\"send({payload: 'third'})\" ng-model=\"value\" value=\"third\">\n    <div id=\"{{'slider_'+$id}}\" class=\"toggle_option_slider\"></div>\n    <label for=\"{{'first_'+$id}}\"><p>First</p></label>\n    <label for=\"{{'second_'+$id}}\"><p>Second</p></label>\n    <label for=\"{{'third_'+$id}}\"><p>Third</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 '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           \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 \"first\":\n                $(\"#first_\"+scope.$id).checked = true;\n                break;\n            case \"second\":\n                $(\"#second_\"+scope.$id).checked = true;\n                break;\n            case \"third\":\n                $(\"#third_\"+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: 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_radio label{\n  display: block;\n  width: 33%;\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  width: 33%;\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\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":370,"y":1060,"wires":[["3670a92a.306666"]]},{"id":"3670a92a.306666","type":"debug","z":"a05ff4cd.1ae7e8","name":"Selected option","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":560,"y":1080,"wires":[]},{"id":"5700ef9e.53e3","type":"inject","z":"a05ff4cd.1ae7e8","name":"Select first","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"first","payloadType":"str","x":180,"y":1040,"wires":[["afa57baa.600738"]]},{"id":"8a1e37d7.526558","type":"inject","z":"a05ff4cd.1ae7e8","name":"Select second","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"second","payloadType":"str","x":170,"y":1080,"wires":[["afa57baa.600738"]]},{"id":"1d487981.905646","type":"inject","z":"a05ff4cd.1ae7e8","name":"Select third","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"third","payloadType":"str","x":170,"y":1120,"wires":[["afa57baa.600738"]]},{"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}]
1 Like

Bit off-topic, but what is the best practice for node developers: is something like the border-radius something that should be specified in the node. Or does this needs to be done in the theme, so all types of buttons look the same. Or do we need to use CSS variables in our code that are defined by the theme?

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