Action button not working using vuetify and vuex - button

Here, I have setup laravel 6 project with vue and vuetify. I am trying to create crud table where I am trying to fetch data from vuex store but for some I am not able to see my action button. I can see my table with data but action coulmn is empty. I have created store folder and inside my store folder I have Store.js file.
Stage.vue
<template>
<div>
<h1 class="text-xs-center info--text mb-2">{{ message }}</h1>
<v-data-table
:headers="headers"
:items="items"
>
<template slot="items" slot-scope="props">
<td>{{ props.item.code }}</td>
<td class="text-xs-right">{{ props.item.name }}</td>
<td class="text-xs-right">{{ props.item.description }}</td>
<td class="justify-center layout px-0">
<v-btn icon class="mx-0" #click="editItem(props.item)">
<v-icon color="teal">edit</v-icon>
</v-btn>
<v-btn icon class="mx-0" #click="deleteItem(props.item)">
<v-icon color="pink">delete</v-icon>
</v-btn>
</td>
</template>
<template slot="no-data">
<v-alert :value="true" color="error" icon="warning">
Sorry, nothing to display here :(
</v-alert>
</template>
</v-data-table>
<v-dialog v-model="dialog" max-width="500px">
<template v-slot:activator="{ on }">
<v-btn color="error" dark class="mb-2" v-on="on">Add New Stage</v-btn>
</template>
<v-card>
<v-card-title>
<span>{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12 sm6 md4>
<v-text-field label="Code" v-model="editedItem.code"></v-text-field>
</v-flex>
<v-flex xs12 sm6 md4>
<v-text-field label="Name" v-model="editedItem.name"></v-text-field>
</v-flex>
<v-flex xs12 sm6 md4>
<v-text-field label="Description" v-model="editedItem.description"></v-text-field>
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text #click.native="close">Cancel</v-btn>
<v-btn color="blue darken-1" text #click.native="save">Save</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
<script>
export default {
name: 'Stage',
props: {
},
data: () => ({
dialog: false,
editedIndex: -1,
editedItem: {
code: '',
name: '',
description: ''
},
defaultItem: {
code: '',
name: '',
description: ''
}
}),
computed: {
message () {
return this.$store.getters.getMessage
},
headers () {
return this.$store.getters.getHeaders
},
items () {
return this.$store.getters.getItems
},
formTitle () {
return this.editedIndex === -1 ? 'New Stage' : 'Edit Stage'
}
},
watch: {
dialog (val) {
val || this.close()
}
},
methods: {
editItem (item) {
this.editedIndex = this.items.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialog = true
},
deleteItem (item) {
const index = this.items.indexOf(item)
confirm('Are you sure you want to delete this item?') && this.$store.commit('deleteItem', index)
// Todo: Make this delete item from store
},
close () {
this.dialog = false
setTimeout(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
}, 300)
},
save () {
if (this.editedIndex > -1) {
Object.assign(this.items[this.editedIndex], this.editedItem)
// TODO: Edit item in the store.
this.$store.commit('updateItem', this.editedItem, this.editedIndex)
} else {
this.$store.commit('newItem', this.editedItem)
}
this.close()
}
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
</style>
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
msg: 'Vuetify table of Vuex state items.',
headers: [
{
text: 'Code',
align: 'left',
sortable: true,
value: 'code'
},
{ text: 'Name', value: 'name' },
{ text: 'Description', value: 'description' },
{ text: 'Actions', value: 'description', sortable: false }
],
items: [
{
value: 'false',
code: 23,
name: 'dsvdf',
description: 'Le Manns'
},
{
value: 'false',
code: 1,
name: 'ddd',
description: 'Le Manns'
}
]
},
mutations: {
newItem (state, payload) {
state.items.push(payload)
},
deleteItem (state, payload) {
state.items.splice(payload, 1)
},
updateItem (state, payload, index) {
state.items[index] = payload
}
},
actions: {
},
getters: {
getMessage (state) {
return state.msg
},
getHeaders (state) {
return state.headers
},
getItems (state) {
return state.items
}
}
})

In the state.headers property of your store, you have:
{ text: 'Actions', value: 'name', sortable: false }
This should be:
{ text: 'Actions', value: 'action', sortable: false }
The value property has the wrong value. Also, you have not assigned the imported headers and items to the v-data-table element. It should be:
<v-data-table
:headers="headers"
:items="items"
>
<!-- ... the rest of your code ... -->
Here is a working codepen.

Related

Vue.3 does not render Vuetify components when using at tag attribute in transition-group

I want to animate some cards using gsap for the following components in Vue.js 3.
<script setup lang="ts">
import gsap from 'gsap'
import { useTranslate } from '#/#core/composable/useTranslate'
import TimeLineItem from './TimeLineItem.vue'
interface ITimeLine {
icon: string
title: string
description: string
color: string
}
const timeLines = ref<ITimeLine[]>([
{
title: 'PadvishInstall',
description: 'timeline-welcome',
icon: 'material-symbols:looks-one-outline',
color: 'warning',
},
{
title: 'InsertingToken',
description: 'timeline-step1',
icon: 'ic:outline-looks-two',
color: 'error',
},
{
title: 'ContactInfo',
description: 'timeline-step2',
icon: 'ph:number-square-three-bold',
color: 'info',
},
])
const { translate } = useTranslate()
/**
* Functions
*/
const beforeEnter = (el: Element) => {
const he = el as HTMLElement
he.style.opacity = '0'
he.style.transform = 'translateX(100px)'
}
const enter = (el: Element, done: () => void) => {
const he = el as HTMLElement
gsap.to(el, {
opacity: 1,
x: 0,
duration: 0.8,
onComplete: done,
delay: Number((el as HTMLElement).dataset.index) * 0.5,
})
}
</script>
<template>
<VCard class="text-center" variant="text" title="card title">
<VCardText>
<transition-group
align="start"
justify="center"
truncate-line="both"
:density="$vuetify.display.smAndDown ? 'compact' : 'default'"
appears
#before-enter="beforeEnter"
#enter="enter"
tag="v-timeline"
>
<TimeLineItem
v-for="(item, index) in timeLines"
:key="index"
:data-index="index"
:title="translate(item.title)"
:description="translate(item.description)"
:icon="item.icon"
:color="item.color"
/>
</transition-group>
</VCardText>
</VCard>
</template>
TimeLineItem component :
<script setup lang="ts">
interface Props {
icon: string
title: string
description: string
color: string
}
const props = defineProps<Props>()
</script>
<template>
<VTimelineItem size="x-small" fill-dot>
<template #icon>
<div class="v-timeline-avatar-wrapper rounded-circle">
<VAvatar size="small">
<VIcon size="100" :icon="icon" :color="color" />
</VAvatar>
</div>
</template>
<VCard>
<VCardText>
<!-- 👉 Header -->
<div class="d-flex justify-space-between">
<h6 class="text-base font-weight-semibold mb-1 me-3">
{{ title }}
</h6>
</div>
<!-- 👉 Content -->
<p class="mb-1">
{{ description }}
</p>
</VCardText>
</VCard>
</VTimelineItem>
</template>
<style lang="scss" scoped>
.v-timeline-avatar-wrapper {
background-color: rgb(var(--v-theme-background));
}
</style>
For animating each element of v-timeline, I used transition-group and set the value of tag to v-timeline. But, when using transition-group, the Vue does not recognize the 'v-timeline' is a vuetify component and must render a component!.
This is a limitation of transition-group or can be considered as a bug in Vue.3?

v-spacer in datatable grouping header

I have a Vuetify project. I'm using the template with a slot group.header, so far so good.
Now I want to use a <v-spacer> in this template. Its in a <v-sheet>.
When I use the <v-spacer></v-spacer> I expect the content on the same line. But it will be on the next line. How can I display the button after the spacing on the same line, user Vuetify's system?
codepen
https://codepen.io/h3ll/pen/VwWOxdJ?editors=1010
HTML
<v-data-table
:headers="headers"
:items="desserts"
item-key="name"
sort-by="name"
group-by="category"
class="elevation-1"
show-group-by
>
<template v-slot:group.header="{items, isOpen, toggle}">
<th #click="toggle" colspan="12" class="ma-0 pa-0">
<v-sheet>
<v-icon class="mr-3">{{ isOpen ? 'mdi-folder-open' : 'mdi-folder' }}</v-icon>
{{ items[0].category }}
<v-spacer></v-spacer>
<v-btn x-small>I WANT TO BE ON THE RIGHT SIDE</v-btn>
</v-sheet>
</th>
</template>
</v-data-table>
javascript
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
headers: [
{
text: 'Dessert (100g serving)',
align: 'start',
value: 'name',
groupable: false,
},
{ text: 'Category', value: 'category', align: 'right' },
{ text: 'Dairy', value: 'dairy', align: 'right' },
],
desserts: [
{
name: 'Frozen Yogurt',
category: 'Ice cream',
dairy: 'Yes',
},
{
name: 'Ice cream sandwich',
category: 'Ice cream',
dairy: 'Yes',
},
...
],
}
},
})
Update
I can fix it with normal css like
<template v-slot:group.header="{items, isOpen, toggle}">
<th colspan="12">
<v-sheet>
<div #click="toggle" style="display: inline; width: 100vw">
<v-icon class="mr-3">{{ isOpen ? 'mdi-folder-open' : 'mdi-folder' }}</v-icon>
{{ items[0].folder }}
</div>
<div style="display: inline; float: right;">
<v-icon #click="deleteItem(item)">mdi-dots-vertical</v-icon>
</div>
</v-sheet>
</th>
</template>
```
But I wondering why Vuetify v-spacer is not working
v-spacer is basically just a div with the style flex-grow: 1. So to get this to work the parent container of the v-spacer needs to have display: flex.
In your case you can add this to v-sheet:
<v-sheet class="d-flex">
<!-- content -->
</v-sheet>

Not able to apply styles/class in vue js while using it in loop

Here is my code. item.fav is initially false, but when I click on button it becomes true, I want color of icon to be purple when item.fav is true. But this is not working.
<template>
<v-container fluid>
<v-row>
<v-col v-for="(item, index) in items">
<v-btn icon #click='changeFav(item)'>
<v-icon :style='{color:`${fillcolor(item)}`}'>mdi-heart</v-icon>
</v-btn>
</v-col>
</v-row>
</v-container>
</template>
<script>
export default {
....,
methods: {
fillcolor(item){
return item.fav ? 'purple' : 'orange';
},
changeFav(item){
item.fav=true;
},
}
</script>
I have also tried using class
.....
<v-icon class='{fillColor: item.fav}'>mdi-heart</v-icon>
.....
<style>
.fillColor {
color: 'purple';
}
</style>
when using a data variable as condition variable it works, but I cannot use it here as I'm having a loop.
What is that I'm doing wrong here or how can I do it better?
Take a look at Class and Style Bindings
Here is an example
<script setup>
import {ref} from 'vue'
const changeFav = (item) => {
item.fav = !item.fav;
}
const items = ref([
{name: "test1", fav: false},
{name: "test2", fav: false},
{name: "test3", fav: true},
{name: "test4", fav: false},
{name: "test5", fav: false},
{name: "test6", fav: false},
{name: "test7", fav: false},
])
</script>
<template>
<div v-for="(item, index) in items" :key="index" #click="changeFav(item)" >
<span :class="[item.fav ? 'orange' : 'purple']">
{{item.name}}
</span>
</div>
</template>
<style >
.purple {
color: purple;
}
.orange {
color: orange;
}
</style>
The problem with your code is that your fillcolor method needs to run on each click, but instead you are calling the changeFav method which only triggers the item.fav and the fillcolor method never runs.
So, You need to call that function too by changing your code to this:
<v-container fluid>
<v-row>
<v-col v-for="(item, index) in items" :key="index">
<v-btn icon #click="changeFav(item)">
<v-icon :style="{ color: `${fillcolor(item)}` }">mdi-heart</v-icon>
</v-btn>
</v-col>
</v-row>
</v-container>
export default {
....,
methods: {
fillcolor(item) {
return item.fav ? "purple" : "orange";
},
changeFav(item) {
item.fav = true;
return this.fillcolor(item)
},
},
}
================================================================
But I would prefer to do it like this:
<template>
<v-app>
<v-container fluid>
<v-row>
<v-col v-for="(item, index) in items" :key="index">
<v-btn icon #click="item.fav = !item.fav">
<v-icon :color="item.fav ? 'purple': 'red'">mdi-heart</v-icon>
</v-btn>
</v-col>
</v-row>
</v-container>
</v-app>
</template>
Although, it really depends on the way you are handling your data. For example, if you are using vuex then you should consider using mutations. I assumed you have the items and can handle them simply like this:
export default {
data: () => ({
items: [
{ name: "t1", fav: false },
{ name: "t2", fav: false },
{ name: "t3", fav: false },
{ name: "t4", fav: false },
{ name: "t5", fav: false },
{ name: "t6", fav: false },
{ name: "t7", fav: false },
],
}),
};

How to show box shadow only when certain tab is selected?

For this website, I have two login tabs as seen in the image below and for the selected tab it shows an icon next to it, but to make it more visible that this tab is selected I want to add some sorta box-shadow or something, but for now, it shows the box-shadow for both of the tabs and I just can't get my head around how to make it only show for the selected Tab.
This is the code that I have - I tried to add v-if statement, but it won't work as I already have v-for so If there is any way how to make this work I would be really happy if you could help me out here. Thank you in advance!
<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}`"
id="boxShadow" //This is where I added the ID
class="pa-0"
>
{{ tab }}
<v-icon id="IconPosition" v-if="tab === selectedTab"
>mdi-login-variant</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-microsoft-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>
<style>
#boxShadow {
-moz-box-shadow: inset 0 0 10px #000000;
-webkit-box-shadow: inset 0 0 10px #000000;
box-shadow: inset 0 0 10px #000000;
}
</style>
the id attribute should be unique, in your case the rendered tab will have the same id, try out to name the #boxShadow to a class .boxShadow like :
<style>
.boxShadow {
-moz-box-shadow: inset 0 0 10px #000000;
-webkit-box-shadow: inset 0 0 10px #000000;
box-shadow: inset 0 0 10px #000000;
}
</style>
then bind it conditionally :
<v-tab
v-for="(tab, i) in tabs"
:key="i"
:class="{
'primary white--text': tab == selectedTab,
caption: tab != selectedTab,
boxShadow: tab === selectedTab,
}
or just :
:class="{
'primary white--text boxShadow': tab == selectedTab,
caption: tab != selectedTab,
}

How to pass parameter of a function with v-for in Vue?

I am trying to pass a parameter to a function by looping through an the array items with v-for.
<template>
<v-app>
<v-app-bar app>
<v-app-bar-nav-icon #click="drawer = !drawer"></v-app-bar-nav-icon>
<v-spacer></v-spacer>
<h1 ref="y"></h1>
</v-app-bar>
<v-content>
<router-view />
<v-navigation-drawer v-model="drawer" class="x">
<v-list-item
v-for="item in items"
:key="item.unidade"
:to="item.link"
:#click="change(item.method)"
>{{item.unidade}}</v-list-item>
</v-navigation-drawer>
</v-content>
</v-app>
</template>
<script>
export default {
name: "App",
data: () => ({
items: [
{ unidade: "IPE", link: "/ipe", method: "IPE" },
{ unidade: "DCSI", link: "/dcsi", method: "DCSI" },
{ unidade: "RT", link: "/rt", method: "RT" }
],
drawer: false
}),
methods: {
change(val) {
console.log(val);
this.$refs.y.innerText = val;
}
}
};
</script>
<style lang="stylus" scoped>
.x {
position: absolute;
}
</style>
I want the parameter in items arrray to be passed to change(val) method giving each v-list-item a distinct event listener.
Then I want h1 with the ref="y" to change it's text based on the v-list-item I click. But so far I am getting the browser error of "Error in render: "TypeError: Cannot set property 'innerText' of undefined""
Instead of setting the innerText of the <h1> you could instead bind the innerText to a reactive variable. You could create a variable in data that could store the selected method and then bind that to the innerText using {{}} syntax. Doing it this way would be more inline with Vue best practices. Let me show you what I mean.
<template>
<v-app>
<v-app-bar app>
<v-app-bar-nav-icon #click="drawer = !drawer"></v-app-bar-nav-icon>
<v-spacer></v-spacer>
<h1 ref="y">{{ selectedMethod }}</h1>
</v-app-bar>
<v-content>
<router-view />
<v-navigation-drawer v-model="drawer" class="x">
<v-list-item
v-for="item in items"
:key="item.unidade"
:to="item.link"
:#click="change(item.method)"
>{{item.unidade}}</v-list-item>
</v-navigation-drawer>
</v-content>
</v-app>
</template>
<script>
export default {
name: "App",
data: () => ({
items: [
{ unidade: "IPE", link: "/ipe", method: "IPE" },
{ unidade: "DCSI", link: "/dcsi", method: "DCSI" },
{ unidade: "RT", link: "/rt", method: "RT" }
],
selectedMethod: '', // Initially blank
drawer: false
}),
methods: {
change(val) {
this.selectedMethod = val; // Update the value of selectedMethod
}
}
};
</script>
<style lang="stylus" scoped>
.x {
position: absolute;
}
</style>
Hope this helps!

Resources