Node-red-contrib-ui-svg: experimenting with foreign elements

Hi folks,

Got yesterday a feature request on Github to support the changeevent in the SVG node.

Currently the SVG node only supports events triggered by the user (e.g. click, touch, keydown, ...), which you can use to trigger actions inside your Node-RED flow. E.g. to turn on lights, and so on ...

However the change doesn't really fit 100% in the list of other events, because the onchange event will only be triggered indirectly (e.g. when the user clicks a circle-element -> a JS event handler sets the value of a text-element -> the onchange event handler of the text-element is triggered). But currently it is NOT possible to trigger the onchange event directly, for the simple reason that there is no input element in SVG where a user can enter text...

However it is possible to add non-SVG (html) elements iinto an SVG drawing as foreign objects, which are supported in the major browsers. For example you can add an html input-element into an SVG:

<svg preserveAspectRatio="none" x="0" y="0" viewBox="0 0 900 710" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
  <image width="889" height="703" id="background" xlink:href="https://www.roomsketcher.com/wp-content/uploads/2016/10/1-Bedroom-Floor-Plans.jpg" />
  <foreignObject x="470" y="80" width="100" height="50">
    <input type="number" id="temp_living" min="10" max="25" style="height: 25px;">
    <label for="temp_living" id="temp_living_label" style="color: black;font-size: larger;font-weight: bold;vertical-align: middle;"> °C</label>
  </foreignObject>
</svg>

I have created a change_event branch to the Github repository, which supports change events and a update_value input command (which can be used to set the value of such an input element via an input message). That branch can be installed like this from within your .node-red folder:

npm install bartbutenaers/node-red-contrib-ui-svg#change_event

The demo below shows this functionality:

  • An html input element is displayed inside the SVG drawing.

  • When you press enter inside the input-element (or when you press the up/down button of the spinner), an output message - containing the new input value - will be send.

  • When the input value is changed, a custom JS client side event handler will be called. This handler will e.g. change the color of the input-element's label, depending on the color:

    if(evt.target.valueAsNumber < 10) {
        $("#temp_living_label")[0].style.color="blue";
    }
    else if(evt.target.valueAsNumber > 20) {
        $("#temp_living_label")[0].style.color="red";
    }
    else {
        $("#temp_living_label")[0].style.color="orange";
    }
    

And this is the final result:

svg_foreign_input

Here is the flow for those who want to experiment with it:

[{"id":"107fa0c1.cb755f","type":"debug","z":"42b7b639.325dd8","name":"Changed temperature","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":740,"y":660,"wires":[]},{"id":"58329d91.3fc564","type":"ui_svg_graphics","z":"42b7b639.325dd8","group":"f014eb03.a3c618","order":1,"width":"14","height":"10","svgString":"<svg preserveAspectRatio=\"none\" x=\"0\" y=\"0\" viewBox=\"0 0 900 710\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:svg=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n  <image width=\"889\" height=\"703\" id=\"background\" xlink:href=\"https://www.roomsketcher.com/wp-content/uploads/2016/10/1-Bedroom-Floor-Plans.jpg\" />\n  <foreignObject x=\"470\" y=\"80\" width=\"100\" height=\"50\">\n    <input type=\"number\" id=\"temp_living\" min=\"10\" max=\"25\" style=\"height: 25px;\">\n    <label for=\"temp_living\" id=\"temp_living_label\" style=\"color: black;font-size: larger;font-weight: bold;vertical-align: middle;\"> °C</label>\n  </foreignObject>\n</svg>","clickableShapes":[{"targetId":"#temp_living","action":"change","payload":"temperature_living","payloadType":"str","topic":"temperature_living"}],"javascriptHandlers":[{"selector":"#temp_living","action":"change","sourceCode":"if(evt.target.valueAsNumber < 10) {\n    $(\"#temp_living_label\")[0].style.color=\"blue\";\n}\nelse if(evt.target.valueAsNumber > 20) {\n    $(\"#temp_living_label\")[0].style.color=\"red\";\n}\nelse {\n    $(\"#temp_living_label\")[0].style.color=\"orange\";\n}"}],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":false,"showBrowserEvents":false,"enableJsDebugging":true,"sendMsgWhenLoaded":false,"outputField":"","editorUrl":"http://drawsvg.org/drawsvg.html","directory":"","panning":"disabled","zooming":"disabled","panOnlyWhenZoomed":false,"doubleClickZoomEnabled":false,"mouseWheelZoomEnabled":false,"name":"","x":500,"y":660,"wires":[["107fa0c1.cb755f"]]},{"id":"b5bd6668.a54ab8","type":"inject","z":"42b7b639.325dd8","name":"Set default temperature","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"command\":\"update_value\",\"selector\":\"#temp_living\",\"value\":17}","payloadType":"json","x":260,"y":660,"wires":[["58329d91.3fc564"]]},{"id":"f014eb03.a3c618","type":"ui_group","name":"Floorplan test","tab":"80068970.6e2868","order":1,"disp":true,"width":"14","collapse":false},{"id":"80068970.6e2868","type":"ui_tab","name":"SVG","icon":"dashboard","disabled":false,"hidden":false}]

The disadvantage is that you need to add the foreign object manually to your SVG. Suppose you have drawn your SVG via a third-party editor (or via the integrated DrawSvg), your foreign objects will be overwritten every time you import an updated drawing from your editor... To solve that, I would have to find a way to specify the foreign objects in the config screen (in a new tabsheet), but no ideas yet how I could achieve this ...

But I believe there will be some interesting IOT use cases for these foreign objects inside an SVG drawing.

As always all 'constructive' feeback is very welcome!!

Have fun with it,
Bart

2 Likes