Ui_template mvc and scope of context/flow/global stores

I am sorry it is a mess. I am happy to chuck my ui_template in the bin as long as I can have a model that allows the change of button on/off colours and texts according to its visual state.

It looks like the only mod to my reference code would be in the button template:

<div>
   <md-button class="md-button remote-button bigger"
    data-ontext="Btn4\nOn"
    data-offtext="Btn4\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>
and the controller:
![image|690x191](upload://ip4cgOP5d2QaGwxYWVKZebhAQcR.png) 
I got the background colour to change.
Is this the right way ?
Like line 145:


But with code to get values from:
data-ontext="Btn4\nOn"
data-offtext="Btn4\nOff"
data-onbgcolour='#0000ff'
data-offbgcolour='#000066'

Is that better ?

Also what do you think of the button style ?

image


Ok this works:
function setButtonState($btn, state){
    console.log("setButtonState", $btn[0], state);
    var oldState = $btn.data("state");
    var btntxt = "";
    var btnbgc = "#333333";
    $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;
        btntxt = $btn.data("ontext");
        btnbgc = $btn.data("onbgcolour");
    } else {
        state = 0; //normalise to a number
        oppositeState = 1;
        btntxt = $btn.data("offtext");
        btnbgc = $btn.data("offbgcolour");
    }
    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 oldState;//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
        }
        $icon.css({"background-color": btnbgc});
    }
    return oldState;
}
I cannot figure how to substitute the text or handle a button without an icon.

I am happy to then try and manipulate the text style to my requirements.

How do I change th button text ?

Don't make the button smart. Button itself should be dummy and stateless. It just must have ability to represent the state. And have enough parameters to be identified and properly controlled.

Logic to tell buttons which state they need to represent should be somewhere else.
Button reflects state of some thing and that information should not be connected with button itself.
For example: ceiling light is on. Button doesn't care. Only thing what is needed is to send to that button proper information how to represent that situation. (add class with different background-color, remove some class, change text, change icon, change size ...)
By pressing that button the magic happened and the ceiling light is off. Button again doesn't care. Again what is needed is to send in some info.

Form that reference flow I see you only miss the "change of the text" part?
To set text "freely" just create another command for

 function processCommand($btn, payload){

...

case "setText":
                $btn.text(payload.value); 
                break;

You can find solutions if you want to use predefined values for button text (in format of data-label-on, data-label-off).

You also want some kind of group defined for buttons, I can't figure out why it is needed so I cant give any advise for that.

The group is for radio button collection as your original example.
I am happy with this:

<div>
   <md-button class="md-button remote-button bigger"
    data-ontext="Btn4\nOn"
    data-offtext="Btn4\nOff"
    data-onbgcolour='#0000ff'
    data-offbgcolour='#000066'
    data-buttontype='radio'
    data-radiogroup='group1'
    data-icon0="fa fa-circle-o"
    data-icon1="fa fa-check-circle-o"
    data-topic="4"
    data-payload="4">
       <i class="fa fa-circle-o"></i> 4
   </md-button>
</div>

as a button template.

How can I handle text with no icon ?

I have:

function setButtonState($btn, state){
        console.log("setButtonState", $btn[0], state);
        var oldState = $btn.data("state");
        var btntxt = "";
        var btnbgc = "#333333";
        $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;
            btntxt = $btn.data("ontext");
            btnbgc = $btn.data("onbgcolour");
        } else {
            state = 0; //normalise to a number
            oppositeState = 1;
            btntxt = $btn.data("offtext");
            btnbgc = $btn.data("offbgcolour");
        }

        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 oldState;//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
            }
            $icon.css({"background-color": btnbgc});
        }
        return oldState;
    }

How do I set the text here ?

$icon.css({"background-color": btnbgc});
???.css({"text": btntxt});

You have reference to $btn and already defined btntxt
So $btn.text(btntxt); should be fine before or after $icon.css({"background-color": btnbgc});

Thank you. I have:

            $icon.css({"background-color": btnbgc});
            $btn.text(btntxt);

but it does not work.
P.S. even if bigger is substituted with small:

<div>
   <md-button class="md-button remote-button small"
    data-ontext="On"
    data-offtext="Off"
    data-onbgcolour='#0000ff'
    data-offbgcolour='#000066'
    data-buttontype='radio'
    data-radiogroup='group1'
    data-icon0="fa fa-circle-o"
    data-icon1="fa fa-check-circle-o"
    data-topic="4"
    data-payload="4">
       <i class="fa fa-circle-o"></i> 4
   </md-button>
</div>

Any idea why ?

Fixed it by adding the missing span:

[{"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-ontext=\"Btn4\\nOn\"\n    data-offtext=\"Btn4\\nOff\"\n    data-onbgcolour='#ff0000'\n    data-offbgcolour='#660000'\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-ontext=\"Btn4\\nOn\"\n    data-offtext=\"Btn4\\nOff\"\n    data-onbgcolour='#ff0000'\n    data-offbgcolour='#660000'\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-ontext=\"Btn4\\nOn\"\n    data-offtext=\"Btn4\\nOff\"\n    data-onbgcolour='#ff0000'\n    data-offbgcolour='#660000'\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":"<!--\n    V1\n    ==\n    data-icon0=\"fa fa-circle-o\"\n    data-icon1=\"fa fa-check-circle-o\"\n//-->\n<div>\n   <md-button class=\"md-button remote-button small\"\n    data-ontext=\"On\"\n    data-offtext=\"Off\"\n    data-onbgcolour='#0000ff'\n    data-offbgcolour='#000066'\n    data-buttontype='radio'\n    data-radiogroup='group1'\n    data-icon0=\"fa fa-circle-o\"\n    data-icon1=\"fa fa-check-circle-o\"\n    data-topic=\"4\"\n    data-payload=\"4\">\n       <i class=\"fa fa-circle-o\"></i>\n       <span>off</span>\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 small\"\n    data-ontext=\"On\"\n    data-offtext=\"Off\"\n    data-onbgcolour='#0000ff'\n    data-offbgcolour='#000066'\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>\n       <span>off</span>\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 small\"\n    data-ontext=\"On\"\n    data-offtext=\"Off\"\n    data-onbgcolour='#0000ff'\n    data-offbgcolour='#000066'\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>\n       <span>off</span>\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        var btntxt = \"\";\n        var btnbgc = \"#333333\";\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            btntxt = $btn.data(\"ontext\");\n            btnbgc = $btn.data(\"onbgcolour\");\n        } else {\n            state = 0; //normalise to a number\n            oppositeState = 1;\n            btntxt = $btn.data(\"offtext\");\n            btnbgc = $btn.data(\"offbgcolour\");\n        }\n        var $icon = $btn.find(\"i\"); //get the <i> element\n        var $span = $btn.find(\"span\"); //get the <span> 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            $icon.css({\"background-color\": btnbgc});\n            $span.css({\"background-color\": btnbgc});\n            $span.text(btntxt);\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 small\"\n    data-ontext=\"On\"\n    data-offtext=\"Off\"\n    data-onbgcolour='#00ff00'\n    data-offbgcolour='#006600'\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>\n       <span>off</span>\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 small\"\n    data-ontext=\"nOn\"\n    data-offtext=\"Off\"\n    data-onbgcolour='#00ff00'\n    data-offbgcolour='#006600'\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>\n       <span>off</span>\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 small\"\n    data-ontext=\"On\"\n    data-offtext=\"Off\"\n    data-onbgcolour='#00ff00'\n    data-offbgcolour='#006600'\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>\n       <span>off</span>\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-ontext=\"Btn4\\nOn\"\n    data-offtext=\"Btn4\\nOff\"\n    data-onbgcolour='#aa00aa'\n    data-offbgcolour='#660066'\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}]

and:
$icon.css({"background-color": btnbgc});
$span.text(btntxt);
Work OK but how do I change the button background color.
I tried:

            $icon.css({"background-color": btnbgc});
            $span.css({"background-color": btnbgc});
            $span.text(btntxt);

It looks like this:

![image|310x484](upload://4tjjXlAjKtvZANzpjsb0gFLgFVG.png)

Put text into span

<div>
   <md-button class="md-button remote-button small"
    data-ontext="On"
    data-offtext="Off"
    data-onbgcolour='#0000ff'
    data-offbgcolour='#000066'
    data-buttontype='radio'
    data-radiogroup='group1'
    data-icon0="fa fa-circle-o"
    data-icon1="fa fa-check-circle-o"
    data-topic="99"
    data-payload="99">
       <i class="fa fa-circle-o"></i><span id="txt"> 99</span>
   </md-button>
</div>

And then use .find() to select that span and set text. Otherwise you will replace also the icon.

$icon.css({"background-color": btnbgc});
var txt = $btn.find("#txt")
txt.text(btntxt)

Done. How do I change the button background colour ?

Have you removed the important statements from CSS?

OK so now I have to merge the new css:

<style>
    .nr-dashboard-template {
        padding: 0px;
    }
    .md-button {
        border-radius: 10px;
    }
    .md-button:hover {
        border:1px solid red;
    }
    .bigger{
        font-size:1.5em;
    }
    .bold {
        font-weight:bold;
    }
    /*  This is for buttons with a lot of text.  `font-size:0.7em` */
    /*  makes the font 70% normal size  */
    .small{
        font-size:0.7em;
    }


    /* New ck-button */    
    .ck-button.disabled{
        pointer-events: none;
        opacity: 0.5;
    }
    .ck-switch {
        position: relative;
    }
    
    .ck-switch input {
        display: none;
    }
    
    .off {
        position: absolute;
        top: 0px;
        height: 100%;
        width: 100%;
        opacity: 1;
    }
    .on {
        height: 100%;
    }
    .ck-button input:checked + .on {
        opacity: 1;
    }
    .ck-button input:checked + div + .off {
        opacity: 0;
    }
    
    
    .ck-button {
        margin: -6px;
        padding: 6px;
        width: 100%;
        height: 100%;
        overflow: auto;
    }
    
    .ck-button label {
        height: 100%;
        text-align: center;
        float: left;
        width: 100%;
    }
    
    .ck-button label div {
        margin: -6px;
        padding: 6px;
        text-align: center;
        display: block;
    }
    
    .ck-button label input {
        position: absolute;
        top: -20px;
    }
    .ck-button label div {
/*ok        white-space: normal;*/
white-space:pre-wrap;
        word-break: break-word;
        display: flex;
        align-items: center;
        justify-content: center;
    }

into your example css.

This is where a cold shiver runs up my spine. It is so easy to loose control.

The main style is from the fragment:

    .ck-button label div {
        white-space:pre-wrap;
        word-break: break-word;
        display: flex;
        align-items: center;
        justify-content: center;
    }

How would you go about this ?

Don't know cos there is no label, there is no ck-button, there is no div
You cant just merge arbitrary things. The structure must be followed

Yes I see that also. So can the style be applied to the span ?
Or do I add a label ?
I do not mind either way. but the most cleanest would be best.

What can I do with span:

Of course you can style the span. And it depends how you use it. Cos with prevous images you have shared, those texts can be different. So hard to tell what is the correct way to do it. I'd have class or multiple classes created for that span depending on how it will change.
<i class="fa fa-circle-o"></i><span class="some-class-name" id="txt"> 99</span>

Unfortunately

    .remote-button label span {
        white-space: pre-wrap;
        word-break: break-word;
        display: flex;
        align-items: center;
        justify-content: center;
    }

does no produce:
image

A lot of my css is there to hide/show a pair of divs that existed in my new template.

If I try the merge in the opposite direction would that be simpler ?
i.e. adapt your controller to work with my template:

<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>

This feels so close. What do you think ?

What ever makes sense for you. I can't see one or other way will be simpler. It is simple if you understand what you are doing cos everything is custom made. And you are the architect. You have requirements and vision. I can only give technical advises.

Thank you I understand. The problem I have is that a week aga I was in the same position as I am now. At the end of the week I had reduced the css to:

<style>
    .nr-dashboard-template {
        padding: 0px;
    }
    .md-button {
        border-radius: 10px;
    }
    .md-button:hover {
        border:1px solid red;
    }
    .bigger{
        font-size:1.5em;
    }
    .bold {
        font-weight:bold;
    }
    /*  This is for buttons with a lot of text.  `font-size:0.7em` */
    /*  makes the font 70% normal size  */
    .small{
        font-size:0.7em;
    }


    /* New ck-button */    
    .ck-button.disabled{
        pointer-events: none;
        opacity: 0.5;
    }
    .ck-switch {
        position: relative;
    }
    
    .ck-switch input {
        display: none;
    }
    
    .off {
        position: absolute;
        top: 0px;
        height: 100%;
        width: 100%;
        opacity: 1;
    }
    .on {
        height: 100%;
    }
    .ck-button input:checked + .on {
        opacity: 1;
    }
    .ck-button input:checked + div + .off {
        opacity: 0;
    }
    
    
    .ck-button {
        margin: -6px;
        padding: 6px;
        width: 100%;
        height: 100%;
        overflow: auto;
    }
    
    .ck-button label {
        height: 100%;
        text-align: center;
        float: left;
        width: 100%;
    }
    
    .ck-button label div {
        margin: -6px;
        padding: 6px;
        text-align: center;
        display: block;
    }
    
    .ck-button label input {
        position: absolute;
        top: -20px;
    }
    .ck-button label div {
        white-space:pre-wrap;
        word-break: break-word;
        display: flex;
        align-items: center;
        justify-content: center;
    }

which is quite minimal in my opinion considering the rendered result.
The template is:

<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>

What would the bottom of setButtonState() look like if it had to reach into the above template ?

  1. Moving back to mess creation if this class is defined to multiple nested elements and that class is an identifier to collect elements to be clickable.

  2. You have seen how to use .find() That's the way to select needed element and adjust its properties.

I certainly do not wish to return to the mess I was in. There is a basic thing I am missing here I think.

Could you explain more about 1 ?

Without this class the button will not style.

If I do:

<div>
<md-button class="md-button 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>

the button height is determined by text lines.
image

The right hand button should be 3 units high.

To get it to render correctly I must:

<md-button class="md-button ck-button on off small">

Which you say breaks everything.

Could you please explain why and how I can remove unwanted side effects ?

I'd tried to get rid this div and declare all properties top level element.

I tried to explain twice. English is not my native language so doing it again probably will not add anything valuable. May be if you try to read once again you can follow the point.

It's 01:30 local time for me. Time to bed.

Thank you again for what you have done so far. My rubbish code is bad because I was trying to add my template to your controller and I did not want to delete any of your functioning code.

If you could spare a little time tomorrow it seems as though I am so close.

Best regards,
oz