user3534080
user3534080

Reputation: 1455

vuetify 3 v-list highlighting active item

First off, I am not a front-end dev, so please bear with me. I'm trying to upgrade a legacy project from Vue/Vuetify 2 to 3, and having issues with event handlers not discussed in the upgrade/migration guide.

I have this v-list:

<v-list density="compact" @update:selected="this.setSelected">
  <v-list-item
    v-for="item in items"
    :key="item.title"
    link
    nav
    :disabled="item.disabled"
    :prepend-icon="item.icon || null"
    :title="item.title"
    :value="item.routeName"
  />
</v-list>

Later in the file:

<script>
import AppLayoutContent from '@/components/AppLayoutContent.vue'

export default {
  name: 'AppLayout',
  components: {
    AppLayoutContent,
  },
  data: () => ({
    items: [
      {
        title: 'Home',
        icon: 'mdi-home',
        routeName: 'Home'
      },
      {
        title: 'Settings',
        icon: 'mdi-cog-outline',
        routeName: 'Settings',
      }
    ]
  }),
  methods: {
    setSelected(routeName) {
      if (routeName && this.$route.name !== routeName) {
        this.$router.push({ name: routeName[0] })
      }
    }
  }

The active v-list-item would be the one where this.$route.name === routeName is true.

I've tried all sorts of things, but nothing seems to work. This solution seems close, but I can't get it to work (possibly because they're using v-list-tile and not v-list-item?). I figure I probably have to do something with https://vuetifyjs.com/en/api/v-list/#props-selected , but there's zero documentation on how to use this

In the Vue2 version, we had

  computed: {
    selected: {
      set(selected) {
        const { routeName } = this.items[selected] || {}
        if (routeName && this.$route.name !== routeName) {
          this.$router.push({ name: routeName })
        }
      },
      get() {
        return findIndex(
          this.items,
          ({ routeName }) => this.$route.name === routeName
        )
      },
    },
  },

Upvotes: 0

Views: 5188

Answers (1)

Moritz Ringler
Moritz Ringler

Reputation: 15816

If you use the :to prop to create the links, Vuetify will handle activation for you automatically (you can change the class for active items with the :active-class prop).

So you could just do:

<v-list-item
  v-for="item in items"
  :to="{name: item.routeName}"
  ...
/>

Here it is in a snippet:

const { createApp, ref } = Vue
const { createVuetify } = Vuetify
const { createRouter, createWebHashHistory } = VueRouter
const vuetify = createVuetify()


const app = {
    data: () => ({
      items: [
        {
          title: 'Home',
          icon: 'mdi-home',
          routeName: 'Home'
        },
        {
          title: 'Settings',
          icon: 'mdi-cog-outline',
          routeName: 'Settings',
        }
      ]
    }),
}
const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    { path: '/', name:'Home', component: app },
    { path: '/', name:'Settings', component: app },
  ]
})

createApp(app).use(vuetify).use(router).mount('#app')
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/vuetify@3/dist/vuetify.min.css" />
<link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
<div id="app">
  <v-app>
    <v-main>
      <v-card class="ma-4">
      <v-list density="compact">
        <v-list-item
          v-for="item in items"
          :key="item.title"
          link
          nav 
          :disabled="item.disabled" 
          :prepend-icon="item.icon || null" 
          :title="item.title"
          :value="item.routeName"
          :to="{name: item.routeName}"
        ></v-list-item>
      </v-list>
      </v-card>
    </v-main>
  </v-app>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@3/dist/vuetify.min.js"></script>
<script src=" https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.global.min.js "></script>


If you want to set the active state manually, v-list-item has an :active prop that highlights the item if the provided value is true. So in your case that would give you:

<v-list-item
  v-for="item in items"
  :active="item.routeName === $route.name"
  ...
/>

Have a look at the snippet to see how it looks

const { createApp, ref } = Vue;
const { createVuetify } = Vuetify
const vuetify = createVuetify()
const app = {
    data: () => ({
      activeItem: null,
      items: [
        {
          title: 'Home',
          icon: 'mdi-home',
          routeName: 'Home'
        },
        {
          title: 'Settings',
          icon: 'mdi-cog-outline',
          routeName: 'Settings',
        }
      ]
    }),
    methods:{
      onSelect(selection){
        const [itemTitle] = selection
        this.activeItem = !itemTitle ? null : this.items.find(item => item.title === itemTitle)
      }
    }
}
createApp(app).use(vuetify).mount('#app')
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/vuetify@3/dist/vuetify.min.css" />
<link href="https://cdn.jsdelivr.net/npm/@mdi/[email protected]/css/materialdesignicons.min.css" rel="stylesheet">
<div id="app">
  <v-app>
    <v-main>
      <v-card class="ma-4">
      <v-list density="compact" @update:selected="onSelect">
        <v-list-item
          v-for="item in items"
          :key="item.title"
          link
          nav 
          :disabled="item.disabled" 
          :prepend-icon="item.icon || null" 
          :title="item.title"
          :value="item.routeName"
          :active="activeItem?.routeName === item.routeName"
        ></v-list-item>
      </v-list>
      </v-card>
      Active item: {{activeItem ?? 'none'}}
    </v-main>
  </v-app>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@3/dist/vuetify.min.js"></script>

Upvotes: 5

Related Questions