Node Dashboard: How to access the msg object in a Template script

Introduction

node-red-dashboard provides an easy to access web UI for Node-Red. It uses Angular, Angular Material and JQuery all of which are available to work with. Most of the available nodes are fairly simple to use but the Template node is available to use for those times when the simple nodes are not enough. Using the Template node can be easy, see the documentation. However, it can get difficult very quickly.

I put this flow together to illustrate how to access the msg object that is passed to the Template node from a JavaScript block within the template. This gives you access to the msg object in the browser.

How things work

Node that interactions with the Dashboards you create happen in two parts. The first is when you initially load the Dashboard UI. This is a single page app (SPA) so all of the Dashboard nodes you've used will all contribute to the UI. You will get some initial data populated at this time.

The second part is when you send a new msg to one of the Dashboard nodes. This data is passed to the Dashboard SPA using websockets and triggers Angular to update its data model which updates the appropriate HTML elements in the app.

While that works well for most HTML elements, it doesn't help for script blocks you may have defined in the template. Script blocks (<script>...</script>) cannot be passed data in the usual way. For example, a simplistic template might be:

<h2>{{msg.topic}}</h2>
<div>{{msg.payload}}</div>

Whenever you pass a new msg to the node, the display will update accordingly. But you cannot do:

<script>
  console.log({{msg.payload}});
</script>

That will only produce an error or nothing at all.

How to access the msg object from browser JavaScript

Thankfully, there is a way to access the msg from script and also to run scripts when the msg changes though it isn't exactly an obvious process! The following code will enable you to see what is going on and should be enough to let you write your own scripts:

<h3>Testing dynamic scripts with Dashboard 2.0</h3>
{{msg.payload}}
<script>
    (function(scope) {
        console.log('Position 1');
        console.dir(scope);
        scope.$watch('msg.payload', function(data) {
            console.log('Position 2');
            console.dir(data);
        });
    })(scope);
</script>

Push some data into the template, switch to the Dashboard UI page and open your browser's console with F12.

You will see on the console output "Position 1" followed by the letter "b" (I've no idea why "b"!), this is the scope object and you can see that the scope object APPEARS to contain the msg object. Not so quick! Actually, when calling the console.dir(scope); line, the msg object does NOT exist, it appears to only because of the way the browser outputs the console commands. The reason for this is understood by looking at the next part.

What this does is to set a watch on the scope object for a sub-object scope.msg.payload, you will note from the console output that this is actually triggered twice and only on the second time is scope.msg.payload actually defined.

I'm not entirely certain why this happens but I think it is something to do with the way that Angular works. So don't forget to check that the watched object contains something useful before relying on it being there.

Anyway, the bottom line is that you can use the watch function to monitor the msg.payload (or of course the whole msg or something else suitable). The function will be triggered whenever you update the msg. Try having two inject nodes into the same template with different payloads and you will see what I mean. Each time you trigger, you will get a new console message - but ONLY if the payload actually changes, sending the same text twice for example will only trigger the function once.

Bonus: Pass HTML dynamically to the Dashboard UI

You may have noted that using {{msg.payload}} in your Template only produces text and not formatted HTML, if you pass in HTML, it will be output verbatim.

Normally, getting Angular to output HTML is simply a case of wrapping in triple brackets {{{msg.payload}}} but this doesn't seem to work with Dashboard.

Instead, use the alternative syntax:

<div ng-bind-html="msg.payload"></div>
[{"id":"3bf99da6.2ab802","type":"debug","z":"106a5b82.ef95a4","name":"","active":true,"console":"false","complete":"false","x":1270,"y":4300,"wires":[]},{"id":"a4b55e4a.98363","type":"inject","z":"106a5b82.ef95a4","name":"","topic":"","payload":"welcome","payloadType":"str","repeat":"","crontab":"","once":false,"x":900,"y":4280,"wires":[["58ee9d18.5e3a24"]]},{"id":"58ee9d18.5e3a24","type":"ui_template","z":"106a5b82.ef95a4","group":"51bf0475.ddfd2c","name":"testing payload","order":0,"width":"6","height":"6","format":"<h3>Testing dynamic scripts with Dashboard 2.0</h3>\n{{msg.payload}}\n<script>\n    (function(scope) {\n        console.log('Position 1');\n        console.dir(scope);\n        console.log(scope.msg);\n        scope.$watch('msg.payload', function(data) {\n            console.log('Position 2');\n            console.dir(data);\n        });\n    })(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"x":1100,"y":4300,"wires":[["3bf99da6.2ab802"]]},{"id":"35efe8e.a959618","type":"inject","z":"106a5b82.ef95a4","name":"","topic":"","payload":"not welcome","payloadType":"str","repeat":"","crontab":"","once":false,"x":910,"y":4320,"wires":[["58ee9d18.5e3a24"]]},{"id":"51bf0475.ddfd2c","type":"ui_group","z":"","name":"Group 2","tab":"279d8078.dcf29","order":2,"disp":true,"width":"6"},{"id":"279d8078.dcf29","type":"ui_tab","z":"106a5b82.ef95a4","name":"New Test","icon":"dashboard"}]
TotallyInformation

Flow Info

created 1 year, 5 months ago

Node Types

Core
  • debug (x1)
  • inject (x2)
Other
  • ui_group (x1)
  • ui_tab (x1)
  • ui_template (x1)

Tags

  • Dashboard
  • UI
  • Angular
  • JavaScript
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option