Creation and development of components using ui-template for Dashboard 2

Below is an example of a 100% configurable and operational OBS button. Each button needs a "key" which can be configured in data ex. a1, a2...

image

<template>
    <v-btn ref="botao" stacked @click="alternar">
        Externa
        <v-icon ref="icone">mdi-beach</v-icon>
    </v-btn>
</template>

<script>
    export default {
        data() {
            return {
                chave: 'a1'
            };
        },
        methods: {
            alternar: function () {
                if (window.localStorage.getItem(this.chave) === 'ON') {
                    this.desligar();
                    this.send({ payload: 'OFF' });
                } else if (window.localStorage.getItem(this.chave) === 'OFF') {
                    this.ligar();
                    this.send({ payload: 'ON' });
                } else {
                    this.desligar();
                }
            },
            ligar: function () {            
                window.localStorage.setItem(this.chave, 'ON');
                this.$refs.icone.$el.style.color = '#BD9608';
                this.$refs.icone.$el.style.textShadow = '0px 0px 10px #BD9608';                              
            },
            desligar: function () {                
                window.localStorage.setItem(this.chave, 'OFF');
                this.$refs.icone.$el.style.color = '#A9A9A9';
                this.$refs.icone.$el.style.textShadow = '0px 0px 0px';                              
            },
            setState (value) {
                if (value === 'ON') {                    
                    this.ligar();
                } else if (value === 'OFF') {                    
                    this.desligar();
                }
            }
        },
        mounted () {            
            this.setState(window.localStorage.getItem(this.chave));            
            this.$socket.on("msg-input:" + this.id, (msg) => {
                this.setState(msg.payload);
            });
            this.$nextTick(() => {
                // tanho do icone
                this.$refs.icone.$el.style.fontSize = '40px';  
                // cor e espessura da borda do botão              
                this.$refs.botao.$el.style.border = '1px solid #000000';
                // altura do botão
                this.$refs.botao.$el.style.width = '75px';
                // largura do botão
                this.$refs.botao.$el.style.height = '75px';
                // cor da letra do titulo do botão
                this.$refs.botao.$el.style.color = '#000000';
                // tamanho da fonte do titulo do botão
                this.$refs.botao.$el.style.fontSize = '11px';
                // cor do fundo do botão
                this.$refs.botao.$el.style.backgroundColor = '#4F4F4F';
                // arredondamento dos cantos do botão
                this.$refs.botao.$el.style.borderRadius = '18px';
            });                    
        }
    }
</script>


4 Likes

Following the example of diegodamaceno, I made a variation of the code. The operation is identical to the example above, the only difference is that in this version, when clicking on the button, it does not change the status with the click, it is only changed after receiving a msg.payload with the new status.
Credits for the initial code diegodamaceno

<template>
    <v-btn ref="botao" stacked @click="enviarPayload">
        Externa
        <v-icon ref="icone">mdi-beach</v-icon>
    </v-btn>
</template>

<script>
    export default {
    data() {
        return {
            chave: 'a1',
            // Adicionando uma variável para rastrear o estado atual do botão
            estadoBotao: ''
        };
    },
    methods: {
        enviarPayload: function () {
            // Inverte o estado do botão ao clicar
            if (this.estadoBotao === 'ON') {
                this.send({ payload: 'OFF' });
            } else {
                this.send({ payload: 'ON' });
            }
        },
        ligar: function () {            
            window.localStorage.setItem(this.chave, 'ON');
            this.$refs.icone.$el.style.color = '#BD9608';
            this.$refs.icone.$el.style.textShadow = '0px 0px 10px #BD9608';   
            this.estadoBotao = 'ON';                           
        },
        desligar: function () {                
            window.localStorage.setItem(this.chave, 'OFF');
            this.$refs.icone.$el.style.color = '#A9A9A9';
            this.$refs.icone.$el.style.textShadow = '0px 0px 0px'; 
            this.estadoBotao = 'OFF';                              
        },
        setState(value) {
            if (value === 'ON') {                    
                this.ligar();
            } else if (value === 'OFF') {                    
                this.desligar();
            }
        }
    },
    mounted() {            
        this.setState(window.localStorage.getItem(this.chave));            
        this.$socket.on("msg-input:" + this.id, (msg) => {
            // Altere o status do botão com base na msg.payload recebida
            this.setState(msg.payload);
        });
        this.$nextTick(() => {
            // tamanho do ícone
            this.$refs.icone.$el.style.fontSize = '40px';  
            // cor e espessura da borda do botão              
            this.$refs.botao.$el.style.border = '1px solid #000000';
            // altura do botão
            this.$refs.botao.$el.style.width = '75px';
            // largura do botão
            this.$refs.botao.$el.style.height = '75px';
            // cor da letra do título do botão
            this.$refs.botao.$el.style.color = '#000000';
            // tamanho da fonte do título do botão
            this.$refs.botao.$el.style.fontSize = '11px';
            // cor do fundo do botão
            this.$refs.botao.$el.style.backgroundColor = '#4F4F4F';
            // arredondamento dos cantos do botão
            this.$refs.botao.$el.style.borderRadius = '18px';
        });                    
    }
}
</script>
2 Likes

Here's one more element. Animated start button. Moving on with CSS. Emits "on" when pressed.

image

<template>
    <v-btn ref="botao" style="background-color: #4F4F4F; color: #000000; border: 1px solid #000000; font-size: 14px; border-radius: 18px;" stacked @click="start">
      Start
      <v-icon ref="icon" style="font-size: 40px;">{{ icone }}</v-icon>
    </v-btn>
</template>

<script>
  export default {
    data() {
      return {
        icone: 'mdi-power-off'
       };
    },
    methods: {
      start: function () {        
        this.icone = 'mdi-power-on';
        this.$refs.icon.$el.style.color = '#BD9608';

        setTimeout(() => {
          this.icone = 'mdi-power-off';
          this.$refs.icon.$el.style.color = '#000000';
          this.send({ payload: 'ON' });
        }, 1000);
      },
    }
  };
</script>


1 Like

Some food for thought - using localStorage will not sustain the test of time (eg; use another browser/device and it won't work).

I think you can also greatly simplify the approach by using styles with dynamic classes, example:

<template>
    <v-btn class="switch" stacked @click="switchState" :class="[state ? 'on': 'off']">
        switch
        <v-icon class="icon">mdi-beach</v-icon>
    </v-btn>
</template>
<script>
    export default {
    
    data() {
        return {
            state: false,
        }
    },
    methods: {
        switchState() {
            this.state ? this.send({ payload: 'OFF' }) : this.send({ payload: 'ON' });
            this.state = !this.state
        },
    },
    mounted() {   
        this.$socket.on("msg-input:" + this.id, (msg) => {
            this.state = msg.payload
        });              
    }
}
</script>
<style>
    .switch {
        border: 1px solid #000;
        width: 75px;
        color: #000;
        height: 75px;
        font-size: 11px;
        background-color: #4F4F4F;
        border-radius: 18px;
    }
    .icon {
        font-size: 40px;
    }
    .on {
        color: #BD9608;
        text-shadow: 0px 0px 10px #BD9608;

    }
    .off {
        color: #A9A9A9;
        text-shadow: 0px 0px 0px;
    }
</style>

note that you need to inject a payload with true/false to switch the state.

4 Likes

I really appreciate the help... I'm working hard to learn. Does this method resist page refreshes in different browsers?

It does not. The best way would be to send something like "connect" message once it is mounted, filter for that incoming message in node-red and respond with the current state.

Playing around a bit:

This is (almost) fully dynamic and keeps the state in a flow variable.
In the flow you can define the switches you want to display, like:

[
    {
        "name": "Cozhina",
        "state": false,
        "icon": "mdi-led-strip-variant"
    },
    {
        "name": "Central",
        "state": false,
        "icon": "mdi-led-strip-variant"
    },
...]

When the page loads, it requests the switches+states and dynamically renders the grid with their labels,icons and state. When pressing one, it sends all the switches and saves it to a flow variable.

If the flow variable does not exist, it load the initial template.

Template code:

<template>
    <div class="grid">
        <v-btn class="switch" stacked v-for="(item,index) in switches" @click="switchState(index,item.state)" :class="[item.state ? 'on': 'off']">
            {{item.name}}
            <v-icon class="icon">{{item.icon}}</v-icon>
        </v-btn>
    </div>
</template>
<script>
    export default {
    
    data() {
        return {
            
            switches:[]
        }
    },
    methods: {
        switchState(index,state) {
            
            this.switches[index].state = !this.switches[index].state
            this.send({payload:{type:'change', 'switches':this.switches,'switch':this.switches[index]}})
      
        },
    },
    mounted() {   
      
        this.send({payload:{type:'load'}})
         this.$socket.on("msg-input:" + this.id, (msg) => {
            console.log(msg)
             if('switches' in msg.payload){
                this.switches = msg.payload.switches
             }
         });              
    }
}
</script>
<style>
    .grid{
        display:grid;
        width:100%;
        grid-template-columns: repeat(3, 1fr);
        gap:8px;
        }
    .switch {
        padding:8px;
        color: #aaa;
        font-size: 11px;
        background-color: #666;
        border-radius: 8px;
    }

    .icon {
        font-size: 40px;
    }

    .on {
        color: #BD9608;
        text-shadow: 0px 0px 10px #BD9608;

    }

    .off {
        
        text-shadow: 0px 0px 0px;
    }
</style>

Example flow: (modify the template node to setup your switches, if you want add more later, update the template, remove the flow variable first and refresh the dashboard)

[{"id":"de6cfe9d86ed5b93","type":"ui-template","z":"bc05bdac7c068faa","group":"1a12ece250764369","dashboard":"64d151d89e8704cf","page":"0a534af73f5f11fb","name":"","order":0,"width":0,"height":0,"head":"","format":"<template>\n    <div class=\"grid\">\n        <v-btn class=\"switch\" stacked v-for=\"(item,index) in switches\" @click=\"switchState(index,item.state)\" :class=\"[item.state ? 'on': 'off']\">\n            {{item.name}}\n            <v-icon class=\"icon\">{{item.icon}}</v-icon>\n        </v-btn>\n    </div>\n</template>\n<script>\n    export default {\n    \n    data() {\n        return {\n            \n            switches:[]\n        }\n    },\n    methods: {\n        switchState(index,state) {\n            \n            this.switches[index].state = !this.switches[index].state\n            this.send({payload:{type:'change', 'switches':this.switches,'switch':this.switches[index]}})\n      \n        },\n    },\n    mounted() {   \n      \n        this.send({payload:{type:'load'}})\n         this.$socket.on(\"msg-input:\" + this.id, (msg) => {\n            console.log(msg)\n             if('switches' in msg.payload){\n                this.switches = msg.payload.switches\n             }\n         });              \n    }\n}\n</script>\n<style>\n    .grid{\n        display:grid;\n        width:100%;\n        grid-template-columns: repeat(3, 1fr);\n        gap:8px;\n        }\n    .switch {\n        padding:8px;\n        color: #aaa;\n        font-size: 11px;\n        background-color: #666;\n        border-radius: 8px;\n    }\n\n    .icon {\n        font-size: 40px;\n    }\n\n    .on {\n        color: #BD9608;\n        text-shadow: 0px 0px 10px #BD9608;\n\n    }\n\n    .off {\n        \n        text-shadow: 0px 0px 0px;\n    }\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":180,"y":760,"wires":[["52d179278266ad5c"]]},{"id":"52d179278266ad5c","type":"switch","z":"bc05bdac7c068faa","name":"request ?","property":"payload.type","propertyType":"msg","rules":[{"t":"eq","v":"load","vt":"str"},{"t":"eq","v":"change","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":340,"y":760,"wires":[["4a32d1d626bd3e12"],["c9cea85b0a55d3bc","4e1ceb072b981ddf"]]},{"id":"f5ce5f0e98be7dff","type":"link out","z":"bc05bdac7c068faa","name":"link out 39","mode":"link","links":["0ea5f03060a5f053","16449a5979598e53"],"x":1045,"y":720,"wires":[]},{"id":"16449a5979598e53","type":"link in","z":"bc05bdac7c068faa","name":"link in 25","links":["f5ce5f0e98be7dff"],"x":75,"y":760,"wires":[["de6cfe9d86ed5b93"]]},{"id":"c9cea85b0a55d3bc","type":"change","z":"bc05bdac7c068faa","name":"","rules":[{"t":"set","p":"switches","pt":"flow","to":"payload.switches","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":550,"y":780,"wires":[[]]},{"id":"6d52057ea8bcdd86","type":"template","z":"bc05bdac7c068faa","name":"","field":"payload","fieldType":"msg","format":"json","syntax":"plain","template":"[\n    {\n        \"name\": \"Cozhina\",\n        \"state\": false,\n        \"icon\": \"mdi-led-strip-variant\"\n    },\n    {\n        \"name\": \"Central\",\n        \"state\": false,\n        \"icon\": \"mdi-led-strip-variant\"\n    },\n    {\n        \"name\": \"Livingroom\",\n        \"state\": false,\n        \"icon\": \"mdi-lightbulb-outline\"\n    },\n    {\n        \"name\": \"Bedroom\",\n        \"state\": false,\n        \"icon\": \"mdi-led-strip-variant\"\n    },\n    {\n        \"name\": \"Toilet\",\n        \"state\": false,\n        \"icon\": \"mdi-led-strip-variant\"\n    },\n    {\n        \"name\": \"Livingroom\",\n        \"state\": false,\n        \"icon\": \"mdi-lightbulb-outline\"\n    },\n    {\n        \"name\": \"Bedroom\",\n        \"state\": false,\n        \"icon\": \"mdi-led-strip-variant\"\n    },\n    {\n        \"name\": \"Toilet\",\n        \"state\": false,\n        \"icon\": \"mdi-led-strip-variant\"\n    }\n]","output":"json","x":720,"y":720,"wires":[["dd3e82cb5f9536c7"]]},{"id":"4a32d1d626bd3e12","type":"switch","z":"bc05bdac7c068faa","name":"get flow var","property":"switches","propertyType":"flow","rules":[{"t":"istype","v":"undefined","vt":"undefined"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":530,"y":740,"wires":[["6d52057ea8bcdd86"],["72dd07b3cc9024e9"]]},{"id":"dd3e82cb5f9536c7","type":"change","z":"bc05bdac7c068faa","name":"set default flow var","rules":[{"t":"set","p":"switches","pt":"flow","to":"payload","tot":"msg","dc":true},{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.switches","pt":"msg","to":"switches","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":890,"y":700,"wires":[["f5ce5f0e98be7dff"]]},{"id":"72dd07b3cc9024e9","type":"change","z":"bc05bdac7c068faa","name":"switches","rules":[{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.switches","pt":"msg","to":"switches","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":720,"y":760,"wires":[["f5ce5f0e98be7dff"]]},{"id":"4e1ceb072b981ddf","type":"debug","z":"bc05bdac7c068faa","name":"debug 513","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":530,"y":820,"wires":[]},{"id":"1a12ece250764369","type":"ui-group","name":"Group Name","page":"0a534af73f5f11fb","width":"3","height":"1","order":-1,"showTitle":false,"className":""},{"id":"64d151d89e8704cf","type":"ui-base","name":"UI Name","path":"/dashboard"},{"id":"0a534af73f5f11fb","type":"ui-page","name":"Page Name","ui":"64d151d89e8704cf","path":"/","layout":"grid","theme":"663a015c70b58f8c","order":-1,"className":""},{"id":"663a015c70b58f8c","type":"ui-theme","name":"Theme Name","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}]
5 Likes

Nice work both!
Can the switch(es) state also be changed by injecting a command?
Also, the switch states are not replicated when viewed on different devices.

That's the main idea. Give the basic characteristic to the ui-temple node of being a base for creating any fully customized component. I've been working with this approach for years. It would be ideal to give the tools for this. The elements must be based on being controlled by payloads and controlled by payloads. Node-red can be very powerful if we have a front element that gives us creative freedom.

If you refresh the page I think you will also find it also loses the state then. If you look back a bit in the thread you will see the ongoing discussion of what to do about state storage in a template node.

1 Like

Yes, I saw that, but then....

OK, I hadn't looked at the flow in detail. I was making unwarranted assumptions.

Can the switch(es) state also be changed by injecting a command?

Not in this flow/template, but should be quite easy to add.

Also, the switch states are not replicated when viewed on different devices.

Ah yeah i forgot that part, can connect like this:

image

it moves objects around like this but it works.

Not clear... can you post an example flow?
In the code that you posted, nothing appears to be saved to context???

Updated example flow.

Can update from both a flow and dashboard, and gets saved into a flow variable, all connected clients receive the update.

[{"id":"de6cfe9d86ed5b93","type":"ui-template","z":"bc05bdac7c068faa","group":"1a12ece250764369","dashboard":"64d151d89e8704cf","page":"0a534af73f5f11fb","name":"","order":0,"width":0,"height":0,"head":"","format":"<template>\n    <div class=\"grid\">\n        <v-btn class=\"switch\" stacked v-for=\"(item,index) in switches\" @click=\"switchState(index,item.state)\" :class=\"[item.state ? 'on': 'off']\">\n            {{item.name}}\n            <v-icon class=\"icon\">{{item.icon}}</v-icon>\n        </v-btn>\n    </div>\n</template>\n<script>\n    export default {\n    \n    data() {\n        return {\n            switches:[]\n        }\n    },\n    methods: {\n        switchState(index,state) {   \n            this.switches[index].state = !this.switches[index].state\n            this.update(index,state)\n        },\n        update(index,state){\n            this.send({payload:{type:'change', 'switches':this.switches,'switch':this.switches[index]}})\n        }\n    },\n    mounted() {   \n      \n        this.send({payload:{type:'load'}})\n     \n         this.$socket.on(\"msg-input:\" + this.id, (msg) => {\n           \n             if('switches' in msg.payload){\n                this.switches = msg.payload.switches\n             }\n             if('update' in msg.payload){\n                const name = msg.payload.update.name\n                const state = msg.payload.update.state\n                const item_index = this.switches.findIndex(x=>x.name == name)\n\n                this.switches[item_index].state = state\n                this.update(index,state)\n               // this.setState(item_index,state)\n             }\n         })              \n    }\n    \n}\n</script>\n<style>\n    .grid{\n        display:grid;\n        width:100%;\n        grid-template-columns: repeat(3, 1fr);\n        gap:8px;\n        }\n    .switch {\n        padding:8px;\n        color: #aaa;\n        font-size: 11px;\n        background-color: #666;\n        border-radius: 8px;\n    }\n\n    .icon {\n        font-size: 40px;\n    }\n\n    .on {\n        color: #BD9608;\n        text-shadow: 0px 0px 10px #BD9608;\n\n    }\n\n    .off {\n        \n        text-shadow: 0px 0px 0px;\n    }\n</style>","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":true,"templateScope":"local","className":"","x":240,"y":760,"wires":[["52d179278266ad5c"]]},{"id":"52d179278266ad5c","type":"switch","z":"bc05bdac7c068faa","name":"request ?","property":"payload.type","propertyType":"msg","rules":[{"t":"eq","v":"load","vt":"str"},{"t":"eq","v":"change","vt":"str"}],"checkall":"true","repair":false,"outputs":2,"x":400,"y":760,"wires":[["4a32d1d626bd3e12"],["c9cea85b0a55d3bc","4e1ceb072b981ddf"]]},{"id":"f5ce5f0e98be7dff","type":"link out","z":"bc05bdac7c068faa","name":"link out 39","mode":"link","links":["0ea5f03060a5f053","16449a5979598e53"],"x":1105,"y":720,"wires":[]},{"id":"16449a5979598e53","type":"link in","z":"bc05bdac7c068faa","name":"link in 25","links":["f5ce5f0e98be7dff","14e3e6c1de302422"],"x":135,"y":760,"wires":[["de6cfe9d86ed5b93"]]},{"id":"c9cea85b0a55d3bc","type":"change","z":"bc05bdac7c068faa","name":"","rules":[{"t":"set","p":"switches","pt":"flow","to":"payload.switches","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":610,"y":780,"wires":[["72dd07b3cc9024e9"]]},{"id":"6d52057ea8bcdd86","type":"template","z":"bc05bdac7c068faa","name":"","field":"payload","fieldType":"msg","format":"json","syntax":"plain","template":"[\n    {\n        \"name\": \"Cozhina\",\n        \"state\": false,\n        \"icon\": \"mdi-led-strip-variant\"\n    },\n    {\n        \"name\": \"Central\",\n        \"state\": false,\n        \"icon\": \"mdi-led-strip-variant\"\n    },\n    {\n        \"name\": \"Livingroom\",\n        \"state\": false,\n        \"icon\": \"mdi-lightbulb-outline\"\n    },\n    {\n        \"name\": \"Bedroom\",\n        \"state\": false,\n        \"icon\": \"mdi-led-strip-variant\"\n    },\n    {\n        \"name\": \"Toilet\",\n        \"state\": false,\n        \"icon\": \"mdi-led-strip-variant\"\n    },\n    {\n        \"name\": \"Livingroom\",\n        \"state\": false,\n        \"icon\": \"mdi-lightbulb-outline\"\n    },\n    {\n        \"name\": \"Bedroom\",\n        \"state\": false,\n        \"icon\": \"mdi-led-strip-variant\"\n    },\n    {\n        \"name\": \"Toilet\",\n        \"state\": false,\n        \"icon\": \"mdi-led-strip-variant\"\n    }\n]","output":"json","x":780,"y":720,"wires":[["dd3e82cb5f9536c7"]]},{"id":"4a32d1d626bd3e12","type":"switch","z":"bc05bdac7c068faa","name":"get flow var","property":"switches","propertyType":"flow","rules":[{"t":"istype","v":"undefined","vt":"undefined"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":590,"y":740,"wires":[["6d52057ea8bcdd86"],["72dd07b3cc9024e9"]]},{"id":"dd3e82cb5f9536c7","type":"change","z":"bc05bdac7c068faa","name":"set default flow var","rules":[{"t":"set","p":"switches","pt":"flow","to":"payload","tot":"msg","dc":true},{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.switches","pt":"msg","to":"switches","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":950,"y":700,"wires":[["f5ce5f0e98be7dff"]]},{"id":"72dd07b3cc9024e9","type":"change","z":"bc05bdac7c068faa","name":"switches","rules":[{"t":"set","p":"payload","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"payload.switches","pt":"msg","to":"switches","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":800,"y":760,"wires":[["f5ce5f0e98be7dff"]]},{"id":"4e1ceb072b981ddf","type":"debug","z":"bc05bdac7c068faa","name":"debug 513","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":590,"y":820,"wires":[]},{"id":"25c96721c32b4632","type":"inject","z":"bc05bdac7c068faa","name":"cozhina on","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"update\":{\"name\":\"Cozhina\",\"state\":true}}","payloadType":"json","x":200,"y":840,"wires":[["14e3e6c1de302422"]]},{"id":"4ad5c0331f11a00a","type":"inject","z":"bc05bdac7c068faa","name":"cozhina off","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"update\":{\"name\":\"Cozhina\",\"state\":false}}","payloadType":"json","x":200,"y":880,"wires":[["14e3e6c1de302422"]]},{"id":"14e3e6c1de302422","type":"link out","z":"bc05bdac7c068faa","name":"link out 40","mode":"link","links":["16449a5979598e53"],"x":315,"y":860,"wires":[]},{"id":"1a12ece250764369","type":"ui-group","name":"Group Name","page":"0a534af73f5f11fb","width":"6","height":"1","order":-1,"showTitle":false,"className":""},{"id":"64d151d89e8704cf","type":"ui-base","name":"UI Name","path":"/dashboard"},{"id":"0a534af73f5f11fb","type":"ui-page","name":"Page Name","ui":"64d151d89e8704cf","path":"/","layout":"grid","theme":"ff0a34f890dc8310","order":-1,"className":""},{"id":"ff0a34f890dc8310","type":"ui-theme","name":"Theme Name","colors":{"surface":"#b51a00","primary":"#ffffff","bgPage":"#b51a00","groupBg":"#606060","groupOutline":"#606060"}}]
3 Likes

One thing to remember (hopefully) the switches are actually controlling a physical or logical object that will need to react, and in my case, save its current state. This state should then be returned to the button when refreshing a page or viewing on another device. The button should never have to save its own state because this may not coincide with the state of whatever is being controlled.

In Dashboard 1 I use switches in the mode Do not pass messages through, and Show State of Input. Then when the switch is clicked it sends the new state, but does not change visually. When the hardware changes and sends back its new state then that is fed into the switch and the state changes on the UI. That means that the switch always shows the actual state of the hardware.
Unfortunately the ui-switch widget in D2 does not yet have this capability (Issue #210), which means all my switches have to have hacks round them to make them behave correctly.

1 Like

The button should never have to save its own state because this may not coincide with the state of whatever is being controlled.

In my example flow, everything comes from a flow variable, the loading and the updating.
When pressing the button it produces a new state object of all switches and a separate property of the button that was switched.

input is handled separately (will not cause an infinite loop so to speak).

image

<template>
    <v-progress-linear
        style="display: flex; margin: auto; width: 200px; height: 20px; color: #000000; background-color: #4F4F4F; border: 1px solid #000000; border-radius: 12px;"
        v-model="entrada">
        <template v-slot:default="{ value }">
            <strong style="color: #A9A9A9">{{ Math.ceil(value) }}%</strong>
        </template>
    </v-progress-linear>
</template>

<script>
  export default {
    data() {
      return {
        chave: 'a1',
        entrada: 0,
      };
    },
    methods: {
      setState(value) {
        this.entrada = value;
        localStorage.setItem(this.chave, value.toString());
    },
    recuperarEstadoAnterior() {
      const savedValue = localStorage.getItem(this.chave);
        if (savedValue !== null) {
          this.entrada = parseInt(savedValue, 10);
        }
      },
    },
    mounted() {
      this.recuperarEstadoAnterior();
      this.$socket.on("msg-input:" + this.id, (msg) => {
        this.setState(msg.payload);
      });
    },
  };
</script>

image

image

<template>
    <v-progress-circular size="20" style="display: flex; margin: auto; width: 80px; height: 80px; color: #BD9608; background-color: #4F4F4F; border: 1px solid #000000; border-radius: 12px;" v-model="entrada">
        <template v-slot:default="{ value }">
            <strong style="color: #000000">{{ Math.ceil(value) }}%</strong>
        </template>
    </v-progress-circular>
</template>

<script>
  export default {
    data() {
      return {
        chave: 'a5',
        entrada: 0,
      };
    },
    methods: {
      setState(value) {
        this.entrada = value;
        localStorage.setItem(this.chave, value.toString());
    },
    recuperarEstadoAnterior() {
      const savedValue = localStorage.getItem(this.chave);
        if (savedValue !== null) {
          this.entrada = parseInt(savedValue, 10);
        }
      },
    },
    mounted() {
      this.recuperarEstadoAnterior();
      this.$socket.on("msg-input:" + this.id, (msg) => {
        this.setState(msg.payload);
      });
    },
  };
</script>

Remember to create a different "Chave" for each node.

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