4

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..
        });
}
Arno van Oordt
  • 2,912
  • 5
  • 33
  • 63

3 Answers3

10

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>
User 28
  • 4,863
  • 1
  • 20
  • 35
  • Worth pointing out, this does not work for async reject() method (e.g. async confirmation dialog), visually it still changes the tab and then changes it back. – wondra Dec 13 '21 at 16:02
  • @wondra Thank you for pointing out. I update my answer. And this is the [codesandbox](https://codesandbox.io/s/vigorous-perlman-c4eup). Unfortunately I can't run it due to poor performance. I copied from my local. I hope it will work. – User 28 Dec 14 '21 at 02:46
3

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";
}
Ulrich Anhalt
  • 172
  • 2
  • 11
0

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)
    }
}

}

smita
  • 298
  • 1
  • 4
  • 17