Dashboard 2 - is there a simple way to configure button so that it toggles

Hi,

Basically, what I'd like is a button that works like a switch.

It looks like a button, but it changes state and colour when you click it.

I can do that using a subflow to figure out a toggle, but surely there's a simpler way? Halp please. I feel like I must be missing a configuration option. It's likely staring at me.

node-red-contrib-toggle

this node may help.

there is also a built in example. that may be for dashboard 1, but i believe it should work for db-2 also. sorry not tested.

image

1 Like

Have a look at the attached flow;

[{"id":"b5f2a639b7d19b66","type":"ui-button","z":"be4568a984cb4685","group":"3037a2992d27ef33","name":"Button 1","label":"Driveway","order":1,"width":"3","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"clickme","payloadType":"str","topic":"button1","topicType":"str","buttonColor":"","textColor":"","iconColor":"","x":320,"y":1760,"wires":[["c4a2394d758d7565"]]},{"id":"c4a2394d758d7565","type":"ui-notification","z":"be4568a984cb4685","ui":"ae3d4aeb3f977a90","position":"top center","colorDefault":true,"color":"#000000","displayTime":"5","showCountdown":true,"outputs":1,"allowDismiss":true,"dismissText":"Cancel","allowConfirm":true,"confirmText":"Confirm","raw":false,"className":"","name":"Confirm","x":480,"y":1760,"wires":[["dc147f8bd7854b78"]]},{"id":"dc147f8bd7854b78","type":"function","z":"be4568a984cb4685","name":"Change status","func":"if (msg.payload == \"confirm_clicked\") {\n    var buttons_status = context.get('buttons') || \"off\"\n    switch (buttons_status) {\n    case \"on\":\n        node.send({payload: \"off\"})\n        buttons_status = \"off\"\n        break;\n    case \"off\":\n        node.send({payload: \"on\"})\n        buttons_status = \"on\"\n        break;\n    default:\n        node.warn(\"Status not matched!\");\n        break;\n    }\n    context.set('buttons', buttons_status)\n}\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":660,"y":1760,"wires":[["af7ec95ad0c6445c","b3d70502cc560854"]]},{"id":"af7ec95ad0c6445c","type":"change","z":"be4568a984cb4685","name":"Change color","rules":[{"t":"change","p":"payload","pt":"msg","from":"on","fromt":"str","to":"#F44336","tot":"str"},{"t":"change","p":"payload","pt":"msg","from":"off","fromt":"str","to":"#A7FFEB","tot":"str"},{"t":"set","p":"ui_update.buttonColor","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":1820,"wires":[["b5f2a639b7d19b66"]]},{"id":"b3d70502cc560854","type":"debug","z":"be4568a984cb4685","name":"debug 2480","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":850,"y":1760,"wires":[]},{"id":"3037a2992d27ef33","type":"ui-group","name":"Lighting","page":"7294756f31e17b81","width":"3","height":"2","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"ae3d4aeb3f977a90","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"temporary","titleBarStyle":"default"},{"id":"7294756f31e17b81","type":"ui-page","name":"Security","ui":"ae3d4aeb3f977a90","path":"/security","icon":"home","layout":"tabs","theme":"52ba8a01d6eda628","order":1,"className":"","visible":true,"disabled":false},{"id":"52ba8a01d6eda628","type":"ui-theme","name":"Mobile","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"density":"compact","pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

button

If you don't want to have to confirm your button presses, the flow can be easily changed.

[{"id":"bcb5504467fc1780","type":"ui-button","z":"be4568a984cb4685","group":"3037a2992d27ef33","name":"Button 2","label":"Pathway","order":2,"width":"3","height":"1","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"clickme","payloadType":"str","topic":"","topicType":"str","buttonColor":"","textColor":"","iconColor":"","x":320,"y":1900,"wires":[["a00bc1bf3f61bfe6"]]},{"id":"a00bc1bf3f61bfe6","type":"function","z":"be4568a984cb4685","name":"Change status","func":"    var buttons_status = context.get('buttons') || \"off\"\n    switch (buttons_status) {\n    case \"on\":\n        node.send({payload: \"off\"})\n        buttons_status = \"off\"\n        break;\n    case \"off\":\n        node.send({payload: \"on\"})\n        buttons_status = \"on\"\n        break;\n    default:\n        node.warn(\"Status not matched!\");\n        break;\n    }\n    context.set('buttons', buttons_status)\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":1900,"wires":[["6933f02d34be3e1e","a4b8b6a554bc0144"]]},{"id":"6933f02d34be3e1e","type":"change","z":"be4568a984cb4685","name":"Change color","rules":[{"t":"change","p":"payload","pt":"msg","from":"on","fromt":"str","to":"#F44336","tot":"str"},{"t":"change","p":"payload","pt":"msg","from":"off","fromt":"str","to":"#A7FFEB","tot":"str"},{"t":"set","p":"ui_update.buttonColor","pt":"msg","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":1960,"wires":[["bcb5504467fc1780"]]},{"id":"a4b8b6a554bc0144","type":"debug","z":"be4568a984cb4685","name":"debug 2481","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":730,"y":1900,"wires":[]},{"id":"3037a2992d27ef33","type":"ui-group","name":"Lighting","page":"7294756f31e17b81","width":"3","height":"2","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"7294756f31e17b81","type":"ui-page","name":"Security","ui":"ae3d4aeb3f977a90","path":"/security","icon":"home","layout":"tabs","theme":"52ba8a01d6eda628","order":1,"className":"","visible":true,"disabled":false},{"id":"ae3d4aeb3f977a90","type":"ui-base","name":"Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"temporary","titleBarStyle":"default"},{"id":"52ba8a01d6eda628","type":"ui-theme","name":"Mobile","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"density":"compact","pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]
3 Likes

Hi @smanjunath211, the toggle node is very interesting, thank you for suggesting it. I appreciate your input.

Hi @Paul-Reed, thank you for your flow. I learned more from your function than you might think; the || for variable definition was a lightbulb moment - didn't know you could do that. Life changing, thank you.

Both of your solutions work to a degree, but I feel like the functionality I want already exists in the dashboard-2 switch node. I really just want a switch to look like a button. This is purely a cosmetics issue at the end of the day.

I had a go at creating what I want myself using a global context array, and this seems to work well so far.

This function gets a global array, or creates it if it doesn't exist. Per unique topic, it either updates the stored value, or pushes it to the array unchanged if it's the first instance of that topic.

Since it stores values per topic, this function can be safely used with links, or have a zillion wires in.

Here is my code, for what it's worth.

[{"id":"83a0a3f15d667639","type":"function","z":"78673de4c81bd2d4","name":"Toggle","func":"var tempstring = \"{\\\"topic\\\": \\\"\" + msg.topic + \"\\\",\\\"state\\\": \" + msg.payload.state + \"}\";\nvar newvalue = JSON.parse(tempstring);\nvar found = false;\nvar thestates = global.get(\"states\") || [];\nvar big = thestates.length;\nif (big > 0) {\n    let keys = Object.keys(thestates);\n    for (let i = 0; i < keys.length; i++) {\n        var temp = thestates[i];\n        if (newvalue.topic == temp.topic){              //this topic is already already in the array\n            found = true;                               //used after the loop\n            if(newvalue.state == temp.state){           //existing states match\n                temp.state = !newvalue.state;           //flip the stored state\n            } else {                                    //existing states don't match;\n                temp.state = newvalue.state;            //store the new state in the array element\n            }\n            msg.payload.state = temp.state;             //and update the payload\n        }\n    }\n};\nif (big == 0 || found == false){\n    thestates.push(newvalue);                           // update the global array\n};\nglobal.set(\"states\", thestates);\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":2570,"y":960,"wires":[[]]}]

Can you post a little flow, including a couple of buttons, to show how your function works?

1 Like

The Switch widget allows the specification of icon and colour for on and off states, so you may be able to find an icon that looks like a buttton.

1 Like

Hi @Colin, thank you for that good suggestion.

I'd just like to clarify that there is bugger all functionally wrong here. This is me being overly partner-buyin focussed. It used to be pretty, now we're pure Node-RED, so the pretty went away.

Works goddamn beautifully. I just have to learn about making pretty again. I'm on it. It's CSS stuff, I'm almost certain.

The drawback for me - tastewise - is I like my labels inside the button. I think it's from old machines that have labelled buttons that light up when you press them. I have weird interests.

So I'm figuring out how to make pretty buttons, because my spouse is patient, but misses the pretty, and frankly deserves all the pretty. They work really hard.

I would love to do that, but I can't figure out how to export without exposing my layout, which for me is not an acceptable security risk. Is there a method? I'm otherwise totally happy to share; everything I know is because another person helped me.

You are one of a few people to have requested this in the past week or so, we should support this on the Switch widget by default.

Issue opened: Switch: Button Type · Issue #1292 · FlowFuse/node-red-dashboard · GitHub

It feels like the button & switch nodes are starting to merge into just one node, that fulfills both node's functionality...

Which makes me nervous. I think, given also the discussion/comments in the issue too, I may make a call for there to be a 3rd party button-state node again. The lines are blurring too much for my liking, and having a simple "button" or "switch" then becomes configuration-overload.

2 Likes

Hi @joepavitt, as someone who's basically an end user, please don't be scared.

Granted, I don't fully understand your end of it, but it does seem to me like the definition between a switch an a button is kind of moot. I've been learning to build things based on rpi and esp32, with physical interfaces, and there's such a thing as a latching switch, or inching, I forget, I'm old, leave me alone.

Point is, seems to me that if there's no second payload, you could call it a button. And if there is, you could call it a switch, but it's still the same thing functionally, I think.

I suck at coding, and continue to learn, but the part I love most is when I figure a way to say the same thing with less lines. That dumps the happy chemicals. Isn't this that, for you?

YMMV. Either way, love your work.

You're very kind. Seems like a lot of trouble just to make my partner happy, but thank you.

Hi @jbudd,

It seems like this is going to be solved elsewhere, but since you asked about my toggle code with an example, here it is.

It's not really finished, but the toggle function is there and works well. I'm also thinking that perhaps I can use this approach to query the current state of arbitrary data; ie, even things you don't have to toggle.

... starts to look like a database after a while. Perhaps I should learn how to database in 2024.

[
	{
		"id": "747140ee84798ba6",
		"type": "debug",
		"z": "fcf4344d64f8d336",
		"name": "RAAAA",
		"active": true,
		"tosidebar": true,
		"console": false,
		"tostatus": false,
		"complete": "payload",
		"targetType": "msg",
		"statusVal": "",
		"statusType": "auto",
		"x": 1420,
		"y": 200,
		"wires": []
	},
	{
		"id": "862ab122cd9bdc0f",
		"type": "ui-button",
		"z": "fcf4344d64f8d336",
		"group": "52af9e4badd86f44",
		"name": "",
		"label": "Probably a light. Maybe a switch for a high voltage contactor. Relays are fun.",
		"order": 1,
		"width": 0,
		"height": 0,
		"emulateClick": false,
		"tooltip": "",
		"color": "",
		"bgcolor": "",
		"className": "",
		"icon": "",
		"iconPosition": "right",
		"payload": "{\"state\": true}",
		"payloadType": "json",
		"topic": "whatever/command",
		"topicType": "str",
		"buttonColor": "Correct me please, but the path to pretty",
		"textColor": "is surely to do with that CSS thingy up there,",
		"iconColor": "isn't it? I bet it is.",
		"x": 600,
		"y": 220,
		"wires": [
			[
				"b5e08f62e93ded0e"
			]
		]
	},
	{
		"id": "d9bfb3a6c9511871",
		"type": "inject",
		"z": "fcf4344d64f8d336",
		"name": "",
		"props": [
			{
				"p": "topic",
				"vt": "str"
			},
			{
				"p": "payload"
			}
		],
		"repeat": "",
		"crontab": "",
		"once": false,
		"onceDelay": 0.1,
		"topic": "whatever/command",
		"payload": "{\"state\":true}",
		"payloadType": "json",
		"x": 450,
		"y": 340,
		"wires": [
			[
				"b5e08f62e93ded0e"
			]
		]
	},
	{
		"id": "b0b8b4f5c44689ee",
		"type": "inject",
		"z": "fcf4344d64f8d336",
		"name": "",
		"props": [
			{
				"p": "topic",
				"vt": "str"
			},
			{
				"p": "payload"
			}
		],
		"repeat": "",
		"crontab": "",
		"once": false,
		"onceDelay": 0.1,
		"topic": "whatever/command",
		"payload": "{\"state\":false}",
		"payloadType": "json",
		"x": 460,
		"y": 380,
		"wires": [
			[
				"b5e08f62e93ded0e"
			]
		]
	},
	{
		"id": "1e8a373d07509be5",
		"type": "switch",
		"z": "fcf4344d64f8d336",
		"name": "This part is unnecessary and doesn't work yet",
		"property": "payload.state",
		"propertyType": "msg",
		"rules": [
			{
				"t": "true"
			},
			{
				"t": "false"
			}
		],
		"checkall": "true",
		"repair": false,
		"outputs": 2,
		"x": 900,
		"y": 440,
		"wires": [
			[
				"99d60729134eaca3"
			],
			[
				"10daf0c7116c3746"
			]
		]
	},
	{
		"id": "99d60729134eaca3",
		"type": "change",
		"z": "fcf4344d64f8d336",
		"name": "Yellow On button",
		"rules": [
			{
				"t": "set",
				"p": "ui_update.buttonColor",
				"pt": "msg",
				"to": "yellow#FFEB3B",
				"tot": "str"
			}
		],
		"action": "",
		"property": "",
		"from": "",
		"to": "",
		"reg": false,
		"x": 1130,
		"y": 260,
		"wires": [
			[
				"747140ee84798ba6",
				"3a6f19cd4cd19bbf"
			]
		]
	},
	{
		"id": "10daf0c7116c3746",
		"type": "change",
		"z": "fcf4344d64f8d336",
		"name": "Green Off button",
		"rules": [
			{
				"t": "set",
				"p": "ui_update.buttonColor",
				"pt": "msg",
				"to": "green#4CAF50",
				"tot": "str"
			}
		],
		"action": "",
		"property": "",
		"from": "",
		"to": "",
		"reg": false,
		"x": 1130,
		"y": 300,
		"wires": [
			[
				"747140ee84798ba6",
				"3a6f19cd4cd19bbf"
			]
		]
	},
	{
		"id": "b5e08f62e93ded0e",
		"type": "function",
		"z": "fcf4344d64f8d336",
		"name": "Toggle",
		"func": "var tempstring = \"{\\\"topic\\\": \\\"\" + msg.topic + \"\\\",\\\"state\\\": \" + msg.payload.state + \"}\";\nvar newvalue = JSON.parse(tempstring);\nvar found = false;\nvar thestates = global.get(\"states\") || [];\nvar big = thestates.length;\nif (big > 0) {\n    let keys = Object.keys(thestates);\n    for (let i = 0; i < keys.length; i++) {\n        var temp = thestates[i];\n        if (newvalue.topic == temp.topic){              //this topic is already already in the array\n            found = true;                               //used after the loop\n            if(newvalue.state == temp.state){           //existing states match\n                temp.state = !newvalue.state;           //flip the stored state\n            } else {                                    //existing states don't match;\n                temp.state = newvalue.state;            //store the new state in the array element\n            }\n            msg.payload.state = temp.state;             //and update the payload\n        }\n    }\n};\nif (big == 0 || found == false){\n    thestates.push(newvalue);                           // update the global array\n};\nglobal.set(\"states\", thestates);\nreturn msg;",
		"outputs": 1,
		"timeout": 0,
		"noerr": 0,
		"initialize": "",
		"finalize": "",
		"libs": [],
		"x": 810,
		"y": 360,
		"wires": [
			[
				"1e8a373d07509be5"
			]
		]
	},
	{
		"id": "3a6f19cd4cd19bbf",
		"type": "mqtt out",
		"z": "fcf4344d64f8d336",
		"name": "",
		"topic": "And away she goes",
		"qos": "",
		"retain": "",
		"respTopic": "",
		"contentType": "",
		"userProps": "",
		"correl": "",
		"expiry": "",
		"broker": "",
		"x": 1460,
		"y": 280,
		"wires": []
	},
	{
		"id": "fa2845ddf01e13fd",
		"type": "comment",
		"z": "fcf4344d64f8d336",
		"name": "I just figured that it would be cool if I could solve for button colour too. That MQTT comes back around, it must be a thing.",
		"info": "",
		"x": 1140,
		"y": 480,
		"wires": []
	},
	{
		"id": "52af9e4badd86f44",
		"type": "ui-group",
		"name": "Security",
		"page": "6c90f1ad3c05f124",
		"width": "4",
		"height": "1",
		"order": 1,
		"showTitle": true,
		"className": "",
		"visible": "true",
		"disabled": "false"
	},
	{
		"id": "6c90f1ad3c05f124",
		"type": "ui-page",
		"name": "Rooms",
		"ui": "cf9dc2ba60d1396c",
		"path": "/page2",
		"icon": "home",
		"layout": "flex",
		"theme": "7ba40ffa60b22682",
		"order": 4,
		"className": "",
		"visible": true,
		"disabled": false
	},
	{
		"id": "cf9dc2ba60d1396c",
		"type": "ui-base",
		"name": "My Dashboard",
		"path": "/dashboard",
		"includeClientData": true,
		"acceptsClientConfig": [
			"ui-notification",
			"ui-control"
		],
		"showPathInSidebar": true,
		"showPageTitle": true,
		"navigationStyle": "default",
		"titleBarStyle": "default"
	},
	{
		"id": "7ba40ffa60b22682",
		"type": "ui-theme",
		"name": "Default Theme",
		"colors": {
			"surface": "#ffffff",
			"primary": "#0080ff",
			"bgPage": "#eeeeee",
			"groupBg": "#ffffff",
			"groupOutline": "#cccccc"
		},
		"sizes": {
			"pagePadding": "2px",
			"groupGap": "12px",
			"groupBorderRadius": "4px",
			"widgetGap": "4px",
			"density": "comfortable"
		}
	}
]