# Wire Free Keypad Radio Button Problem

**URL:** https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386
**Category:** Dashboard
**Created:** [26 September 2020 17:06 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386 "2020-09-26T17:06:54Z")
**Posts on this page:** 14
**Page:** 1

<div class="post-metadata">

### Author: ![ozpos](https://avatars.discourse-cdn.com/v4/letter/o/278dde/32.png) [@ozpos](https://discourse.nodered.org/u/ozpos)
#### Post date: [26 September 2020 17:06 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/1 "2020-09-26T17:06:54Z")

</div>

Hi All, I have adapted some code created by Steve-mcl.  
[https://discourse.nodered.org/t/adding-output-function-to-buttons-designed-in-template-node/25591/65](https://discourse.nodered.org/t/adding-output-function-to-buttons-designed-in-template-node/25591/65) to implement radio buttons but with unpredictable results. I chose this method because large keypads have way too much wiring.

![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/7/e/7ede68d672e518cfcd70af219526c4b82d6f1c50.png) Buttons 1 - 3 are toggle buttons and work fine:-

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/3/b/3b2743bddbeffc3a6db928dffb0e704a455b87d4.png)

The radio buttons 4, 5 and 6 however output msgs when they should not.

What I am looking for is a payload=1 for the button click and a payload=0 for any radio button that was not clicked. Both with topic= (in this case 4,5 or 6).

I cannot seem to test for radio.states[] !== null

Any help would be greatly appreciated.

---

<div class="post-metadata">

### Author: ![zenofmud](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/zenofmud/32/316_2.png) [@zenofmud](https://discourse.nodered.org/u/zenofmud)
#### Post date: [26 September 2020 17:16 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/2 "2020-09-26T17:16:02Z")

</div>

Please provide your flow since have adapted it

---

<div class="post-metadata">

### Author: ![ozpos](https://avatars.discourse-cdn.com/v4/letter/o/278dde/32.png) [@ozpos](https://discourse.nodered.org/u/ozpos)
#### Post date: [26 September 2020 17:27 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/3 "2020-09-26T17:27:35Z")

</div>

Do I just paste my clipboard ?

---

<div class="post-metadata">

### Author: ![ozpos](https://avatars.discourse-cdn.com/v4/letter/o/278dde/32.png) [@ozpos](https://discourse.nodered.org/u/ozpos)
#### Post date: [26 September 2020 17:29 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/4 "2020-09-26T17:29:51Z")

</div>

```auto
[{"id":"defd53ac.d6c42","type":"tab","label":"Flow 10","disabled":false,"info":""},{"id":"bafac9ff.557788","type":"ui_template","z":"defd53ac.d6c42","group":"32d37377.76b76c","name":"1","order":2,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype=\"toggle\"\n data-topic=\"1\"\n data-payload=\"1\">1\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":170,"y":80,"wires":[[]]},{"id":"d72df766.7d9038","type":"ui_template","z":"defd53ac.d6c42","group":"32d37377.76b76c","name":"2","order":3,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype=\"toggle\"\n data-topic=\"2\"\n data-payload=\"2\">2\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":290,"y":80,"wires":[[]]},{"id":"c293015e.c57f3","type":"ui_template","z":"defd53ac.d6c42","group":"32d37377.76b76c","name":"3","order":4,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype=\"toggle\"\n data-topic=\"3\"\n data-payload=\"3\">3\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":410,"y":80,"wires":[[]]},{"id":"8b99bf3c.c2ee1","type":"ui_template","z":"defd53ac.d6c42","group":"32d37377.76b76c","name":"4","order":5,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype='radio'\n data-topic=\"4\"\n data-payload=\"4\">4\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":170,"y":120,"wires":[[]]},{"id":"cae168c.3c6a798","type":"ui_template","z":"defd53ac.d6c42","group":"32d37377.76b76c","name":"5","order":6,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype=\"radio\"\n data-topic=\"5\"\n data-payload=\"5\">5\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":290,"y":120,"wires":[[]]},{"id":"d1f86cad.cd149","type":"ui_template","z":"defd53ac.d6c42","group":"32d37377.76b76c","name":"6","order":7,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype=\"radio\"\n data-topic=\"6\"\n data-payload=\"6\">6\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":410,"y":120,"wires":[[]]},{"id":"aeaabcf8.c7b6","type":"ui_template","z":"defd53ac.d6c42","group":"32d37377.76b76c","name":"script for all buttons with class remote-button","order":8,"width":"1","height":"1","format":"<div>\n<!--diliberately emtpy - only need the script below -->\n</div>\n\n<script>\n\n(function($scope) {\n//debugger\n\n //cause a small delay while things load \n //ideally this would be an init event or on all parts of document loaded\n //(may not be necessary!)\n setTimeout(function() {\n //debugger\n $scope.init();\n },100);\n \n var BUTTON_CLASS = \".remote-button\";\n var radio = {\"state\": 0,\"bits\": 0, \"states\": [], \"btns\": []};\n var buts =[];\n var states =[0,0,0,0,0,0,0,0,0,0,0];\n /**\n * Initialise all buttons with class BUTTON_CLASS\n */\n $scope.init = function () {\n //debugger\n console.log(\"$scope.init called. Adding event handlers to all buttons with class '\" + BUTTON_CLASS + \"'.\");\n var clickButtons = $(BUTTON_CLASS + \":not([data-buttontype='radio'])\") \n clickButtons.click(function(e){\n //debugger\n var btn = $(this)\n var type = btn.data(\"buttontype\");//get the button type from attribute data-buttontype=\"xxxxx\"\n var topic = btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n var payload = btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n \n if(type == \"toggle\"){\n var newPayload = payload == 1 ? 0 : 1;\n setButtonState(btn,newPayload)\n $scope.send({\"type\": type, \"topic\":topic,\"payload\":newPayload, \"event\": \"click\"})\n } else {\n $scope.send({\"type\": type, \"topic\":topic,\"payload\":payload})\n }\n });\n \n var radioButtons = $(BUTTON_CLASS + \":not([data-buttontype='toggle'])\")\n radioButtons.click(function(e){\n //debugger\n var btn = $(this);\n var type = btn.data(\"buttontype\");//get the button type from attribute data-buttontype=\"xxxxx\"\n var topic = btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n var payload = btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n\n radio.btns[topic]=topic;\n radio.bits|=1<<parseInt(topic,10);\n radio.state=1<<parseInt(topic,10);\n radio.states[topic]=1;\n if(type == \"radio\"){\n var newPayload = 1;\n$scope.send({\"type\":type,\"btns_len\":radio.btns.length, \"btns\": Object.keys(radio.btns), \"states\": Object.keys(radio.states)});\n \n setButtonState(btn,newPayload); //$btn.data(\"payload\", state)\n for(var bt=0;bt < radio.btns.length;bt++){\n if(radio.states[bt] !== null) {\n if( bt!==topic){\n radio.states[bt]=0;\n $scope.send({\"type\":type,\"radio\":bt,\"payload\":0, \"event\": \"click\"})\n } else {\n $scope.send({\"type\":type,\"radio\":topic,\"payload\":newPayload, \"event\": \"click\"});\n }\n } else {\n $scope.send({\"type\":type,\"skip_null\":bt,\"payload\":payload, \"btns\": Object.keys(radio.btns), \"states\": Object.keys(radio.states)});\n }\n }\n } else {\n $scope.send({\"type\": type, \"topic\":topic,\"payload\":payload})\n }\n }); \n };\n\n //watch for node-red msgs\n $scope.$watch('msg', function(msg) {\n //debugger\n if(!msg){ //if no msg \n console.log(\"$scope.$watch('msg', ...) - msg is empty\");\n return;\n }\n if(!msg.topic){ //if no topic set found\n console.log(\"msg.topic is empty - cannot match this to any button\")\n return; //stop processing!\n }\n\n var buttonSelector = BUTTON_CLASS + \"[data-topic='\" + msg.topic + \"']\" \n var $btn = $(buttonSelector);//get the button\n \n if(!$btn.length){ //if no button found\n console.log(buttonSelector + \" not found - cannot set state\")\n return; //stop processing!\n }\n \n if($btn.length > 1){ //if MORE than one button found\n console.log(buttonSelector + \" found more than 1 button - is this intended? Do you have the same data-topic set on multiple buttons?\")\n }\n \n //see if this is a command - if so, process the command\n if(typeof msg.payload === \"object\" && msg.payload.command){\n processCommand($btn, msg.payload)\n return;\n } \n \n if($btn.data(\"buttontype\") === \"radio\"){\n //set clicked and clear others\n if(msg.payload == \"1\"){\n setButtonState($btn, 1);\n } else if(msg.payload == \"0\"){\n setButtonState($btn, 0);\n } else {\n console.log(\"Invalid radio value in msg.payload, cannot set \" + buttonSelector + \". Ensure msg.payload is either 0 or 1\") \n }\n }\n if($btn.data(\"buttontype\") === \"toggle\"){\n if(msg.payload == \"1\"){\n setButtonState($btn, 1);\n } else if(msg.payload == \"0\"){\n setButtonState($btn, 0);\n } else {\n console.log(\"Invalid toggle value in msg.payload, cannot set \" + buttonSelector + \". Ensure msg.payload is either 0 or 1\") \n }\n }\n\n }); \n \n /** \n * helper function to set the correct icon & update the \"data-payload\" memory \n */\n function setButtonState($btn, state){\n \n $btn.data(\"payload\", state);//set data-payload to new state value (used as a memory)\n \n //determine the opposite state\n var oppositeState;\n if(state == \"1\" || state === 1){\n state = 1; //normalise to a number\n oppositeState = 0;\n } else {\n state = 0; //normalise to a number\n oppositeState = 1;\n }\n \n var $icon = $btn.find(\"i\"); //get the <i> element\n if(!$icon.length){\n $icon = $btn.find(\"span\"); //get the <span> element instead!\n }\n if(!$icon.length){\n console.log(\"<i> or <span> not found inside button - cant toggle the icon!\")\n return;//exit this function - nothing to toggle!\n }\n \n //get the old icon and new icon names\n var oldIcon = $btn.data(\"icon\" + oppositeState); //get icon1 or icon2 depending on oppositeState\n var newIcon = $btn.data(\"icon\" + state); //get icon1 or icon2 depending on newPayload\n \n //if we have newIcon and an actual DOM element ($icon) - update it...\n if(newIcon && $icon.length){\n if(newIcon.includes(\"fa-\")){ \n $icon.removeClass(oldIcon).addClass(newIcon); // fontawesome\n } else { \n $icon.text(newIcon); // MDI\n }\n }\n }\n \n function processCommand($btn, payload){\n //first check payload is correct format...\n if(!payload || !payload.command || !payload.value){\n console.log(\"Cannot process command. Expected a payload object with .command and .value. \")\n }\n var cmd = payload.command.trim();\n switch(cmd){\n case \"addClass\":\n $btn.addClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n case \"toggleClass\":\n $btn.toggleClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n case \"removeClass\":\n $btn.removeClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n default:\n console.log(\"command '\" + payload.command + \"' is not supported\")\n }\n } \n \n /** \n * helper function to determine a value is REALLY a number \n */\n function isNumeric(n){\n if(n === \"\") return false;\n if(n === true || n === false) return false;\n return !isNaN(parseFloat(n)) && isFinite(n);\n }\n \n\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":310,"y":260,"wires":[["18bd4c3f.f94a34"]]},{"id":"18bd4c3f.f94a34","type":"debug","z":"defd53ac.d6c42","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":370,"y":200,"wires":[]},{"id":"372a906b.c1d2a","type":"inject","z":"defd53ac.d6c42","name":"toggleClass blinking, on 1","topic":"1","payload":"{\"command\":\"toggleClass\",\"value\":\"blinking\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":210,"y":320,"wires":[["aeaabcf8.c7b6"]]},{"id":"3657de47.b04622","type":"inject","z":"defd53ac.d6c42","name":"toggleClass disabled, on 5","topic":"5","payload":"{\"command\":\"toggleClass\",\"value\":\"disabled\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":210,"y":360,"wires":[["aeaabcf8.c7b6"]]},{"id":"cf930dc5.b063","type":"ui_template","z":"defd53ac.d6c42","group":"32d37377.76b76c","name":"CSS only ","order":1,"width":0,"height":0,"format":"<style id=\"remote-buttons\">\n :root {\n --dashboard-unit-width: 48px;\n --dashboard-unit-height: 48px;\n --remote-button-background: black;\n --remote-button-foreground: #cccccc;\n }\n .nr-dashboard-template {\n padding: 0px;\n }\n \n .remote-button.disabled{\n cursor: not-allowed;\n pointer-events: none;\n color: #aaaaaa !important;\n }\n \n .remote-button:not([disabled]):hover{\n background-color: #232323 !important;\n }\n\n /* This is the normal button definition */\n .remote-button{\n background-color: var(--remote-button-background) !important;\n color: var(--remote-button-foreground) !important;\n height: var(--dashboard-unit-height);\n width: 100%;\n border-radius: 10px;\n font-size:1.0em;\n font-weight:normal;\n margin: 0;\n min-height: 36px;\n min-width: unset;\n line-height: unset;\n }\n /* This is a sub-set which is invoked by */\n /* <md-button class=\"md-button remote-button bigger\"> */\n /* note the (space) \"bigger\" at the end. */\n .remote-button.bigger{\n font-weight:bold;\n font-size:1.5em;\n }\n /* This is for buttons with a lot of text. `font-size:0.7em` */\n /* makes the font 70% normal size */\n .remote-button.small{\n font-size:0.7em;\n }\n /* This is for buttons with just icons, to upsize the size */\n /* of the icon with the line: */\n /* <i class=\"fa fa-fw fa-plus remote-icon\"> in the other node */\n .remote-icon{\n font-size:2.0em;\n }\n /* This is the same as the other one, but it makes the icon smaller */\n .remote-iconS{\n font-size:0.5em;\n }\n\n .remote-button.black{\n background-color: var(--remote-button-background) !important;\n color: var(--remote-button-foreground) !important;\n }\n\n .remote-button.red{\n background-color: red !important;\n color: var(--remote-button-foreground) !important;\n }\n .remote-button.red:not([disabled]):hover{\n background-color: orange !important;\n }\n\n .remote-button.blinking{\n \tanimation:blinkingAnim1 0.8s infinite;\n }\n @keyframes blinkingAnim1{\n \t0%{\t\tbackground-color: var(--remote-button-background); color:var(--remote-button-foreground);}\n \t50%{\tbackground-color: var(--remote-button-foreground); color:var(--remote-button-background);}\n \t100%{\tbackground-color: var(--remote-button-background);\tcolor:var(--remote-button-foreground);}\n } \n \n .remote-button.red.blinking{\n \tanimation:blinkingAnim2 0.8s infinite;\n }\n @keyframes blinkingAnim2{\n \t0%{\t\tbackground-color: red; color:#cccccc;}\n \t50%{\tbackground-color: #cccccc; color:red;}\n \t100%{\tbackground-color: red;\tcolor:#cccccc;}\n } \n</style>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"global","x":180,"y":20,"wires":[[]]},{"id":"32d37377.76b76c","type":"ui_group","z":"","name":"HOME","tab":"80445fec.6981","order":1,"disp":true,"width":3,"collapse":false},{"id":"80445fec.6981","type":"ui_tab","z":"","name":"TEST","icon":"dashboard","order":3,"disabled":false,"hidden":false}]

```

---

<div class="post-metadata">

### Author: ![zenofmud](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/zenofmud/32/316_2.png) [@zenofmud](https://discourse.nodered.org/u/zenofmud)
#### Post date: [28 September 2020 23:18 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/5 "2020-09-28T23:18:47Z")

</div>

Just looked at your flow and it would seem that you made a lot of changes in the `script for all buttons with class remote-button` you got from @Steve-Mcl. I'm not going to have the time to try to debug that script for you. I can suggest adding some debugging like:

```auto
console.log("debug02");
console.log(msg);
console.log("============");

```

that will show you the values the console log or maybe Steve will offer a suggestion.

---

<div class="post-metadata">

### Author: ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)
#### Post date: [29 September 2020 09:03 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/6 "2020-09-29T09:03:42Z")

</div>

@ozpos try this...

![2020-09-29_10-00-40](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/0/e/0e434ef7898f186dce9d3c96260074994efd527d.gif)

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/2/c/2c3fc5359d791d8362410c3cb7e4b5a85099c772.png)

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/1/f/1f5c67bd42cb1c20a97b937200a2a633f75af07e.png)

alternatively, if you dont want `payload: 0` messages send for cleared radio buttons in the group, set `var SEND_0_FOR_CLEARED_RADIO_BUTTONS = false;` inside `script for all buttons with class remote-button`

 ![image](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/5/0/5048d316bb20223f0161d354f0fe3dcc5ce6a2c3.png)

NOTES: Set `data-group` to the same values for all buttons in a group. e.g...

```auto
<!-- radio group "group1" button "4"-->
<div>
   <md-button class="md-button remote-button bigger"
    data-buttontype='radio'
    data-group='group1'
    data-topic="4"
    data-icon0="fa fa-circle-o"
    data-icon1="fa fa-check-circle-o"
    data-payload="4">
       <i class="fa fa-circle-o"></i> 4
   </md-button>
</div>

<!-- radio group "group1" button "5"-->
<div>
   <md-button class="md-button remote-button bigger"
    data-buttontype='radio'
    data-group='group1'
    data-topic="5"
    data-icon0="fa fa-circle-o"
    data-icon1="fa fa-check-circle-o"
    data-payload="5">
       <i class="fa fa-circle-o"></i> 5
   </md-button>
</div>

```

There should be no need to change any scrip (unless you add new features or change the logic) - just add your buttons with the data- values set appropriately

the flow...

```auto
[{"id":"92c9aee1.98f3e","type":"ui_template","z":"529f171e.8d6188","group":"297cab81.d2f624","name":"1","order":2,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype=\"toggle\"\n data-topic=\"1\"\n data-payload=\"1\">1\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":170,"y":60,"wires":[[]]},{"id":"4a5c9a28.2108a4","type":"ui_template","z":"529f171e.8d6188","group":"297cab81.d2f624","name":"2","order":3,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype=\"toggle\"\n data-topic=\"2\"\n data-payload=\"2\">2\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":290,"y":60,"wires":[[]]},{"id":"cf08802f.6ca8e","type":"ui_template","z":"529f171e.8d6188","group":"297cab81.d2f624","name":"3","order":4,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype=\"toggle\"\n data-topic=\"3\"\n data-payload=\"3\">3\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":410,"y":60,"wires":[[]]},{"id":"f2fdc6ef.603f28","type":"ui_template","z":"529f171e.8d6188","group":"297cab81.d2f624","name":"4","order":5,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype='radio'\n data-radiogroup='group1'\n data-topic=\"4\"\n data-icon0=\"fa fa-circle-o\"\n data-icon1=\"fa fa-check-circle-o\"\n data-payload=\"4\">\n <i class=\"fa fa-circle-o\"></i> 4\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":170,"y":100,"wires":[[]]},{"id":"f894a78a.cb6a68","type":"ui_template","z":"529f171e.8d6188","group":"297cab81.d2f624","name":"5","order":6,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype='radio'\n data-radiogroup='group1'\n data-topic=\"5\"\n data-icon0=\"fa fa-circle-o\"\n data-icon1=\"fa fa-check-circle-o\"\n data-payload=\"5\">\n <i class=\"fa fa-circle-o\"></i> 5\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":290,"y":100,"wires":[[]]},{"id":"5c97caae.a0c5b4","type":"ui_template","z":"529f171e.8d6188","group":"297cab81.d2f624","name":"6","order":7,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype='radio'\n data-radiogroup='group1'\n data-topic=\"6\"\n data-icon0=\"fa fa-circle-o\"\n data-icon1=\"fa fa-check-circle-o\"\n data-payload=\"6\">\n <i class=\"fa fa-circle-o\"></i> 6\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":410,"y":100,"wires":[[]]},{"id":"1a6db4c.4a2214b","type":"ui_template","z":"529f171e.8d6188","group":"297cab81.d2f624","name":"script for all buttons with class remote-button","order":11,"width":"1","height":"1","format":"<div>\n<!--diliberately emtpy - only need the script below -->\n</div>\n\n<script>\n\n(function($scope) {\n//debugger\n\n //cause a small delay while things load \n //ideally this would be an init event or on all parts of document loaded\n //(may not be necessary!)\n setTimeout(function() {\n //debugger\n $scope.init();\n },100);\n \n \n //SETTINGS...\n var BUTTON_CLASS = \".remote-button\";\n var SEND_0_FOR_CLEARED_RADIO_BUTTONS = false;\n \n \n /**\n * Initialise all buttons with class BUTTON_CLASS\n */\n $scope.init = function () {\n //debugger\n console.log(\"$scope.init called. Adding event handlers to all buttons with class '\" + BUTTON_CLASS + \"'.\");\n var clickButtons = $(BUTTON_CLASS) \n clickButtons.click(function(e){\n \n var btn = $(this)\n var type = btn.data(\"buttontype\");//get the button type from attribute data-buttontype=\"xxxxx\"\n var topic = btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n var payload = btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n // debugger\n if(type == \"radio\"){\n setRadioButton(btn, true);\n } else if(type == \"toggle\"){\n var state = btn.data(\"state\") || 0;//get the last state payload from attribute data-state=\"n\"\n var newPayload = state == 1 ? 0 : 1;\n setButtonState(btn,newPayload)\n var tm = {\"type\": type, \"topic\":topic,\"payload\":newPayload, \"event\": \"click\"}\n console.log(\"Sending msg for clicked toggle button\", tm)\n $scope.send(tm)\n } else {\n var sm = {\"type\": type, \"topic\":topic,\"payload\":payload, \"event\": \"click\"}\n console.log(\"Sending msg for clicked button\", tm)\n $scope.send(sm)\n }\n });\n \n };\n\n //watch for node-red msgs\n $scope.$watch('msg', function(msg) {\n debugger\n if(!msg){ //if no msg \n console.log(\"$scope.$watch('msg', ...) - msg is empty\");\n return;\n }\n if(!msg.topic){ //if no topic set found\n console.log(\"msg.topic is empty - cannot match this to any button\")\n return; //stop processing!\n }\n\n var buttonSelector = BUTTON_CLASS + \"[data-topic='\" + msg.topic + \"']\" \n var $btn = $(buttonSelector);//get the button\n \n if(!$btn.length){ //if no button found\n console.log(buttonSelector + \" not found - cannot set state\")\n return; //stop processing!\n }\n \n if($btn.length > 1){ //if MORE than one button found\n console.log(buttonSelector + \" found more than 1 button - is this intended? Do you have the same data-topic set on multiple buttons?\")\n }\n \n //see if this is a command - if so, process the command\n if(typeof msg.payload === \"object\" && msg.payload.command){\n processCommand($btn, msg.payload)\n return;\n } \n \n if($btn.data(\"buttontype\") === \"radio\"){\n setRadioButton($btn, false);\n }\n \n if($btn.data(\"buttontype\") === \"toggle\"){\n if(msg.payload == \"1\"){\n setButtonState($btn, 1);\n } else if(msg.payload == \"0\"){\n setButtonState($btn, 0);\n } else {\n console.log(\"Invalid toggle value in msg.payload, cannot set \" + buttonSelector + \". Ensure msg.payload is either 0 or 1\") \n }\n }\n\n }); \n \n /** \n * helper function to set the correct icon & update the \"data-payload\" memory \n */\n function setButtonState($btn, state){\n console.log(\"setButtonState\", $btn[0], state);\n $btn.data(\"state\", state);//set data-payload to new state value (used as a memory)\n \n //determine the opposite state\n var oppositeState;\n if(state == \"1\" || state === 1){\n state = 1; //normalise to a number\n oppositeState = 0;\n } else {\n state = 0; //normalise to a number\n oppositeState = 1;\n }\n \n var $icon = $btn.find(\"i\"); //get the <i> element\n if(!$icon.length){\n $icon = $btn.find(\"span\"); //get the <span> element instead!\n }\n if(!$icon.length){\n console.log(\"<i> or <span> not found inside button - cant toggle the icon!\")\n return;//exit this function - nothing to toggle!\n }\n \n //get the old icon and new icon names\n var oldIcon = $btn.data(\"icon\" + oppositeState); //get icon1 or icon2 depending on oppositeState\n var newIcon = $btn.data(\"icon\" + state); //get icon1 or icon2 depending on newPayload\n \n //if we have newIcon and an actual DOM element ($icon) - update it...\n if(newIcon && $icon.length){\n if(newIcon.includes(\"fa-\")){ \n $icon.removeClass(oldIcon).addClass(newIcon); // fontawesome\n } else { \n $icon.text(newIcon); // MDI\n }\n }\n }\n\n\n /**\n * helper function \n */\n function setRadioButton($btn, sendMsgs){\n var topic = $btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n var group = $btn.data(\"radiogroup\");\n var groupBtns = $(\"[data-radiogroup='\"+group+\"']\");\n var m = null;\n for(var i = 0; i < groupBtns.length; i++) {\n var rb = $(groupBtns[i]);\n if(!rb || !rb.length) continue;\n var t = rb.data(\"topic\");\n if(t == topic) continue;//skip clicked button\n setButtonState(rb,0);\n if(sendMsgs && SEND_0_FOR_CLEARED_RADIO_BUTTONS) {\n m = {\"type\": \"radio\", \"topic\":t,\"payload\":0, \"event\": \"click\"};\n console.log(\"Sending 0 payload for other radio button in group\", m)\n $scope.send(m);\n }\n }\n if(sendMsgs) {\n var p = $btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n var m = {\"type\": \"radio\", \"topic\":topic, \"payload\": p, \"event\": \"click\"};\n console.log(\"Sending msg for radio button\", m)\n $scope.send(m);\n }\n setButtonState($btn,1);//set state of this button\n setTimeout(function(){\n setButtonState($btn,1);//set state of this button \n },200) \n\n }\n \n function processCommand($btn, payload){\n //first check payload is correct format...\n if(!payload || !payload.command || !payload.value){\n console.log(\"Cannot process command. Expected a payload object with .command and .value. \")\n }\n var cmd = payload.command.trim();\n switch(cmd){\n case \"addClass\":\n $btn.addClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n case \"toggleClass\":\n $btn.toggleClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n case \"removeClass\":\n $btn.removeClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n default:\n console.log(\"command '\" + payload.command + \"' is not supported\")\n }\n } \n \n /** \n * helper function to determine a value is REALLY a number \n */\n function isNumeric(n){\n if(n === \"\") return false;\n if(n === true || n === false) return false;\n return !isNaN(parseFloat(n)) && isFinite(n);\n }\n \n\n})(scope);\n</script>","storeOutMessages":false,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":290,"y":260,"wires":[["c2acb13f.786fd"]]},{"id":"c2acb13f.786fd","type":"debug","z":"529f171e.8d6188","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":570,"y":260,"wires":[]},{"id":"160fc7aa.f58908","type":"inject","z":"529f171e.8d6188","name":"choose radio btn 4","topic":"4","payload":"4","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":190,"y":420,"wires":[["1a6db4c.4a2214b"]]},{"id":"fc689651.9aac88","type":"inject","z":"529f171e.8d6188","name":"toggleClass disabled, on 5","topic":"5","payload":"{\"command\":\"toggleClass\",\"value\":\"disabled\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":210,"y":360,"wires":[["1a6db4c.4a2214b"]]},{"id":"6bd18d04.018254","type":"ui_template","z":"529f171e.8d6188","group":"297cab81.d2f624","name":"CSS only ","order":1,"width":0,"height":0,"format":"<style id=\"remote-buttons\">\n :root {\n --dashboard-unit-width: 48px;\n --dashboard-unit-height: 48px;\n --remote-button-background: black;\n --remote-button-foreground: #cccccc;\n }\n .nr-dashboard-template {\n padding: 0px;\n }\n \n .remote-button.disabled{\n cursor: not-allowed;\n pointer-events: none;\n color: #aaaaaa !important;\n }\n \n .remote-button:not([disabled]):hover{\n background-color: #232323 !important;\n }\n\n /* This is the normal button definition */\n .remote-button{\n background-color: var(--remote-button-background) !important;\n color: var(--remote-button-foreground) !important;\n height: var(--dashboard-unit-height);\n width: 100%;\n border-radius: 10px;\n font-size:1.0em;\n font-weight:normal;\n margin: 0;\n min-height: 36px;\n min-width: unset;\n line-height: unset;\n }\n /* This is a sub-set which is invoked by */\n /* <md-button class=\"md-button remote-button bigger\"> */\n /* note the (space) \"bigger\" at the end. */\n .remote-button.bigger{\n font-weight:bold;\n font-size:1.5em;\n }\n /* This is for buttons with a lot of text. `font-size:0.7em` */\n /* makes the font 70% normal size */\n .remote-button.small{\n font-size:0.7em;\n }\n /* This is for buttons with just icons, to upsize the size */\n /* of the icon with the line: */\n /* <i class=\"fa fa-fw fa-plus remote-icon\"> in the other node */\n .remote-icon{\n font-size:2.0em;\n }\n /* This is the same as the other one, but it makes the icon smaller */\n .remote-iconS{\n font-size:0.5em;\n }\n\n .remote-button.black{\n background-color: var(--remote-button-background) !important;\n color: var(--remote-button-foreground) !important;\n }\n\n .remote-button.red{\n background-color: red !important;\n color: var(--remote-button-foreground) !important;\n }\n .remote-button.red:not([disabled]):hover{\n background-color: orange !important;\n }\n\n .remote-button.blinking{\n \tanimation:blinkingAnim1 0.8s infinite;\n }\n @keyframes blinkingAnim1{\n \t0%{\t\tbackground-color: var(--remote-button-background); color:var(--remote-button-foreground);}\n \t50%{\tbackground-color: var(--remote-button-foreground); color:var(--remote-button-background);}\n \t100%{\tbackground-color: var(--remote-button-background);\tcolor:var(--remote-button-foreground);}\n } \n \n .remote-button.red.blinking{\n \tanimation:blinkingAnim2 0.8s infinite;\n }\n @keyframes blinkingAnim2{\n \t0%{\t\tbackground-color: red; color:#cccccc;}\n \t50%{\tbackground-color: #cccccc; color:red;}\n \t100%{\tbackground-color: red;\tcolor:#cccccc;}\n } \n</style>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"global","x":180,"y":220,"wires":[[]]},{"id":"437c7fc6.e679f","type":"inject","z":"529f171e.8d6188","name":"choose radio btn 5","topic":"5","payload":"5","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":190,"y":460,"wires":[["1a6db4c.4a2214b"]]},{"id":"2a9fc14c.0ce35e","type":"ui_template","z":"529f171e.8d6188","group":"297cab81.d2f624","name":"7","order":8,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype='radio'\n data-radiogroup='group2'\n data-topic=\"7\"\n data-icon0=\"fa fa-circle-o\"\n data-icon1=\"fa fa-check-circle-o\"\n data-payload=\"7\">\n <i class=\"fa fa-circle-o\"></i> 7\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":170,"y":140,"wires":[[]]},{"id":"6e494e77.959f8","type":"ui_template","z":"529f171e.8d6188","group":"297cab81.d2f624","name":"8","order":9,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype='radio'\n data-radiogroup='group2'\n data-topic=\"8\"\n data-icon0=\"fa fa-circle-o\"\n data-icon1=\"fa fa-check-circle-o\"\n data-payload=\"8\">\n <i class=\"fa fa-circle-o\"></i> 8\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":290,"y":140,"wires":[[]]},{"id":"66382246.76e5ec","type":"ui_template","z":"529f171e.8d6188","group":"297cab81.d2f624","name":"9","order":10,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-buttontype='radio'\n data-radiogroup='group2'\n data-topic=\"9\"\n data-icon0=\"fa fa-circle-o\"\n data-icon1=\"fa fa-check-circle-o\"\n data-payload=\"9\">\n <i class=\"fa fa-circle-o\"></i> 9\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":410,"y":140,"wires":[[]]},{"id":"e8e853c1.498f5","type":"comment","z":"529f171e.8d6188","name":"radiogroup1","info":"","x":530,"y":100,"wires":[]},{"id":"371cfb8d.0cd664","type":"comment","z":"529f171e.8d6188","name":"radiogroup2","info":"","x":530,"y":140,"wires":[]},{"id":"297cab81.d2f624","type":"ui_group","z":"","name":"HOME","tab":"cb20aab3.b85178","order":1,"disp":true,"width":3,"collapse":false},{"id":"cb20aab3.b85178","type":"ui_tab","z":"","name":"TEST","icon":"dashboard","order":3,"disabled":false,"hidden":false}]

```

---

<div class="post-metadata">

### Author: ![ozpos](https://avatars.discourse-cdn.com/v4/letter/o/278dde/32.png) [@ozpos](https://discourse.nodered.org/u/ozpos)
#### Post date: [29 September 2020 09:34 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/7 "2020-09-29T09:34:43Z")

</div>

Thank you. Did you see the fixes to the css that allow colour/disable/flash to all work simultaneously.

```auto
[{"id":"67e2f110.35bbe","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"CSS only ","order":1,"width":0,"height":0,"format":"<style>\n :root {\n --dashboard-unit-width: 48px;\n --dashboard-unit-height: 48px;\n --remote-button-background: black;\n --remote-button-foreground: #cccccc;\n }\n .nr-dashboard-template {\n padding: 0px;\n }\n \n .remote-button:not([disabled]):hover{\n background-color: #232323 !important;\n }\n\n .remote-button.red:not([disabled]) {\n background-color: #cc0000;\n color: var(--remote-button-foreground);\n }\n .remote-button.green:not([disabled]) {\n background-color: #00bb00;\n color: var(--remote-button-foreground);\n }\n \n\n /* This is the normal button definition */\n .remote-button{\n background-color: var(--remote-button-background) !important;\n color: var(--remote-button-foreground);\n height: var(--dashboard-unit-height);\n width: 100%;\n border-radius: 10px;\n font-size:1.0em;\n font-weight:normal;\n margin: 0;\n min-height: 36px;\n min-width: unset;\n line-height: unset;\n }\n /* This is a sub-set which is invoked by */\n /* <md-button class=\"md-button remote-button bigger\"> */\n /* note the (space) \"bigger\" at the end. */\n .remote-button.bigger{\n font-weight:bold;\n font-size:1.5em;\n }\n /* This is for buttons with a lot of text. `font-size:0.7em` */\n /* makes the font 70% normal size */\n .remote-button.small{\n font-size:0.7em;\n }\n /* This is for buttons with just icons, to upsize the size */\n /* of the icon with the line: */\n /* <i class=\"fa fa-fw fa-plus remote-icon\"> in the other node */\n .remote-icon{\n font-size:2.0em;\n }\n /* This is the same as the other one, but it makes the icon smaller */\n .remote-iconS{\n font-size:0.5em;\n }\n .remote-button.red{\n background-color: #cc0000 !important;\n }\n .remote-button.green{\n background-color: #00bb00 !important;\n }\n .remote-button.blue{\n background-color: #0000dd !important;\n }\n .remote-button.red.disabled {\n background-color: #440000 !important;\n color: #666666 !important;\n }\n .remote-button.green.disabled {\n background-color: #004400 !important;\n color: #666666 !important;\n }\n .remote-button.blue.disabled {\n background-color: #000066 !important;\n color: #666666 !important;\n }\n .remote-button.disabled {\n cursor: not-allowed;\n pointer-events: none;\n background-color: #222222;\n color: #666666;\n }\n\n .remote-button.blinking {\n animation: blink-animation 1s steps(5, start) infinite;\n animation-timing-function: ease;\n -webkit-animation: blink-animation 1s steps(5, start) infinite;\n }\n @keyframes blink-animation {\n to { visibility: hidden; }\n }\n @-webkit-keyframes blink-animation {\n to { visibility: hidden; }\n }\n.remote-button.warning {\n color: red;\n}\n\n.remote-button.animated .remote-button.warning {\n -webkit-animation: pulse 1s infinite;\n animation: pulse 1s infinite;\n}\n\n@-webkit-keyframes pulse {\n 0% {\n opacity: 0.2\n }\n\n 35% {\n opacity: 1.0\n }\n\n 100% {\n opacity: 0.2\n }\n}\n\n@keyframes pulse {\n 0% {\n opacity: 0.2\n }\n\n 35% {\n opacity: 1.0\n }\n\n 100% {\n opacity: 0.2\n }\n}\n\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"global","x":160,"y":40,"wires":[[]]},{"id":"1df0f0ce.3ed5cf","type":"ui_group","z":"","name":"Full_Remote2","tab":"d8c345fb.e094c8","order":3,"disp":false,"width":"3","collapse":false},{"id":"d8c345fb.e094c8","type":"ui_tab","z":"","name":"HDMI_TV_control","icon":"dashboard","order":7,"disabled":false,"hidden":false}]

```

---

<div class="post-metadata">

### Author: ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)
#### Post date: [29 September 2020 09:36 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/8 "2020-09-29T09:36:48Z")

</div>

> [@ozpos](#):
>
> Did you see the fixes to the css that allow colour/disable/flash to all work simultaneously.

No & it wasnt in your question either.

---

<div class="post-metadata">

### Author: ![ozpos](https://avatars.discourse-cdn.com/v4/letter/o/278dde/32.png) [@ozpos](https://discourse.nodered.org/u/ozpos)
#### Post date: [29 September 2020 09:37 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/9 "2020-09-29T09:37:59Z")

</div>

Thank you fixed I think.

```auto
[{"id":"1dc2aa68.579f46","type":"tab","label":"Flow 1","disabled":false,"info":""},{"id":"67e2f110.35bbe","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"CSS only ","order":1,"width":0,"height":0,"format":"<style>\n :root {\n --dashboard-unit-width: 48px;\n --dashboard-unit-height: 48px;\n --remote-button-background: black;\n --remote-button-foreground: #cccccc;\n }\n .nr-dashboard-template {\n padding: 0px;\n }\n \n .remote-button:not([disabled]):hover{\n background-color: #232323 !important;\n }\n\n .remote-button.red:not([disabled]) {\n background-color: #cc0000;\n color: var(--remote-button-foreground);\n }\n .remote-button.green:not([disabled]) {\n background-color: #00bb00;\n color: var(--remote-button-foreground);\n }\n \n\n /* This is the normal button definition */\n .remote-button{\n background-color: var(--remote-button-background) !important;\n color: var(--remote-button-foreground);\n height: var(--dashboard-unit-height);\n width: 100%;\n border-radius: 10px;\n font-size:1.0em;\n font-weight:normal;\n margin: 0;\n min-height: 36px;\n min-width: unset;\n line-height: unset;\n }\n /* This is a sub-set which is invoked by */\n /* <md-button class=\"md-button remote-button bigger\"> */\n /* note the (space) \"bigger\" at the end. */\n .remote-button.bigger{\n font-weight:bold;\n font-size:1.5em;\n }\n /* This is for buttons with a lot of text. `font-size:0.7em` */\n /* makes the font 70% normal size */\n .remote-button.small{\n font-size:0.7em;\n }\n /* This is for buttons with just icons, to upsize the size */\n /* of the icon with the line: */\n /* <i class=\"fa fa-fw fa-plus remote-icon\"> in the other node */\n .remote-icon{\n font-size:2.0em;\n }\n /* This is the same as the other one, but it makes the icon smaller */\n .remote-iconS{\n font-size:0.5em;\n }\n .remote-button.red{\n background-color: #cc0000 !important;\n }\n .remote-button.green{\n background-color: #00bb00 !important;\n }\n .remote-button.blue{\n background-color: #0000dd !important;\n }\n .remote-button.red.disabled {\n background-color: #440000 !important;\n color: #666666 !important;\n }\n .remote-button.green.disabled {\n background-color: #004400 !important;\n color: #666666 !important;\n }\n .remote-button.blue.disabled {\n background-color: #000066 !important;\n color: #666666 !important;\n }\n .remote-button.disabled {\n cursor: not-allowed;\n pointer-events: none;\n background-color: #222222;\n color: #666666;\n }\n\n .remote-button.blinking {\n animation: blink-animation 1s steps(5, start) infinite;\n animation-timing-function: ease;\n -webkit-animation: blink-animation 1s steps(5, start) infinite;\n }\n @keyframes blink-animation {\n to { visibility: hidden; }\n }\n @-webkit-keyframes blink-animation {\n to { visibility: hidden; }\n }\n.remote-button.warning {\n color: red;\n}\n\n.remote-button.animated .remote-button.warning {\n -webkit-animation: pulse 1s infinite;\n animation: pulse 1s infinite;\n}\n\n@-webkit-keyframes pulse {\n 0% {\n opacity: 0.2\n }\n\n 35% {\n opacity: 1.0\n }\n\n 100% {\n opacity: 0.2\n }\n}\n\n@keyframes pulse {\n 0% {\n opacity: 0.2\n }\n\n 35% {\n opacity: 1.0\n }\n\n 100% {\n opacity: 0.2\n }\n}\n\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"global","x":160,"y":40,"wires":[[]]},{"id":"c3cd9eec.3dd3","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"script for all buttons with class remote-button","order":25,"width":"1","height":"1","format":"<div>\n<!--diliberately emtpy - only need the script below -->\n</div>\n\n<script>\n\n(function($scope) {\n//debugger\n\n //cause a small delay while things load \n //ideally this would be an init event or on all parts of document loaded\n //(may not be necessary!)\n setTimeout(function() {\n //debugger\n $scope.init();\n },100);\n \n var BUTTON_CLASS = \".remote-button\";\n\n /**\n * Initialise all buttons with class BUTTON_CLASS\n */\n $scope.init = function () {\n //debugger\n console.log(\"$scope.init called. Adding event handlers to all buttons with class '\" + BUTTON_CLASS + \"'.\");\n var clickButtons = $(BUTTON_CLASS + \":not([data-buttontype='repeater'])\") \n clickButtons.click(function(e){\n //debugger\n var btn = $(this)\n var type = btn.data(\"buttontype\");//get the button type from attribute data-buttontype=\"xxxxx\"\n var topic = btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n var payload = btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n \n if(type == \"toggle\"){\n var newPayload = payload == 1 ? 0 : 1;\n setButtonState(btn,newPayload)\n $scope.send({\"topic\":topic,\"payload\":newPayload, \"event\": \"click\"})\n } else {\n $scope.send({\"topic\":topic,\"payload\":payload})\n }\n }); \n \n\n var repeatButtons = $(BUTTON_CLASS + \"[data-buttontype='repeater']\") \n repeatButtons.on('mousedown touchstart',function(e) {\n e.preventDefault();\n e.stopPropagation();\n console.log(e.type);//remove me after debugging\n var btn = this;\n var $btn = $(btn);\n if(btn._intervalId) return; //already in operation\n btn._topic = $btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n btn._payload = $btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n btn._interval = $btn.data(\"interval\");//get the desired repeat duration\n if(isNumeric(btn._interval) == false || btn._interval < 1) btn._interval = 300;//prevent zero & non numeric timeout value\n var max = $btn.data(\"max\");//get the max repeat count\n if(isNumeric(max) && max > 0) \n btn._max = parseInt(max);\n else\n btn._max = undefined;\n btn._count = 1;\n $scope.send({\"topic\":btn._topic,\"payload\":btn.payload,\"event\":\"down\", \"count\": btn._count})\n btn._lastevent = \"down\";\n //remove any existing timer\n if(btn._intervalId){\n clearInterval(btn._intervalId);\n btn._intervalId = null;\n }\n //start the timer if conditions are ok\n if(!btn._max || (btn._max && btn._count < btn._max)){\n btn._intervalId = setInterval(function() {\n if(btn._max && btn._count >= btn._max){\n clearInterval(btn._intervalId);\n btn._intervalId = null;\n return;\n } \n btn._count = btn._count + 1;\n $scope.send({\"topic\":btn._topic,\"payload\":btn.payload,\"event\":\"down\", \"count\": btn._count})\n },btn._interval); \n }\n }).on('mouseup blur focusout mouseout touchend',function(e) {\n e.preventDefault();\n e.stopPropagation();\n console.log(e.type);//remove me after debugging\n var btn = this;\n var $btn = $(btn);\n \n //next, if data-fireonup == true, then fire up event\n var sendUpEvent = $btn.data(\"fireonup\") == true;\n if(sendUpEvent && btn._lastevent === \"down\"){\n btn._lastevent = \"up\"\n $scope.send({\"topic\":btn._topic,\"payload\":btn._payload,\"event\":\"up\"})\n }\n \n //finally, if the timer id is still valid, clear it (stop repeater)\n if(btn._intervalId){\n clearInterval(btn._intervalId);//stop timer\n btn._intervalId = null;//clear timer id\n }\n });\n \n };\n \n //watch for node-red msgs\n $scope.$watch('msg', function(msg) {\n //debugger\n if(!msg){ //if no msg \n console.log(\"$scope.$watch('msg', ...) - msg is empty\");\n return;\n }\n if(!msg.topic){ //if no topic set found\n console.log(\"msg.topic is empty - cannot match this to any button\")\n return; //stop processing!\n }\n\n var buttonSelector = BUTTON_CLASS + \"[data-topic='\" + msg.topic + \"']\" \n var $btn = $(buttonSelector);//get the button\n \n if(!$btn.length){ //if no button found\n console.log(buttonSelector + \" not found - cannot set state\")\n return; //stop processing!\n }\n \n if($btn.length > 1){ //if MORE than one button found\n console.log(buttonSelector + \" found more than 1 button - is this intended? Do you have the same data-topic set on multiple buttons?\")\n }\n \n //see if this is a command - if so, process the command\n if(typeof msg.payload === \"object\" && msg.payload.command){\n processCommand($btn, msg.payload)\n return;\n } \n \n if($btn.data(\"buttontype\") === \"toggle\"){\n if(msg.payload == \"1\"){\n setButtonState($btn, 1);\n } else if(msg.payload == \"0\"){\n setButtonState($btn, 0);\n } else {\n console.log(\"Invalid toggle value in msg.payload, cannot set \" + buttonSelector + \". Ensure msg.payload is either 0 or 1\") \n }\n }\n\n }); \n \n /** \n * helper function to set the correct icon & update the \"data-payload\" memory \n */\n function setButtonState($btn, state){\n \n $btn.data(\"payload\", state);//set data-payload to new state value (used as a memory)\n \n //determine the opposite state\n var oppositeState;\n if(state == \"1\" || state === 1){\n state = 1; //normalise to a number\n oppositeState = 0;\n } else {\n state = 0; //normalise to a number\n oppositeState = 1;\n }\n \n var $icon = $btn.find(\"i\"); //get the <i> element\n if(!$icon.length){\n $icon = $btn.find(\"span\"); //get the <span> element instead!\n }\n if(!$icon.length){\n console.log(\"<i> or <span> not found inside button - cant toggle the icon!\")\n return;//exit this function - nothing to toggle!\n }\n \n //get the old icon and new icon names\n var oldIcon = $btn.data(\"icon\" + oppositeState); //get icon1 or icon2 depending on oppositeState\n var newIcon = $btn.data(\"icon\" + state); //get icon1 or icon2 depending on newPayload\n \n //if we have newIcon and an actual DOM element ($icon) - update it...\n if(newIcon && $icon.length){\n if(newIcon.includes(\"fa-\")){ \n $icon.removeClass(oldIcon).addClass(newIcon); // fontawesome\n } else { \n $icon.text(newIcon); // MDI\n }\n }\n }\n \n function processCommand($btn, payload){\n //first check payload is correct format...\n if(!payload || !payload.command || !payload.value){\n console.log(\"Cannot process command. Expected a payload object with .command and .value. \")\n }\n var cmd = payload.command.trim();\n switch(cmd){\n case \"addClass\":\n $btn.addClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n case \"toggleClass\":\n $btn.toggleClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n case \"removeClass\":\n $btn.removeClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n default:\n console.log(\"command '\" + payload.command + \"' is not supported\")\n }\n } \n \n /** \n * helper function to determine a value is REALLY a number \n */\n function isNumeric(n){\n if(n === \"\") return false;\n if(n === true || n === false) return false;\n return !isNaN(parseFloat(n)) && isFinite(n);\n }\n \n\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":230,"y":400,"wires":[["8fc2df4d.a1bd3"]]},{"id":"8fc2df4d.a1bd3","type":"debug","z":"1dc2aa68.579f46","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":450,"y":400,"wires":[]},{"id":"f9050d6a.1bd0c","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"lock","order":23,"width":"1","height":"1","format":"<div>\n <md-button class=\"md-button remote-button bigger\" \n data-payload=\"0\" \n data-buttontype=\"toggle\"\n data-topic=\"lock\"\n data-icon0=\"fa-unlock\"\n data-icon1=\"fa-lock\"\n aria-label=\"lock\"\n >\n <span class=\"fa fa-unlock\" aria-hidden=\"true\"> </span>\n </md-button>\n</div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":230,"y":360,"wires":[[]]},{"id":"f8c597b1.8d5b88","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass blinking, on lock","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"toggleClass\",\"value\":\"blinking\"}","payloadType":"json","x":160,"y":640,"wires":[["c3cd9eec.3dd3"]]},{"id":"49f3f717.1f2d38","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass red, on lock","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"toggleClass\",\"value\":\"red\"}","payloadType":"json","x":140,"y":680,"wires":[["c3cd9eec.3dd3"]]},{"id":"149b90bd.321fbf","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass disabled, on lock","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"lock","payload":"{\"command\":\"toggleClass\",\"value\":\"disabled\"}","payloadType":"json","x":160,"y":720,"wires":[["c3cd9eec.3dd3"]]},{"id":"dd392375.944b4","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"1","order":2,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-topic=\"1\"\n data-payload=\"1\">1\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":110,"y":80,"wires":[[]]},{"id":"708944c1.3a82dc","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass blinking, on 1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"1","payload":"{\"command\":\"toggleClass\",\"value\":\"blinking\"}","payloadType":"json","x":150,"y":460,"wires":[["c3cd9eec.3dd3"]]},{"id":"779c226a.f6180c","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass red, on 1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"1","payload":"{\"command\":\"toggleClass\",\"value\":\"red\"}","payloadType":"json","x":140,"y":560,"wires":[["c3cd9eec.3dd3"]]},{"id":"c328f7d2.a1e6a8","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass disabled, on 1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"1","payload":"{\"command\":\"toggleClass\",\"value\":\"disabled\"}","payloadType":"json","x":150,"y":600,"wires":[["c3cd9eec.3dd3"]]},{"id":"12615388.2078ac","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"2","order":3,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-topic=\"2\"\n data-payload=\"2\">2\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":230,"y":80,"wires":[[]]},{"id":"a144043c.5c5208","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"3","order":4,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-topic=\"3\"\n data-payload=\"3\">3\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":350,"y":80,"wires":[[]]},{"id":"37182909.4335e6","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"4","order":5,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-topic=\"4\"\n data-payload=\"4\">4\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":110,"y":120,"wires":[[]]},{"id":"7c0b2d.405e64d4","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"5","order":6,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-topic=\"5\"\n data-payload=\"5\">5\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":230,"y":120,"wires":[[]]},{"id":"90bf9750.269e18","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"6","order":7,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-topic=\"6\"\n data-payload=\"6\">6\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":350,"y":120,"wires":[[]]},{"id":"c3b1aca7.213f5","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"7","order":8,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-topic=\"7\"\n data-payload=\"7\">7\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":110,"y":160,"wires":[[]]},{"id":"4c4a663f.3c9e58","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"8","order":9,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-topic=\"8\"\n data-payload=\"8\">8\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":230,"y":160,"wires":[[]]},{"id":"f03c10ff.9eff","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"9","order":10,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-topic=\"9\"\n data-payload=\"9\">9\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":350,"y":160,"wires":[[]]},{"id":"e792b2b7.e1a23","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"info","order":11,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button\"\n data-topic=\"info\"\n data-payload=\"info\"\n aria-label=\"info\"\n >\n <i class=\"fa fa-info-circle remote-icon\"></i>\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":110,"y":200,"wires":[[]]},{"id":"cd1651b5.a4d9d","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"0","order":12,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button bigger\"\n data-topic=\"0\"\n data-payload=\"0\">0\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":230,"y":200,"wires":[[]]},{"id":"49f4c2c7.96749c","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"prev","order":13,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button\"\n data-topic=\"prev\"\n data-payload=\"prev\"\n aria-label=\"previous\"\n >\n <i class=\"fa fa-rotate-left remote-icon\"></i>\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":350,"y":200,"wires":[[]]},{"id":"633a01d0.4e67b","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"vol + *","order":14,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button\" \n data-buttontype=\"repeater\"\n data-interval=\"200\"\n data-topic=\"volume/plus\"\n data-payload=\"volume/plus\"\n aria-label=\"volume plus\"\n >\n <span class=\"fa fa-plus remote-icon\"> </span>\n </md-button>\n</div>\n\n\n","storeOutMessages":true,"fwdInMessages":false,"resendOnRefresh":false,"templateScope":"local","x":110,"y":240,"wires":[[]],"info":"<div id=\"regular_plus\">\n <md-button class=\"md-button remote-button\">\n <i class=\"fa fa-plus remote-icon\"></i>\n </md-button>\n</div>"},{"id":"e6e86f98.668b6","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"mute","order":15,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button\" \n data-payload=\"1\" \n data-buttontype=\"toggle\"\n data-topic=\"mute\"\n data-icon0=\"volume_off\"\n data-icon1=\"volume_mute\"\n aria-label=\"volume mute\"\n >\n <i class=\"material-icons md-48\">volume_mute</i>\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":230,"y":240,"wires":[[]],"info":" class=\"material-icons\"> volume_off"},{"id":"4d29a992.a9e078","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"Ch +","order":16,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button\" \n data-topic=\"channel/up\"\n data-payload=\"up\" \n aria-label=\"channel up\"\n >\n <i class=\"fa fa-chevron-up remote-icon\"></i>\n </md-button>\n</div>\n\n\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":350,"y":240,"wires":[[]]},{"id":"f4f0a4be.6d7dd8","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"vol - *","order":17,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button\"\n data-buttontype=\"repeater\"\n data-interval=\"200\"\n data-topic=\"volume/minus\"\n data-payload=\"volume/minus\"\n aria-label=\"volume minus\"\n >\n <span style=\"color:{{msg.colour}}\" class=\"fa fa-minus remote-icon\"> </span>\n </md-button>\n</div>\n\n","storeOutMessages":true,"fwdInMessages":false,"templateScope":"local","x":110,"y":280,"wires":[[]],"info":"<div id=\"regular_plus\">\n <md-button class=\"md-button remote-button\">\n <i class=\"fa fa-minus remote-icon\"></i>\n </md-button>\n</div>\n"},{"id":"e477c8bb.78bab8","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"ch list","order":18,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button\"\n data-topic=\"channel/list\"\n data-payload=\"list\"\n aria-label=\"channel list\"\n >\n <i class=\"fa fa-list-alt remote-icon\"></i>\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":230,"y":280,"wires":[[]]},{"id":"566b6c8e.81a674","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"Ch -","order":19,"width":1,"height":1,"format":"<div>\n <md-button class=\"md-button remote-button\" \n data-topic=\"channel/down\"\n data-payload=\"down\" \n aria-label=\"channel down\"\n >\n <i class=\"fa fa-chevron-down remote-icon\"></i>\n </md-button>\n</div>\n\n\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":350,"y":280,"wires":[[]]},{"id":"9bd60a53.fa18e8","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"NetFlix","order":20,"width":"1","height":"1","format":"<div>\n <md-button \n class=\"md-button remote-button\"\n data-topic=\"netflix\"\n data-payload=\"netflix\"\n aria-label=\"Netflix\"\n >\n <img\n class=\"remote-icon\"\n style=\"width: 36px; padding-top: 2px\"\n src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAE2klEQVR4Xu3bW4hVVRzH8c8ZE/GCtywry5qcarrhpbKLmoVl9FZ0s2zS0DIJrIceSl+EwIgIDCQCp4t2L4guBJUgTmnlXYtUirIpU9EZxyayqWbmxPKcARFn1hKDaW/7v64fM/v3Pf/1X2v/91oFx3kUjnP//gdwLBnQxMB2Hk75G81sreTNFO0CbioyKkX7Nwvnsz9FeyTNMWXAbip78kPKP29n++uMmkNzTL+AlzAtpgvjBSof48cU7b8O4Ckqp7G8B2elPMB2pl7KazFtZgA8wVkTWHI+V8dMhfE/eec0bolpMwWgNxtq6IeeMWM4sIzRU/i2K22mABTZPoUvB3BFAgD7mHcOC3IFoJKNkxmdAqCNTc9y2XxaO9NnLgPCUjKTnys4IwXCNm4Yxye5ARCMXEvduUxMAdBC7TDuyxWAPjTUMBAnJEBo/Jjqu2g4kjZzU6DDxJ2s7s/lCQA0cX8Vi3MFoIoNkxiTAqCV5UOZlCsABYoz2VnBsAQI7VsYM4HNh2szOwWCkUnUVSUWwwM8eQaP5gpAP/ZMZQgqYllQpP4Dqu+l5VBtpjMgGJnK2n5cFgMQxhu4+TzezRWAatZOTATQyttDuT1XACpon8meAqckZEFLPSPGsLNDm/kpEIxcT93ZicXwLx45ladzBWAAu6aUMiDadWpn8xBGh2U0QMhFBgQjd7OuL5cmTIOwMxxfxapcATifNVczNgVAO8+dxOxcAaigbSaNBU5OgNC0lTPH81tupkAwfSN1w9OL4bRTWZorAAPYMYXTEzIgVMDlQ5iUKwDB+D1s7J3WMise4LyXmZeJ7wKhLR6aorFf9yK+HJfYNG3n8cUMzxWAHrTO4NcCJ8Zg4ada6tqoSdB275eh1AwoF8MVw7kmxVQdn2xjcoq2Wz+NHQ2Awbx9G7em7Ax/5Ys3uDJXALBkVqlTdF2Csb+WcuCPUpO1y8hMBgQAD7CsyCsxU2H8az77nAkxbaYA1DC7D79gUMxYK989zzkxXaYAzGV6I89gTsxYGH+fbbuo7kqbOQD7uKh4MMPjsZvP3otMg8wBCLYbS6+9V8URaK6lVxu9OtNmEkAD0wu8mADAWj7f0AWsTALYSZ9epR7ggBiEFjYvYWSuMiCY2ceiIg/GAITxt9jR1MkbZSYzIJhqYmQ7m1IA/Ejdx530FDILoFwMV0tomRXZvZihxSM0WLMOYAZqU7JgOeu/45LDtZkGsId+PdildMqsy2hm9etHOHuQaQDlafAcZsUAoHUpzX8w+FBt5gHsZUwF6xMA+IZPVx52KDPzAMpL4vpiwsmSNr6vZUSuMqAMYFaRMBWi8SFbdnBBhzAXGdBIf6WdYd8Ygb2sfIfxuQJQLoZhOQzLYpdR5PcXqGildxDmIgOCkQbGFggbo2isY9V6xuUKQNjl7WOjLl58Osi08PUSLs4VgHIWPFhgUTQFSi9I9U2cmZspEEyX7yCFYnhwfncV9dR9xMRcASgXw9AomR4DUGRPLUOKjOi2O0NH82EktMVDUzRmrLHU/Tl4UiQWK1j7LbfnCkC5GIam6YUxAL+z5lXuyBWAcjGcUyi1z2PR9hVV13bXtbn5DOyZeHGywKa5h50I7czdfga18VDMfRivYOGg7ro4mfKA/3VN9Azff93AsT7fcQ/gH8HaMl8q26yhAAAAAElFTkSuQmCC\">\n />\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":110,"y":320,"wires":[[]]},{"id":"7acbb76f.72a2d8","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"Home","order":21,"width":"1","height":"1","format":"<div>\n <md-button class=\"md-button remote-button\" \n data-topic=\"home\"\n data-payload=\"home\" \n aria-label=\"home\"\n >\n <i class=\"fa fa-home remote-icon\"> </i>\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":230,"y":320,"wires":[[]]},{"id":"ffe2d03d.fe585","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"Vid","order":22,"width":"1","height":"1","format":"<div>\n <md-button class=\"md-button remote-button small\" \n data-topic=\"video\"\n data-payload=\"video\" \n >Video\n </md-button>\n</div>\n","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":350,"y":320,"wires":[[]]},{"id":"19ea2b3a.6c3355","type":"ui_template","z":"1dc2aa68.579f46","group":"1df0f0ce.3ed5cf","name":"lock2 (red)","order":24,"width":"1","height":"1","format":"<div>\n <md-button class=\"md-button remote-button bigger red\" \n data-payload=\"0\" \n data-buttontype=\"toggle\"\n data-topic=\"lock2\"\n data-icon0=\"fa-unlock\"\n data-icon1=\"fa-lock\"\n aria-label=\"lock\"\n >\n <span class=\"fa fa-unlock\" aria-hidden=\"true\"> </span>\n </md-button>\n</div>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":370,"y":360,"wires":[[]]},{"id":"957b76f5.c32d58","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass blinking, on volume/plus","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"volume/plus","payload":"{\"command\":\"toggleClass\",\"value\":\"blinking\"}","payloadType":"json","x":460,"y":460,"wires":[["c3cd9eec.3dd3"]]},{"id":"6ef314bc.d81e1c","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass red, on volume/plus","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"volume/plus","payload":"{\"command\":\"toggleClass\",\"value\":\"red\"}","payloadType":"json","x":450,"y":600,"wires":[["c3cd9eec.3dd3"]]},{"id":"caf4da77.ef4268","type":"inject","z":"1dc2aa68.579f46","name":"addClass animated, on volume/plus","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"volume/plus","payload":"{\"command\":\"addClass\",\"value\":\"animated\"}","payloadType":"json","x":500,"y":720,"wires":[["c3cd9eec.3dd3"]]},{"id":"d08bcb74.2dc7b8","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass green, on volume/plus","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"volume/plus","payload":"{\"command\":\"toggleClass\",\"value\":\"green\"}","payloadType":"json","x":480,"y":640,"wires":[["c3cd9eec.3dd3"]]},{"id":"6cad115.0b982f","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass blue, on volume/plus","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"volume/plus","payload":"{\"command\":\"toggleClass\",\"value\":\"blue\"}","payloadType":"json","x":490,"y":680,"wires":[["c3cd9eec.3dd3"]]},{"id":"9ca57523.191eb8","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass blinking, on volume/plus","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"volume/plus","payload":"{\"command\":\"toggleClass\",\"value\":\"blinking\"}","payloadType":"json","x":480,"y":500,"wires":[["c3cd9eec.3dd3"]]},{"id":"73e56d61.edc2d4","type":"inject","z":"1dc2aa68.579f46","name":"toggleClass warning, on volume/plus","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"volume/plus","payload":"{\"command\":\"toggleClass\",\"value\":\"warning\"}","payloadType":"json","x":500,"y":540,"wires":[["c3cd9eec.3dd3"]]},{"id":"71b4ada0.246c04","type":"inject","z":"1dc2aa68.579f46","name":"addClass animated, on 1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"1","payload":"{\"command\":\"addClass\",\"value\":\"animated\"}","payloadType":"json","x":150,"y":500,"wires":[["c3cd9eec.3dd3"]]},{"id":"1df0f0ce.3ed5cf","type":"ui_group","z":"","name":"Full_Remote2","tab":"d8c345fb.e094c8","order":3,"disp":false,"width":"3","collapse":false},{"id":"d8c345fb.e094c8","type":"ui_tab","z":"","name":"HDMI_TV_control","icon":"dashboard","order":7,"disabled":false,"hidden":false}]

```

---

<div class="post-metadata">

### Author: ![ozpos](https://avatars.discourse-cdn.com/v4/letter/o/278dde/32.png) [@ozpos](https://discourse.nodered.org/u/ozpos)
#### Post date: [29 September 2020 09:40 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/10 "2020-09-29T09:40:44Z")

</div>

I really did not know how to frame my original question. I had no idea what was going on having not used these techniques before.

If they are fixes then I am happy for them to be made more prominent. I do not have the confidence here to do so myself.

Thank you again for your help.

---

<div class="post-metadata">

### Author: ![ozpos](https://avatars.discourse-cdn.com/v4/letter/o/278dde/32.png) [@ozpos](https://discourse.nodered.org/u/ozpos)
#### Post date: [29 September 2020 10:11 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/11 "2020-09-29T10:11:41Z")

</div>

Is it possible to only send msg on state change ? I could use rbe node of not.

Could you please tell me what this is for:

```auto
        setTimeout(function(){
            setButtonState($btn,1);//set state of this button    
        },200)        

```

p.s. not asking you to do it for me just reassurance that it is possible to access the state of other buttons in the group.

---

<div class="post-metadata">

### Author: ![Steve-Mcl](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/steve-mcl/32/4826_2.png) [@Steve-Mcl](https://discourse.nodered.org/u/Steve-Mcl)
#### Post date: [29 September 2020 10:29 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/12 "2020-09-29T10:29:51Z")

</div>

> [@ozpos](#):
>
> Could you please tell me what this is for:
> 
> ```auto
> setTimeout(function(){
> setButtonState($btn,1);//set state of this button    
> },200)        
> 
> ```

I encountered an odd scenario on my installation where the icon would set then immediately reset. This was a "hack" workaround. Comment it out see if its needed or not on your install.

> [@ozpos](#):
>
> Is it possible to only send msg on state change

One option - set a flag in the button e.g.

```auto
var buttonAlreadyActive = $btn.data("radio-active");
if(buttonAlreadyActive) return; // **ALREADY ACTIVE** dont send message
groupBtns.data(false); //clear active flag for all other buttons in this group
$btn.data("radio-active", true);//set active flag for THIS button

```

as a side note: you could create a CSS class

```css
[data-radio-active="true"] {
  color: red;
}

:not([data-radio-active="true"]) {
  color: white;
}

```

---

<div class="post-metadata">

### Author: ![ozpos](https://avatars.discourse-cdn.com/v4/letter/o/278dde/32.png) [@ozpos](https://discourse.nodered.org/u/ozpos)
#### Post date: [29 September 2020 10:33 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/13 "2020-09-29T10:33:36Z")

</div>

Thank you. I am sorry but I cannot get your previous 'overrided-styles' example to work.  
I have added it to your radio flow example.

---

<div class="post-metadata">

### Author: ![ozpos](https://avatars.discourse-cdn.com/v4/letter/o/278dde/32.png) [@ozpos](https://discourse.nodered.org/u/ozpos)
#### Post date: [29 September 2020 12:01 UTC](https://discourse.nodered.org/t/wire-free-keypad-radio-button-problem/33386/14 "2020-09-29T12:01:07Z")

</div>

Sorry to ask yet more questions. Could your function:

```auto
    /** 
    * helper function to set the correct icon & update the "data-payload" memory 
    */
    function setButtonState($btn, state){
        console.log("setButtonState", $btn[0], state);
        $btn.data("state", state);//set data-payload to new state value (used as a memory)
        
        //determine the opposite state
        var oppositeState;
        if(state == "1" || state === 1){
            state = 1; //normalise to a number
            oppositeState = 0;
        } else {
            state = 0; //normalise to a number
            oppositeState = 1;
        }
        
        var $icon = $btn.find("i"); //get the <i> element
        if(!$icon.length){
            $icon = $btn.find("span"); //get the <span> element instead!
        }
        if(!$icon.length){
            console.log("<i> or <span> not found inside button - cant toggle the icon!")
            return;//exit this function - nothing to toggle!
        }
        
        //get the old icon and new icon names
        var oldIcon = $btn.data("icon" + oppositeState); //get icon1 or icon2 depending on oppositeState
        var newIcon = $btn.data("icon" + state); //get icon1 or icon2 depending on newPayload
       
        //if we have newIcon and an actual DOM element ($icon) - update it...
        if(newIcon && $icon.length){
            if(newIcon.includes("fa-")){ 
                $icon.removeClass(oldIcon).addClass(newIcon); // fontawesome
            } else { 
                $icon.text(newIcon); // MDI
            }
        }
    }

```

Be modified to return the old state ?

Sussed it and added the coders option SEND\_ONLY\_STATE\_CHANGES\_RADIO\_BUTTONS

```auto
[{"id":"b045c8d3.031958","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","name":"script for all buttons with class remote-button","order":11,"width":"1","height":"1","format":"<div>\n<!--diliberately emtpy - only need the script below -->\n</div>\n\n<script>\n\n(function($scope) {\n//debugger\n\n //cause a small delay while things load \n //ideally this would be an init event or on all parts of document loaded\n //(may not be necessary!)\n setTimeout(function() {\n //debugger\n $scope.init();\n },100);\n \n \n //SETTINGS...\n var BUTTON_CLASS = \".remote-button\";\n var SEND_0_FOR_CLEARED_RADIO_BUTTONS = true;\n var SEND_ONLY_STATE_CHANGES_RADIO_BUTTONS = true;\n \n \n /**\n * Initialise all buttons with class BUTTON_CLASS\n */\n $scope.init = function () {\n //debugger\n console.log(\"$scope.init called. Adding event handlers to all buttons with class '\" + BUTTON_CLASS + \"'.\");\n var clickButtons = $(BUTTON_CLASS) \n clickButtons.click(function(e){\n \n var btn = $(this)\n var type = btn.data(\"buttontype\");//get the button type from attribute data-buttontype=\"xxxxx\"\n var topic = btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n var payload = btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n // debugger\n if(type == \"radio\"){\n setRadioButton(btn, true);\n } else if(type == \"toggle\"){\n var state = btn.data(\"state\") || 0;//get the last state payload from attribute data-state=\"n\"\n var newPayload = state == 1 ? 0 : 1;\n setButtonState(btn,newPayload)\n var tm = {\"type\": type, \"topic\":topic,\"payload\":newPayload, \"event\": \"click\"}\n console.log(\"Sending msg for clicked toggle button\", tm)\n $scope.send(tm)\n } else {\n var sm = {\"type\": type, \"topic\":topic,\"payload\":payload, \"event\": \"click\"}\n console.log(\"Sending msg for clicked button\", tm)\n $scope.send(sm)\n }\n });\n \n };\n\n //watch for node-red msgs\n $scope.$watch('msg', function(msg) {\n debugger\n if(!msg){ //if no msg \n console.log(\"$scope.$watch('msg', ...) - msg is empty\");\n return;\n }\n if(!msg.topic){ //if no topic set found\n console.log(\"msg.topic is empty - cannot match this to any button\")\n return; //stop processing!\n }\n\n var buttonSelector = BUTTON_CLASS + \"[data-topic='\" + msg.topic + \"']\" \n var $btn = $(buttonSelector);//get the button\n \n if(!$btn.length){ //if no button found\n console.log(buttonSelector + \" not found - cannot set state\")\n return; //stop processing!\n }\n \n if($btn.length > 1){ //if MORE than one button found\n console.log(buttonSelector + \" found more than 1 button - is this intended? Do you have the same data-topic set on multiple buttons?\")\n }\n \n //see if this is a command - if so, process the command\n if(typeof msg.payload === \"object\" && msg.payload.command){\n processCommand($btn, msg.payload)\n return;\n } \n \n if($btn.data(\"buttontype\") === \"radio\"){\n setRadioButton($btn, false);\n }\n \n if($btn.data(\"buttontype\") === \"toggle\"){\n if(msg.payload == \"1\"){\n setButtonState($btn, 1);\n } else if(msg.payload == \"0\"){\n setButtonState($btn, 0);\n } else {\n console.log(\"Invalid toggle value in msg.payload, cannot set \" + buttonSelector + \". Ensure msg.payload is either 0 or 1\") \n }\n }\n\n }); \n \n /** \n * helper function to set the correct icon & update the \"data-payload\" memory \n */\n function setButtonState($btn, state){\n console.log(\"setButtonState\", $btn[0], state);\n var oldState = $btn.data(\"state\");\n $btn.data(\"state\", state);//set data-payload to new state value (used as a memory)\n \n //determine the opposite state\n var oppositeState;\n if(state == \"1\" || state === 1){\n state = 1; //normalise to a number\n oppositeState = 0;\n } else {\n state = 0; //normalise to a number\n oppositeState = 1;\n }\n \n var $icon = $btn.find(\"i\"); //get the <i> element\n if(!$icon.length){\n $icon = $btn.find(\"span\"); //get the <span> element instead!\n }\n if(!$icon.length){\n console.log(\"<i> or <span> not found inside button - cant toggle the icon!\")\n return oldState;//exit this function - nothing to toggle!\n }\n \n //get the old icon and new icon names\n var oldIcon = $btn.data(\"icon\" + oppositeState); //get icon1 or icon2 depending on oppositeState\n var newIcon = $btn.data(\"icon\" + state); //get icon1 or icon2 depending on newPayload\n \n //if we have newIcon and an actual DOM element ($icon) - update it...\n if(newIcon && $icon.length){\n if(newIcon.includes(\"fa-\")){ \n $icon.removeClass(oldIcon).addClass(newIcon); // fontawesome\n } else { \n $icon.text(newIcon); // MDI\n }\n }\n return oldState;\n }\n\n\n /**\n * helper function \n */\n function setRadioButton($btn, sendMsgs){\n var topic = $btn.data(\"topic\");//get the topic from attribute data-topic=\"xxxxx\"\n var group = $btn.data(\"radiogroup\");\n var groupBtns = $(\"[data-radiogroup='\"+group+\"']\");\n var m = null;\n for(var i = 0; i < groupBtns.length; i++) {\n var oldState;\n var rb = $(groupBtns[i]);\n if(!rb || !rb.length) continue;\n var t = rb.data(\"topic\");\n if(t == topic) continue;//skip clicked button\n oldState = setButtonState(rb,0);\n if(sendMsgs && SEND_0_FOR_CLEARED_RADIO_BUTTONS) {\n if(SEND_ONLY_STATE_CHANGES_RADIO_BUTTONS && oldState == 0) continue;\n m = {\"type\": \"radio\", \"topic\":t,\"payload\":0, \"event\": \"click\"};\n console.log(\"Sending 0 payload for other radio button in group\", m)\n $scope.send(m);\n }\n }\n if(sendMsgs) {\n// var p = $btn.data(\"payload\");//get the payload from attribute data-payload=\"xxxxx\"\n// var m = {\"type\": \"radio\", \"topic\":topic, \"payload\": p, \"event\": \"click\"};\n var m = {\"type\": \"radio\", \"topic\":topic, \"payload\": 1, \"event\": \"click\"};\n console.log(\"Sending msg for radio button\", m)\n $scope.send(m);\n }\n setButtonState($btn,1);//set state of this button\n setTimeout(function(){\n setButtonState($btn,1);//set state of this button \n },200) \n\n }\n \n function processCommand($btn, payload){\n //first check payload is correct format...\n if(!payload || !payload.command || !payload.value){\n console.log(\"Cannot process command. Expected a payload object with .command and .value. \")\n }\n var cmd = payload.command.trim();\n switch(cmd){\n case \"addClass\":\n $btn.addClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n case \"toggleClass\":\n $btn.toggleClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n case \"removeClass\":\n $btn.removeClass(payload.value); //this calls the jquery function by name (specified in .command) on the $btn and passes in .value\n break;\n default:\n console.log(\"command '\" + payload.command + \"' is not supported\")\n }\n } \n \n /** \n * helper function to determine a value is REALLY a number \n */\n function isNumeric(n){\n if(n === \"\") return false;\n if(n === true || n === false) return false;\n return !isNaN(parseFloat(n)) && isFinite(n);\n }\n \n\n})(scope);\n</script>","storeOutMessages":false,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":250,"y":340,"wires":[["ac92a2e7.532df"]]},{"id":"50a1d6b5.ce2c88","type":"ui_group","z":"","name":"HOME","tab":"6ff5405c.a8e6","order":1,"disp":true,"width":3,"collapse":false},{"id":"6ff5405c.a8e6","type":"ui_tab","z":"","name":"TEST","icon":"dashboard","order":3,"disabled":false,"hidden":false}]

```

Many thanks again.
