Migrating Dashboard 1.0 UI Template to Dashboard 2.0

Moved over from a GitHub issue for better visibility to others using Dashboard 2.0, and especially for those less familiar with VueJS.

@diegodamaceno had posted:

as an example of a ui-template that they used in Dashboard 1.0, followed by:

as an example of Diego's attempt running inside Dashboard 2.0's ui-template, alongside the message:

Make this possible in dashboard 2.0. Let us create this kind of thing like in angular

So, firstly, everything you've presented here is possible in Vue. Will firstly give an overview of the key features you can use:

  • this.$socket.on("msg-input:" + this.id) - as mentioned in our docs this function call can be used to create your own on-input listeners.

There are then two options for assigning the styling dynamically:

Option 1 - $ref

Using your current method, there is a Vue-equivalent to document.get...:

  • $ref - this will allow you to do scoped selections of elements within your template. document.querySelector will work, but won't be constrained to the scope of the ui-template and will be a nightmare when copying/pasting templates, or re-using elements/icons as you're likely to do here.

Putting the above together, you would result in the following:

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

<style>
.botao {
    background-color: #4F4F4F;
    border: 1px solid #808080;
    border-radius: 10px;
    color: black;
    font-size: 11px !important;
}

.icon {
    font-size: 40px;
}
</style>

<script>
    export default {
        methods: {
            // expose a method to our <template> and Vue Application
            alternar: function () {
                // flip the switch
                if (window.localStorage.getItem('botao01') === 'ON') {
                    this.desligar()
                } else {
                    this.ligar()
                }
            },
            ligar: function () {
                // store in local storage
                window.localStorage.setItem('botao01', 'ON')
                // set the style 
                this.$refs.icon.$el.style.color = '#BD9608'
                this.$refs.icon.$el.style.textShadow = '0px 0px 10px #BD9608'
                // send on a message
                this.send({ payload: 'ON' })
            },
            desligar: function () {
                // store in local storage
                window.localStorage.setItem('botao01', 'OFF')
                // set the style
                this.$refs.icon.$el.style.color = '#A9A9A9'
                this.$refs.icon.$el.style.textShadow = '0px 0px 0px'
                // send on a message
                this.send({ payload: 'OFF' })
            },
            setState (value) {
                if (value === 'ON') {
                    // turn light on
                    this.ligar()
                } else if (value === 'OFF') {
                    // turn light off
                    this.desligar()
                }
            }
        },
        mounted () {
            // load state from localstorage
            this.setState(window.localStorage.getItem('botao01'))

            // set up a listener for incoming messages
            this.$socket.on("msg-input:" + this.id, (msg) => {
                this.setState(msg.payload)
            })
        }
    }
</script>

I have to admit though, i've just spent an hour getting this to work. I hadn't appreciated that, when using ref= with a Vuetify component (or any Vue component for that matter) the this.$refs["my-ref"] returns the associated Vue component, not the raw DOM element, so you need to include .$el as you'll see in my example.

Option 2 - Computed Variables

If you're interested though, a more Vue-way of doing this would be:

<template>
    <v-btn class="botao" stacked @click="alternar">
        Externa
        <v-icon ref="icon" class="icon" :class="iconOnOff">mdi-lightbulb</v-icon>
    </v-btn>
</template>

<style>
.botao {
    background-color: #4F4F4F;
    border: 1px solid #808080;
    border-radius: 10px;
    color: black;
    font-size: 11px !important;
}

.icon {
    font-size: 40px;
}
.icon.on {
    color: #BD9608;
    text-shadow: 0px 0px 10px #BD9608;
}
.icon.off {
    color: #A9A9A9;
    text-shadow: 0px 0px 0px;
}
</style>

<script>
    export default {
        data () {
            return {
                state: 'OFF'
            }
        },
        computed: {
            iconOnOff: function () {
                return this.state.toLowerCase()
            }
        },
        methods: {
            // expose a method to our <template> and Vue Application
            alternar: function () {
                // flip the switch
                if (window.localStorage.getItem('botao01') === 'ON') {
                    this.setState('OFF')
                } else {
                    this.setState('ON')
                }
            },
            ligar: function () {
                // store in local storage
                window.localStorage.setItem('botao01', 'ON')
                // send on a message
                this.send({ payload: 'ON' })
            },
            desligar: function () {
                // store in local storage
                window.localStorage.setItem('botao01', 'OFF')
                // send on a message
                this.send({ payload: 'OFF' })
            },
            setState (value) {
                this.state = value
                if (value === 'ON') {
                    // turn light on
                    this.ligar()
                } else if (value === 'OFF') {
                    // turn light off
                    this.desligar()
                }
            }
        },
        mounted () {
            // load state from localstorage
            this.setState(window.localStorage.getItem('botao01'))

            // set up a listener for incoming messages
            this.$socket.on("msg-input:" + this.id, (msg) => {
                this.setState(msg.payload)
            })
        }
    }
</script>

Here, I utilise a computed variable which automatically updates the class on the icon whenever this.state changes.

1 Like

if this was read-only in Dashboard, i.e. not a clickable button whereby localStorage is then required, then a much much simpler solution would be:

This is great because it utilises the fact that ui-template (and every node/widget for that matter) store a this.msg variable in their Vue component, which represents the last received message, and is also populated when the Dashboard loads with whichever msg is found server-side in our statestore.

This is brilliant and simplifies a lot.. sorry for my limitation in programming.. but I make up for it with the determination to search for information and the blows I give to my ability.

1 Like
<template>
    <v-btn class="botao" stacked @click="alternar">
        Externa
        <v-icon ref="icon" class="icon">mdi-lightbulb</v-icon>
    </v-btn>
</template>

<style>
.botao {
    background-color: #4F4F4F;
    border: 1px solid #808080;
    border-radius: 10px;
    color: black;
    font-size: 11px !important;
}

.icon {
    font-size: 40px;
}
</style>

<script>
    export default {
        methods: {
            
            alternar: function () {
                
                if (window.localStorage.getItem('botao01') === 'ON') {
                    this.desligar()
                    this.send({ payload: 'OFF' })
                } else {
                    this.ligar()
                    this.send({ payload: 'ON' })
                }
            },

            ligar: function () {            
                window.localStorage.setItem('botao01', 'ON')                
                this.$refs.icon.$el.style.color = '#BD9608'
                this.$refs.icon.$el.style.textShadow = '0px 0px 10px #BD9608'
                
                
            },

            desligar: function () {                
                window.localStorage.setItem('botao01', 'OFF')                
                this.$refs.icon.$el.style.color = '#A9A9A9'
                this.$refs.icon.$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('botao01'))            
            this.$socket.on("msg-input:" + this.id, (msg) => {
                this.setState(msg.payload)
            })
        }
    }
</script>

I adjusted how it works because when I updated the page it displayed the status and overloaded mqtt.. with a lot of components this would be a problem.. I changed this.send({ payload: 'OFF' }) and this.send({ payload: 'ON' }) instead.

1 Like
<template>
    <v-btn ref="botao" stacked @click="alternar">
        Externa
        <v-icon ref="icon">mdi-lightbulb</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 {
                    this.ligar();
                    this.send({ payload: 'ON' });
                }
            },
            ligar: function () {            
                window.localStorage.setItem(this.chave, 'ON');
                this.$refs.icon.$el.style.color = '#BD9608';
                this.$refs.icon.$el.style.textShadow = '0px 0px 10px #BD9608';                              
            },
            desligar: function () {                
                window.localStorage.setItem(this.chave, 'OFF');
                this.$refs.icon.$el.style.color = '#A9A9A9';
                this.$refs.icon.$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.$refs.icon.$el.style.fontSize = '40px';
            this.$refs.botao.$el.style.border = '1px solid #808080';
            this.$refs.botao.$el.style.color = 'black';
            this.$refs.botao.$el.style.fontSize = '11px';
            this.$refs.botao.$el.style.backgroundColor = '#4F4F4F';
            this.$refs.botao.$el.style.borderRadius = '10px';            
        }
    }
</script>

I improved more. I removed the classes and used ref in everything... I also created a place to change the localStorage key since there is no way to use anything self-configurable. We have a single element that does not conflict with others and receives and sends configurable messages.. We just need to adjust the unlimited passage of messages from input to output.

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