bartbutenaers / node-red-contrib-ui-svg

A Node-RED widget node to show interactive SVG (vector graphics) in the dashboard
Apache License 2.0
94 stars 27 forks source link

Cannot use events unless node output is linked to another node #109

Open letshin opened 2 years ago

letshin commented 2 years ago

I'm not sure if the functionality of the the onclick changes to update the style tags are meant to work without linking the node output to another node, even if referencing an ID within itself (the node in question). In order for that to work, I've found out that the node has to be linked to another node before any onclick events work/fire.

This doesn't work:

[{"id":"05dcc7f5feb7f5e1","type":"ui_svg_graphics","z":"901cd7663e91ec40","group":"4552fe72.fdfa5","order":4,"width":0,"height":0,"svgString":"<svg x=\"0\" y=\"0\" height=\"100\" viewBox=\"0 0 100 200\" width=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<rect id=\"temp1\" width=\"100\" height=\"100\" style=\"fill:red;\"/>\n<rect id=\"temp2\" x=\"100\" width=\"100\" height=\"100\" style=\"fill:green;\"/>\n</svg>","clickableShapes":[{"targetId":"#temp1","action":"click","payload":"{\"command\":\"update_style\",\"selector\":\"#temp2\",\"attributeName\":\"fill\",\"attributeValue\":\"purple\"}","payloadType":"json","topic":"#temp1"}],"javascriptHandlers":[],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":false,"showBrowserEvents":false,"enableJsDebugging":false,"sendMsgWhenLoaded":false,"noClickWhenDblClick":false,"outputField":"payload","editorUrl":"//drawsvg.org/drawsvg.html","directory":"","panning":"disabled","zooming":"disabled","panOnlyWhenZoomed":false,"doubleClickZoomEnabled":false,"mouseWheelZoomEnabled":false,"dblClickZoomPercentage":150,"cssString":"div.ui-svg svg{\n color: var(--nr-dashboard-widgetColor);\n fill: currentColor !important;\n}\ndiv.ui-svg path {\n fill: inherit;\n}","name":"","x":590,"y":740,"wires":[[]]},{"id":"4552fe72.fdfa5","type":"ui_group","name":"Col 1","tab":"2ecb90e3.af7a5","order":1,"disp":false,"width":"14","collapse":false,"className":""},{"id":"2ecb90e3.af7a5","type":"ui_tab","name":"Dashboard_SVG_Will","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

This works: [{"id":"05dcc7f5feb7f5e1","type":"ui_svg_graphics","z":"901cd7663e91ec40","group":"4552fe72.fdfa5","order":4,"width":0,"height":0,"svgString":"<svg x=\"0\" y=\"0\" height=\"100\" viewBox=\"0 0 100 200\" width=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<rect id=\"temp1\" width=\"100\" height=\"100\" style=\"fill:red;\"/>\n<rect id=\"temp2\" x=\"100\" width=\"100\" height=\"100\" style=\"fill:green;\"/>\n</svg>","clickableShapes":[{"targetId":"#temp1","action":"click","payload":"{\"command\":\"update_style\",\"selector\":\"#temp2\",\"attributeName\":\"fill\",\"attributeValue\":\"purple\"}","payloadType":"json","topic":"#temp1"}],"javascriptHandlers":[],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":false,"showBrowserEvents":false,"enableJsDebugging":false,"sendMsgWhenLoaded":false,"noClickWhenDblClick":false,"outputField":"payload","editorUrl":"//drawsvg.org/drawsvg.html","directory":"","panning":"disabled","zooming":"disabled","panOnlyWhenZoomed":false,"doubleClickZoomEnabled":false,"mouseWheelZoomEnabled":false,"dblClickZoomPercentage":150,"cssString":"div.ui-svg svg{\n color: var(--nr-dashboard-widgetColor);\n fill: currentColor !important;\n}\ndiv.ui-svg path {\n fill: inherit;\n}","name":"","x":590,"y":740,"wires":[["7b07c632149fce71"]]},{"id":"7b07c632149fce71","type":"ui_svg_graphics","z":"901cd7663e91ec40","group":"4552fe72.fdfa5","order":4,"width":0,"height":0,"svgString":"<svg x=\"0\" y=\"0\" height=\"100\" viewBox=\"0 0 100 200\" width=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<rect id=\"temp1\" width=\"100\" height=\"100\" style=\"fill:red;\"/>\n<rect id=\"temp2\" x=\"100\" width=\"100\" height=\"100\" style=\"fill:green;\"/>\n</svg>","clickableShapes":[{"targetId":"#temp1","action":"click","payload":"{\"command\":\"update_style\",\"selector\":\"#temp2\",\"attributeName\":\"fill\",\"attributeValue\":\"purple\"}","payloadType":"json","topic":"#temp1"}],"javascriptHandlers":[],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":false,"showBrowserEvents":false,"enableJsDebugging":false,"sendMsgWhenLoaded":false,"noClickWhenDblClick":false,"outputField":"payload","editorUrl":"//drawsvg.org/drawsvg.html","directory":"","panning":"disabled","zooming":"disabled","panOnlyWhenZoomed":false,"doubleClickZoomEnabled":false,"mouseWheelZoomEnabled":false,"dblClickZoomPercentage":150,"cssString":"div.ui-svg svg{\n color: var(--nr-dashboard-widgetColor);\n fill: currentColor !important;\n}\ndiv.ui-svg path {\n fill: inherit;\n}","name":"","x":800,"y":740,"wires":[[]]},{"id":"4552fe72.fdfa5","type":"ui_group","name":"Col 1","tab":"2ecb90e3.af7a5","order":1,"disp":false,"width":"14","collapse":false,"className":""},{"id":"2ecb90e3.af7a5","type":"ui_tab","name":"Dashboard_SVG_Will","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

bartbutenaers commented 2 years ago

I see what you mean. Very creative to solve it with two nodes in series.

This is how your first example works:

  1. When you click temp1, the click-event handler will be called which sends an output message (with the payload and topic that you have specified).
  2. You don't do anything with that output message in your flow, so nothing happens.

This is how your second example works:

  1. When you click temp1, the click-event handler will be called which sends an output message (with the payload and topic that you have specified).
  2. You inject that message into the second node.
  3. The second node will execute your command: it will find the temp2 element (in the svg of your other node because that comes first in your page) and it will change its color.

However that is not the way to go... There are multiple ways how to do this kind of stuff properly:

Round trip

Inject the output message back into the first node (e.g. via Link-out and Link-in nodes):

image

[{"id":"1646b727f5edb888","type":"ui_svg_graphics","z":"599e2b625dbbad25","group":"4552fe72.fdfa5","order":4,"width":0,"height":0,"svgString":"<svg x=\"0\" y=\"0\" height=\"100\" viewBox=\"0 0 100 200\" width=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<rect id=\"temp1\" width=\"100\" height=\"100\" style=\"fill:red;\"/>\n<rect id=\"temp2\" x=\"100\" width=\"100\" height=\"100\" style=\"fill:green;\"/>\n</svg>","clickableShapes":[{"targetId":"#temp1","action":"click","payload":"{\"command\":\"update_style\",\"selector\":\"#temp2\",\"attributeName\":\"fill\",\"attributeValue\":\"purple\"}","payloadType":"json","topic":"#temp1"}],"javascriptHandlers":[],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":false,"showBrowserEvents":false,"enableJsDebugging":false,"sendMsgWhenLoaded":false,"noClickWhenDblClick":false,"outputField":"payload","editorUrl":"//drawsvg.org/drawsvg.html","directory":"","panning":"disabled","zooming":"disabled","panOnlyWhenZoomed":false,"doubleClickZoomEnabled":false,"mouseWheelZoomEnabled":false,"dblClickZoomPercentage":150,"cssString":"div.ui-svg svg{\n color: var(--nr-dashboard-widgetColor);\n fill: currentColor !important;\n}\ndiv.ui-svg path {\n fill: inherit;\n}","name":"","x":560,"y":1400,"wires":[["93c6d83edef53c8b"]]},{"id":"93c6d83edef53c8b","type":"link out","z":"599e2b625dbbad25","name":"Svg output","mode":"link","links":["a1a877824b3ed191"],"x":695,"y":1400,"wires":[]},{"id":"a1a877824b3ed191","type":"link in","z":"599e2b625dbbad25","name":"","links":["93c6d83edef53c8b"],"x":415,"y":1400,"wires":[["1646b727f5edb888"]]},{"id":"4552fe72.fdfa5","type":"ui_group","name":"Chart","tab":"2ecb90e3.af7a5","order":3,"disp":true,"width":"15","collapse":false},{"id":"2ecb90e3.af7a5","type":"ui_tab","name":"Ovens","icon":"dashboard","order":39,"disabled":false,"hidden":false}]

This works however your message will make an entire roundtrip from frontend to server and back to frontend. Which is slow and silly because you don't need the message on your server side flow.

Javascript

Use a JS click-event handler, and do it all in the frontend (without going to the server):

[{"id":"1646b727f5edb888","type":"ui_svg_graphics","z":"599e2b625dbbad25","group":"4552fe72.fdfa5","order":4,"width":0,"height":0,"svgString":"<svg x=\"0\" y=\"0\" height=\"100\" viewBox=\"0 0 100 200\" width=\"200\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n<rect id=\"temp1\" width=\"100\" height=\"100\" style=\"fill:red;\"/>\n<rect id=\"temp2\" x=\"100\" width=\"100\" height=\"100\" style=\"fill:green;\"/>\n</svg>","clickableShapes":[],"javascriptHandlers":[{"selector":"#temp1","action":"click","sourceCode":"$(\"#temp2\").css(\"fill\", \"purple\")"}],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":false,"showBrowserEvents":false,"enableJsDebugging":false,"sendMsgWhenLoaded":false,"noClickWhenDblClick":false,"outputField":"payload","editorUrl":"//drawsvg.org/drawsvg.html","directory":"","panning":"disabled","zooming":"disabled","panOnlyWhenZoomed":false,"doubleClickZoomEnabled":false,"mouseWheelZoomEnabled":false,"dblClickZoomPercentage":150,"cssString":"div.ui-svg svg{\n color: var(--nr-dashboard-widgetColor);\n fill: currentColor !important;\n}\ndiv.ui-svg path {\n fill: inherit;\n}","name":"","x":560,"y":1400,"wires":[[]]},{"id":"4552fe72.fdfa5","type":"ui_group","name":"Chart","tab":"2ecb90e3.af7a5","order":3,"disp":true,"width":"15","collapse":false},{"id":"2ecb90e3.af7a5","type":"ui_tab","name":"Ovens","icon":"dashboard","order":39,"disabled":false,"hidden":false}]

Bart

letshin commented 2 years ago

I have a follow-up question on this: is it possible to use global.get("xx") together in the javascript as I have little icons for which I'd like the colours updated depending on sensor readings. The idea is to store sensor readings in a global file and update the svgs every time a refresh button is clicked.

I have tried global.get("xx) but can't seem to get it to work.

bartbutenaers commented 2 years ago

"global" refers to the Node-RED global memory on your server, but your Js event handler code runs on your client device (where you browser is running). Your client might be on the other side of the world via the internet. So luckily that is security wise not possible to access your memory, otherwise you would be in big troubles...

In that case the option with the roundtrip might be better. Just add the context data to the message, before you inject it again in the svg node.

letshin commented 2 years ago

OK, I thought that might be the case - is it also impossible to post a GET request from the through javascript then? As I'm not getting anything back in the console from running this code.

// GET Request.
fetch('https://api.github.com/users/letshin')
    // Handle success
    .then(response => response.json())  // convert to json
    .then(json => console.log(json))    //print data to console
    .catch(err => console.log('Request Failed', err)); // Catch errors
bartbutenaers commented 2 years ago

I can't think of any reason why that shouldn't work. Unless your fetch is never triggered... But of course I don't know in which context you are using it. You can put a debbuger statement in front of your fetch statement and open your browser Developer Tools (or do it like this). Then you can easily check whether you arrive in your code or not...

You are the first one asking to get data via http from within a Js event handler. Until now I assumed everybody gets all their data in their Node-RED flow, and then they send that data to the SVG node. If you need to send data to a Js handler, here is another way to do it via a 'custom_msg"....