Passing msg to template node showing a svg

I am playing around with dashboard-2. I want to show a svg with a dynamic path property. What ever I do, I didn't get it working.
The template node is:

<template>
    <svg xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 -960 960 960" width="32" >
        <path d="{{ path }}" />
    </svg>
</template>

I connect a function to which a inject node is attached. The function node looks like:

msg = {
    "path": "M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-80q-100 0-170-70t-70-170q0-100 70-170t170-70q100 0 170 70t70 170q0 100-70 170t-170 70Zm0-80q66 0 113-47t47-113q0-66-47-113t-113-47q-66 0-113 47t-47 113q0 66 47 113t113 47Zm0-80q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Z"
}
return msg;

The path property {{path}} isn't replaced. I have tried different formats as well, but I didn't get this to work.
Any hints what I am doing wrong?

Hi .. in order for the svg d to be dynamic you have to v-bind it (shorthand :)
also you dont need the curly brackets in this case but msg.path instead

<template>
    <svg xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 -960 960 960" width="32" >
        <path :d="msg.path" />
    </svg>
</template>

Test Flow :

[{"id":"b7af0e79e864239a","type":"ui-template","z":"54efb553244c241f","group":"fb2e77ec2c2a5dd1","page":"","ui":"","name":"","order":0,"width":"0","height":"0","head":"","format":"<template>\n    <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"32\" viewBox=\"0 -960 960 960\" width=\"32\" >\n        <path :d=\"msg.path\" />\n    </svg>\n</template>\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":520,"y":2800,"wires":[[]]},{"id":"202d0b41f2f8b4b5","type":"inject","z":"54efb553244c241f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":210,"y":2800,"wires":[["b467592ba9d3e2b5"]]},{"id":"b467592ba9d3e2b5","type":"function","z":"54efb553244c241f","name":"data","func":"msg = {\n    \"path\": \"M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-80q-100 0-170-70t-70-170q0-100 70-170t170-70q100 0 170 70t70 170q0 100-70 170t-170 70Zm0-80q66 0 113-47t47-113q0-66-47-113t-113-47q-66 0-113 47t-47 113q0 66 47 113t113 47Zm0-80q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Z\"\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":2800,"wires":[["b7af0e79e864239a"]]},{"id":"fb2e77ec2c2a5dd1","type":"ui-group","name":"SVG","page":"c466b7e4bb472f99","width":"25","height":"1","order":-1,"showTitle":false,"className":"","visible":"true","disabled":"false"},{"id":"c466b7e4bb472f99","type":"ui-page","name":"SVG","ui":"957559fb8731ef90","path":"/tables","icon":"table","layout":"grid","theme":"0d92c765bfad87e6","order":-1,"className":"","visible":"true","disabled":"false"},{"id":"957559fb8731ef90","type":"ui-base","name":"My Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"navigationStyle":"default"},{"id":"0d92c765bfad87e6","type":"ui-theme","name":"Basic Blue Theme","colors":{"surface":"#0080c0","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

Thanks @UnborN for your hint, it works that way. My idea is, to move my complete dashboard to flowfuse. All my controls are structured to have a text label a icon (which is also dynamic in terms of the state e.g. light on/off) and a control to change the state (combobox, slider, switch). This currently looks like that in the legacy dashboard.


I want to achieve to have a template node for each kind of control (shutter, thermostat, lights, power, etc.). Then I want to switch to node red 4.0 in order to move each template to a subflow and make them instantiable and override the values like knx addresses via env. variables. That way I can maintain each control type in one place.
To achieve that, I need some more help.
I want each template to be surrounded by a border to get it more structured when multiple controls are displayed next to each other. So something like that (drawn with paint):
image

I want to use the mounted() and methods: elements of the script block. For the light control the whole control should be 3x2, the label 3x1, the icon 1x1 and the combobox 2x1.
image

My first question is how the names of the controls I need to put in the div tag inside the template are, for instance the combobox or the label (and where I can find those names)?
How to bind a msg.value input to the item of the combobox via the script section (and also pass it to the output msg once it has been changed) and also set a internal variable this.color to change the fill of the svg via style.

That way I can also manipulate the color and the path property of the icon. But therefor I also need to use the mastache style within the path d= property, thats why I initialy started with that.
When I have such a working example I can create the other controls based on that.

1 Like

Nice design!

Dashboard 2 is based on Vue 3 front end language and comes with the Vuetify UI library pre-loaded ...
read about .. v-card (link)

It can have a title which you can v-bind to a msg.title and a rounded outline

<v-card :title="msg.title" text="..." variant="outlined"></v-card>

Inside the Card you can add a v-combobox or a v-switch and also v-icons that you can also style with their color property

<v-icon color="warning" icon="mdi-alert-circle"></v-icon>

Dynamic with msg.color

<v-icon :color="msg.color" icon="mdi-alert-circle"></v-icon>
1 Like

Thanks @UnborN for your hints. I wanted to create a working example for the slider usecase.
Assuming I have a function node connected to the template which sends the following values:

{
  "path": "svg icon path",
  "style": "WhiteFill",
  "value": 42
}

With the path and style I control the svg. This works via v-bind. However, the slider should send the value to the template output once it is changed released (end event).
I want the Icon to be update in case the value is greater 0, this works. I am struggling how to sync the value via v-model once a new value is send to the template. For the initial load the method mounted works. Here is the code:

<template>
    <svg :class="msg.style" xmlns="http://www.w3.org/2000/svg" height="32" viewBox="0 -960 960 960" width="32" >
        <path :d="msg.path" />
    </svg>
    <v-slider thumb-label="always" v-model="value" max="100" min="0" step="1" @end="end(value)">
      <template v-slot:thumb-label="{ modelValue }">
          {{modelValue}}%
      </template>
    </v-slider>
</template>
<style>
    .WhiteFill {
        fill: "white";
     }
    .YellowFill {
        fill: #FEA500;
    }
</style>
<script>
  export default {
    data () {
      return {
        value: 0,
      }
    },
    methods: {
        // expose a method to our <template> and Vue Application
        end: function (value) {
            this.send({payload: value})
        }
    },
    mounted() {
          // code here when the component is first loaded
        this.value = this.msg.value
    },
  }
</script>

So I need a sync point to update this.value when a new msg.value is recieved by the template node, how to achieve that?

v-binding style classes has a little different syntax.
You can see some examples from the Vue website

Regarding listening for msgs in mounted you can use

    this.$socket.on('msg-input:' + this.id, (msg) => {
             
            this.value = msg.value
            this.svgStyle = msg.style
    
         })

and when a msg arrives, assign your Vue data values accordingly

Test flow :

[{"id":"b7af0e79e864239a","type":"ui-template","z":"54efb553244c241f","group":"fb2e77ec2c2a5dd1","page":"","ui":"","name":"","order":0,"width":"0","height":"0","head":"","format":"<template>\n    <svg :class=\"[ svgStyle ]\" xmlns=\"http://www.w3.org/2000/svg\" height=\"32\" viewBox=\"0 -960 960 960\" width=\"32\">\n        <path :d=\"msg.path\" />\n    </svg>\n    <v-slider thumb-label=\"always\" v-model=\"value\" max=\"100\" min=\"0\" step=\"1\" @end=\"end(value)\">\n        <template v-slot:thumb-label=\"{ modelValue }\">\n            {{modelValue}}%\n        </template>\n    </v-slider>\n</template>\n<style scoped>\n    .WhiteFill {\n        fill: \"white\";\n    }\n\n    .YellowFill {\n        fill: #FEA500;\n    }\n</style>\n<script>\n    export default {\n    data () {\n      return {\n        value: 0,\n        svgStyle: \"\"\n      }\n    },\n    methods: {\n        // expose a method to our <template> and Vue Application\n        end: function (value) {\n            this.send({payload: value})\n        }\n    },\n    mounted() {\n        // listen for msg events\n         this.$socket.on('msg-input:' + this.id, (msg) => {\n             \n            this.value = msg.value\n            this.svgStyle = msg.style\n    \n         })\n\n    },\n  }\n</script>","storeOutMessages":true,"passthru":false,"resendOnRefresh":true,"templateScope":"local","className":"","x":570,"y":2820,"wires":[["69e07ba9cdb95f42"]]},{"id":"202d0b41f2f8b4b5","type":"inject","z":"54efb553244c241f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":210,"y":2880,"wires":[["b467592ba9d3e2b5"]]},{"id":"b467592ba9d3e2b5","type":"function","z":"54efb553244c241f","name":"42 YellowFill","func":"msg = {\n  \"path\": \"M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-80q-100 0-170-70t-70-170q0-100 70-170t170-70q100 0 170 70t70 170q0 100-70 170t-170 70Zm0-80q66 0 113-47t47-113q0-66-47-113t-113-47q-66 0-113 47t-47 113q0 66 47 113t113 47Zm0-80q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Z\",\n  \"style\": \"YellowFill\",\n  \"value\": 42\n}\n\nreturn msg","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":2880,"wires":[["b7af0e79e864239a"]]},{"id":"69e07ba9cdb95f42","type":"debug","z":"54efb553244c241f","name":"debug 75","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":760,"y":2820,"wires":[]},{"id":"940768c83c1f77d8","type":"inject","z":"54efb553244c241f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":210,"y":2760,"wires":[["588a6ee4a6c87b43"]]},{"id":"588a6ee4a6c87b43","type":"function","z":"54efb553244c241f","name":"10 WhiteFill","func":"msg = {\n  \"path\": \"M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-80q-100 0-170-70t-70-170q0-100 70-170t170-70q100 0 170 70t70 170q0 100-70 170t-170 70Zm0-80q66 0 113-47t47-113q0-66-47-113t-113-47q-66 0-113 47t-47 113q0 66 47 113t113 47Zm0-80q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Z\",\n  \"style\": \"WhiteFill\",\n  \"value\": 10\n}\n\nreturn msg","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":2760,"wires":[["b7af0e79e864239a"]]},{"id":"fb2e77ec2c2a5dd1","type":"ui-group","name":"SVG","page":"c466b7e4bb472f99","width":"25","height":"1","order":-1,"showTitle":false,"className":"","visible":"true","disabled":"false"},{"id":"c466b7e4bb472f99","type":"ui-page","name":"SVG","ui":"957559fb8731ef90","path":"/tables","icon":"table","layout":"grid","theme":"0d92c765bfad87e6","order":-1,"className":"","visible":"true","disabled":"false"},{"id":"957559fb8731ef90","type":"ui-base","name":"My Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"navigationStyle":"default"},{"id":"0d92c765bfad87e6","type":"ui-theme","name":"Basic Blue Theme","colors":{"surface":"#0080c0","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]
1 Like

Thanks @UnborN I wasn't aware of the generic $socket.on() event registration, this works like a charme!

@UnborN one last thing I have noticed. When having a black background, the WhiteFill doesn't work and the icon is not drawn (at least not in black). In your Test flow, the icon is drawn in black once the style is WhiteFill which is kind of strange to me. And when I choose a back Blackground (in my case the default) the Icon is not drwan in white as defined.

yes .. i believe its because in the styles we had double quotes around the color which is not valid in css

try

<style scoped>
    .WhiteFill {
        fill: white;
        background-color: black;
    }

    .YellowFill {
        fill: #FEA500;
    }
</style>

Thx, the quotes need to be removed.

@UnborN there is one issue left with that solution, when reloading the page. Similar to that post, once the page is reloaded, the state of the internal data is gone.
I have tried to use alert('message received: ' + JSON.stringify(this.msg)) inside the this.$socket.on('msg-input:' + this.id, (msg) => {} event, but there is no msg send when reloading the page. So I wonder how to deal with that usecase?

Im not very familiar with Dashboard 2 developments regarding this.
But from what i understand in Colin's post is to create a watch on the msg
so when the ui-template component loads and its value "restored", to execute any reassignment of Vue data values.

...

 mounted() {
      // listen for msg events
      //   this.$socket.on('msg-input:' + this.id, (msg) => {
      //      this.value = msg.value
      //    this.svgStyle = msg.style 
     //    })

    },
 watch: { 
        msg: function() {
         // log what is stored in msg after reload
         console.log("Msg stored", this.msg)
         this.value = this.msg.value
         this.svgStyle = this.msg.style
        }
    }

...

Now that i think of it .. this way (with watch) you dont even need the this.$socket.on listener because simply watching the msg for changes should trigger on receiving a new msg also.

Test Flow :

[{"id":"b7af0e79e864239a","type":"ui-template","z":"54efb553244c241f","group":"fb2e77ec2c2a5dd1","page":"","ui":"","name":"","order":0,"width":"0","height":"0","head":"","format":"<template>\n    <svg :class=\"[ svgStyle ]\" xmlns=\"http://www.w3.org/2000/svg\" height=\"32\" viewBox=\"0 -960 960 960\" width=\"32\">\n        <path :d=\"msg.path\" />\n    </svg>\n    <v-slider thumb-label=\"always\" v-model=\"value\" max=\"100\" min=\"0\" step=\"1\" @end=\"end(value)\">\n        <template v-slot:thumb-label=\"{ modelValue }\">\n            {{modelValue}}%\n        </template>\n    </v-slider>\n</template>\n<style scoped>\n    .WhiteFill {\n        fill: white;\n        background-color: black;\n    }\n\n    .YellowFill {\n        fill: #FEA500;\n    }\n</style>\n<script>\n    export default {\n    data () {\n      return {\n        value: 0,\n        svgStyle: \"\"\n      }\n    },\n    methods: {\n        // expose a method to our <template> and Vue Application\n        end: function (value) {\n            this.send({payload: value})\n        }\n    },\n    mounted() {\n        // listen for msg events\n        // this.$socket.on('msg-input:' + this.id, (msg) => {\n             \n        //     this.value = msg.value\n        //     this.svgStyle = msg.style\n    \n        //  })\n\n    },\n    watch: { \n        msg: function() {\n         // log what is stored in msg after reload\n         console.log(\"Msg stored\", this.msg)\n         this.value = this.msg.value\n         this.svgStyle = this.msg.style\n        }\n    }\n  }\n</script>","storeOutMessages":true,"passthru":false,"resendOnRefresh":true,"templateScope":"local","className":"","x":570,"y":2820,"wires":[["69e07ba9cdb95f42"]]},{"id":"202d0b41f2f8b4b5","type":"inject","z":"54efb553244c241f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":210,"y":2880,"wires":[["b467592ba9d3e2b5"]]},{"id":"b467592ba9d3e2b5","type":"function","z":"54efb553244c241f","name":"42 YellowFill","func":"msg = {\n  \"path\": \"M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-80q-100 0-170-70t-70-170q0-100 70-170t170-70q100 0 170 70t70 170q0 100-70 170t-170 70Zm0-80q66 0 113-47t47-113q0-66-47-113t-113-47q-66 0-113 47t-47 113q0 66 47 113t113 47Zm0-80q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Z\",\n  \"style\": \"YellowFill\",\n  \"value\": 42\n}\n\nreturn msg","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":2880,"wires":[["b7af0e79e864239a"]]},{"id":"69e07ba9cdb95f42","type":"debug","z":"54efb553244c241f","name":"debug 75","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":760,"y":2820,"wires":[]},{"id":"940768c83c1f77d8","type":"inject","z":"54efb553244c241f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":210,"y":2760,"wires":[["588a6ee4a6c87b43"]]},{"id":"588a6ee4a6c87b43","type":"function","z":"54efb553244c241f","name":"10 WhiteFill","func":"msg = {\n  \"path\": \"M480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-80q-100 0-170-70t-70-170q0-100 70-170t170-70q100 0 170 70t70 170q0 100-70 170t-170 70Zm0-80q66 0 113-47t47-113q0-66-47-113t-113-47q-66 0-113 47t-47 113q0 66 47 113t113 47Zm0-80q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Z\",\n  \"style\": \"WhiteFill\",\n  \"value\": 10\n}\n\nreturn msg","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":370,"y":2760,"wires":[["b7af0e79e864239a"]]},{"id":"fb2e77ec2c2a5dd1","type":"ui-group","name":"SVG","page":"c466b7e4bb472f99","width":"25","height":"1","order":-1,"showTitle":false,"className":"","visible":"true","disabled":"false"},{"id":"c466b7e4bb472f99","type":"ui-page","name":"SVG","ui":"957559fb8731ef90","path":"/tables","icon":"table","layout":"grid","theme":"0d92c765bfad87e6","order":-1,"className":"","visible":"true","disabled":"false"},{"id":"957559fb8731ef90","type":"ui-base","name":"My Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"navigationStyle":"default"},{"id":"0d92c765bfad87e6","type":"ui-theme","name":"Basic Blue Theme","colors":{"surface":"#0080c0","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}]

Thanks for the hint, this way it works and the msg is passed in even during a complete reload. Thank you!

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.