# Migrating Dashboard 1.0 UI Template to Dashboard 2.0

**URL:** <https://discourse.nodered.org/t/migrating-dashboard-1-0-ui-template-to-dashboard-2-0/83578>\
**Category:** Dashboard\
**Tags:** dashboard-2\
**Created:** [10 December 2023 14:55 UTC](https://discourse.nodered.org/t/migrating-dashboard-1-0-ui-template-to-dashboard-2-0/83578 "2023-12-10T14:55:55Z")\
**Posts on this page:** 7\
**Page:** 1

<div class="post-metadata">

**Author:** ![joepavitt](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/joepavitt/32/59722_2.png) [@joepavitt](https://discourse.nodered.org/u/joepavitt)\
**Post date:** [10 December 2023 14:55 UTC](https://discourse.nodered.org/t/migrating-dashboard-1-0-ui-template-to-dashboard-2-0/83578/1 "2023-12-10T14:55:55Z")

</div>

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:

 ![Screenshot 2023-12-10 at 14.54.22](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/e/4/e4616fae5b25a1360e0b79d0968fd08f7f92d44d.jpeg)

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

 ![Screenshot 2023-12-10 at 14.54.16](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/3X/b/a/baab2403f8c560ba12ee6e33283a13a5541ae5f2.jpeg)

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

---

<div class="post-metadata">

**Author:** ![joepavitt](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/joepavitt/32/59722_2.png) [@joepavitt](https://discourse.nodered.org/u/joepavitt)\
**Post date:** [10 December 2023 16:22 UTC](https://discourse.nodered.org/t/migrating-dashboard-1-0-ui-template-to-dashboard-2-0/83578/2 "2023-12-10T16:22:11Z")

</div>

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](https://dashboard.flowfuse.com/nodes/widgets/ui-template.html#built-in-functions) 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:

```html
<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:

```auto
<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.

---

<div class="post-metadata">

**Author:** ![joepavitt](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/joepavitt/32/59722_2.png) [@joepavitt](https://discourse.nodered.org/u/joepavitt)\
**Post date:** [10 December 2023 16:29 UTC](https://discourse.nodered.org/t/migrating-dashboard-1-0-ui-template-to-dashboard-2-0/83578/3 "2023-12-10T16:29:12Z")

</div>

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

> [@Dashboard 2 Beta development](https://discourse.nodered.org/t/dashboard-2-beta-development/83550/17):
>
> Actually, I went too far, too soon Ted! It is as simple as this: The template \<v-icon :color="msg.payload?.state === 'on' ? (msg.payload?.color || 'green') : 'grey'" :icon="msg.payload?.state === 'on' ? 'mdi-led-on' : 'mdi-led-off'"\> \</v-icon\> Demo flow [{"id":"fb5c574dc9088451","type":"ui-template","z":"1ca1817447127d22","group":"34a8586ca5a5880e","dashboard":"ID-BASE-1","page":"ID-PAGE-1","name":"","order":0,"width":0,"height":0,"head":"","format":"\n\<v-icon \n :color=\"msg.pa…

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.

---

<div class="post-metadata">

**Author:** ![diegodamaceno](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/diegodamaceno/32/85742_2.png) [@diegodamaceno](https://discourse.nodered.org/u/diegodamaceno)\
**Post date:** [10 December 2023 18:36 UTC](https://discourse.nodered.org/t/migrating-dashboard-1-0-ui-template-to-dashboard-2-0/83578/4 "2023-12-10T18:36:25Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![diegodamaceno](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/diegodamaceno/32/85742_2.png) [@diegodamaceno](https://discourse.nodered.org/u/diegodamaceno)\
**Post date:** [10 December 2023 18:48 UTC](https://discourse.nodered.org/t/migrating-dashboard-1-0-ui-template-to-dashboard-2-0/83578/5 "2023-12-10T18:48:26Z")

</div>

```auto
<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.

---

<div class="post-metadata">

**Author:** ![diegodamaceno](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/diegodamaceno/32/85742_2.png) [@diegodamaceno](https://discourse.nodered.org/u/diegodamaceno)\
**Post date:** [10 December 2023 19:57 UTC](https://discourse.nodered.org/t/migrating-dashboard-1-0-ui-template-to-dashboard-2-0/83578/6 "2023-12-10T19:57:42Z")

</div>

```auto
<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.

---

<div class="post-metadata">

**Author:** ![system](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/1X/d073cd938eafa2e558d7c2cd59003b3ef4963033.png) [@system](https://discourse.nodered.org/u/system)\
**Post date:** [9 January 2024 19:58 UTC](https://discourse.nodered.org/t/migrating-dashboard-1-0-ui-template-to-dashboard-2-0/83578/7 "2024-01-09T19:58:37Z")

</div>

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