Ui_template mvc and scope of context/flow/global stores

var $spanOn = $btn.find(spanSelectorOn);//get the span
$btn selection must be successful beforehand

colsole shows: spanOn=function(e){return (this,function(e){return void 0===e?S.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)}

No sign of the 'O-N' text from:

<md-button class="md-button remote-button3x bigger" 
    data-payload="1" 
    data-buttontype="checkbox"
    data-topic="10"
    >
    <label class="remote-button3x-switch">
        <input type="checkbox" id="10">
        <div class="remote-button3x-slider round">
            <span class="remote-button3x-on">O-N</span><span class="remote-button3x-off">O-F-F</span>
        </div>
    </label>       
</md-button>

Is there supposed to be a . in the class name ?

Just wrong syntax..

$spanOn.text() //to get text,
$spanOn.text("hello") //to set text

Thanks getting there.
Found my problem a missing . on class string. Thank you for maintaining my sanity and being an anchor.

var spanSelectorOn =  ".remote-button3x-on"; 
var $spanOn = $btn.find(spanSelectorOn);//get the span
console.log("$spanOn=" + $spanOn.text());

$spanOn.text("hello");

Works perfectly.

Nearly there except when working with the button state I can toggle the button display through msg.payload = 0 or 1 and no command on the incoming msg.

        if($btn.data("buttontype") === "checkbox"){
            var $chck=$btn.find("input[type=checkbox]");//get the checkbox
            var $state=$chck.is(":checked");
            console.log("$chck=" + $state);
            if(msg.payload == "1"){
                $chck[0].checked=true;
                //setButtonState($btn, 1);
            } else if(msg.payload == "0"){
                $chck[0].checked=false;
                //setButtonState($btn, 0);
            } else {
               console.log("Invalid checkbox value in msg.payload, cannot set " + buttonSelector + ". Ensure msg.payload is either 0 or 1") 
            }
        }

That seems to work visually but now button click has no effect ?

I am obviously missing a big thing here but what ?

Many things my be wrong.
Maybe test for $spanOn.length. If it is 0 the selection is not correct.

what is wrong is var spanSelectorOn = "remote-button3x-on";
You are looking for class but missing the dot.
var spanSelectorOn = ".remote-button3x-on";

Yes I know thank you I spotted that. I think the reason is the css is a little more complex.

I can read the checkbox state correctly so if my incoming state does not match I could trigger a button click.

I think the element that should be sent a click is either<label class="remote-button3x-switch"> or <div class="remote-button3x-slider

How can I trigger a button click on a class ?

I have found the element to send a click to:

            var $slider=$btn.find(".remote-button3x-slider");//get the checkbox
            var $chck=$btn.find("input[type=checkbox]");//get the checkbox
            var $state=$chck.is(":checked");  //Reliably reports the visual state.
            console.log("$chck=" + $state);
            var $lbl=$btn.find(".remote-button3x-switch");
            if(msg.payload == "1"){
                if(!$state) {
//RACE CONDITION                    $slider.trigger("click");
//  FAILS  on user click                $chck.prop('checked',true);
//  FAILS  on user click                $lbl.prop("selected",true);
//  FAILS  on user click                $spanOn.prop("selected",true);

The trouble is a race condition. Wow just look at the performance. The button is responsive to user clicks (sort of) and injected msg.payload=0 or 1 (sort of). However the rendered button is flashing sporadically between its two displayable states.

What is going on ?

Isn't that the infinite loop you just created

clearly so but what can I do about it ?

An inadequately cooled target would melt pretty quickly.

As for now you have changed a lot , I can't be sure but really I can't see why you need to trigger the click. It is against all best practices also. If incoming state is different from internal state, just change the internal state. If there is click on button, send out that event and also change internal state.

So the CSS displays or hides a span class accordong to the checkbox state.

        <div class="remote-button3x-slider round">
            <span class="remote-button3x-on">O-N</span><span class="remote-button3x-off">O-F-F</span>
        </div>

Is it because I am sending a click to both spans by sending click to the outer div ?

Thank you it is good to know about best practices.

All the other element selectors that I tried in various combinations (commented out lines) actually toggled the image but left user click induced toggle inoperative.

I have this simple piece of html

   <label class="remote-button3x-switch">
        <input type="checkbox" id="10">
        <div class="remote-button3x-slider round">
            <span class="remote-button3x-on">O-N</span><span class="remote-button3x-off">O-F-F</span>
        </div>
    </label>       

with the pertinant (I think) CSS

.remote-button3x-on
{
  display: none;
}

.remote-button3x-on, .remote-button3x-off
{
  color: white;
  position: absolute;
  transform: translate(-50%,-50%);
  top: 50%;
  left: 50%;
}

.remote-button3x input:checked + .remote-button3x-slider .remote-button3x-on
{display: block;}

.remote-button3x input:checked + .remote-button3x-slider .remote-button3x-off
{display: none;}

I can probe the current visual state (checked/unchecked and select any element.

Why is it so difficult to trigger programattically a change of state ?

This is really doing my head in. Will sleep on it.

I have taken your advice and am just changing state of the checkbox under control of an incoming msg.payload of 1 or 0 and button id from msg.topic. It appears to work perfectly.

If I leave the button in the on state I can click it and it changes to off. But if I do the reverse the button will not turn on in response to a mouse click.

I still cannot see the big picture here.
Could you please tell me in a simple way; what, where and in which order code is executed from the point of a mouse click on the button ?

I'm a bit busy right now, but also I can't really create that big picture also just from my head. You should share reasonable amount of your flow for investigations. Should contain the ui_template nodes with controller logic, button (or couple of them) and css.
Can look at it maybe later today. Also I'm not the only one here so sharing helps ..

Thank you. Your help has been invaluable. I will pear things back and compare console activity with your original working version.

At last I have it working from the outside. The msgs need some tidying.
Next is to bring all aspects together and some more polishing. I will publish the outcome soon I hope.

Hi @hotNipi ,

A week has gone by and I have made a massive move forwards with button styling and attribute control.

In order to get that done I had to chop out all other styles and modify the html.

I still have your original example running under two instances of chromium (flow & ui) and my current code under two instances of firefox.

This arrangement allows me to stop in the chromium and firefox debuggers at the same point and compare the results.

Right from the start my code is broken:



showing null and undefined attributes.
Your code however:
image

Shows all ok.

Any idea why my group is 'null' and other attributes are 'undefined' ?
Sorry html is:

<div>
<md-button class="md-button ck-button on off small" 
    data-payload="1"
    data-state="1"
    data-buttontype='checkbox'
    data-radiogroup='group1'
    data-topic="11">
    <div class="ck-button">
        <label class="ck-switch">
            <input type="checkbox" unchecked>
            <div style="background-color:#097479);" class="on">radio<br>group1<br><br>btn 11<br>with a lot more text fun<br>on</div>
            <div style="background-color:#003333;" class="off">radio<br>group1<br><br>btn 11<br>off</div>
        </label>
    </div>
</md-button>
</div>

Something I noticed by messing around if I change the 'BUTTON_CLASS' from .ck-buitton to .on I get:
image
Why are these attributes not initialised when the BUTTON_CLASS=".ck-button' ?

Hi. Hard to tell without seeing the flow. Share and I'll try to figure out.

Thank you.
My Broken Flow:

[{"id":"5666698b.c679c8","type":"inject","z":"503e64bc.acab6c","name":" disabled, on 11","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"11","payload":"{\"command\":\"toggleClass\",\"value\":\"disabled\"}","payloadType":"json","x":120,"y":360,"wires":[["d63508d7.9bea48"]]},{"id":"c3be9084.fc1fe","type":"inject","z":"503e64bc.acab6c","name":"9:0","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"9","payload":"0","payloadType":"num","x":90,"y":200,"wires":[["d63508d7.9bea48"]]},{"id":"35d1aadc.5941e6","type":"inject","z":"503e64bc.acab6c","name":"9:1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"9","payload":"1","payloadType":"num","x":90,"y":240,"wires":[["d63508d7.9bea48"]]},{"id":"2f8b4bb4.988a54","type":"debug","z":"503e64bc.acab6c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":330,"y":400,"wires":[]},{"id":"bef9867d.b18028","type":"ui_template","z":"503e64bc.acab6c","group":"c3500612.c24038","name":":r8g1","order":3,"width":"1","height":1,"format":"<!--\n    V1\n    ==\n    data-buttontype=\"radio\"\n    data-radiogroup='group1'\n//-->\n<div>\n<md-button class=\"md-button ck-button on off small\" \n    data-payload=\"1\" \n    data-topic=\"8\"\n    data-buttontype=\"checkbox\"\n    >\n    <div class=\"ck-button\">\n        <label class=\"ck-switch\">\n            <input type=\"checkbox\" unchecked>\n            <div style=\"background-color:#cc0000\" class=\"on\">btn<br>on</div>\n            <div style=\"background-color:#660000\" class=\"off\">btn<br>off</div>\n        </label>\n    </div>\n</md-button>\n</div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":310,"y":160,"wires":[[]]},{"id":"fdb1290a.963198","type":"ui_template","z":"503e64bc.acab6c","group":"c3500612.c24038","name":"CSS only ","order":2,"width":0,"height":0,"format":"/!--\n\n-->\n<style>\n    .nr-dashboard-template {\n        padding: 0px;\n    }\n    .md-button {\n        border-radius: 10px;\n    }\n    .md-button:hover {\n        border:1px solid red;\n    }\n    .bigger{\n        font-size:1.5em;\n    }\n    .bold {\n        font-weight:bold;\n    }\n    /*  This is for buttons with a lot of text.  `font-size:0.7em` */\n    /*  makes the font 70% normal size  */\n    .small{\n        font-size:0.7em;\n    }\n\n\n    /* New ck-button */    \n    .ck-button.disabled{\n        pointer-events: none;\n        opacity: 0.5;\n    }\n    .ck-switch {\n        position: relative;\n    }\n    \n    .ck-switch input {\n        display: none;\n    }\n    \n    .off {\n        position: absolute;\n        top: 0px;\n        height: 100%;\n        width: 100%;\n        opacity: 1;\n    }\n    .on {\n        height: 100%;\n    }\n    .ck-button input:checked + .on {\n        opacity: 1;\n    }\n    .ck-button input:checked + div + .off {\n        opacity: 0;\n    }\n    \n    \n    .ck-button {\n        margin: -6px;\n        padding: 6px;\n        width: 100%;\n        height: 100%;\n        overflow: auto;\n    }\n    \n    .ck-button label {\n        height: 100%;\n        text-align: center;\n        float: left;\n        width: 100%;\n    }\n    \n    .ck-button label div {\n        margin: -6px;\n        padding: 6px;\n        text-align: center;\n        display: block;\n    }\n    \n    .ck-button label input {\n        position: absolute;\n        top: -20px;\n    }\n    .ck-button label div {\n/*ok        white-space: normal;*/\nwhite-space:pre-wrap;\n        word-break: break-word;\n        display: flex;\n        align-items: center;\n        justify-content: center;\n    }\n/*\nHTML\n====\n<div>\n<md-button class=\"md-button ck-button ck-switch on off small\" \n    data-payload=\"1\" \n    data-buttontype=\"radio\"\n    data-radiogroup='group1'\n    data-topic=\"8\"\n    >\n    <div class=\"ck-button\">\n        <label class=\"ck-switch\">\n            <input type=\"checkbox\" unchecked>\n            <div style=\"background-color:#00cc00\" class=\"on\">btn<br>on</div>\n            <div style=\"background-color:#006600\" class=\"off\">btn<br>off</div>\n        </label>\n    </div>\n</md-button>\n</div>\n*/\n\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"global","x":100,"y":40,"wires":[[]]},{"id":"bc2d0de2.d27c8","type":"comment","z":"503e64bc.acab6c","name":"data-buttontype:checkbox","info":"","x":130,"y":80,"wires":[]},{"id":"ee44ef95.2afab","type":"inject","z":"503e64bc.acab6c","name":"disabled, on 9","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"9","payload":"{\"command\":\"toggleClass\",\"value\":\"disabled\"}","payloadType":"json","x":110,"y":160,"wires":[["d63508d7.9bea48"]]},{"id":"d63508d7.9bea48","type":"ui_template","z":"503e64bc.acab6c","group":"c3500612.c24038","name":"script for all buttons with class .remote-button3x","order":14,"width":"0","height":"0","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 = \".ck-button\";\n    var SEND_0_FOR_CLEARED_RADIO_BUTTONS = true;\n    var SEND_ONLY_STATE_CHANGES_FOR_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 + \":not([data-buttontype='repeater'])\") \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\"\nvar group = btn.data(\"radiogroup\") || null;\nvar state = btn.data(\"state\") || 0;//get the last state payload from attribute data-state=\"n\"\nvar ck = {\"class\": BUTTON_CLASS, \"new type\": type, \"topic\":topic,\"payload\":payload, \"group\": group, \"state\": state, \"event\": \"click\"};\nconsole.log(\"click():\",ck);\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 if(type == \"checkbox\"){\n                var group = btn.data(\"radiogroup\") || null;\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                if(group) {\n                    setRadioButton(btn, true);\n                } else {\n                    setButtonState(btn,newPayload)\n                    var tm = {\"type\": type, \"topic\":topic,\"payload\":newPayload, \"event\": \"click\"}\n                    console.log(\"Sending msg for clicked checkbox button\", tm)\n                    $scope.send(tm)\n                }\n            } else {\n                var sm = {\"class\": BUTTON_CLASS, \"new type\": type, \"topic\":topic,\"payload\":payload, \"radiogroup\":group, \"event\": \"click\"}\n                console.log(\"Sending msg for clicked button\", sm)\n                $scope.send(sm)\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        var buttonSelector =  BUTTON_CLASS + \"[data-topic='\" + msg.topic + \"']\" \n        var $btn = $(buttonSelector);//get the button\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        }        \nvar lsn = {\"class\": BUTTON_CLASS, \"new type\": $btn.data(\"buttontype\"),\n        \"topic\":$btn.data(\"topic\"),\"payload\":$btn.data(\"payload\"), \"group\": $btn.data(\"radiogroup\"), \"state\": $btn.data(\"state\")};\nconsole.log(\"msg-in():\",lsn);\n        if($btn.data(\"buttontype\") === \"radio\"){\n            setRadioButton($btn, false);\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//<md-button class=\"md-button remote-button3x bigger\" \n//    data-payload=\"1\" \n//    data-buttontype=\"checkbox\"\n//    data-topic=\"10\"\n//    >\n//   <label class=\"remote-button3x-switch\">\n//        <input type=\"checkbox\" id=\"10\">\n//        <div class=\"remote-button3x-slider round\">\n//            <span class=\"remote-button3x-on\">O-N</span><span class=\"remote-button3x-off\">O-F-F</span>\n//        </div>\n//    </label>       \n//</md-button>    \n        if($btn.data(\"buttontype\") === \"checkbox\"){\n            var group = $btn.data(\"radiogroup\");\n            var groupBtns = $(\"[data-radiogroup='\"+group+\"']\");\n            var m = null;\n            if(groupBtns.length>0) {\n                setRadioButton($btn, false);\n            } else {\n                var oldState;                \n                var $chck=$btn.find(\"input[type=checkbox]\");//get the checkbox\n                var $state=$chck.is(\":checked\");\n                console.log(\"$chck=\" + $state);\n                if(msg.payload == \"1\"){\n/*                    if(!$state) {*/\n                        $chck.prop('checked',true);\n                        oldState=setButtonState($btn, 1);\n/*                    }*/\n                } else if(msg.payload == \"0\"){\n                    // Not sure if this can happen ?\n                    // a click on a radio button is always 1\n/*                    if($state) {*/\n                        $chck.prop('checked',false);\n                        oldState=setButtonState($btn, 0);\n/*                    }*/\n                } else {\n                    console.log(\"Invalid checkbox value in msg.payload, cannot set \" + buttonSelector + \". Ensure msg.payload is either 0 or 1\") \n                }\n            }\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        var btntype=$btn.data(\"buttontype\");\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            if( btntype !== \"checkbox\") {\n                $icon = $btn.find(\"span\"); //get the <span> element instead!\n            }\n        }\n        if(!$icon.length && btntype !== \"checkbox\"){\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// original\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 checkbox button\", tm)\n//                $scope.send(tm)\n// checkbox\n//                var oldState;                \n//                var $chck=$btn.find(\"input[type=checkbox]\");//get the checkbox\n//                var $state=$chck.is(\":checked\");\n//                console.log(\"$chck=\" + $state);\n//                if(msg.payload == \"1\"){\n//                    if(!$state) {$chck.prop('checked',true);}\n// //           setButtonState($btn, 1);\n//                } else if(msg.payload == \"0\"){\n//                    if($state) {$chck.prop('checked',false);}\n// //           setButtonState($btn, 1);\n//                } else {\n//                    console.log(\"Invalid checkbox value in msg.payload, cannot set \" + buttonSelector + \". Ensure msg.payload is either 0 or 1\") \n//                }\n\n\n\n        var $chck=$btn.find(\"input[type=checkbox]\");//get the checkbox\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            setButtonState(rb,0);\n            $chck.prop('checked',false);\n            if(sendMsgs && SEND_0_FOR_CLEARED_RADIO_BUTTONS) {\n                if(SEND_ONLY_STATE_CHANGES_FOR_RADIO_BUTTONS && oldState == 0) continue;\n                m = {\"type\": \"checkbox\", \"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\": \"checkbox\", \"topic\":topic, \"payload\": 1, \"event\": \"click\"};\n            console.log(\"Sending msg for radio button\", m)\n            $scope.send(m);\n        }\n        $chck.prop('checked',true);\n        setButtonState($btn,1);//set state of this button\n\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        //{\"selector\":\".remote-button3x-on\",\"command\":\"setContent\",\"value\":\"HELLO\"}\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 \"setColour\":\n                var spanSelector = payload.selector.trim();\n                var $span = $btn.find(spanSelector);//get the span\n                console.log(\"$spanBefore=\" + $span.text() + \" $spanBefore=\" + payload.value);\n\n                $span.css({\"background-color\": payload.value});\n                \n//$btn.text(\"Hello world!\");\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 \"setContent\":\n                var spanSelector = payload.selector.trim();\n                var $span = $btn.find(spanSelector);//get the span\n                console.log(\"$spanBefore=\" + $span.text() + \" $spanBefore=\" + payload.value);\n\n                $span.text(payload.value);\n                \n//$btn.text(\"Hello world!\");\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":275,"y":400,"wires":[["2f8b4bb4.988a54"]],"l":false},{"id":"2c95dab1.537be6","type":"inject","z":"503e64bc.acab6c","name":"10:0","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"10","payload":"0","payloadType":"num","x":90,"y":400,"wires":[["d63508d7.9bea48"]]},{"id":"b9742854.b0ff28","type":"inject","z":"503e64bc.acab6c","name":"10:1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"10","payload":"1","payloadType":"num","x":90,"y":440,"wires":[["d63508d7.9bea48"]]},{"id":"2993cfb3.e315b","type":"inject","z":"503e64bc.acab6c","name":".on 9:button\\non 1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"9","payload":"{\"selector\":\".on\",\"command\":\"setContent\",\"value\":\"button\\non 1\"}","payloadType":"json","x":130,"y":280,"wires":[["d63508d7.9bea48"]]},{"id":"3be9b0e2.b4ef3","type":"inject","z":"503e64bc.acab6c","name":".off 10:off","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"10","payload":"{\"selector\":\".off\",\"command\":\"setContent\",\"value\":\"off\"}","payloadType":"json","x":100,"y":480,"wires":[["d63508d7.9bea48"]]},{"id":"6395717.4416a9","type":"inject","z":"503e64bc.acab6c","name":".on 9:red","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"9","payload":"{\"selector\":\".on\",\"command\":\"setColour\",\"value\":\"#cc0000\"}","payloadType":"json","x":100,"y":320,"wires":[["d63508d7.9bea48"]]},{"id":"c7b32b4c.bf0798","type":"comment","z":"503e64bc.acab6c","name":"ck-button","info":"","x":320,"y":360,"wires":[]},{"id":"1237f7a0.5cace8","type":"ui_template","z":"503e64bc.acab6c","group":"c3500612.c24038","name":":t9","order":3,"width":"2","height":1,"format":"<!--\n    V1\n    ==\n    data-buttontype='radio'\n    data-radiogroup='group1'    \n//-->\n<div>\n<md-button class=\"md-button ck-button on off\" \n    data-payload=\"1\" \n    data-buttontype=\"checkbox\"\n    data-topic=\"9\"\n    >\n    <div class=\"ck-button\">\n        <label class=\"ck-switch\">\n            <input type=\"checkbox\" checked>\n            <div style=\"background-color:#00cc00\" class=\"on\">9:1<br>on</div>\n            <div style=\"background-color:#006600\" class=\"off\">9:0<br>off</div>\n        </label>\n    </div>\n</md-button>\n</div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":310,"y":200,"wires":[[]]},{"id":"bb33fa8e.a48f68","type":"ui_template","z":"503e64bc.acab6c","group":"c3500612.c24038","name":"c11g1","order":3,"width":"1","height":"3","format":"<!--\n    V1\n    ==\n    data-radiogroup='group1'\n//-->\n<div>\n<md-button class=\"md-button ck-button ck-switch on off small\">\n    <div class=\"ck-button\"     \n        data-payload=\"1\"\n        data-state=\"1\"\n        data-buttontype='checkbox'\n        data-radiogroup='group1'\n        data-topic=\"11\">\n        <label class=\"ck-switch\">\n            <input type=\"checkbox\" unchecked>\n            <div style=\"background-color:#097479);\" class=\"on\">radio<br>group1<br><br>btn 11<br>with a lot more text fun<br>on</div>\n            <div style=\"background-color:#003333;\" class=\"off\">radio<br>group1<br><br>btn 11<br>off</div>\n        </label>\n    </div>\n</md-button>\n</div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":70,"y":600,"wires":[[]]},{"id":"b856ea19.2280f8","type":"ui_template","z":"503e64bc.acab6c","group":"c3500612.c24038","name":":r10g1","order":3,"width":"2","height":"2","format":"<!--\n    V1\n    ==\n    <div class=\"ck-button\">\n       <label class=\"ck-switch\">\n          <input type=\"checkbox\">\n            <span style=\"background-color:#0000ff\" class=\"on\">11:1</span>\n            <span style=\"background-color:#000066\" class=\"off\">11:0</span>\n        </label>\n    </div>\n<md-button class=\"md-button ck-button on off bigger bold\" \n    data-payload=\"1\" \n    data-buttontype=\"checkbox\"\n    data-topic=\"10\"\n    >\n\n    <div class=\"ck-button\">\n        <label class=\"ck-switch\">\n            <input type=\"checkbox\">\n            <div style=\"background-color:#0000ff\" class=\"on\">10:1<br>on</div>\n            <div style=\"background-color:#000066\" class=\"off\">10:0<br>off</div>\n        </label>\n    </div>\n</md-button>\n    \n//-->\n<div>\n<md-button class=\"md-button ck-button on off bigger bold\">\n\n    <div class=\"ck-button\" \n    data-payload=\"1\" \n    data-buttontype=\"checkbox\"\n    data-topic=\"10\"\n    >\n        <label class=\"ck-switch\">\n            <input type=\"checkbox\">\n            <div style=\"background-color:#0000ff\" class=\"on\">10:1<br>on</div>\n            <div style=\"background-color:#000066\" class=\"off\">10:0<br>off</div>\n        </label>\n    </div>\n</md-button>\n</div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":310,"y":240,"wires":[[]]},{"id":"eacf7593.fa2ca8","type":"ui_button","z":"503e64bc.acab6c","name":"","group":"c3500612.c24038","order":6,"width":"2","height":"2","passthru":false,"label":"button","tooltip":"","color":"","bgcolor":"","icon":"","payload":"","payloadType":"str","topic":"","x":310,"y":280,"wires":[[]]},{"id":"b6aa0725.b2a758","type":"ui_template","z":"503e64bc.acab6c","group":"c3500612.c24038","name":"r12g1","order":3,"width":"2","height":"2","format":"<!--\n    V1\n    ==\n//-->\n<div>\n<md-button class=\"md-button ck-button ck-switch on off small\" \n    data-payload=\"1\" \n    data-state=\"1\" \n    data-buttontype=\"checkbox\"\n    data-radiogroup='group1'\n    data-topic=\"12\"\n    >\n    <div class=\"ck-button\">\n       <label class=\"ck-switch\">\n          <input type=\"checkbox\" unchecked>\n            <div style=\"background-color:#00aaaa\" class=\"on\">radio<br>group1<br><br>btn 12 with a lot morrrrrrrrrrrrrrrrrrrre text fun<br>on</div>\n            <div style=\"background-color:#097479\" class=\"off\">radio<br>group1<br><br>btn 12<br>off</div>\n        </label>\n    </div>\n</md-button>\n</div>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":210,"y":600,"wires":[[]]},{"id":"b2e90b1c.78bbc8","type":"comment","z":"503e64bc.acab6c","name":"data-buttontype:checkbox group1","info":"","x":150,"y":520,"wires":[]},{"id":"d706e1b9.853eb","type":"inject","z":"503e64bc.acab6c","name":"11:0","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"11","payload":"0","payloadType":"num","x":90,"y":640,"wires":[["d63508d7.9bea48"]]},{"id":"a61d6558.bdbe58","type":"inject","z":"503e64bc.acab6c","name":"11:1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"11","payload":"1","payloadType":"num","x":90,"y":680,"wires":[["d63508d7.9bea48"]]},{"id":"731bf5a.fa03a0c","type":"inject","z":"503e64bc.acab6c","name":"12:0","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"12","payload":"0","payloadType":"num","x":230,"y":640,"wires":[["d63508d7.9bea48"]]},{"id":"dfbdd891.b006e8","type":"inject","z":"503e64bc.acab6c","name":"12:1","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"12","payload":"1","payloadType":"num","x":230,"y":680,"wires":[["d63508d7.9bea48"]]},{"id":"23ffa11f.b1acfe","type":"comment","z":"503e64bc.acab6c","name":"Disable/enable, change on/off text and on/off colour","info":"","x":210,"y":120,"wires":[]},{"id":"2eaffa84.972856","type":"comment","z":"503e64bc.acab6c","name":"Work in progress","info":"","x":100,"y":560,"wires":[]},{"id":"c3500612.c24038","type":"ui_group","z":"","name":"Btn2","tab":"94f74e9f.6a717","order":1,"disp":true,"width":"4","collapse":false},{"id":"94f74e9f.6a717","type":"ui_tab","z":"503e64bc.acab6c","name":"TEST","icon":"dashboard","order":7,"disabled":false,"hidden":false}]

This attempt had the template set as:

<div>
<md-button class="md-button ck-button ck-switch on off small">
    <div class="ck-button"     
        data-payload="1"
        data-state="1"
        data-buttontype='checkbox'
        data-radiogroup='group1'
        data-topic="11">
        <label class="ck-switch">
            <input type="checkbox" unchecked>
            <div style="background-color:#097479);" class="on">radio<br>group1<br><br>btn 11<br>with a lot more text fun<br>on</div>
            <div style="background-color:#003333;" class="off">radio<br>group1<br><br>btn 11<br>off</div>
        </label>
    </div>
</md-button>
</div>

My reference flow, derived from your example:

[{"id":"449863c1.3b7fec","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","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":130,"y":140,"wires":[[]]},{"id":"700f1600.04891c","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","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,"resendOnRefresh":false,"templateScope":"local","x":250,"y":140,"wires":[[]]},{"id":"4c6394f2.9d8d9c","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","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,"resendOnRefresh":false,"templateScope":"local","x":370,"y":140,"wires":[[]]},{"id":"98688ec.eae177","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","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":130,"y":180,"wires":[[]]},{"id":"b6b4ee71.45c86","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","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":250,"y":180,"wires":[[]]},{"id":"6fa8e5a1.c6102c","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","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":370,"y":180,"wires":[[]]},{"id":"b045c8d3.031958","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","name":"script for all buttons with class remote-button","order":14,"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\"\nvar group = btn.data(\"radiogroup\") || null;\nvar state = btn.data(\"state\") || 0;//get the last state payload from attribute data-state=\"n\"\nvar ck = {\"class\": BUTTON_CLASS, \"new type\": type, \"topic\":topic,\"payload\":payload, \"group\": group, \"state\": state, \"event\": \"click\"};\nconsole.log(\"click():\",ck);\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":"ac92a2e7.532df","type":"debug","z":"cb2d7de9.28bbe","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":530,"y":340,"wires":[]},{"id":"7f2122c8.6d6c3c","type":"inject","z":"cb2d7de9.28bbe","name":"choose radio btn 4","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"4","payload":"4","payloadType":"num","x":150,"y":500,"wires":[["b045c8d3.031958"]]},{"id":"97dbd289.389b","type":"inject","z":"cb2d7de9.28bbe","name":"toggleClass disabled, on 5","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"5","payload":"{\"command\":\"toggleClass\",\"value\":\"disabled\"}","payloadType":"json","x":170,"y":440,"wires":[["b045c8d3.031958"]]},{"id":"75fbec6e.793664","type":"inject","z":"cb2d7de9.28bbe","name":"choose radio btn 5","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"5","payload":"5","payloadType":"num","x":150,"y":540,"wires":[["b045c8d3.031958"]]},{"id":"5295fbe8.20ecf4","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","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":130,"y":220,"wires":[[]]},{"id":"8118294d.6b4298","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","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":250,"y":220,"wires":[[]]},{"id":"4f76813a.a6fe6","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","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":370,"y":220,"wires":[[]]},{"id":"c9b79c0f.e8ef1","type":"comment","z":"cb2d7de9.28bbe","name":"radiogroup1","info":"","x":490,"y":180,"wires":[]},{"id":"71c9ddee.9f3d34","type":"comment","z":"cb2d7de9.28bbe","name":"radiogroup2","info":"","x":490,"y":220,"wires":[]},{"id":"fb6d5cde.77948","type":"inject","z":"cb2d7de9.28bbe","name":"toggleClass blinking, on 5","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"5","payload":"{\"command\":\"toggleClass\",\"value\":\"blinking\"}","payloadType":"json","x":470,"y":440,"wires":[["b045c8d3.031958"]]},{"id":"772502cd.dad3ac","type":"ui_template","z":"cb2d7de9.28bbe","group":"49c9cca6.e7da04","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":140,"y":300,"wires":[[]]},{"id":"a01fe5a2.5ff8f8","type":"inject","z":"cb2d7de9.28bbe","name":"toggleClass red, on 5","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"5","payload":"{\"command\":\"toggleClass\",\"value\":\"red\"}","payloadType":"json","x":460,"y":480,"wires":[["b045c8d3.031958"]]},{"id":"19c84e72.c23fe2","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","name":"mute","order":12,"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,"resendOnRefresh":false,"templateScope":"local","x":250,"y":260,"wires":[[]],"info":"  class=\"material-icons\"> volume_off"},{"id":"c1c9b13b.49d93","type":"ui_template","z":"cb2d7de9.28bbe","group":"50a1d6b5.ce2c88","name":"0","order":2,"width":"3","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,"resendOnRefresh":false,"templateScope":"local","x":130,"y":100,"wires":[[]]},{"id":"50a1d6b5.ce2c88","type":"ui_group","z":"","name":"HOME","tab":"6ff5405c.a8e6","order":1,"disp":true,"width":3,"collapse":false},{"id":"49c9cca6.e7da04","type":"ui_group","z":"","name":"Full_Remote2","tab":"58443ae1.3adab4","order":3,"disp":false,"width":"3","collapse":false},{"id":"6ff5405c.a8e6","type":"ui_tab","z":"","name":"TEST","icon":"dashboard","order":3,"disabled":false,"hidden":false},{"id":"58443ae1.3adab4","type":"ui_tab","z":"","name":"HDMI_TV_control","icon":"dashboard","order":7,"disabled":false,"hidden":false}]

Edit: Just out of interest, could I have modified your original template:

<div>
   <md-button class="md-button remote-button bigger"
    data-buttontype='radio'
    data-radiogroup='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>

To:

<div>
   <md-button class="md-button remote-button bigger"
    data-ontext="Button 4\nOn"
    data-offtext="Button 4\nOff"
    data-onbgcolour="#0000ff"
    data-offbgcolour="#000066"
    data-buttontype='radio'
    data-radiogroup='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>

If so what would the controller code look like ?

First of all, you have created such a mess, that it takes a days to figure out what is going on.
But if just focus to your question
You have defined the ck-button class here twice. One for md-button element and the other for div element
image

 $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'])") 

Now the clickButtons is declared and it gets its value by selecting every element which has the ck-button class. It does not matter if they are inside each other. They all selected and then the click function will be given for every selected element.

As your elements are inside each other, your single button fires 2 click events. What to do with them?Which one is the correct one?

Also, the md-button element does not have any data-something defined. So it can not behave by the rules you try to use or create.

Those are just few things of whole mess. I cant look deeper to even try to figure out what it is and how it should work.

I think it may be something much more simple than what it is now, but really, I don't remember that you are explained the whole concept of your button and surrounding logic clear enough.