Vuetify tabs with components - mounted called twice on components - vue.js

I have a set of vuetify tabs where the content of each tab is conditionally rendered. This works as expected when the first tab is opened. When a second tab or beyond is opened, mounted is fired twice on the component inside that tab.
Here's how the tabs are structured:
<v-tabs
dark
background-color="blue-grey darken-4"
show-arrows
height="30"
v-model="name"
v-if="openTabs.length"
>
<v-tabs-slider color="accent"></v-tabs-slider>
<v-tab
v-for="item in openTabs"
:key="item.name"
v-model="activeTab"
class="pr-0 white--text"
color="accent"
active-class=""
#click="setActiveTab(item)"
>
<template class="white--text text-capitalize">
<v-icon>
{{ item.icon }}
</v-icon>
{{ item.name }}
<div v-if="item.changes">*</div>
<v-btn plain width="10" class="px-0" #click="closeTab(item.id)">
<v-icon
dark
small
width="10"
>mdi-close</v-icon>
</v-btn>
</template>
</v-tab>
</v-tabs>
<v-tabs-items v-model="name">
<v-tab-item
v-for="item in openTabs"
:key="item.name"
style="height: 100%"
:transition="false"
>
<BlockForm v-if="activeType === 'blocks'" v-bind:name="activeTab" v-bind:activeProject="activeProject" />
<BiomeForm v-if="activeType === 'biomes'" v-bind:name="activeTab" v-bind:projectName="activeProject" />
<ItemForm v-if="activeType === 'items'" v-bind:name="activeTab" v-bind:projectName="activeProject" />
<RecipeForm v-if="activeType === 'recipes'" v-bind:name="activeTab" v-bind:projectName="activeProject" />
</v-tab-item>
</v-tabs-items>
Here's what happens in setActiveTab:
setActiveTab: function (item) {
this.activeTab = item.name;
this.activeType = item.type;
}
BlockForm, ItemForm, BiomeForm, and RecipeForm are the components where mounted fires twice if they are the second (or third etc) tab opened. I'm not sure if this has something to do with the way I'm using v-if.

Hey mate have the same issue. I found explanation and as result code it.
It works properly. Try it.
<template>
<v-tabs centered="centered" grow v-model="activeTab">
<v-tab
v-for="tab of tabs"
:key="tab.id"
:id="tab.id"
:to="tab.route" exact>
{{ tab.title }}
</v-tab>
<v-tabs-items
v-model="activeTab"
#change="updateRouter($event)">
<v-tab-item
v-for="tab of tabs"
:key="tab.id"
:value="tab.route"
class="tab_content">
<router-view
v-if="tab.route === $route.fullPath && tab.route === activeTab">
</router-view>
</v-tab-item>
</v-tabs-items>
</v-tabs>
</template>
Here script data.
data() {
return {
activeTab: '',
tabs: [
{id: 1, title: 'Business', route: '/business'},
{id: 2, title: 'System', route: '/system'},
]
};
},
methods: {
updateRouter(tab) {
this.$router.push(tab)
},
}
And router
children: [
{
path: '/business',
name: 'business',
component: () => import(
/* webpackChunkName: "dashboard-page" */ './views/DashboardPage/DashboardTabs/DashboardBusinessTab'
),
meta: {
permission: null,
},
},
{
path: 'system',
name: 'system',
component: () => import(
/* webpackChunkName: "dashboard-page" */ './views/DashboardPage/DashboardTabs/DashboardSystemTab'
),
meta: {
permission: null,
},
},
],
This was key for solution
v-if="tab.route === $route.fullPath && tab.route === activeTab">

Related

Keyboard movement Vuetify v-list-item

I have a Vuetify v-list-item that is iterated over to create me a list, I want to be able to use the up and down arrows to traverse it. How can I do this? It doesn't seem to be default behavior.
The default focusing between elements behavior works through tab. You can try something like this in order to use arrows keys:
<template>
<v-card
class="mx-auto"
max-width="300"
tile
>
<v-list dense>
<v-subheader>REPORTS</v-subheader>
<v-list-item-group
v-model="selectedItem"
color="primary"
>
<v-list-item
v-for="(item, i) in items"
:key="i"
>
<v-list-item-icon>
<v-icon v-text="item.icon"></v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title v-text="item.text"></v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list-item-group>
</v-list>
</v-card>
</template>
<script>
export default {
data: () => ({
selectedItem: 1,
items: [
{ text: 'Real-Time', icon: 'mdi-clock' },
{ text: 'Audience', icon: 'mdi-account' },
{ text: 'Conversions', icon: 'mdi-flag' },
],
}),
methods: {
nextItem () {
if (event.keyCode == 38 && this.selectedItem > 0) {
this.selectedItem--
} else if (event.keyCode== 40 && this.selectedItem < 3) {
this.selectedItem++
}
}
},
mounted () {
document.addEventListener("keyup", this.nextItem);
},
}
</script>

Cannot get click in Vue.js to work the way I'd like

everyone, I am new to vuejs and vuetify and trying to make a book app but the problem is all the code is complete but when I click on the dropdown item it shows all the books item but I want to show only selected book item.
This is my HTML where I am stuck I am doing mistakes here this and I am failed many times cannot get my answer
<v-container fluid>
<v-row aign="center">
<v-col cols="12">
<v-toolbar-title>State Selection</v-toolbar-title>
<div v-on-clickaway="away" class="searchField dropdown">
<v-text-field
:label="labeling"
v-model="search"
#input="waitForSearch"
>
</v-text-field>
<!-- <div class="bookParent" v-for="item in items" :key="item.id"> -->
<!-- <img :src="item.volumeInfo.imageLinks.thumbnail" /> -->
<div
class="clickUpdateElement"
v-for="(item, index) in items"
:key="item.id"
>
<HelloWorld v-show="show">
<template v-slot:contentHandler class="option" id="option1">
<div
#click="clickCard(item, $event.target)"
class="containerForI"
>
<v-img>
<img :src="item.volumeInfo.imageLinks.thumbnail" />
</v-img>
<a class="anchorTag">{{ item.volumeInfo.title }}</a>
<!-- <span>{{ item.volumeInfo.author }}</span> -->
</div>
</template>
</HelloWorld>
<div class="content">
<Content v-show="show2" #load="updateCard(item, $event.target)">
<template v-slot:cardContent>
<v-card class="mx-auto" elevation="2" outlined shaped>
<v-list-item three-line>
<v-list-item-avatar>
<!-- <v-img :src="imageSrc"></v-img> -->
</v-list-item-avatar>
<v-list-item-content>
<v-card-title>
{{ item.volumeInfo.title }}
</v-card-title>
<v-card-subtitle>
{{ index }} {{ item.volumeInfo.description }}
</v-card-subtitle>
</v-list-item-content>
<v-card-actions>
<v-btn primary>Act</v-btn>
</v-card-actions>
</v-list-item>
</v-card>
</template>
</Content>
</div>
</div>
<v-btn #click="show2 = false">Remove</v-btn>
</div>
</v-col>
</v-row>
</v-container>
MY JS
import axios from "axios";
import { directive as onClickaway } from "vue-clickaway";
import HelloWorld from "../components/HelloWorld";
import Content from "../components/Content";
export default {
name: "Home",
directives: {
onClickaway: onClickaway,
},
data() {
return {
BASE_URL: "https://www.googleapis.com/books/v1/volumes",
items: [],
search: "",
timerId: "",
labeling: "Search For Book",
show: false,
show2: false,
imageSrc: "",
showTitle: "",
showDescription: "",
};
},
components: {
HelloWorld,
Content,
},
created() {},
computed: {},
//Fetch the required Information from Google Book Api
watch: {},
methods: {
async getBooks() {
this.show = true;
let response = await axios.get(`${this.BASE_URL}`, {
params: {
q: this.search,
apikey: "",
},
});
this.items = response.data.items;
console.log(this.items);
},
away() {
console.log("clicked away");
this.show = false;
},
clickCard(item, target) {
console.log(item);
console.log(target);
this.show = false;
this.show2 = true;
},
updateCard(item, target) {
console.log(item);
console.log(target);
},
//Method That Take and wait for the Input
waitForSearch() {
// this.search = "";
clearTimeout(this.timerId);
this.timerId = setTimeout(() => {
this.getBooks();
}, 1000);
},
},
Content.vue i just used slot nothing else
<div class="container">
<div class="row">
<slot name="cardContent"></slot>
</div>
</div>
HelloWorld.vue
html
<div class="menu pointerCursor hide">
<slot name="contentHandler"></slot>
</div>
I try many times but failed. Any leads, please?
What I would try is to keep a record of the index that I want to show.
So what you could do is the following:
1.Change the code calling ClickCard
<div #click="clickCard(item, index)" class="containerForI">
2.Assign the value of index to a component variable
clickCard(item, index) {
console.log(item);
this.indexToShow = index;
this.show = false;
this.show2 = true;
},
3.Initialize the aforementioned variable in your component
data() {
return {
BASE_URL: "https://www.googleapis.com/books/v1/volumes",
indexToShow: null,
...
4.Finally, check if the currently selected index is the one to show
<Content v-show="indexToShow === null || indexToShow === index"
If you want to show all items, after showing only 1, you will have to set indexToShow to null. The Content component is checking for it.

How to show an icon only when certain tab is selected?

For this website I have two login tabs as seen in the image below, and right now it's hard to understand which login tab is selected.
I'm trying to display an icon next to the selected tab, but currently it shows the icon for both tabs. So overall I'm trying to show the icon only for the tab that is selected and if I change the tab, it hides it from the previously selected tab and shows it to the current one.
This is the whole code that I have -
<template>
<v-main id="login">
<v-container fill-height fluid>
<v-layout align-center justify-center>
<v-flex md4 sm8 xs12>
<v-card class="elevation-12">
<v-toolbar color="primary" dark>
<v-toolbar-title>
<v-icon left> mdi-login-variant </v-icon>
{{ $t("welcome") }}
</v-toolbar-title>
</v-toolbar>
<v-divider />
<v-tabs v-model="selectedTab" grow hide-slider>
<v-tab
v-for="(tab, i) in tabs"
:key="i"
:class="{
'primary white--text': tab == selectedTab,
caption: tab != selectedTab,
}"
:href="`#${tab}`"
class="pa-0"
>
{{ tab }}
//This is the icon line that I added
<v-icon right >mdi-check-circle-outline</v-icon>
</v-tab>
<v-tab-item
v-for="(tab, i) in tabs"
:key="i"
:value="tab"
reverse-transition="scale-transition"
transition="scale-transition"
>
<v-divider />
<v-card-text>
<v-form #submit.prevent="login">
<v-text-field
v-model.lazy="username"
:label="$t('username')"
:prepend-inner-icon="
tab === 'Windows' ? 'mdi-windows' : 'mdi-account'
"
:rules="[username !== null || required]"
name="username"
outlined
placeholder=" "
type="text"
/>
<v-text-field
v-model.lazy="password"
:label="$t('password')"
:rules="[password !== null || required]"
name="password"
outlined
placeholder=" "
prepend-inner-icon="mdi-lock"
type="password"
/>
<!-- If error, rended error component -->
<error-view
v-if="error"
:error="error"
:is-login="true"
class="pa-0"
/>
<v-card-actions class="pa-0">
<v-spacer />
<v-btn :loading="loading" color="primary" type="submit">
{{ $t("submit") }}
</v-btn>
</v-card-actions>
</v-form>
</v-card-text>
</v-tab-item>
</v-tabs>
<div id="version-div">
<app-version />
</div>
</v-card>
</v-flex>
</v-layout>
</v-container>
</v-main>
</template>
<script>
import AppVersion from "#/components/version";
const errorView = () => import("#/components/errorView");
export default {
name: "Login",
components: {
errorView,
AppVersion,
},
data() {
return {
tabs: ["Windows", "Standard"],
selectedTab: "Standard",
username: null,
password: null,
loading: false,
error: null,
required: (value) => !!value || this.$t("req"),
};
},
methods: {
resetForm(value) {
this.username = this.password = value;
},
login() {
if (!this.username || !this.password) {
this.error = this.$t("warn");
this.resetForm(null);
} else {
this.loading = true;
const encodedPass = window.btoa(
unescape(encodeURIComponent(this.password))
);
this.$store
.dispatch("retrieveUser", {
username: this.username,
password: encodedPass,
outside: this.selectedTab === "Windows" ? false : true,
})
.then(() => {
this.$router.push({ name: "home" });
this.error = null;
})
.catch((error) => {
this.error = error;
})
.finally(() => {
this.resetForm("");
this.loading = false;
});
}
},
},
};
</script>
Thank you in advance!
You've actually already implemented the solution in a different part of your code.
Apply the same conditioning as you do to the class binding on 'primary white--text'. The v-icon can be conditionally rendered based on the tab selected.
<v-icon v-if="tab === selectedTab" right >mdi-check-circle-outline</v-icon>

How can I make Vuetify mobile responsive navigation bar and linked drawer have NESTED menus?

I am making a navbar component through the Vue framework using Vuetify. I would like to make the products item have a drop down into two links.
This is the template html and script code (I have some additional custom CSS for color and such that I am not adding here):
<template>
<div>
<v-toolbar id="navbar" dense elevation=1 dark >
<v-app-bar-nav-icon class="hidden-md-and-up" #click="sidebar = !sidebar"></v-app-bar-nav-icon>
<v-navigation-drawer v-model="sidebar" app hide-overlay temporary>
<v-list>
<v-list-item v-for="(item, i) in menuItems" exact :key="i" :to="item.path">{{item.title}}</v-list-item>
</v-list>
</v-navigation-drawer>
<v-toolbar-items d-flex>
<v-btn href="#" id="logo" flat depressed text>Company Name</v-btn>
</v-toolbar-items>
<v-spacer></v-spacer>
<v-toolbar-items class="hidden-sm-and-down">
<v-btn text v-for="item in menuItems" :key="item.title">
<router-link :to="item.path">{{item.title}}</router-link>
</v-btn>
</v-toolbar-items>
</v-toolbar>
</div>
</template>
<script>
export default {
data: function() {
return {
sidebar: false,
menuItems: [
{ path: "/product", name: "product", title: "Product" },
{ path: "/us", name: "us", title: "Us" },
{ path: "resources", name: "resources", title: "Resources" },
{ path: "/portal", name: "login", title: "Login" }
]
};
}
};
</script>
How about app-bar?
v-menu tag support drop down
https://vuetifyjs.com/en/components/app-bars/#dense

Vue draggable to follow v-chip-group selection

I am using Vue draggable to change the position of my v-chips. When you click on a chip, the chip shows Active and is binded to selection with its index. The problem is that when you drag a chip and change its position it does not update the selection index.
How can I make sure one follows the other?
<v-chip-group
v-model="selection"
active-class="primary--text"
column
>
<draggable v-model="items" #start="drag=true" #end="drag=false">
<v-chip v-for="(item, i) in items" :key="i"
close
draggable>
{{item.name}}
</v-chip>
</draggable>
</v-chip-group>
You can able to set the selection index using #start and #end event in draggable component
Here is the working codepen: https://codepen.io/chansv/pen/zYvOYyd?editors=1010
Find the working code here:
<div id="app">
<v-app id="inspire">
<v-card
max-width="400"
class="mx-auto"
>
<v-card-text>
<v-chip-group
v-model="selection"
column
active-class="primary--text"
>
<draggable v-model="tags" #start="dragStart" #end="dragEnd">
<v-chip v-for="(tag, i) in tags" :key="i" draggable>
{{ tag.name }}
</v-chip>
</draggable>
</v-chip-group>
</v-card-text>
</v-card>
</v-app>
</div>
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
selection: null,
currentTag: null,
tags: [{
name: 'Shoping',
},{
name: 'Art',
}, {
name: 'Tech',
}, {
name: 'Creative Writing'
}
],
}),
methods: {
dragStart() {
if (this.tags[this.selection]) this.currentTag = this.tags[this.selection].name;
else this.currentTag = null;
},
dragEnd() {
var self = this;
if (this.currentTag) {
this.tags.forEach((x, i) => {
if (x.name === self.currentTag) self.selection = i;
});
}
}
}
})