In the vein of the very successful First september give-away (let’s create one gauge) topic I thought I would introduce something that I haven't seen in the forum before (not to say it isn't here but I have not found it.)
When I first started using node-red I was also starting HTML & CSS and decide to use the ui_template to develop my own button-as-a-widget. I wanted to use the HTML data-* attribute as a way to control the button but I had problems getting the HTML element Object using a uniquely generated ID (as shown in one of the node examples). In the end I gave up and just used a hard coded ID with each widget having to be coded individually.
Recently I needed an LED widget because for some reason my node-red running on Ubuntu 20.4 in a VM refused to install the ui_led I used elsewhere. This raised the issue of a unique ID all over again. Well I finally cracked it, and thought I would share the results of all the experimentation & investigation that lead to that.
The main problem was using a generated ID as the attribute to get the relevant HTML element. The answer is to use the JQuery document ready function.
I have added the style section here but in reality I have it in another ui_template added to the site head section. In the HTML the data-colour attribute is a JSON array of the colours, and the data-input is a JSON array of the relevant msg.payload inputs required to display those colours (much like the ui_led node)
I have also include a flow so that anyone interested can try it out. P.S. I added a send entry so that the colour selected can be seen in the debug panel
<body>
<div id = "{{'led' + $id}}"
class = "led"
data-colour = '["red", "yellow", "grey"]'
data-input = '["on", "auto", "off"]'>
</div>
<script>
(function(scope) {
// Important. Use JQuery document ready function so that the Unique ID is created & loaded
$(function() {
const self = $('#led' + scope.$id)
// Data to use from the HTML
let ledData = {
colour: self.data('colour'),
input: self.data('input'),
}
let ledState = 0 // Current state
let currentClass = 'led-' + ledData.colour[ledState] // Current class
// Data to return to HTML led
self.addClass(currentClass) // Base LED class + colour class
/* Functions to use in the Scope */
// Triggered if the node receives a msg. Updates the LED according to the payload received
scope.$watch('msg', function(msg) {
let payloadState = msg.payload.toLowerCase()
// Use Math.max to ensure that the ledState is never -1
ledState = Math.max(0, ledData.input.indexOf(payloadState))
showChange()
})
// Remove the current colour class, update and add the new colour class
function showChange() {
self.removeClass(currentClass)
currentClass = 'led-' + ledData.colour[ledState]
self.addClass(currentClass)
}
})
})(scope)
</script>
</body>
<style>
.led {
background-color: rgba(255, 255, 255, 0.25);
min-height: 14px;
min-width: 14px;
height: 14px;
width: 14px;
max-height: 14px;
max-width: 14px;
text-align: center;
margin: 11px;
border-radius: 50%;
}
.led-red {
background-color: #ff0000;
box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #ff0000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;
}
.led-orange {
background-color: #FF7000;
box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #FF7000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;
}
.led-yellow {
background-color: #ffff00;
box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #ffff00 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;
}
.led-green {
background-color: #008000;
box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #008000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;
}
.led-blue {
background-color: #0000ff;
box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #0000ff 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;
}
.led-grey {
background-color: #808080;
box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #808080 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;
}
</style>
[{"id":"086c424425ea3dde","type":"ui_template","z":"2be5023b.9f1aee","group":"6e92dc7a.d2b0e4","name":"LED CSS Style Sheet","order":3,"width":0,"height":0,"format":"\n<style>\n .led {\n background-color: rgba(255, 255, 255, 0.25);\n min-height: 14px;\n min-width: 14px;\n height: 14px;\n width: 14px;\n max-height: 14px;\n max-width: 14px;\n text-align: center;\n margin: 11px;\n border-radius: 50%;\n }\n\n .led-red {\n background-color: #ff0000;\n box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #ff0000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;\n }\n\n .led-orange {\n background-color: #FF7000;\n box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #FF7000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;\n }\n\n .led-yellow {\n background-color: #ffff00;\n box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #ffff00 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;\n }\n\n .led-green {\n background-color: #008000;\n box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #008000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;\n }\n\n .led-blue {\n background-color: #0000ff;\n box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #0000ff 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;\n }\n\n .led-grey {\n background-color: #808080;\n box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #808080 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;\n }\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"global","className":"","x":740,"y":140,"wires":[[]]},{"id":"72c59839b7e0ffd1","type":"inject","z":"2be5023b.9f1aee","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"on","payloadType":"str","x":1290,"y":1400,"wires":[["0a87e0f394887302"]]},{"id":"3c5adedb0a0e0611","type":"inject","z":"2be5023b.9f1aee","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"off","payloadType":"str","x":1290,"y":1440,"wires":[["0a87e0f394887302"]]},{"id":"5597bf2dab182881","type":"inject","z":"2be5023b.9f1aee","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"auto","payloadType":"str","x":1290,"y":1480,"wires":[["0a87e0f394887302"]]},{"id":"0a87e0f394887302","type":"ui_template","z":"2be5023b.9f1aee","group":"6e92dc7a.d2b0e4","name":"","order":5,"width":0,"height":0,"format":"<body>\n <div id = \"{{'led' + $id}}\" \n class = \"led\" \n data-colour = '[\"red\", \"yellow\", \"grey\"]'\n data-test = '[\"one\", \"two\"]'\n data-input = '[\"on\", \"auto\", \"off\"]'>\n\n </div>\n\n<script>\n\n (function(scope) {\n // Important. Use JQuery document ready function so that the Unique id is created & loaded\n $(function() {\n const self = $(\"#led\" + scope.$id)\n\n // Data to use from the HTML\n let ledData = {\n colour: self.data('colour'),\n input: self.data(\"input\"),\n\n }\n\n let ledState = 0 // Current state\n let currentClass = 'led-' + ledData.colour[ledState]\n\n // Data to return to HTML led\n self.addClass(currentClass) // Base LED class + colour class\n \n /* Functions to use in the Scope */\n // Triggered if the node receives a msg. Updates the LED according to the payload received\n scope.$watch('msg', function(msg) {\n let payloadState = msg.payload.toLowerCase()\n\n // Use Math.max to ensure that ledState is never -1\n ledState = Math.max(0, ledData.input.indexOf(payloadState))\n \n showChange()\n \n scope.send(ledData.colour[ledState])\n\n })\n \n // Remove the current colour class, update and add the new colour class\n function showChange() {\n self.removeClass(currentClass)\n \n currentClass = \"led-\" + ledData.colour[ledState]\n \n self.addClass(currentClass)\n\n }\n\n })\n\n })(scope)\n\n</script>\n\n</body>","storeOutMessages":true,"fwdInMessages":false,"resendOnRefresh":true,"templateScope":"local","className":"","x":1520,"y":1440,"wires":[["39add9872f7822a2"]],"info":"<div id=\"{{'led-' + $id}}\" style=\"{{'color:'+theme.base_color}}\">Some text</div>\r\n<script>\r\n (function(scope) {\r\n scope.$watch('msg', function(msg) {\r\n if (msg) {\r\n // Do something when msg arrives\r\n //$(\"#my_\"+scope.$id).html(msg.payload);\r\n $('#led-' + scope.$id).addClass(\"led\")\r\n }\r\n });\r\n})(scope);\r\n</script>\r\n\r\n------------------------------------------------------------------------\r\n\r\n<div id=\"{{'led-' + $id}}\" class=\"led\" style=\"{{'color:'+theme.base_color}}\">Some text</div>\r\n<script>\r\n (function(scope) {\r\n const self = '#led-' + scope.$id\r\n\r\n scope.$watch('msg', function(msg) {\r\n if (msg) {\r\n // Do something when msg arrives\r\n //$(\"#my_\"+scope.$id).html(msg.payload);\r\n $(self).addClass(\"led-red\")\r\n\r\n scope.send('#led-' + scope.$id)\r\n }\r\n });\r\n})(scope);\r\n</script>\r\n\r\n---------------------------------------------------------------------------\r\n\r\n<!DOCTYPE html>\r\n<html>\r\n\r\n<body>\r\n <div id = \"{{'led-' + $id}}\" \r\n class = \"led\" \r\n data-colour = '[\"red\", \"yellow\", \"grey\"]' \r\n data-input = '[\"on\", \"auto\", \"off\"]'>\r\n </div>\r\n\r\n <script>\r\n (function(scope) {\r\n // Could also use [ angular.element(document.getElementById('sonoffBtn') ] or [ angular.element(\"#sonoffBtn\") ]\r\n // Introduction of a bit of JQuery to get the Element object\r\n const el = angular.element(document.querySelector('#led-' + scope.$id))\r\n const self = $(el)\r\n //const self = $('#led-' + scope.$id)\r\n\r\n // Data to use in the Scope\r\n let ledData = {\r\n colour: self.data(\"colour\"),\r\n input: self.data(\"input\"),\r\n\r\n } \r\n\r\n let ledState = 0 // Current state\r\n ledData.colour = ['red', 'yellow', 'grey']\r\n // Data to return to HTML led\r\n $('#led-' + scope.$id).addClass(\"led-\" + ledData.colour[ledState]) // Base LED class + colour class\r\n\r\n /* Functions to use in the Scope */\r\n // Triggered if the node receives a msg. Updates the LED according to the payload received\r\n scope.$watch('msg', function(msg) {\r\n let payloadState = msg.payload.toLowerCase()\r\n ledState = Math.max(0, ledData.input.indexOf(payloadState))\r\n\r\n showChange()\r\n\r\n //msg.payload = ledData.colour\r\n scope.send(selfNew)\r\n\r\n })\r\n \r\n // Update the scope variables to reflect back to the HTML\r\n function showChange() {\r\n //$('#led-' + scope.$id).addClass(alterClass(\"led-*\", \"led-\" + ledData.colour[ledState]))\r\n $('#led-' + scope.$id).addClass(\"led-\" + ledData.colour[ledState])\r\n\r\n }\r\n\r\n function alterClass(removals, additions) {\r\n const self = this;\r\n \r\n if (removals.indexOf('*') === -1) {\r\n // Use native jQuery methods if there is no wildcard matching\r\n self.removeClass(removals);\r\n return !additions ? self : self.addClass(additions);\r\n }\r\n \r\n let patt = new RegExp('\\\\s' +\r\n removals.replace(/\\*/g, '[A-Za-z0-9-_]+').split(' ').join('\\\\s|\\\\s') + '\\\\s', 'g');\r\n \r\n self.each(function (i, it) {\r\n let cn = ' ' + it.className + ' ';\r\n while (patt.test(cn)) {\r\n cn = cn.replace(patt, ' ');\r\n }\r\n it.className = $.trim(cn);\r\n });\r\n \r\n return !additions ? self : self.addClass(additions);\r\n }\r\n\r\n })(scope)\r\n\r\n </script>\r\n\r\n</body>\r\n\r\n<style>\r\n .led {\r\n background-color: rgba(255, 255, 255, 0.25);\r\n min-height: 14px;\r\n min-width: 14px;\r\n height: 14px;\r\n width: 14px;\r\n max-height: 14px;\r\n max-width: 14px;\r\n text-align: center;\r\n margin: 11px;\r\n border-radius: 50%;\r\n }\r\n\r\n .led-red {\r\n background-color: #ff0000;\r\n box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #ff0000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;\r\n }\r\n\r\n .led-orange {\r\n background-color: #FF7000;\r\n box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #FF7000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;\r\n }\r\n\r\n .led-yellow {\r\n background-color: #ffff00;\r\n box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #ffff00 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;\r\n }\r\n\r\n .led-green {\r\n background-color: #008000;\r\n box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #008000 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;\r\n }\r\n\r\n .led-blue {\r\n background-color: #0000ff;\r\n box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #0000ff 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;\r\n }\r\n\r\n .led-grey {\r\n background-color: #808080;\r\n box-shadow: #0000009e 0 0px 2.6666666666666665px 0px, #808080 0 0px 7px 2px, inset #00000017 0 -1px 1px 0px;\r\n }\r\n</style>\r\n\r\n</html>\r\n\r\n<!DOCTYPE html>\r\n<html>\r\n<!DOCTYPE html>\r\n<html>\r\n\r\n<body>\r\n <div id = \"{{'led-' + $id}}\" \r\n class=\"led\" \r\n data-colour='[\"red\", \"yellow\", \"grey\"]' \r\n data-input='[\"on\", \"auto\", \"off\"]'>\r\n\r\n </div>\r\n\r\n <script>\r\n (function(scope) {\r\n // Could also use [ angular.element(document.getElementById('sonoffBtn') ] or [ angular.element(\"#sonoffBtn\") ]\r\n // Introduction of a bit of JQuery to get the Element object\r\n //const el = angular.element(document.querySelector('#led-' + scope.$id))\r\n //const self = $(el)\r\n //const self = $('#led-' + scope.$id)\r\n\r\n // Data to use in the Scope\r\n let ledData = {\r\n colour: self.data(\"colour\"),\r\n input: self.data(\"input\"),\r\n\r\n } \r\n\r\n let ledState = 0 // Current state\r\n ledData.colour = ['red', 'yellow', 'grey']\r\n let currentClass = 'led-' + ledData.colour[ledState]\r\n // Data to return to HTML led\r\n $('#led-' + scope.$id).addClass(currentClass) // Base LED class + colour class\r\n\r\n /* Functions to use in the Scope */\r\n // Triggered if the node receives a msg. Updates the LED according to the payload received\r\n scope.$watch('msg', function(msg) {\r\n let payloadState = msg.payload.toLowerCase()\r\n ledState = Math.max(0, ledData.input.indexOf(payloadState))\r\n\r\n showChange()\r\n\r\n //msg.payload = ledData.colour\r\n scope.send(currentClass)\r\n\r\n })\r\n \r\n // Update the scope variables to reflect back to the HTML\r\n function showChange() {\r\n $('#led-' + scope.$id).removeClass(currentClass)\r\n\r\n currentClass = \"led-\" + ledData.colour[ledState]\r\n\r\n $('#led-' + scope.$id).addClass(currentClass)\r\n\r\n }\r\n\r\n })(scope)\r\n\r\n </script>\r\n\r\n</body>\r\n\r\n------------------------------------ Working Template ----------------------------------------------\r\n<body>\r\n <div id = \"{{'led' + $id}}\" \r\n class = \"led\" \r\n data-colour = \"red, yellow, grey\" \r\n data-input = '[\"on\", \"auto\", \"off\"]'>\r\n\r\n </div>\r\n\r\n<script>\r\n\r\n (function(scope) {\r\n // Important. Use JQuery document ready function so that the Unique id is created & loaded\r\n $(function() {\r\n const self = $(\"#led\" + scope.$id)\r\n\r\n // Data to use in the Scope\r\n let ledData = {\r\n colour: self.data('colour'),\r\n input: self.data(\"input\"),\r\n \r\n }\r\n\r\n let currentClass = 'led-red'\r\n\r\n scope.$watch('msg', function(msg) {\r\n if (msg) {\r\n // Do something when msg arrives\r\n $(\"#led\" + scope.$id).addClass(currentClass)\r\n\r\n scope.send('#led-' + scope.$id + \" : \" + self.data(\"colour\") + ' : ' + ledData.input[0])\r\n\r\n }\r\n\r\n })\r\n\r\n })\r\n\r\n })(scope)\r\n\r\n</script>\r\n\r\n</body>"},{"id":"39add9872f7822a2","type":"debug","z":"2be5023b.9f1aee","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1750,"y":1440,"wires":[]},{"id":"6e92dc7a.d2b0e4","type":"ui_group","name":"Group","tab":"625c92c7.aced44","order":1,"disp":true,"width":"6","collapse":false,"className":""},{"id":"625c92c7.aced44","type":"ui_tab","name":"Home Automation","icon":"dashboard","order":1,"disabled":false,"hidden":false}]