Ui_template Node and Unique HTML Element ID's

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}]
1 Like