Skip to content
This repository has been archived by the owner on Aug 10, 2024. It is now read-only.

Use non SVG html elements (as foreign elements)

bartbutenaers edited this page Dec 29, 2021 · 1 revision

It is possible to use non-SVG html elements inside a drawing, by adding those elements inside a foreign element. That way your browser knows that the elements are not SVG shapes.

Foreign elements are supported by the major browsers. A 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. I have not found a solution for that...

Example 1 - Use a html input element

This first example shows how to add an html input-element to an SVG drawing:

<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>

The update_value input command can be used to set the value of such an input element via an input message. And 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.

The example flow below demonstrates how to use a custom JS client side event handler for the input element. As soon as the input value changes, the handler will called, e.g. to 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";
}

This example flow demonstrates this:

[{"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}]

As you can see in the result:

demo_html_input

Example 2 - Use an html table element

This example shows how to display a html table inside a drawing, and add rows to that table via input messages.

The example flow contains this 1 SVG drawing of a board, and I have added a standard HTML table as a foreign object to that SVG drawing:

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" height="263" viewBox="0 0 263 263" width="263">
  <g>
    <path d="M257.333,25.497h-32.867c0.124-0.48,0.196-0.98,0.196-1.499c0-3.313-2.687-6-6-6h-46.51
		c0.012-0.161,0.025-0.321,0.025-0.485c0-3.581-2.904-6.485-6.485-6.485h-68.05c-3.581,0-6.485,2.903-6.485,6.485
		c0,0.164,0.013,0.324,0.025,0.485h-46.51c-3.313,0-6,2.687-6,6c0,0.518,0.073,1.019,0.196,1.499H6c-3.314,0-6,2.686-6,6v148.492
		c0,3.313,2.686,6,6,6h115.926l-34.101,57.244c-1.696,2.847-0.763,6.53,2.084,8.226c2.848,1.696,6.53,0.763,8.226-2.084
		l27.532-46.217v33.019c0,3.313,2.687,6,6,6s6-2.687,6-6v-33.019l27.532,46.217c1.123,1.884,3.116,2.93,5.161,2.93
		c1.044,0,2.103-0.273,3.065-0.846c2.847-1.696,3.78-5.378,2.084-8.226l-34.101-57.244h115.926c3.314,0,6-2.687,6-6V31.497
		C263.333,28.183,260.647,25.497,257.333,25.497z M251.333,173.989H12V37.497h239.333V173.989z" />
  </g>
  <foreignObject x="30" y="50" width="200" height="110">
    <table style="width: 100%; border:2px solid;text-align:left">
      <colgroup>
        <col span="1" style="width: 70%;">
        <col span="1" style="width: 30%;">
      </colgroup>
      <tbody id="mytable">
        <tr>
          <th style="font-weight: bolder;text-decoration: underline">Room</th>
          <th style="font-weight: bolder;text-decoration: underline">°C</th>
        </tr>
        <tr>
          <td>Kitchen</td>
          <td>21</td>
        </tr>
      </tbody>
    </table>
  </foreignObject>
</svg>

To be able to add non-SVG elements via input messages, it is required to add foreignElement=true to the input message (because the SVG node should use another namespace for those elements). That way you can create standard HTML elements, like e.g. a

element for a table row.

Below you can see a message that is used to add dynamically a table row, by injecting a message that creates a tr HTML element as a child of the table body (with id "mytable"). The table row (tr) contains two table cells (td):

{
    "command": "add_element",
    "elementType": "tr",
    "textContent": "<td>Bathroom</td><td>22</td>",
    "parentElementId": "mytable",
    "foreignElement": true
}

The following example flow demonstrates uses this kind of messages:

[{"id":"ac37cc973bbb46fe","type":"ui_svg_graphics","z":"3f01ab2a1bf97f25","group":"3ce32370.c60f1c","order":11,"width":"18","height":"10","svgString":"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" x=\"0\" y=\"0\" height=\"263\" viewBox=\"0 0 263 263\" width=\"263\">\n  <g>\n    <path d=\"M257.333,25.497h-32.867c0.124-0.48,0.196-0.98,0.196-1.499c0-3.313-2.687-6-6-6h-46.51\n\t\tc0.012-0.161,0.025-0.321,0.025-0.485c0-3.581-2.904-6.485-6.485-6.485h-68.05c-3.581,0-6.485,2.903-6.485,6.485\n\t\tc0,0.164,0.013,0.324,0.025,0.485h-46.51c-3.313,0-6,2.687-6,6c0,0.518,0.073,1.019,0.196,1.499H6c-3.314,0-6,2.686-6,6v148.492\n\t\tc0,3.313,2.686,6,6,6h115.926l-34.101,57.244c-1.696,2.847-0.763,6.53,2.084,8.226c2.848,1.696,6.53,0.763,8.226-2.084\n\t\tl27.532-46.217v33.019c0,3.313,2.687,6,6,6s6-2.687,6-6v-33.019l27.532,46.217c1.123,1.884,3.116,2.93,5.161,2.93\n\t\tc1.044,0,2.103-0.273,3.065-0.846c2.847-1.696,3.78-5.378,2.084-8.226l-34.101-57.244h115.926c3.314,0,6-2.687,6-6V31.497\n\t\tC263.333,28.183,260.647,25.497,257.333,25.497z M251.333,173.989H12V37.497h239.333V173.989z\" />\n  </g>\n  <foreignObject x=\"30\" y=\"50\" width=\"200\" height=\"110\">\n    <table style=\"width: 100%; border:2px solid;text-align:left\">\n      <colgroup>\n        <col span=\"1\" style=\"width: 70%;\">\n        <col span=\"1\" style=\"width: 30%;\">\n      </colgroup>\n      <tbody id=\"mytable\">\n        <tr>\n          <th style=\"font-weight: bolder;text-decoration: underline\">Room</th>\n          <th style=\"font-weight: bolder;text-decoration: underline\">°C</th>\n        </tr>\n        <tr>\n          <td>Kitchen</td>\n          <td>21</td>\n        </tr>\n      </tbody>\n    </table>\n  </foreignObject>\n</svg>","clickableShapes":[],"javascriptHandlers":[],"smilAnimations":[],"bindings":[],"showCoordinates":false,"autoFormatAfterEdit":false,"showBrowserErrors":true,"showBrowserEvents":true,"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,"name":"","x":660,"y":80,"wires":[[]]},{"id":"41fc31c058631fe9","type":"inject","z":"3f01ab2a1bf97f25","name":"Add table row","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"command\":\"add_element\",\"elementType\":\"tr\",\"textContent\":\"<td>Bathroom</td><td>22</td>\",\"parentElementId\":\"mytable\",\"foreignElement\":true}","payloadType":"json","x":430,"y":80,"wires":[["ac37cc973bbb46fe"]]},{"id":"3ce32370.c60f1c","type":"ui_group","name":"Demo","tab":"d74bbed4.c2cfb","order":2,"disp":false,"width":"18","collapse":false},{"id":"d74bbed4.c2cfb","type":"ui_tab","name":"Demo","icon":"dashboard","order":1,"disabled":false,"hidden":false}]

Which will look like demonstrated in this demo:

demo_dynamic_table

Note that I quickly created this example flow, and I can improved a lot: scrollbars, fixed headers during scrolling, fixed number of visible rows ... So if anybody wants to improve the example flow and share it here, that would be nice!