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

This is a bit of a continuation of my CSS thread.
This one.

Given that we have the button design understood now, we not add a but of code so when the button is pressed an output is generated.

This is the code from which we will be building.

[{"id":"bded269f.23a12","type":"ui_template","z":"1781e581.31721a","group":"6ab22327.a2f71c","name":"THIS IS THE TEMPLATE","order":7,"width":0,"height":0,"format":"<style id=\"remote-buttons\">\n    :root {\n      --dashboard-unit-width: 48px;\n      --dashboard-unit-height: 48px;\n    }\n    .nr-dashboard-template {\n        padding: 0px;\n    }\n    .remote-button:not([disabled]):hover{\n         background-color: #232323 !important;\n    }\n\n    /*   This is the normal button definition  */\n    .remote-button{\n        background-color: black !important;\n        color: #cccccc !important;\n        height: var(--dashboard-unit-height);\n        width: 100%;\n        border-radius: 10px;\n        font-size:1.0em;\n        font-weight:normal;\n        margin: 0;\n        min-height: 36px;\n        min-width: unset;\n        line-height: unset;\n    }\n    /*  This is a sub-set which is invoked by */\n    /*  <md-button class=\"md-button remote-button bigger\"> */\n    /*  note the (space) \"bigger\" at the end.  */\n    .remote-button.bigger{\n        font-weight:bold;\n        font-size:1.5em;\n    }\n    /*  This is for buttons with a lot of text.  `font-size:0.7em` */\n    /*  makes the font 70% normal size  */\n    .remote-button.small{\n        font-size:0.7em;\n    }\n    /*  This is for buttons with just icons, to upsize the size */\n    /*  of the icon with the line: */\n    /*  <i class=\"fa fa-fw fa-plus remote-icon\"> in the other node  */\n    .remote-icon{\n        font-size:2.0em;\n    }\n    /*  This is the same as the other one, but it makes the icon smaller  */\n    .remote-iconS{\n        font-size:0.5em;\n    }\n\n    .remote-button.black{\n        background-color: black !important;\n        color: #cccccc !important;\n    }\n\n    .remote-button.red{\n        background-color: red !important;\n        color: #cccccc !important;\n    }\n    .remote-button.red:not([disabled]):hover{\n         background-color: orange !important;\n    }\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"global","x":530,"y":1410,"wires":[[]]},{"id":"797fdca7.e44f6c","type":"ui_template","z":"1781e581.31721a","group":"eabe7d43.b23f48","name":"1","order":1,"width":1,"height":1,"format":"<div id=\"regular_1\">\n   <md-button class=\"md-button remote-button bigger\">1\n   </md-button>\n</div>\n\n<script>\n\n(function($scope) {\n\n$('#regular_1').on('click', function(e) {\n    e.preventDefault(); //prevent default behavior\n    $scope.send({\"topic\":\"momentary_regular\",\"payload\": 1});\n});\n    \n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":250,"y":1550,"wires":[["1cf586b3.c22e71"]]},{"id":"45e5a406.7764d4","type":"ui_template","z":"1781e581.31721a","group":"eabe7d43.b23f48","name":"vol +  *","order":13,"width":1,"height":1,"format":"<div id=\"regular_plus\">\n   <md-button class=\"md-button remote-button\">\n      <span style=\"color:{{msg.colour}}\" class=\"fa fa-plus remote-icon\"> </span>\n   </md-button>\n</div>\n\n<script>\n\n(function($scope) {\n    \n$('#regular_plus').on('click', function(e) {\n    e.preventDefault(); //prevent default behavior\n    $scope.send({\"topic\":\"regular_plus\",\"payload\": \"vol-up\"});\n});\n    \n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":false,"resendOnRefresh":true,"templateScope":"local","x":250,"y":1750,"wires":[["69848a07.c10464"]],"info":"<div id=\"regular_plus\">\n   <md-button class=\"md-button remote-button\">\n      <i class=\"fa fa-plus remote-icon\"></i>\n   </md-button>\n</div>"},{"id":"bc978fd4.0ec948","type":"ui_template","z":"1781e581.31721a","group":"eabe7d43.b23f48","name":"mute","order":14,"width":1,"height":1,"format":"<div>\n   <md-button id=\"mute-button\" class=\"md-button remote-button\" \n              data-state=\"on\"  ng-click=\"toggleMute()\" >\n      <i class=\"material-icons md-48\">volume_off</i>\n   </md-button>\n</div>\n\n<script>\n\n(function($scope) {\n\n    var btnSelector = \"#mute-button\";//Set the selector to match the button ID above\n    var topic = \"mute\"; //edit me to suit - this is sent to your flow as msg.topic\n    var icon1 = \"volume_up\";//on icon\n    var icon2 = \"volume_off\";//muted icon\n    \n    //create a function to call in ng-click (see the md-button attributes above)\n    scope.toggleMute = function(){\n        debugger\n        var $btn = $(btnSelector);\n        var currentState = $btn.data(\"state\");\n        //if currently on, change to off state & send new payload\n        if(currentState == \"on\"){\n            mute($btn);\n            $scope.send({\"topic\":topic,\"payload\": \"off\"});\n        } else {\n            unmute($btn);\n            $scope.send({\"topic\":topic,\"payload\": \"on\"});\n        }\n    } \n    //watch for node-red msgs\n    scope.$watch('msg', function(msg) {\n        var $btn = $(btnSelector);\n        if (msg) {\n            if(msg.payload == \"on\"){\n                unmute($btn);\n            } else if(msg.payload == \"off\"){\n                mute($btn);\n            }\n          \n        }\n    }); \n    //helper function to set the correct icon & update the \"data-state\" memory\n    function mute($btn){\n        var $ico = $btn.find(\"i\");\n        $btn.data(\"state\",\"off\");//set data-state to off (remember state)\n        // $ico.removeClass(\"fa-volume_mute\")\n        // $ico.addClass(\"fa-volume_off\")\n        $ico.text(icon2);\n    }\n    //helper function to set the correct icon & update the \"data-state\" memory\n    function unmute($btn){\n        var $ico = $btn.find(\"i\");\n        $btn.data(\"state\",\"on\");//set data-state to on (remember state)\n        // ico.removeClass(\"fa-volume_off\");\n        // ico.addClass(\"fa-volume_mute\");\n        $ico.text(icon1);\n    }\n\n})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","x":490,"y":1750,"wires":[["9d7f3cad.d7b8f"]],"info":"  class=\"material-icons\"> volume_off"},{"id":"6ab22327.a2f71c","type":"ui_group","z":"","name":"HOME","tab":"6d306f92.ccc54","order":1,"disp":true,"width":3,"collapse":false},{"id":"eabe7d43.b23f48","type":"ui_group","z":"","name":"Full_Remote2","tab":"b128eb09.9f681","order":3,"disp":false,"width":"3","collapse":false},{"id":"6d306f92.ccc54","type":"ui_tab","z":"","name":"TEST","icon":"dashboard","order":3,"disabled":false,"hidden":false},{"id":"b128eb09.9f681","type":"ui_tab","z":"","name":"HDMI_TV_control","icon":"dashboard","order":7,"disabled":false,"hidden":false}]

Three types of buttons.

1 - Basic button. You press it and it sends a message.
2 - Two message button. One sent when pressed, and one sent when released.
This button also accepts input to set the colour of the icon.
3 - Another button which also accepts input to determine which icon is shown.
(The mute button)

First we need to make sure that our code does not produce any errors or warnings in browser console.
As we worked previously more with CSS (the look and feel) but not much with buttons and technical requirements, The buttons missing some things.

The aria-label.

Any element where text is not visible should have it. Even if it doesn't gain any usability for you, it is not nice if you have console full of warnings.
In this case there is many buttons with only the icon is presented. Those will fire warnings.

Unique.
To have ability to make buttons work, we need to identify them one by one.
This is done by giving the unique "id" for every interactive element.

Take button 1

  • add aria-label (I used "One" for button 1)

  • adjust the id to be unique (I used "btn_1" for button 1)
    See that this id is used at least twice in button template!
    All buttons must have it!

<div id="btn_1">
   <md-button class="md-button remote-button bigger" aria-label="One">1
   </md-button>
</div>

<script>

(function($scope) {

$('#btn_1').on('click', function(e) {
    e.preventDefault(); //prevent default behavior
    $scope.send({"topic":"momentary_regular","payload": 1});
});
    
})(scope);
</script>

Is it me or does that contradict itself?

1 - Use it in cases where a text label is not visible on the screen.
2 - If there is visible text labeling the element, use aria-labelledby instead.

I'm obviously missing something there.

You may of course use aria-labelledby, but main thing is to use aria-label where button has only icon and no text.

<div id="btn_1">
   <md-button class="md-button remote-button bigger" aria-label="One">1

So the btn_1 and aria-label="One" need to be unique to each button?

So button 2 would be:
btn_2 and aria-label="Two"
and so on....

Ok, only for buttons with icons - as text buttons kind of don't have this problem.

But.... These names should be unique to make finding problems easier. OK, I get it.

I have a bit of work to do.

If the "text" buttons have this id="blah" and the blah is the same.... Is that looking for headaches in the future when problems happen?

Argh! So it is the second part (the aria-label="blah") which is more important.
I just did the id="blah" part.

As there is 2 things to adjust, pay attention to make the ID part correctly

image

ARGH! That too! Ok. Thanks.

I'll have to catch that next time. I want to complete this pass adding the stuff I missed first time.

Stand by.

Stuck on the Mute button:
I added the id="Mute" and the aria-label="Mute" on line 3.

<div id="Mute">
   <md-button id="mute-button" class="md-button remote-button" 
              data-state="on"  ng-click="toggleMute()"  aria-label="Mute">
      <i class="material-icons md-48">volume_off</i>
   </md-button>
</div>

<script>

(function($scope) {

    var btnSelector = "#mute-button";//Set the selector to match the button ID above
    var topic = "mute"; //edit me to suit - this is sent to your flow as msg.topic
    var icon1 = "volume_up";//on icon
    var icon2 = "volume_off";//muted icon
    
    //create a function to call in ng-click (see the md-button attributes above)
    scope.toggleMute = function(){
        debugger
        var $btn = $(btnSelector);
        var currentState = $btn.data("state");
        //if currently on, change to off state & send new payload
        if(currentState == "on"){
            mute($btn);
            $scope.send({"topic":topic,"payload": "off"});
        } else {
            unmute($btn);
            $scope.send({"topic":topic,"payload": "on"});
        }
    } 
    //watch for node-red msgs
    scope.$watch('msg', function(msg) {
        var $btn = $(btnSelector);
        if (msg) {
            if(msg.payload == "on"){
                unmute($btn);
            } else if(msg.payload == "off"){
                mute($btn);
            }
          
        }
    }); 
    //helper function to set the correct icon & update the "data-state" memory
    function mute($btn){
        var $ico = $btn.find("i");
        $btn.data("state","off");//set data-state to off (remember state)
        // $ico.removeClass("fa-volume_mute")
        // $ico.addClass("fa-volume_off")
        $ico.text(icon2);
    }
    //helper function to set the correct icon & update the "data-state" memory
    function unmute($btn){
        var $ico = $btn.find("i");
        $btn.data("state","on");//set data-state to on (remember state)
        // ico.removeClass("fa-volume_off");
        // ico.addClass("fa-volume_mute");
        $ico.text(icon1);
    }

})(scope);
</script>

If I get any more I'll append them to this post.

The Select button:

<div id="Select">
   <md-button class="md-button remote-button" aria-label="Select">Sel
   </md-button>
</div>

And dumb question on the names... The can't have + or - in them, right?

Here you already made the button ready to have output and accept the input. And it 's probably works that way.
Whole thing stands on how to identify your elements and this is the ID part you made. Just watch out to preserve the uniqueness.

And yes - avoid anything non-literal in id's (numbers will be ok, but don't start with number)

Thanks.

I guess what was killing me was I would copy the code from button to button when making new buttons.
Or, copying the node.

Seems it can't be that simple. No problems. It is what it is. No sense getting upset.

So + should be replaced with up..... :frowning: No problems. Doing that now.

(Gee I am appreciating learning to type better than I could at 15)

So, sorry... the mute button.... And select.... Select seems sparse for code to give an output.

(I'm taking it you use VisualStudio to edit the code? I've heard of it and have it installed, but do all the editing the old way....)

(And I am going to have to copy all these buttons between the two screens that use them.)

I think I am ready for the next step. Found a few name duplications and fixed them.
I hope no more exist.

Next thing is to be sure, that every button has an output.
To do so, we need to be able to see the output and press buttons one by one.
As there will be different logic for buttons, do at first most simple ones. Those which have just click.
I'd would connect one debug node for all of them, and then see if they produce output. (no matter yet what the output is, cos this part is not corrected yet.)

If all those simple buttons have the output then let's correct that output to be ready for your logic.

I did that with the buttons 1 - 0 already.
Though I did make a goof with the names and when I pressed 1, I got all the numbers from 1 to 0.

I went through and edited all their names and they do work.
And a couple of the other ones. All of them may work, as I have used a few more of them.

Select doesn't work.
(I haven't checked the volume ones...)

Good. Have you decided about the topic of output message also? Will it be used? should it carry something important? Or you don't need it at all..?

No, topic isn't needed.

What is sent is not important. All these (well most) are sent into a node that doesn't really care. I just wants a signal.

The only one/s that may be of concern are the two volume; mute; and power.

Ok. Does any of those simple buttons require to accept also the input (change in some way if msg comes in)

Mute and Power are the only two simple buttons.

Mute expects the icon name and Power expects the icon colour.

(Sorry)
I got the select button working.
Not rocket science. :wink:

Something that I just remembered:
The buttons which don't have text (icon only) it may be good to have text pop up if you hover over the button?

So the buttons with state
This is the territory of many many possible ways to do. So I cant really force to chose any particular way but can only give some advises.

The mute button is already made. Does it work for you as you expect? And can you understand the code?

(I was getting worried. The notification went away with your posting.)
Actually it does. Sorry. I just listed it because I didn't realise it did the state stuff in itself.

That is kind of good, and kind of bad. I am not biased.

I may swap the icons around, not sure. And if they are all done locally (in the node) I may also mess about with the colour too.

Oh! I also mentioned it because when I was doing the name setting, I didn't find the part you mentioned so was not sure.

I'm looking at the code, but it is a bit of a void. I may need to sit down and really look at it to understand what is happening.

Where I am getting stumped:
Other buttons:

<div id="ChList">
   <md-button class="md-button remote-button" aria-label="ChList">
      <i class="fa fa-list-alt remote-icon"></i>
   </md-button>
</div>
<script>
(function($scope) {
   
$('#ChList').on('click', function(e) 

The Mute button code:

<div id="Mute">
   <md-button id="mute-button" class="md-button remote-button" 
              data-state="on"  ng-click="toggleMute()"  aria-label="Mute">
      <i class="material-icons md-48">volume_off</i>
   </md-button>
</div>

<script>

(function($scope) {

    var btnSelector = "#mute-button";//Set the selector to match the button ID above
    var topic = "mute"; //edit me to suit - this is sent to your flow as msg.topic
    var icon1 = "volume_up";//on icon
    var icon2 = "volume_off";//muted icon

I am getting confused with the id here.

Does this node also accept input?
(Ok, it does)
But the input doesn't change the icon. So not too good on that part.

Logic.
Button itself actually should not care about what is the state. It should only show the graphics to indicate the state of something and be able to send out just one command ("clicked"). That command is handled somewhere in your flow. That handling changes the state. Changed state will be sent back to the button and button again shows that state.

Imo, the button (or you could say - client side) does need to know it's current state (kinda) to be able to send the correct payload depending on it's state when operated from client side.

You could of course handle that server side but I suspect you risk getting out of sync (visual state Vs server side state)

Interesting.

I don't (didn't) have any inputs to the button.

I click it and after the initial (maybe) setting itself up it works.
The TV is muted, the icon changes.
I click it again, and the TV is unmuted and the icon changes back.

After seeing it accepts input, I stuck two inject nodes and sent it the expected messages.
on and off.
Clicking the inject node/s the TV toggles it state, but after one press I flipped to the GUI and the icon hadn't changed.

(ARGH!)

Looking again, it does.

I may have done two clicks and didn't see it change. Sorry.