Prevent Vuetify v-tabs from change

Viewed 5092

Is there a way to prevent the v-tabs from actually changing when being clicked on?

In my case I first need to check if stuff on the page has changed and want to cancel the switch to another tab if it has.

Neither a event.prevent nor event.stop will stop the v-tabs from changing: <v-tab @click.prevent.stop="..."> ... </v-tab>

At the moment I'm using a window.requestAnimationFrame to reset the tab index to the old value. It gets the job done but this feels like a really nasty technique to me.

HTML:

    <v-tabs v-model="currentIndex">
        <v-tab v-for="(route, index) in list" :key="index" @change="handleTabChange(route, $event)" >
            {{ route.meta.title }}
        </v-tab>
    </v-tabs>

TS:

public handleTabChange(routeConf:RouteConfig):void {
    let currentIndex:number = this.currentIndex;
    window.requestAnimationFrame(() => {
            this.currentIndex = currentIndex;
            Store.app.router.goto(routeConf.name, null, this.$route.params);
            // Once the page actually changes this.currentIndex is set to the correct index..
        });
}
3 Answers

I solve this problem by using separate variable between v-tabs and v-tabs-items.

<v-tabs v-model="tab" @change="onTabChange">
  <v-tab v-for="item in items" :key="item">
    {{ item }}
  </v-tab>
</v-tabs>

<v-tabs-items v-model="currentTab">
  <v-tab-item v-for="item in items" :key="item">
    <v-card>
      <v-card-text>{{ item }}</v-card-text>
    </v-card>
  </v-tab-item>
</v-tabs-items>
methods: {
  onTabChange() {
    if (/* reject */) {
      this.$nextTick(() => {
        this.tab = this.currentTab
      })
    } else {
      this.currentTab = this.tab
    }
  }
}

Demo

Another possible solution is to extend the v-tab component which is a bit more complicated but can actually override the behavior.

Create new file my-tab.js:

import { VTab } from 'vuetify/lib'

export default {
  extends: VTab,

  methods: {
    async click (e) {
      if (this.disabled) {
        e.preventDefault()
        return
      }

      // <-- your conditions
      let ok = await new Promise(resolve => {
        setTimeout(() => {
          resolve(false)
        }, 2000)
      })

      if (!ok) {
        this.$el.blur()
        return
      }
      // -->

      if (this.href &&
        this.href.indexOf('#') > -1
      ) e.preventDefault()

      if (e.detail) this.$el.blur()

      this.$emit('click', e)

      this.to || this.toggle()
    }
  }
}

The original source code is here. You can also override the render function to change the styles.

Then just use it as normal component:

<v-tabs v-model="tab">
  <my-tab v-for="item in items" :key="item">
    {{ item }}
  </my-tab>
</v-tabs>

<v-tabs-items v-model="tab">
  <v-tab-item v-for="item in items" :key="item">
    <v-card>
      <v-card-text
        >{{ item }} ipsum dolor sit amet, consectetur adipiscing elit, sed
        do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
        enim ad minim veniam, quis nostrud exercitation ullamco laboris
        nisi ut aliquip ex ea commodo consequat.</v-card-text
      >
    </v-card>
  </v-tab-item>
</v-tabs-items>

For me it works the following way:

...
<v-tab href="#tab-1">Tab-1</v-tab>
<v-tab href="#tab-2" @click.native.prevent.stop.capture="goto2()">Tab-2</v-tab>
...

...
private goto2() {
  if(tab-1Changes) {
    // do something
    return;
  }
  this.tab = "tab-2";
}

You should have to follow this way in your code this is example which will help you:

In ts file:

<template>
  <v-tabs v-model="activeTab">
     <v-tab v-for="tab in tabs" :key="tab.id" :to="tab.route">{{ tab.name }} 
     </v-tab>

     <v-tabs-items v-model="activeTab" @change="updateRouter($event)">
        <v-tab-item v-for="tab in tabs" :key="tab.id" :to="tab.route">
            <router-view />          
        </v-tab-item>
     </v-tabs-items>
   </v-tabs>
</template>

Script:

export default {
data: () => ({
    activeTab: '',
    tabs: [
        {id: '1', name: 'Tab A', route: 'component-a'},
        {id: '2', name: 'Tab B', route: 'component-b'}
    ]
}),
methods: {
    updateRouter(val){
        this.$router.push(val)
    }
}

}

Related