Router breaks vuetify tabs - vuetify.js

I have some vuetify tabs that work as expected before I add routes.
I want the route to change when the active tab changes.
When I add the :to directive to the the v-tab the routes work correctly (route changes when the tab changes) but the tab items do not display. The area where the v-tab-item was displayed is now blank.
<v-tabs v-model='activeTab'>
<v-tab
v-for='cc in cost_centres'
:key='cc.id'
:to="{ name: 'Element', params: {id: cc.id}}"
>
{{ cc.name }}
</v-tab>
<v-tabs-items v-model='activeTab'>
<v-tab-item
v-for='cc in cost_centres'
:key='cc.id'>
<!-- Content -->
</v-tab-item>
</v-tabs-items>
</v-tabs>
The vuetify docs reference the vue-router docs and I think I am wiring it up right but I just can't get it to work.
What am I missing?

the :to was setting the value of the v-tab and therefore the v-model of the v-tabs to the path instead of the id (or the index of the array).
So I removed the :to directive and watched the v-model.
When the v-model changed I extracted the value (index of the array) and used it to get the cost centre id and then set the route using router.push
On page load (mounted) I use the route param (cost centre id) to find the cost_centre index and set the activeTab v-model.

You might use <router-view> instead of the <v-tabs-items>:
const Foo = {
computed: {
id() {
return this.$route.params.id || ''
}
},
template: '<v-card flat>foo {{ id }}</v-card>'
}
const Bar = {
computed: {
id() {
return this.$route.params.id || ''
}
},
template: '<v-card flat>bar {{ id }}</v-card>'
}
const routes = [{
path: "/",
redirect: {
name: "foo"
}
},
{
path: "/foo",
name: "foo",
component: Foo
},
{
path: "/bar",
name: "bar",
component: Bar
}
]
const router = new VueRouter({
routes
})
new Vue({
el: '#app',
router,
vuetify: new Vuetify(),
data() {
return {
activeTab: "foo",
cost_centres: [{
id: 1,
name: "foo"
},
{
id: 2,
name: "bar"
},
]
}
},
watch: {
$route: function(val) {
console.log(val)
}
},
mounted() {
console.log(this.$route)
}
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#5.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-container>
<v-row>
<v-col>
<v-tabs v-model="activeTab">
<v-tab v-for="cc in cost_centres" :key="cc.id" :to="{ name: cc.name, params: { id: cc.id }}">
{{ cc.name }}
</v-tab>
</v-tabs>
<router-view></router-view>
</v-col>
</v-row>
</v-container>
</v-app>
</div>
This way the content is not synchronized by the v-model, but by the :to & the routes.
EDIT: NEW SOLUTION
If you just use the query part of the route, then you can control the tabs:
const routes = [{
path: "/",
redirect: {
path: "/0"
}
},
{
path: "/:idx",
name: "foo"
}
]
const router = new VueRouter({
routes
})
new Vue({
el: '#app',
router,
vuetify: new Vuetify(),
data() {
return {
activeTabRoute: 0,
activeTab: 0,
cost_centres: [{
id: 0,
name: "foo"
},
{
id: 1,
name: "bar"
},
]
}
},
watch: {
$route: function(val) {
this.activeTab = val.params.idx
}
}
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#5.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-container>
<v-row>
<v-col>
<v-tabs v-model="activeTabRoute">
<v-tab v-for="(cc, i) in cost_centres" :key="cc.id" :to="{ name: 'foo', params: { idx: i } }">
{{ cc.name }} {{ cc.id }} {{ activeTab }}
</v-tab>
</v-tabs>
<v-tabs-items v-model="activeTab">
<v-tab-item v-for="cc in cost_centres" :key="cc.id">
<v-card flat>
Name: {{ cc.name }}<br /> ID: {{ cc.id }}<br /> IDx: {{ $route.params.idx }}
</v-card>
</v-tab-item>
</v-tabs-items>
</v-col>
</v-row>
<v-row>
<v-col>$route: {{ $route }}</v-col>
</v-row>
</v-container>
</v-app>
</div>
I created the snippet above to use the index of the for...in, because the reflects the order of the tabs, and doesn't mess up the IDs of the items.
This way you can use this component with vue-router.

Related

what am I doing wrong by passing a prop from the parent to the child in vue if it only works in the template side?

So i am trying to do a basic WebSocket notification system. I am passing the user as a prop from App.vue to the navbar component. it Can be shown in the template but when I try to call it in the script section. it says undefined.You can take a look at this picture, it shows the id shown in the navbar and when I try to console.log it, it says undefined.
Here is my App.vue "The parent"
<template lang="">
<div>
<v-app>
<template v-if="isLoggedIn">
<AdminMenu></AdminMenu>
</template>
<template v-else-if="!isUserLoggedIn">
<Nav></Nav>
</template>
<template v-if="isUserLoggedIn">
<Navbar :user='user.id'></Navbar>
</template>
<v-main app>
<router-view></router-view>
</v-main>
</v-app>
</div>
</template>
<script>
import 'vuetify/dist/vuetify.min.css' // Ensure you are using css-loader
import axios from 'axios';
import AdminMenu from './layouts/AdminMenu.vue'
import Navbar from './layouts/user/Navbar.vue'
import Nav from './layouts/user/Nav.vue'
export default {
name:'app',
components:{ 'AdminMenu': AdminMenu, 'Navbar':Navbar, 'Nav':Nav},
data(){
return{
user:[],
isLoggedIn: false,
isUserLoggedIn: false,
}
},
created() {
if (window.Laravel.isLoggedin) {
this.isLoggedIn = true
}
if (window.Laravel.isUserLoggedin) {
this.isUserLoggedIn = true
}
},
mounted(){
this.axios.get('/api/user').then(response=>{
this.user = response.data
}).catch(error=>{
console.log(error)
})
},
}
</script>
Here is the child
<template lang="">
<div>
<v-toolbar app dark>
<span class="hidden-sm-and-up">
<v-toolbar-side-icon #click="sidebar = !sidebar">
</v-toolbar-side-icon>
</span>
<v-toolbar-title>
<router-link to="/" tag="span" style="cursor: pointer">
{{ appTitle }}
</router-link>
</v-toolbar-title>
<v-spacer></v-spacer>
<v-toolbar-items class="hidden-xs-only">
<v-btn
text
v-for="item in menuItems"
:key="item.title"
:to="item.path">
{{ item.title }}
</v-btn>
<v-btn flat icon color="primary" disabled>
<v-icon></v-icon>{{user}}
</v-btn>
<v-btn #click="logout">Logout</v-btn>
</v-toolbar-items>
</v-toolbar>
</div>
</template>
<script>
export default {
name: "nav",
props:['user'],
data(){
return {
appTitle: 'My template',
sidebar: false,
menuItems: [
{ title: 'Home', path: '/home', icon: 'home' },
{ title: 'Profile', path: '/profile', icon: 'face' },
]
}
},
created(){
console.log(this.user)
},
methods: {
logout(e) {
console.log('ss')
e.preventDefault()
this.axios.get('/sanctum/csrf-cookie').then(response => {
this.axios.post('/api/logout')
.then(response => {
if (response.data.success) {
window.location.href = "/"
} else {
console.log(response)
}
})
.catch(function (error) {
console.error(error);
});
})
},
},
};
</script>
<style lang="">
</style>
First of all define your user in data() with default values so you should not recieve undefined error Moreover there is no async/await when you are calling api in the mounted state
App.vue
<template>
<div>
<v-app>
<template v-if="isLoggedIn">
<AdminMenu></AdminMenu>
</template>
<template v-else-if="!isUserLoggedIn">
<Nav></Nav>
</template>
<template v-if="isUserLoggedIn">
<Navbar :user='user.id'></Navbar>
</template>
<v-main app>
<router-view></router-view>
</v-main>
</v-app>
</div>
</template>
<script>
import 'vuetify/dist/vuetify.min.css' // Ensure you are using css-loader
import axios from 'axios';
import AdminMenu from './layouts/AdminMenu.vue'
import Navbar from './layouts/user/Navbar.vue'
import Nav from './layouts/user/Nav.vue'
export default {
name:'app',
components:{ 'AdminMenu': AdminMenu, 'Navbar':Navbar, 'Nav':Nav},
data(){
return{
user:[],
isLoggedIn: false,
isUserLoggedIn: false,
}
},
created() {
if (window.Laravel.isLoggedin) {
this.isLoggedIn = true
}
if (window.Laravel.isUserLoggedin) {
this.isUserLoggedIn = true
}
},
async mounted(){
await this.axios.get('/api/user').then(response=>{
this.user = response.data
}).catch(error=>{
console.log(error)
})
},
}
</script>

Vue - Vue routes doesn´t exist

I have a project in Laravel + Vue. In Laravel i have some routes for create endpoints and start page.
Laravel Routes
Route::get('/', 'Auth\LoginController#showLoginForm');
Route::post('/login', 'Auth\LoginController#login');
Auth::routes();
Route::resource('gateways', 'GatewayController');
Route::resource('contadores', 'ContadorController');
'/' route go to Blade file with Login Component.
Login Component has this code.
<template>
<v-content slot="content">
<v-container class="fill-height" fluid>
<v-row align="center" justify="center">
<v-col cols="12" md="8">
<v-card class="elevation-12">
<v-toolbar dark flat>
<v-toolbar-title>LoRaWAN</v-toolbar-title>
</v-toolbar>
<v-card-text>
<v-form>
<v-text-field
label="Usuario"
name="username"
prepend-icon="mdi-account"
type="text"
v-model="username"
/>
<v-text-field
label="Contraseña"
name="password"
prepend-icon="mdi-key"
:append-icon="value ? 'mdi-eye' : 'mdi-eye-off'"
#click:append="() => (value = !value)"
:type="value ? 'password' : 'text'"
v-model="password"
/>
</v-form>
</v-card-text>
<v-card-actions>
<v-btn block dark #click="submit()">Entrar</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</v-content>
</template>
<script>
export default {
data() {
return {
value: String,
username: "",
password: ""
};
},
methods: {
submit() {
axios
.post("http://127.0.0.1:8000/login", {
username: this.username,
password: this.password
})
.then(response => {
if (response.data.token != null) {
localStorage.setItem("token", response.data.token);
console.log("ok");
this.$router.push({
name: "lora",
params: { user: this.username }
});
}
})
.catch(function(errors) {
let error = errors.response.data.errors;
let mensaje = "Error no identificado";
if (error.hasOwnProperty("username")) {
mensaje = error.username[0];
} else {
mensaje = error.password[0];
}
Swal.fire({
title: "Error",
text: mensaje,
icon: "error",
confirmButtonText: "Ok"
});
});
}
}
};
</script>
As we can see when login endpoint return token we want to push to other 'lora' route.
Vue routes file
import ContadorComponent from "./components/contador/ContadorComponent.vue";
import GatewayComponent from "./components/gateway/GatewayComponent.vue";
import HomeComponent from "./components/home/HomeComponent.vue";
import MainComponent from "./components/main/MainComponent.vue";
const routes = [{
path: "/lora",
name: "lora",
component: MainComponent,
props: true,
children: [{
path: "",
name: "home",
component: HomeComponent
},
{
path: "contadores",
name: "contadores",
component: ContadorComponent
},
{
path: "gateways",
name: "gateways",
component: GatewayComponent
}
]
}];
const router = new VueRouter({
mode: 'history',
routes: routes
});
new Vue({
vuetify: new Vuetify(),
router
}).$mount("#app");
And lora route (Main Component)
<template>
<v-app id="app">
<layoutDrawer></layoutDrawer>
<layoutHeader></layoutHeader>
<v-content>
<router-view></router-view>
</v-content>
<layoutFooter></layoutFooter>
</v-app>
</template>
<script>
import layoutHeader from "./partials/HeaderComponent.vue";
import layoutFooter from "./partials/FooterComponent.vue";
import layoutDrawer from "./partials/SidebarComponent.vue";
export default {
props: {
username: { type: String, default: "Invitado" }
},
components: {
layoutHeader,
layoutDrawer,
layoutFooter
}
};
</script>
The problem: If i go to http://127.0.0.1:8000/lora returns that this route doesn´t exist. In the vue routes file i declare it, so i don´t know why returns this. Maybe Laravel generate a conflict or something with routes. In laravel routes file i test this code and works
Route::get('/test', function () {
return view('home');
})->name('home');
The view home is blade file with Main Component. Maybe something happens with the vue routes that project doesn't recognize and only works Laravel routes..
The question: Are the vue routes properly declares? Anybody see some error?
Your client and server are running on the same port: http://127.0.0.1:8000.
The url for your lora route should be something like http://127.0.0.1:8001/lora
I found a partially solution. In Laravel routes i need to put this
Route::get('{any?}', function () {
return view('layout');
})->where('any', '.*');
Every time the user push to another page load Layout blade.
#extends('layouts.app')
#section('content')
<layout-component></layout-component>
#endsection
Layout Component
<template>
<v-app id="app">
<router-view></router-view>
</v-app>
</template>

Vuetify v-combobox validations not working

I have a Vuetify(v. 1.9.0) v-combobox with validation that at least one item should be selected from the menu. For some reason the validation is not triggered and there are no errors in the console.
<v-combobox
multiple
item-text="name"
item-value="id"
:items="items"
:rules="[rules.required]"
></v-combobox>
<script>
export default {
data: () => ({
rules: {
required: value =>
!!value || "Required."
}
})
}
</script>
What am I missing?
Try this example:
<template>
<v-app>
<v-content>
<v-form ref="form">
<div>
<v-combobox
multiple
item-text="name"
item-value="id"
:items="items"
:rules="[required]"
v-model="selected"
/>
selected = {{ selected }}
</div>
<div>
<v-btn #click="$refs.form.validate()">Validate</v-btn>
</div>
</v-form>
</v-content>
</v-app>
</template>
<script>
export default {
name: 'app',
data: () => ({
selected: null,
items: [
{ id: 1, name: 'Option 1' },
{ id: 2, name: 'Option 2' },
{ id: 3, name: 'Option 3' },
],
}),
methods: {
required(value) {
if (value instanceof Array && value.length == 0) {
return 'Required.';
}
return !!value || 'Required.';
},
},
};
</script>

Problem with expandable child component in parent v-data-table using Vuetify 2

I upgraded to Vuetify 2 from 1.5. Everything went pretty smoothly except for one thing. I have a parent component with a v-data-table and I want to pass data and expand each row with a child component.
ScanGrid(parent component):
<template>
<v-container>
<v-card>
<v-card-text>
<v-layout row align-center>
<v-data-table
:headers="headers"
:items="items"
:hide-default-footer="true"
item-key="id"
>
<template slot="items" slot-scope="props">
<tr #click="props.expanded = !props.expanded">
<td>{{ props.item.name }}</td>
<td class="text-xs-left large-column">
{{ props.item.scanned }}
</td>
<td class="text-xs-left large-column">
{{ props.item.incoming }}
</td>
<td class="text-xs-left large-column">
{{ props.item.outgoing }}
</td>
<td class="text-xs-left large-column">
{{ props.item.unknown }}
</td>
</tr>
</template>
<template slot="expand" slot-scope="props">
<ScanGridChild :value="props.item"></ScanGridChild>
</template>
</v-data-table>
</v-layout>
</v-card-text>
</v-card>
</v-container>
</template>
ScanGridChild(child component):
<template>
<v-card>
<v-card-text>{{ value }}</v-card-text>
</v-card>
</template>
<script>
export default {
name: "ScanGridChildComponent",
props: {
value: {
Type: Object,
Required: true
}
},
computed: {},
watch: {
props: function(newVal, oldVal) {
console.log("Prop changed: ", newVal, " | was: ", oldVal);
this.render();
}
}
};
</script>
<style></style>
It worked fine in Vuetify 1.5.19. I'm on Vuetify 2.1.6 and using single file components. Thanks.
Vuetify 2.x has major changes to many components, slot-scopes are replaced with v-slot, and many new properties and slots added to vuetify data table
Here is the working codepen reproduced the same feature with above code
https://codepen.io/chansv/pen/BaaWbKR?editors=1010
You need to make sure that you have vue js 2.x and vuetify 2.x
Parent component code:
<template>
<v-container>
<v-card>
<v-card-text>
<v-layout row align-center>
<v-data-table
:headers="headers"
:items="items"
item-key="name"
single-expand
:expanded="expanded"
hide-default-footer
#click:row="clickedRow"
>
<template v-slot:expanded-item="{ item }">
<td :colspan="headers.length">
<ScanGridChild :value="item"></ScanGridChild>
</td>
</template>
</v-data-table>
</v-layout>
</v-card-text>
</v-card>
</v-container>
</template>
<script>
export default {
...
data () {
return {
expanded: [],
headers: [
{
text: "Localisation",
sortable: true,
value: "name"
},
{
text: "Paquets scannés",
sortable: true,
value: "scanned"
},
{
text: "Paquets entrants",
sortable: true,
value: "incoming"
},
{
text: "Paquets sortants",
sortable: true,
value: "outgoing"
},
{
text: "Paquets inconnus",
sortable: true,
value: "unknown"
}
],
items: [
{
id: 1,
name: "Location 1",
scanned: 159,
incoming: 6,
outgoing: 24,
unknown: 4,
test: "Test 1"
},
{
id: 2,
name: "Location 2",
scanned: 45,
incoming: 6,
outgoing: 24,
unknown: 4,
test: "Test 2"
}
],
}
},
methods: {
clickedRow(value) {
if (this.expanded.length && this.expanded[0].id == value.id) {
this.expanded = [];
} else {
this.expanded = [];
this.expanded.push(value);
}
}
}
...
}
</script>
In child component
Replace
props: {
value: {
Type: Object,
Required: true
}
},
with( Type and Required change to lower case type and required)
props: {
value: {
type: Object,
required: true
}
},

Vue js applications code throws error (Js Expected)

I am trying to build vue js applications but I am getting following errors .
Severity Code Description Project File Line Suppression State
Warning TS1005 (JS) ':' expected. VuejsApp JavaScript Content Files C:\Users\Khundokar Nirjor\Documents\Visual Studio 2017\Projects\VuejsApp\VuejsApp\src\App.vue 19 Active
This is Home.vue code
<template>
<div id="databinding">
<div id="counter-event-example">
<p style="font-size:25px;">Language displayed : <b>{{ languageclicked }}</b></p>
<button-counter v-for="(item, index) in languages"
v-bind:item="item"
v-bind:index="index"
v-on:showlanguage="languagedisp"></button-counter>
</div>
</div>
</template>
<script>
// import Home from './components/Home.vue';
// import component1 from './components/component1.vue';
export default {
name: 'app',
Vue.components('button-counter', {
template: '<button v-on:click = "displayLanguage(item)"><span style = "font-size:25px;">{{ item }}</span></button>',
data: function () {
return {
counter: 0
}
},
props: ['item'],
methods: {
displayLanguage: function (lng) {
console.log(lng);
this.$emit('showlanguage', lng);
}
},
});
new Vue({
el: '#databinding',
data: {
languageclicked: "",
languages: ["Java", "PHP", "C++", "C", "Javascript", "C#", "Python", "HTML"]
},
methods: {
languagedisp: function (a) {
this.languageclicked = a;
}
}
})
};
</script>
<style>
</style>
I want to display list of buttons and when i clicked the any of them button , I want to display the message that button is clicked.
I believe your error is related to the component. First, the function name is wrong. The correct name is Vue.component and it is Vue.components. Second, your component declaration is not correct.
I created this codepen where you can see how to declare the component globally and locally.
const buttonCounter = {
template:
`<button #click="displayLanguage(item)">
<span style="font-size:25px;">{{ item }}</span>
</button>`,
props: ["item"],
methods: {
displayLanguage: function(lng) {
this.$emit("show-language", lng);
}
}
}
// Declare your component globally, will be able to access it in any another component in the application
Vue.component("button-counter", buttonCounter );
new Vue({
el: "#databinding",
// declarete your component locally, it only will be available inside this context
components:{
'button-counter-2' : buttonCounter
},
data: function() {
return {
languageclicked: "",
languages: ["Java", "PHP", "C++", "C", "Javascript", "C#", "Python", "HTML"]
};
},
methods: {
languageDisp: function(a) {
this.languageclicked = a;
}
}
});
body {
margin: 20px;
}
.btn-wrapper {
display: flex;
margin-bottom: 20px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="databinding">
<div id="counter-event-example">
<p style="font-size:25px;">Language displayed : <b>{{ languageclicked }}</b></p>
<div class="btn-wrapper">
<button-counter v-for="(item, index) in languages" :item="item" :key="item" #show-language="languageDisp"/>
</div>
<button-counter-2 v-for="(item, index) in languages" :item="item" :key="item" #show-language="languageDisp"/>
</div>
</div>

Resources