# Adding \`output\` function to buttons designed in \`template\` node

**URL:** <https://discourse.nodered.org/t/adding-output-function-to-buttons-designed-in-template-node/25591>\
**Category:** Dashboard\
**Created:** [27 April 2020 06:12 UTC](https://discourse.nodered.org/t/adding-output-function-to-buttons-designed-in-template-node/25591 "2020-04-27T06:12:30Z")\
**Posts on this page:** 1\
**Showing post:** 54

<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:** [27 April 2020 15:25 UTC](https://discourse.nodered.org/t/adding-output-function-to-buttons-designed-in-template-node/25591/54 "2020-04-27T15:25:54Z")

</div>

Andrew, some home work for you tomorrow...

I have updated the code to also handle repeat buttons - try to implement them.

PS, backup your flows first 😃

Dont worry, the idea is exactly the same (add some attributes to a button then magic happens automatically)

### Visual Demo

note...  
holding VOL buttons sends repeat messages  
the lock button is font awesome (the rest are material design)

 ![buttons3](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/0/0/00bfc92e868e063057370cbfb32befdac052c3b2.gif)

### The flow

```auto
[{"id":"f4fac1e9.e93e4","type":"ui_template","z":"e1dfad68.fee2a","group":"be17d3d4.a92a6","name":"CSS only ","order":7,"width":0,"height":0,"format":"<style id=\"remote-buttons\">\n :root {\n --dashboard-unit-width: 48px;\n --dashboard-unit-height: 48px;\n }\n .nr-dashboard-template {\n padding: 0px;\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: black !important;\n color: #cccccc !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: black !important;\n color: #cccccc !important;\n }\n\n .remote-button.red{\n background-color: red !important;\n color: #cccccc !important;\n }\n .remote-button.red:not([disabled]):hover{\n background-color: orange !important;\n }\n</style>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"global","x":740,"y":80,"wires":[[]]},{"id":"bc691572.02ce88","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":730,"y":380,"wires":[[]]},{"id":"57de4e.f4cfb1b4","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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,"templateScope":"local","x":850,"y":380,"wires":[[]]},{"id":"a6df6e36.1d16b","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":970,"y":380,"wires":[[]]},{"id":"2a646eff.3f7cf2","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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,"templateScope":"local","x":730,"y":140,"wires":[[]]},{"id":"ef0701c1.ddb63","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":850,"y":140,"wires":[[]]},{"id":"a8296eda.ec9d6","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":970,"y":140,"wires":[[]]},{"id":"cf65f510.c80d68","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":730,"y":180,"wires":[[]]},{"id":"84949bc8.2bd298","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":850,"y":180,"wires":[[]]},{"id":"b341d1f1.adf42","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":970,"y":180,"wires":[[]]},{"id":"76507e83.940e6","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":730,"y":220,"wires":[[]]},{"id":"fbafce7d.80576","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":850,"y":220,"wires":[[]]},{"id":"684b5568.bf5c6c","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":970,"y":220,"wires":[[]]},{"id":"114192b6.67ea7d","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":850,"y":260,"wires":[[]]},{"id":"84c69bfc.13faa8","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":730,"y":260,"wires":[[]]},{"id":"25910d61.070c82","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":970,"y":260,"wires":[[]]},{"id":"787787b2.c80b88","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":970,"y":300,"wires":[[]]},{"id":"7eb81447.3ea8dc","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":850,"y":300,"wires":[[]],"info":" class=\"material-icons\"> volume_off"},{"id":"6eeb00d0.da35b","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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,"templateScope":"local","x":730,"y":300,"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":"c58bb5ab.ebc658","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":730,"y":340,"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":"782351a7.0f3f6","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":850,"y":340,"wires":[[]]},{"id":"18ff979.4cf7768","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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":970,"y":340,"wires":[[]]},{"id":"6b43ca3e.3b35a4","type":"inject","z":"e1dfad68.fee2a","name":"","topic":"mute","payload":"1","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":690,"y":440,"wires":[["c322da00.abb3a8"]]},{"id":"54ff0f5c.2703e","type":"inject","z":"e1dfad68.fee2a","name":"","topic":"mute","payload":"0","payloadType":"num","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":690,"y":480,"wires":[["c322da00.abb3a8"]]},{"id":"c322da00.abb3a8","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","name":"script for all buttons with class remote-button","order":23,"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})\n } else {\n $scope.send({\"topic\":topic,\"payload\":payload})\n }\n }); \n\n var repeatButtons = $(BUTTON_CLASS + \"[data-buttontype='repeater']\") \n repeatButtons.on('mousedown',function(e) {\n var btn = $(this);\n if(this._intervalId) return; //already in operation\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 var interval = btn.data(\"interval\");//get the desired repeat duration\n if(isNumeric(interval) == false || interval < 1) interval = 300;//prevent zero & non numeric timeout value\n this._intervalId = setInterval(function() {\n $scope.send({\"topic\":topic,\"payload\":payload})\n },interval); \n }).on('mouseup blur focusout',function(e) {\n var btn = $(this);\n if(this._intervalId){\n clearInterval(this._intervalId);\n this._intervalId = null;\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 var buttonSelector = \".remote-button[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 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 /** \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":850,"y":540,"wires":[["a90df8f.7eac108"]]},{"id":"a90df8f.7eac108","type":"debug","z":"e1dfad68.fee2a","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":930,"y":480,"wires":[]},{"id":"b5beb822.d8e4d8","type":"ui_template","z":"e1dfad68.fee2a","group":"a381801e.309e1","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,"templateScope":"local","x":850,"y":420,"wires":[[]]},{"id":"be17d3d4.a92a6","type":"ui_group","z":"","name":"HOME","tab":"6af80cca.442cb4","order":1,"disp":true,"width":3,"collapse":false},{"id":"a381801e.309e1","type":"ui_group","z":"","name":"Full_Remote2","tab":"9e72c753.ebf048","order":3,"disp":false,"width":"3","collapse":false},{"id":"6af80cca.442cb4","type":"ui_tab","z":"","name":"TEST","icon":"dashboard","order":3,"disabled":false,"hidden":false},{"id":"9e72c753.ebf048","type":"ui_tab","z":"","name":"HDMI_TV_control","icon":"dashboard","order":7,"disabled":false,"hidden":false}]

```

### How it works...

NOTE: All of the below code snippets are in the FLOW above (no real need to copy paste this) - these are for you to use as a reference.

#### A normal / simple button...

```auto
<div>
   <md-button class="md-button remote-button bigger"
    data-topic="7"
    data-payload="7">7
   </md-button>
</div>

```

#### Toggle buttons

##### Material Icon toggle...

```auto
<div>
 
   <md-button id="mute-button" class="md-button remote-button" 
              data-payload="1" 
              data-buttontype="toggle"
              data-topic="mute"
              data-icon0="volume_off"
              data-icon1="volume_mute"
              aria-label="volume mute"
              >
      <i class="material-icons md-48">volume_mute</i>
   </md-button>
</div>

```

##### font awesome Icon toggle...

```auto
<div>
   <md-button class="md-button remote-button bigger" 
              data-payload="0" 
              data-buttontype="toggle"
              data-topic="lock"
              data-icon0="fa-unlock"
              data-icon1="fa-lock"
              aria-label="lock"
              >
      <span class="fa fa-unlock" aria-hidden="true"> </span>
   </md-button>
</div>

```

#### A repeater button (set to resend every 200ms while pressed)

```auto
<div>
   <md-button class="md-button remote-button" 
        data-buttontype="repeater"
        data-interval="200"
        data-topic="volume/plus"
        data-payload="volume/plus"
        aria-label="volume plus"
    >
    <span class="fa fa-plus remote-icon"> </span>
   </md-button>
</div>

```

#### the script that handles all the `.remote-button` buttons...

```auto
<div>
<!--diliberately emtpy - only need the script below -->
</div>

<script>

(function($scope) {
//debugger

    //cause a small delay while things load 
    //ideally this would be an init event or on all parts of document loaded
    //(may not be necessary!)
    setTimeout(function() {
        //debugger
        $scope.init();
    },100);
    
    var BUTTON_CLASS = ".remote-button"; // The class we want this code to handle. Code will only work for buttons with this class!

    /** 
     * Initialise event handlers all buttons with class BUTTON_CLASS
    */
    $scope.init = function () {
        //debugger
        console.log("$scope.init called. Adding event handlers to all buttons with class '" + BUTTON_CLASS + "'.");
        var clickButtons = $(BUTTON_CLASS + ":not([data-buttontype='repeater'])") 
        clickButtons.click(function(e){
            //debugger
            var btn = $(this)
            var type = btn.data("buttontype");//get the button type from attribute data-buttontype="xxxxx"
            var topic = btn.data("topic");//get the topic from attribute data-topic="xxxxx"
            var payload = btn.data("payload");//get the payload from attribute data-payload="xxxxx"
            
            if(type == "toggle"){
                var newPayload = payload == 1 ? 0 : 1;
                setButtonState(btn,newPayload)
                $scope.send({"topic":topic,"payload":newPayload})
            } else {
                $scope.send({"topic":topic,"payload":payload})
            }
        }); 

        var repeatButtons = $(BUTTON_CLASS + "[data-buttontype='repeater']") 
        repeatButtons.on('mousedown',function(e) {
            var btn = $(this);
            if(this._intervalId) return; //already in operation
            var topic = btn.data("topic");//get the topic from attribute data-topic="xxxxx"
            var payload = btn.data("payload");//get the payload from attribute data-payload="xxxxx"
            var interval = btn.data("interval");//get the desired repeat duration
            if(isNumeric(interval) == false || interval < 1) interval = 300;//prevent zero & non numeric timeout value
            this._intervalId = setInterval(function() {
                $scope.send({"topic":topic,"payload":payload})
            },interval); 
        }).on('mouseup blur focusout',function(e) {
            var btn = $(this);
            if(this._intervalId){
                clearInterval(this._intervalId);
                this._intervalId = null;
            }
        });
           
    };
    
    //watch for node-red msgs
    $scope.$watch('msg', function(msg) {
        //debugger
        if(!msg){ //if no msg 
            console.log("$scope.$watch('msg', ...) - msg is empty");
            return;
        }
        if(!msg.topic){ //if no topic set found
            console.log("msg.topic is empty - cannot match this to any button")
            return; //stop processing!
        }
        var buttonSelector = ".remote-button[data-topic='" + msg.topic + "']" 
        var $btn = $(buttonSelector);//get the button
        
        if(!$btn.length){ //if no button found
            console.log(buttonSelector + " not found - cannot set state")
            return; //stop processing!
        }
        
        if($btn.length > 1){ //if MORE than one button found
            console.log(buttonSelector + " found more than 1 button - is this intended? Do you have the same data-topic set on multiple buttons?")
        }
        
        if($btn.data("buttontype") === "toggle"){
            if(msg.payload == "1"){
                setButtonState($btn, 1);
            } else if(msg.payload == "0"){
                setButtonState($btn, 0);
            } else {
               console.log("Invalid toggle value in msg.payload, cannot set " + buttonSelector + ". Ensure msg.payload is either 0 or 1") 
            }
        }

    }); 
    
    /** 
    * helper function to set the correct icon & update the "data-payload" memory 
    */
    function setButtonState($btn, state){
        
        $btn.data("payload", 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
            }
        }
    }
        
    /** 
    * helper function to determine a value is REALLY a number 
    */
    function isNumeric(n){
        if(n === "") return false;
        if(n === true || n === false) return false;
        return !isNaN(parseFloat(n)) && isFinite(n);
    }
   

})(scope);
</script>

```

---

_[View the full topic](https://discourse.nodered.org/t/adding-output-function-to-buttons-designed-in-template-node/25591)._
