How to retain the state of a button on and off between page updates and different browsers?

@joepavitt

I would like to understand and form a consistent variable that can be retrieved on page updates and across different browsers. I currently use the code below, but I'm not sure how to retrieve the states. Anyone who can help I would appreciate it.

image

<template>
    <v-btn class="button_fita_cozinha" ref="button" stacked @click="alternar">
        <div class="title_fita_cozinha">Cozinha</div>
        <v-icon class="icon_fita_cozinha" ref="icon">{{icon}}</v-icon>
    </v-btn>
</template>

<script>
    export default {
        data() {
            return {
                value: "OFF",
                icon: "mdi-led-strip-variant"
            };
        },
        methods: {
            alternar: function () {
                if (this.value === 'ON') {
                    this.desligar();
                } else if (this.value === 'OFF') {
                    this.ligar();
                } else {
                    this.desligar();
                }
            },
            ligar: function () {
                this.icon = "mdi-led-strip-variant";
                this.$refs.icon.$el.style.color = '#BD9608';
                this.$refs.icon.$el.style.textShadow = '0px 0px 10px #BD9608';
                if (this.value === 'OFF') {
                    this.send({ payload: 'ON' });
                    this.value = "ON"; 
                }
            },
            desligar: function () {
                this.icon = "mdi-led-strip-variant";
                this.$refs.icon.$el.style.color = '#A9A9A9';
                this.$refs.icon.$el.style.textShadow = '0px 0px 0px';
                if (this.value === 'ON') {
                    this.send({ payload: 'OFF' });
                    this.value = "OFF"; 
                }
            }
        },
        watch: {
            msg: function(){
                if(this.msg.payload != undefined){
                    console.log('got message :',this.msg)
                    if (this.msg.payload === "ON") {
                        this.ligar();
                    } else if (this.msg.payload === "OFF") {
                        this.desligar();                        
                    }
                }
            }
        }
    }
</script>

<style>
    .button_fita_cozinha {
        display: flex; 
        flex-direction: column; 
        margin: auto; 
        height: 75px; 
        width: 75px; 
        background-color: #4F4F4F; 
        color: #000000; 
        border: 1px solid #000000; 
        ont-size: 14px; 
        border-radius: 18px;"
    }
    .title_fita_cozinha {
        font-size: 75%;
    }
    .icon_fita_cozinha {
        font-size: 40px;
    }
</style>

My solution to a similar problem was to ensure that every message sent to the template, or sent from the template, includes all the state information (in a property, msg.state for example). Then when the dashboard is opened in a new browser, or the page is refreshed, the last message sent to or sent from the template will be sent to it so it knows the current state.

I recently updated the Events Architecture documentation to assist with understanding in this space, in particular, the "Dashboard Actions" Events Flow.

This is the important (new) diagram:

What it shows is the difference between the client actions, when you run send() in a ui-template, that fires the widget-send event, which does store any payload/msg you send into the server-side store for that widget.

In turn, what that means is, given our "Loading Event Flow", we can see that this retrieves the latest msg stored against a widget/ui-template:

The widget itself will automatically populate the client-side msg object when the ui-template first loads, with whatever data is stored in the server-side data store (i.e. the last thing sent via send()), and so your watch on msg is a feasible approach here as that will catch on first load. The disadvantage of this approach is that it will run that for all incoming messages too, which also modify msg.

The other option, which I haven't actually exposed too much in the docs because the state is auto-loaded into msg, but does work (I've just tested, and I'll update docs accordingly):

this.$socket.on('widget-load:'+this.id, (msg) => {
   // do stuff with the loaded msg
})

Which will only run when the widget is first loaded, and if there is a msg to load. It does not fire when there is no data stored in the server-side store for a widget.

FYI @hotNipi as I know this will be useful for you too, potentially even @BartButenaers too

1 Like

Just to provide an example too:

<template>
  <div>Last Loaded Message {{ loaded }}:</div>
  <pre>{{lastMsg}}</pre>
  <div>
    <v-text-field v-model="payload" label="Payload"></v-text-field>
    <v-btn @click="send({payload})">Save Payload</v-btn>
  </div>
</template>
<script>
  export default {
    data () {
        return {
          loaded: false,
          lastMsg: null,
          payload: ''
        }
    },
    mounted () {
      console.log('mounted', this.id)
      this.$socket.on('widget-load:' + this.id, (msg) => {
        this.loaded = true
        this.lastMsg = msg
      })
    }
  }
</script>

@Colin
Similar concept I have used for years in my html based hack; when the browser is opened and initiates the web socket connection, a status request is sent to Node-RED that responds with all known statuses in a number of messages. For dashboard v1 I use "theScope" instead

The response messages to the request are received by all connected browser clients so they all get synchronized as well (regardless if it is needed or not)

So would this be the correct assembly idea to save the status between browsers and when reloading?

image

<template>    
    <div class="contaner_ar_condicionado">
        <div class="title_ar_condicionado">Ar Condicionado</div>
        <div class="buttons_ar_condicionado">
            <v-btn class="button_ar_condicionado" @click="mais">        
                <v-icon class="icon_ar_condicionado">mdi-plus-thick</v-icon>
            </v-btn>
            <v-btn class="button_ar_condicionado" @click="alternar">
                <v-icon class="icon_ar_condicionado" ref="icon">mdi-power</v-icon>
            </v-btn>
            <v-btn class="button_ar_condicionado" @click="menos">
                <v-icon class="icon_ar_condicionado">mdi-minus</v-icon>
            </v-btn>
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                value: ''
            };
        },
        mounted() {            
            this.$socket.on('widget-load:' + this.id, (msg) => {
                this.value = msg.payload;
                if (this.value === "ON") {
                    this.on();
                } else {
                    this.off();
                }
            });
        },
        methods: {
            alternar() {
                if (this.value === 'ON') {
                    this.off();
                    this.value = "OFF";
                    this.send({ payload: 'OFF' });
                } else {
                    this.on();
                    this.value = "ON";
                    this.send({ payload: 'ON' });
                }
            },
            mais() {
                if (this.value === 'ON') {                    
                    this.send({ payload: 'MAIS' });
                }                
            },
            menos() {
                if (this.value === 'ON') {                    
                    this.send({ payload: 'MENOS' });
                } 
            },
            on() {
                this.$refs.icon.$el.style.color = '#BD9608';
                this.$refs.icon.$el.style.textShadow = '0px 0px 10px #BD9608';                             
            },
            off() {
                this.$refs.icon.$el.style.color = '#A9A9A9';
                this.$refs.icon.$el.style.textShadow = '0px 0px 0px';
            }
        }
    }
</script>

<style>
    .contaner_ar_condicionado {
        display: flex; 
        justify-content: center;
        align-items: center; 
        flex-direction: column; 
        height: 75px; 
        width: 210px; 
        background-color: #4F4F4F; 
        border: 1px solid #000000; 
        border-radius: 18px;
    }
    .buttons_ar_condicionado {
        display: flex; 
        justify-content: center;
        align-items: center; 
        flex-direction: row;         
    }
    .button_ar_condicionado {
        display: flex; 
        justify-content: center; 
        align-items: center;        
        background-color: transparent;
    }
    .title_ar_condicionado {
        font-size: 14px;
    }
    .icon_ar_condicionado {
        font-size: 30px; 
    }
</style>

That looks good to me

I'm applying this concept to all my material, but I still have a challenge. There are 2 panels open, when the button is toggled in one, this change does not appear instantly in the other. Whenever I toggle one button, I need to refresh the page on the other to get the new status.

image

<template>
    <v-btn class="button_Quarto" stacked @click="alternar">
        <div class="title_Quarto">Quarto</div>
        <v-icon class="icon_Quarto" ref="icon">mdi-bed</v-icon>
    </v-btn>
</template>

<script>
    export default {
        data() {
            return {
                value: ''
            };
        },
        mounted() {            
            this.$socket.on('widget-load:' + this.id, (msg) => {
                this.value = msg.payload;
                if (this.value === "ON") {
                    this.on();
                } else {
                    this.off();
                }
            });
        },
        methods: {
            alternar() {
                if (this.value === 'ON') {
                    this.off();
                    this.value = "OFF";
                    this.send({ payload: 'OFF' });
                } else {
                    this.on();
                    this.value = "ON";
                    this.send({ payload: 'ON' });
                }
            },
            on() {
                this.$refs.icon.$el.style.color = '#BD9608';
                this.$refs.icon.$el.style.textShadow = '0px 0px 10px #BD9608';                             
            },
            off() {
                this.$refs.icon.$el.style.color = '#A9A9A9';
                this.$refs.icon.$el.style.textShadow = '0px 0px 0px';
            }
        },
        watch: {
            msg: function() {
                if (this.msg.payload === "ON") {
                    this.on();
                    this.send({ payload: 'ON' });
                } else {
                    this.off();
                    this.send({ payload: 'OFF' });
                }                        
            }
        }
    }
</script>

<style>
    .button_Quarto {
        display: flex; 
        flex-direction: column; 
        margin: auto; 
        height: 75px; 
        width: 75px; 
        background-color: #4F4F4F; 
        color: #000000; 
        border: 1px solid #000000; 
        font-size: 14px; 
        border-radius: 18px;"
    }
    .title_Quarto {
        font-size: 85%;
    }
    .icon_Quarto {
        font-size: 40px;
    }
</style>

This is actually a bug in the underlying Dashboard 2.0 architecture at the moment that @Colin had recently flagged to me. I need to re-visit the events architecture and workout a solution here.

I don't think that applies when using the ui-template does it? That was about core nodes, though I can't find an open issue about it.

@diegodamaceno the solution is to send the new state information back and into the template again so the other instances get updated. Make sure you don't end up in an infinite loop obviously.

Your original point was about core nodes, but the same infrastructure is in place for the underlying ui-template, and so they are related.

I've captured an issue here: Clients get out of sync from centralised datastore · Issue #679 · FlowFuse/node-red-dashboard · GitHub

How would this work with a ui-template?

That's partly what I need to work out :smiley:

OK, I will await your deliberations with eager anticipation :slight_smile:

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