Access msg object in dashboard ui_template

Hi,
I have implemented a Google Maps interface using ui_template node with some functionality. Next step for me is to load the markers from a database, as they are currently hardcoded. I figured that this would be possible if I trigger a query everytime the "Maps" tab is refreshed.
However the template code will not "run" after a

var len = msg.payload.length 

command, neither can I access the

msg.payload[0].lat
msg.payload[0].lng

as they are returned from the query.

I am posting the necessary nodes from the flow + a screenshot from the db returned items below.
Please note that a Gmaps API key is necessary for the script to work (bottom of the code).
If you don't have one, it would be equally good if you could just tell me how to access the msg object from
within the ui_template node.
Thank you in advance

EDIT: I've already tried to include everything in

(function(scope)
    scope.$watch('msg',function(msg){
    .
    .
    .
})(scope);
[{"id":"5be741bd.6e203","type":"ui_template","z":"ba566a8f.bd7bc8","group":"10ede6b0.919749","name":"Map","order":0,"width":"27","height":"16","format":"<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>My Map</title>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta charset=\"utf-8\">\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css\">\n    \n    <style>\n        /* Always set the map height explicitly to define the size of the div\n         * element that contains the map. */\n        #map {\n          height: 100%;\n          /*margin-left: 500px;*/\n          padding: 0;\n    \n        }\n        /* Optional: Makes the sample page fill the window. */\n        body, html{\n          height: 100%;\n          padding: 0;\n        }\n        ol{\n            display:none;\n            font-weight:bold;\n            list-style-type:none;\n           /* background-color:black; */\n        }\n        li{\n            /*background-color:white; */\n            margin: 1px 0 0 0;\n            list-style-type:none;\n            cursor:pointer;\n            color:#000;\n        }\n        \n    </style>\n    \n  </head>\n\n  <body>\n\n  <div id=\"map\"></div>\n  \n  <ol id=\"ol0\">\n      <li onclick=\"theScope.send({topic:'unit1'});\">Sensor1</li>\n      <li onclick=\"theScope.send({topic:'unit2'});\">Sensor2</li>\n      <li onclick=\"theScope.send({topic:'unit3'});\">Sensor3</li>\n      <li onclick=\"theScope.send({topic:'unit4'});\">Sensor4</li>\n      <li onclick=\"theScope.send({topic:'unit5'});\">Sensor5</li>\n  </ol>\n  <ol id=\"ol1\">\n      <li onclick=\"theScope.send({topic:'unit1'});\">Sensor1</li>\n      <li onclick=\"theScope.send({topic:'unit2'});\">Sensor2</li>\n      <li onclick=\"theScope.send({topic:'unit3'});\">Sensor3</li>\n      <li onclick=\"theScope.send({topic:'unit4'});\">Sensor4</li>\n  </ol>\n  <ol id=\"ol2\">\n      <li onclick=\"theScope.send({topic:'unit1'});\">Sensor1</li>\n      <li onclick=\"theScope.send({topic:'unit2'});\">Sensor2</li>\n      <li onclick=\"theScope.send({topic:'unit3'});\">Sensor3</li>\n  </ol>\n  <ol id=\"ol3\">\n      <li onclick=\"theScope.send({topic:'unit1'});\">Sensor1</li>\n      <li onclick=\"theScope.send({topic:'unit2'});\">Sensor2</li>\n  </ol>\n  <button class=\"inner\" id=\"myBtn1\" ng-click=\"send({topic: '1'})\">Archive</button>\n\n    <script>\n        var stockholm = {lat: 59.334591, lng: 18.063240};\n        var newYork = {lat:40.730610 ,lng: -73.935242};\n        var beijing = {lat:39.913818,lng:116.363625};\n        var thessaloniki = {lat: 40.736851,lng: 22.920227};\n        \n        var value=\"Dashbrd\";\n        var map;\n        var theScope = scope;\n        \n        var imHereAgain = \"im here again\";\n        var overIt = \"Over it\";\n        \n        theScope.send({payload:overIt});\n        \n      function initMap() \n      {\n        var options={\n          zoom:3,\n          center:stockholm\n        }\n        \n      \n      map = new google.maps.Map(document.getElementById('map'), options);\n      \n    var infoWindow = new google.maps.InfoWindow();\n    \n        function _newGoogleMapsMarker(param) {\n            var r = new google.maps.Marker({\n            map: param._map,\n            position: new google.maps.LatLng(param._lat, param._lng),\n            title: param._head,\n            animation: google.maps.Animation.DROP\n        });\n            if (param._data) {\n                google.maps.event.addListener(r, 'click', function() {\n                    // this -> the marker on which the onclick event is being attached\n                    if (!this.getMap()._infoWindow) {\n                        this.getMap()._infoWindow = new google.maps.InfoWindow();\n                    }\n                    this.getMap()._infoWindow.close();\n                    param._data.style.display=\"block\";\n                    this.getMap()._infoWindow.setContent(param._data);\n                    this.getMap()._infoWindow.open(this.getMap(), this);\n                });\n            }\n            return r;\n    }\n    \n    var dealer_markers = [{\n\n        //Random UK spot\n        lat: '51.595212',\n        lng: '-2.400068',\n        position: '1'\n    },\n    {\n        //New York\n        lat: '40.730610',\n        lng: '-73.935242',\n        position: '2'\n    },\n    {\n        //Thessaloniki\n        lat: '40.736851',\n        lng: '22.920227',\n        position: '3'\n    },\n    {\n        //Stockholm\n        lat: '59.334591',\n        lng: '18.063240',\n        position: '4'\n    }];\n        /*for (var a = 0; a < dealer_markers.length; a++) {\n            theScope.send({payload:imHereAgain});\n        var tmpLat = dealer_markers[a].lat;\n        var tmpLng = dealer_markers[a].lng;\n        var cont=document.getElementById('ol'+a); */\n       \n        var len = msg.payload.length;\n        theScope.send({payload:overIt});\n        for (var a = 0; a < len; a++) {\n            //theScope.send({payload:imHereAgain});\n            var tmpLat = msg.payload[a].lat;\n            var tmpLng = msg.payload[a].lng;\n        //var cont=document.getElementById('ol'+a); \n\n        var marker = _newGoogleMapsMarker({\n            _map: map,\n            _lat: tmpLat,\n            _lng: tmpLng,\n            _head: '|' + new google.maps.LatLng(tmpLat, tmpLng),\n            _data: cont\n        });\n    } \n}\n    </script>\n    \n    <script id=\"titleScript\" type=\"text/javascript\">\n    $('#myID').remove();\n    var toolbar = $('.md-toolbar-tools');\n    var div = $('<div/ id=\"myID\">');\n    //var div = document.getElementById(\"divara\");\n    //var form = $('<form/ id=\"forma\">');\n    var input = $('<input/ id=\"inpt\" type=\"text\" name=\"searchTerm\" placeholder=\"Search..\" style=\"font-style:italic; background-color:#016064; border:none\">');\n    var btn = $('<button/  class=\"fa fa-search\" onclick=\"theScope.send({payload:1,topic:takeInput()});\" style=\"height:32px; width:32px; border:none; background:transparent; padding: 8px 15px 8px 5px ; \">');\n        \n    function takeInput(){\n        var x = document.getElementById(\"inpt\").value;\n        return x;\n    }\n \n  //  $('#titleScript').parent().hide();\n    //form.append(input);\n    //form.append(btn);\n    div.append(input);\n    div.append(btn);\n    div[0].style.margin = '5px 5px 5px auto';\n    toolbar.append(div);\n\n    </script>\n    \n    <script src=\"https://maps.googleapis.com/maps/api/js?key=YOUR_KEY_HERE&callback=initMap\"\n    async defer></script>\n  </body>\n</html>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":571.1000366210938,"y":582.6000366210938,"wires":[["34d604c0.d80f5c","8e105e9d.1d75d","83a41ec1.a12f8"]]},{"id":"34d604c0.d80f5c","type":"debug","z":"ba566a8f.bd7bc8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":474,"y":490,"wires":[]},{"id":"3b392773.a32f08","type":"mysql","z":"ba566a8f.bd7bc8","mydb":"cca3b54f.41e9a8","name":"","x":417,"y":582,"wires":[["b911ee3e.14aea","5be741bd.6e203"]]},{"id":"6558677c.4475a8","type":"function","z":"ba566a8f.bd7bc8","name":"pin query","func":"msg.topic=\"SELECT * FROM pins\";\nreturn msg;","outputs":1,"noerr":0,"x":280,"y":605,"wires":[["3b392773.a32f08"]]},{"id":"138f13a8.59c3fc","type":"link in","z":"ba566a8f.bd7bc8","name":"","links":["80cbf799.5988a8","83a41ec1.a12f8"],"x":162,"y":569,"wires":[["6558677c.4475a8"]]},{"id":"b911ee3e.14aea","type":"debug","z":"ba566a8f.bd7bc8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":267,"y":469,"wires":[]},{"id":"83a41ec1.a12f8","type":"link out","z":"ba566a8f.bd7bc8","name":"","links":["138f13a8.59c3fc"],"x":589,"y":648,"wires":[]},{"id":"10ede6b0.919749","type":"ui_group","z":"","name":"Map","tab":"511291be.008a","disp":false,"width":"27","collapse":false},{"id":"cca3b54f.41e9a8","type":"MySQLdatabase","z":"","host":"127.0.0.1","port":"3306","db":"maps","tz":""},{"id":"511291be.008a","type":"ui_tab","z":"","name":"Map","icon":"map"}]

image

Did you try:

var len = {{msg.payload.length}}
?

same applies to the properties:

{{msg.payload[0].lat}}
{{msg.payload[0].lng}}

Thanks for your answer.
Yes I have.
However this does not work in <script> tags

And this?

$scope.msg.id
$scope.msg.lat
$scope.msg.lng
$scope.msg.position

Thanks krambriw.
To use that I have to wrap the necessary part in function(scope) etc (?)
Whenever I wrap something in

(function(scope){
     scope.$watch('msg',function(){
          ...
   })
})(scope)

the map in the dashboard template does not even render.
Most likely I am typing something the wrong way because that seems to be the answer everywhere I looked.
Can someone please provide me with a working simple example of pushing a msg, with the structure given in the screenshot, into a dashboard template node and output from the ui_templ just the msg.payload[0].lat or something?
It would be just what I need.
Jason

EDIT: output it with code in <script> tags

Well, this example works if you put it into a ui_template node and sends a json payload to the same

Example (I did put it into an inject node)

{"id":1,"lat":51.59,"lng":-2.4,"position":"UK"}
<!DOCTYPE html>
<html>
<script type="text/javascript">
theScope = scope;

// Watch the payload and update
(function(scope) {
    scope.$watch('msg.payload', function(data) {
        update(data);
    });
})(scope);

function update(dta) {
    theScope.send({payload:dta.id});
    theScope.send({payload:dta.lat});
    theScope.send({payload:dta.lng});
    theScope.send({payload:dta.position});
}
</script>
</html>

But to make it work; I keep the dashboard page open where the ui_template node is placed. I suppose that is the intention, you would like to update a dashboard with the values?

just checking you are aware of the node-red-contrib-web-worldmap node that can do maps in the dashboard.

Hello dceejay,
yes I am however its description states that markers and other elements on it are not clickable
yet and attaching actionlisteners to markers is essential for my application.

Thank you krambriw, your answer pointed me to the right direction for what I wanted!
The trick was executing the

initMap(data)

google maps callback function in place of your " update(data) ".
Then I could normally access the msg returned from the query like

for (var a = 0; a < data.length; a++) {
            var tmpLat = data[a].lat;
            var tmpLng = data[a].lng;   

        var marker = _newGoogleMapsMarker({
            _map: map,
            _lat: tmpLat,
            _lng: tmpLng
        }); 
        
 }

Ah that was soooo yesterday.. I think they are now :slight_smile:

Apologies if I'm muddying an existing thread but I have a related question on this topic:

@krambriw I've been researching this for a while now and am missing something very basic I suspect.

How can I access the payload in the script block in a UI_template node?

ie.

<script>
    (function(scope) {
        console.log('Position 1');
        console.dir(scope);
        scope.$watch('msg.payload', function(data) {
            console.log('Position 2');
            console.dir(data);
        });
    })(scope);

var bleh = $scope.msg.payload

//now I can use bleh to do other javascript things

</script>

Hi, not sure if you have a question or if you already solved the issue. Anyway, based on my previous example, this is how you can access the payload and assign values to other variables in the script

Lets assume, you send a javascript object {"name":"jmacle"} as payload to the ui_template node

In the template node, the script captures the payload, sends it further to the debug node then extracts the "name", assigns it to you new variable bleh, and sends the value of bleh to the debug node

As soon as you have assigned the value to bleh, you can do whatever is required, basically just extend the function otherStuff

Hope this helped you

<!DOCTYPE html>
<html>
<script type="text/javascript">
var theScope = scope;
var bleh = null;

// Watch the payload and update
(function(scope) {
    scope.$watch('msg.payload', function(data) {
        update(data);
    });
})(scope);

function update(dta) {
    theScope.send({payload:dta});
    bleh = dta.name;
    otherStuff();
}

function otherStuff() {
    theScope.send({payload:bleh});
}

</script>
</html>

image

[{"id":"20cb4ed7.366822","type":"ui_template","z":"d01d2553.fd9838","group":"16fdc5e1.9ee0fa","name":"","order":0,"width":0,"height":0,"format":"<!DOCTYPE html>\n<html>\n<script type=\"text/javascript\">\nvar theScope = scope;\nvar bleh = null;\n\n// Watch the payload and update\n(function(scope) {\n scope.$watch('msg.payload', function(data) {\n update(data);\n });\n})(scope);\n\nfunction update(dta) {\n theScope.send({payload:dta});\n bleh = dta.name;\n otherStuff();\n}\n\nfunction otherStuff() {\n theScope.send({payload:bleh});\n}\n\n</script>\n</html>","storeOutMessages":false,"fwdInMessages":false,"templateScope":"local","x":940,"y":380,"wires":[["e5bfeff6.d88e2"]]},{"id":"4e748181.57245","type":"inject","z":"d01d2553.fd9838","name":"","topic":"","payload":"{\"name\":\"jmacle\"}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":880,"y":290,"wires":[["20cb4ed7.366822"]]},{"id":"e5bfeff6.d88e2","type":"debug","z":"d01d2553.fd9838","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1040,"y":460,"wires":[]},{"id":"16fdc5e1.9ee0fa","type":"ui_group","z":"","name":"Buttons","tab":"8f57f520.e87078","order":1,"disp":false,"width":"6","collapse":false},{"id":"8f57f520.e87078","type":"ui_tab","z":"","name":"Some buttons","icon":"dashboard","order":1}]

No I hadn't solved it yet but now with your help it is working! Much appreciated @krambriw

I am however confused on how it is handling the msg.payload value.

With the first section:

// Watch the payload and update
(function(scope) {
 scope.$watch('msg.payload', function(data) {
 update(data);
 });
})(scope);

What is actually happening here? Is the payload being retrieved by the scope.watch? And then the update() function stores it in a variable?

Just trying to wrap my head around this. And thanks again for your help.

This is javascript, using the watch method. I think it is pretty good explained in the link below.
So in our case, we are watching for a property (msg.payload) to be assigned a new value. When this happens, the specified function will be called (update) with the payload as parameter and then the next call to otherStuff will be called

In this way you can watch other properties (msg, msg.topic, msg.whatever etc). The scope in this case I believe is related to the dashboard internals, it's anyway how you have to watch for changes when you are in a ui_template, it is described in the info for the node, cannot explain and do not know the inner details

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch

I can't possibly add better examples than the ones already given but can share a couple of links from my bookmark.

1 Like

Hi guys,
sorry for commenting on this topic after one year:

I am a little bit confused, since the linked reference (javascript:watch object) is claiming, that you should not use the watch object since it is not supported by any current browser anymore. Does this recommendation not apply to the ui_template node?

Is there any other way how to get the msg.payload object into a script block within an ui_template?