Vuetify - How can I use the Data-Table search feature to filter by a dynamically calculated field value? - vue.js

Trying to use Vuetify's Data-Table to search/filter based on the computed value of a field. As feel free to jump into the codepen and type in some values for yourself. For example, I have it setup in such a way that the email address field can be searched by; however, the name of the employee and phone record fields are not functional.
Codepen
https://codepen.io/Jasilo/pen/PooLjbE
VUE:
<div id="app">
<v-app id="inspire">
<v-card>
<header>How can I search by the computed Name and Phone fields?</header>
<v-card-title>
<header>Employee List</header>
<v-spacer></v-spacer>
<v-text-field v-model="search" append-icon="search" label="Search" single-line hide-details></v-text-field>
</v-card-title>
<v-data-table v-bind:headers="headers" v-bind:items="employeesArray" v-bind:search="search">
<template v-slot:item.email="{ item }">
<div>
{{ item["email"] }} </div>
</template>
<template v-slot:item.name="{ item }">
<div>
{{ getName(item) }}
</div>
</template>
<template v-slot:item.phone="{ item }">
<div> {{ getPhone(item) }}</div>
</template>
<template v-slot:no-data>
<div icon="warning">
{{ gridEmpty }}
</div>
</template>
</v-data-table>
</v-card>
</v-app>
</div>
HTML:
<div id="app">
<v-app id="inspire">
<v-card>
<header>How can I search by the computed Name and Phone fields?</header>
<v-card-title>
<header>Employee List</header>
<v-spacer></v-spacer>
<v-text-field v-model="search" append-icon="search" label="Search" single-line hide-details></v-text-field>
</v-card-title>
<v-data-table v-bind:headers="headers" v-bind:items="employeesArray" v-bind:search="search">
<template v-slot:item.email="{ item }">
<div>
{{ item["email"] }} </div>
</template>
<template v-slot:item.name="{ item }">
<div>
{{ getName(item) }}
</div>
</template>
<template v-slot:item.phone="{ item }">
<div> {{ getPhone(item) }}</div>
</template>
<template v-slot:no-data>
<div icon="warning">
{{ gridEmpty }}
</div>
</template>
</v-data-table>
</v-card>
</v-app>
</div>

Simplest way is to use a computed property:
computed: {
employeeTableData() {
return this.employeesArray.map(e => {
return {
email: e.email,
name: this.getName(e),
phone: this.getPhone(e),
};
});
},
},
Then change v-data-table to use employeeTableData instead and directly reference the attributes.
Working codepen
You can then search XD or 666- and it will correctly filter on name and phone number.

Related

Vuetify access v-slot:item in datatable custom component

I've made a custom datatable component, where my Table.vue file is shown below:
<template>
<div>
<v-data-table
:headers="headers"
:items="items"
:search="search"
:loading="loading"
loading-text="Loading... Please wait"
dense
>
<template v-slot:top>
<v-toolbar dark flat dense>
<v-toolbar-title>{{ title }}</v-toolbar-title>
<v-spacer></v-spacer>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
></v-text-field>
<v-spacer></v-spacer>
</v-toolbar>
</template>
</v-data-table>
</div>
</template>
<script>
export default {
name: "Table",
props: [
"headers",
"items",
"title",
"itemsPerPage",
"loading",
],
data: function () {
return {
search: '',
}
},
methods: {
},
};
</script>
And I'm using it like that:
<Table
:headers="headers"
:items="groups"
:loading="loading"
title="Baseny"
>
</Table>
Everything is fine, but I want to add custom columns with actions for every input (every input has different ID)
Normally (without a custom component) I'd use the following code:
<v-data-table
:headers="headers"
:items="times"
:items-per-page="5"
:search="search"
:loading="loading"
loading-text="Ładowanie... Proszę czekać"
>
<template v-slot:top>
<v-toolbar dark flat dense>
<v-toolbar-title>Lista zajęć</v-toolbar-title>
<v-spacer></v-spacer>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Szukaj"
single-line
hide-details
></v-text-field>
<v-spacer></v-spacer>
<v-btn
color="primary"
:to="{ name: 'admin.times.create' }"
>
Dodaj zajęcie
</v-btn>
</v-toolbar>
</template>
<template v-slot:item.actions="{ item }">
<v-icon
small
class="mr-2"
#click="show(item)"
>
mdi-pool
</v-icon>
<v-icon
small
class="mr-2"
#click="edit(item)"
>
mdi-pencil
</v-icon>
</template>
</v-data-table>
I'm using this v-slot:
<template v-slot:item.actions="{ item }">
<v-icon
small
class="mr-2"
#click="show(item)"
>
mdi-pool
</v-icon>
<v-icon
small
class="mr-2"
#click="edit(item)"
>
mdi-pencil
</v-icon>
</template>
However, when I wrote the custom reusable table components it didn't work when I put these lines into tag.
How can I use my custom components properly in this scenario?
What you want to achieve is I believe a wrapper component. You want to wrap a component on top of another one to let him have some custom properties that you want to reuse in your application.
What you need is a small snippet that will allow your slots to be used:
<!-- pass through scoped slots -->
<template v-for="(_, scopedSlotName) in $scopedSlots" v-slot:[scopedSlotName]="slotData">
<slot :name="scopedSlotName" v-bind="slotData" />
</template>
<!-- pass through normal slots -->
<template v-for="(_, slotName) in $slots" v-slot:[slotName]>
<slot :name="slotName" />
</template>
You can find the source of this here
Basically, here how you can rewrite your CustomTable.vue:
<template>
<div>
<v-data-table
v-bind="$attrs"
v-on="$listeners"
:search="search"
loading-text="Loading... Please wait"
dense
>
<!-- pass through scoped slots -->
<template
v-for="(_, scopedSlotName) in $scopedSlots"
v-slot:[scopedSlotName]="slotData"
>
<slot :name="scopedSlotName" v-bind="slotData" />
</template>
<!-- pass through normal slots -->
<template v-for="(_, slotName) in $slots" v-slot:[slotName]>
<slot :name="slotName" />
</template>
<template v-slot:top>
<v-toolbar dark flat dense>
<v-toolbar-title>{{ title }}</v-toolbar-title>
<v-spacer></v-spacer>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
label="Search"
single-line
hide-details
></v-text-field>
<v-spacer></v-spacer>
</v-toolbar>
</template>
</v-data-table>
</div>
</template>
<script>
export default {
name: "CustomTable",
props: ["title"],
data: function () {
return {
search: "",
};
},
methods: {},
};
</script>
I made a codesandbox to show you how it works:
https://codesandbox.io/s/vuetify-2-forked-3lp9y?file=/src/components/CustomTable.vue
I also added automatic attribute and listeners bindings on your table, to allow you to use all the features that Vuetify provides.

Get value from v-item-group

I have the following v-item-group where a user can select a pre-defined amount or type himself a custom amount.
Here's the design of the v-item-group. However, I can't figure out how to get the value from Custom Amount. The property this.item is undefined. I thought I need to create another v-model but perhaps there is a simple and straignt-forward solution.
<v-item-group v-model="item">
<v-container>
<v-row>
<v-item v-for="n in amounts" :key="n" v-slot="{ active, toggle }">
<v-card
:color="active ? 'deep-purple accent-3' : 'grey'"
class="d-flex align-center rounded-circle mr-3"
dark
height="64"
width="64"
#click="toggle"
>
<v-scroll-y-transition>
<div v-if="active" class="h3 flex-grow-1 text-center">
{{ n }}
</div>
<div v-else class=" h5 flex-grow-1 text-center">
{{ n }}
</div>
</v-scroll-y-transition>
</v-card>
</v-item>
<v-item v-slot="{ toggle }" key="5">
<v-text-field
label="Custom Amount"
hide-details="auto"
#click="toggle"
></v-text-field>
</v-item>
</v-row>
</v-container>
</v-item-group>
Declare a local data property, and use v-model on v-text-field:
<v-text-field v-model="customAmount">
export default {
data() {
return {
customAmount: '2000'
}
}
}
The function that processes the user's selection could then get the custom value simply by this.customAmount.
demo

Is there a Vue or Vuetify component that does not render ANY output?

I have a custom navMenu component that I show twice on my page - once across the top, and once hidden in a v-navigation-drawer until the screen width gets small enough to show it:
<template>
<nav>
<v-app-bar app hide-on-scroll>
<template #extension v-if="$vuetify.breakpoint.smAndUp">
<v-container>
<v-row>
<v-spacer />
<navMenu :items="menuItems" />
<v-spacer />
</v-row>
</v-container>
</template>
<v-app-bar-nav-icon
#click="toggleDrawer()"
v-if="$vuetify.breakpoint.xs"
/>
<img id="logo"
alt="corporate logo"
src="#/assets/full_logo.svg"
width="200"
height="60"
/>
<v-spacer />
<h3 class="info--text headline">My Fancy Website</h3>
</v-app-bar>
<v-navigation-drawer app
v-model="drawer"
v-if="$vuetify.breakpoint.smAndDown">
<navMenu :items="menuItems" />
</v-navigation-drawer>
</nav>
</template>
NavMenu.vue
<template>
<v-col v-for="(item, index) in items" :key="index">
<div v-if="item.children">
<v-menu transition="slide-y-transition" bottom>
<template #activator="{ on }">
<v-btn text v-on="on">{{ item.label }}</v-btn>
</template>
<v-list>
<v-list-item
v-for="(child, j) in item.children"
:key="j"
router
:exact="child.exact"
:to="{ name: child.routeName }"
>
<v-list-item-title class="text-capitalize">
{{ child.label }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
<div v-else>
<v-btn text router :to="{ name: item.routeName }" :exact="item.exact">
{{ item.label }}
</v-btn>
</div>
</v-col>
</template>
...you may have noticed a fatal flaw in my component: you can't iterate on a root element! Simple enough, wrap it in a <div />, right? Wrong. Wrapping the contents of the template in a div really screws up the layout of the menu items - it renders them stacked vertically instead of horizontally - I think the CSS is looking for a direct child or something.
Is there some alternative element that I can use for the template to satisfy the "one root element" edict that doesn't render any output? Oh, and I tried using the <template /> element already - you can't use it as a root element.
You were so close to finding the answer...
Actually, you should put in a div container, but then just change the display property of the container so the items are still positioned horizontally:
NavMenu.vue
<template>
<div style="display: flex">
<v-col v-for="(item, index) in items" :key="index">
...
I updated your code below. Essentially, all you need to do is to move the entire <v-navigation-drawer> component into your NavMenu.vue file and add the additional drawer prop. Since the <v-navigation-drawer component doesn't actually do much on its own. The #input event listener is only so that you can have the parent component update the drawer value outside of the child component.
<template>
<nav>
<v-app-bar app hide-on-scroll>
<template #extension v-if="$vuetify.breakpoint.smAndUp">
<v-container>
<v-row>
<v-spacer />
<navMenu :items="menuItems" />
<v-spacer />
</v-row>
</v-container>
</template>
<v-app-bar-nav-icon
#click="toggleDrawer()"
v-if="$vuetify.breakpoint.xs"
/>
<img id="logo"
alt="corporate logo"
src="#/assets/full_logo.svg"
width="200"
height="60"
/>
<v-spacer />
<h3 class="info--text headline">My Fancy Website</h3>
</v-app-bar>
<navMenu
v-if="$vuetify.breakpoint.smAndDown"
:drawer="drawer"
:items="menuItems"
#navInput="toggleDrawer()"
/>
</nav>
</template>
NavMenu.vue
<template>
<v-navigation-drawer app :value="drawer" #input="$emit('navInput')>
<v-col v-for="(item, index) in items" :key="index">
<div v-if="item.children">
<v-menu transition="slide-y-transition" bottom>
<template #activator="{ on }">
<v-btn text v-on="on">{{ item.label }}</v-btn>
</template>
<v-list>
<v-list-item
v-for="(child, j) in item.children"
:key="j"
router
:exact="child.exact"
:to="{ name: child.routeName }"
>
<v-list-item-title class="text-capitalize">
{{ child.label }}
</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</div>
<div v-else>
<v-btn text router :to="{ name: item.routeName }" :exact="item.exact">
{{ item.label }}
</v-btn>
</div>
</v-col>
</v-navigation-drawer>
</template>
<script>
export default {
props: {
drawer: Boolean,
items: {
type: Array,
default: () => []
}
}
};
</script>

How can i pass background color as Prop in for loop?

its me again!
so i have a own component:
<template>
<div class='mynewcomponent'>
<v-layout>
<v-flex xs12 sm6 offset-sm3>
<v-card v-bind:style="{ backgroundColor: this.myColor}">
<!-- Picture
<v-img
src="https://cdn.vuetifyjs.com/images/cards/sunshine.jpg"
height="200px"
>
</v-img>
-->
<v-card-title primary-title>
<div>
<slot name="header">Top western road trips</slot>
<br>
<slot name="TestDesciption">1,000 miles of wonder</slot>
</div>
</v-card-title>
<v-card-actions>
<v-btn flat>Share</v-btn>
<v-btn flat color="purple">Explore</v-btn>
<v-spacer></v-spacer>
<v-btn icon #click="show = !show">
<v-icon>{{ show ? 'keyboard_arrow_down' : 'keyboard_arrow_up' }}</v-icon>
</v-btn>
</v-card-actions>
<v-slide-y-transition>
<v-card-text v-show="show">
I'm a thing. But, like most politicians, he promised more than he could deliver. You won't have time for sleeping, soldier, not with all the bed making you'll be doing. Then we'll go with that data file! Hey, you add a one and two zeros to that or we walk! You're going to do his laundry? I've got to find a way to escape.
</v-card-text>
</v-slide-y-transition>
</v-card>
</v-flex>
</v-layout>
</div>
</template>
<script>
export default {
data: () => ({
show: false,
myColor:'#ffffff'
})
}
</script>
and in my about.vue i load it in a for loop:
<template>
<div class='about'>
<mynewcomponent v-for="(item,index) in 100"/>>
<template v-slot:header>
<h3 style="text-align: left;"><span style="color: #3366ff">ID: 1234</span></h3>
</template>
<template v-slot:TestDesciption>
<h3 style="text-align: left">example shit</h3>
</template>
</mynewcomponent>
</div>
</template>
<script>
import myNewComponent from '#/components/myNewComponent.vue'; // # is an alias to /src
export default {
name: 'about',
components: {
'mynewcomponent': myNewComponent
}
}
</script>
now i want the even and odd Cards in other background color.
i tried everything what google says but whitout success.
i will pass the color if index % 2 == 0 (even or odd)
how can i pass the color in the for loop ?
or can someone tell me a better way to do this?
Thank you
You can create a method to bind the class attribute and pass the index as a parameter. For each line, you can evaluate and return a different class to this element.
You can check am example here
methods:{
spanClass: function(index) {
return {
in: index % 2 === 0,
out: index % 2 !== 0
}
}
li.in {
background-color:red;
}
li.out {
background-color:black;
}
<template>
<div class='about'>
<mynewcomponent v-for="(item,index) in 100"/>>
<template v-slot:header>
<h3 style="text-align: left;"><span :class="spanClass(item)">ID: 1234</span></h3>
</template>
<template v-slot:TestDesciption>
<h3 style="text-align: left">example shit</h3>
</template>
</mynewcomponent>
</div>
</template>

Vue.js, vuetify.js Accordion do not open

I have a custom component blog and with an accordion (from vuetify.js) I show the posts in the blog (at the end ul>li) using a second custom component blog-post. I tried it without nesting and it worked fine. With the nesting of custom component I can expand and close only the first post.
Here the code. The template of blog.vue:
<template>
<v-content class="blog">
<v-container fluid>
<v-row>
<v-col xs3="xs3"></v-col>
<v-col xs6="xs6">
<h5>Blog</h5>
<div class="loading" v-if="loading">
Loading...
</div>
<div v-if="error" class="error">
{{ error }}
</div>
<div v-if="posts" class="content">
<v-collapsible>
<template v-for="post in posts" >
<blog-post :post="post"></blog-post>
</template>
</v-collapsible>
</div>
</v-col>
</v-row>
</v-container>
</v-content>
</template>
And the template of blog-post.vue:
<template>
<li class="blog-post">
<v-collapsible-header>{{ post.title }}</v-collapsible-header>
<v-collapsible-body>
<v-card>
<v-card-text class="grey lighten-3">{{ post.body }}</v-card-text>
</v-card>
</v-collapsible-body>
</li>
</template>
Heders and bodies of the accordeon are filled correctly.
instead of <template v-for try <li v-for, like following inside blog.vue:
<div v-if="posts" class="content">
<v-collapsible>
<li v-for="post in posts" >
<blog-post :post="post"></blog-post>
</li>
</v-collapsible>
</div>
and in template of blog-post.vue:
<template class="blog-post">
<v-collapsible-header>{{ post.title }}</v-collapsible-header>
<v-collapsible-body>
<v-card>
<v-card-text class="grey lighten-3">{{ post.body }}</v-card-text>
</v-card>
</v-collapsible-body>
</template>