Vuetify is a popular UI framework for Vue apps.
In this article, we’ll look at how to work with the Vuetify framework.
Data Iterator — Expanding Items
We can expand and collapse items with the v-data-iterator
component.
For example, we can write:
<template>
<v-container fluid>
<v-data-iterator
:items="items"
item-key="name"
:items-per-page="4"
:single-expand="expand"
hide-default-footer
>
<template v-slot:default="{ items, isExpanded, expand }">
<v-row>
<v-col v-for="item in items" :key="item.name" cols="12" sm="6" md="4" lg="3">
<v-card>
<v-card-title>
<h4>{{ item.name }}</h4>
</v-card-title>
<v-switch
:input-value="isExpanded(item)"
:label="isExpanded(item) ? 'Expanded' : 'Closed'"
class="pl-4 mt-0"
@change="(v) => expand(item, v)"
></v-switch>
<v-divider></v-divider>
<v-list v-if="isExpanded(item)" dense>
<v-list-item>
<v-list-item-content>Calories:</v-list-item-content>
<v-list-item-content class="align-end">{{ item.calories }}</v-list-item-content>
</v-list-item>
<v-list-item>
<v-list-item-content>Fat:</v-list-item-content>
<v-list-item-content class="align-end">{{ item.fat }}</v-list-item-content>
</v-list-item>
</v-list>
</v-card>
</v-col>
</v-row>
</template>
</v-data-iterator>
</v-container>
</template>
<script>
export default {
name: "HelloWorld",
data: () => ({
itemsPerPage: 4,
items: [
{
name: "Yogurt",
calories: 159,
fat: 6.0,
},
{
name: "Ice cream sandwich",
calories: 237,
fat: 9.0,
},
{
name: "Donuts",
calories: 262,
fat: 16.0,
},
{
name: "Cupcake",
calories: 305,
fat: 3.7,
},
],
}),
};
</script>
We get the isExpanded
function from the default
slot to style our entries differently if it’s expanded or not.
The expand
function from the slot lets us expand or collapse the entry if we click on the v-switch
.
Filter Items
We can order, filter, and paginate items.
For example, we can write:
<template>
<v-container fluid>
<v-data-iterator
:items="items"
:items-per-page.sync="itemsPerPage"
:page="page"
:search="search"
:sort-by="sortBy.toLowerCase()"
:sort-desc="sortDesc"
hide-default-footer
>
<template v-slot:header>
<v-toolbar dark color="blue darken-3" class="mb-1">
<v-text-field
v-model="search"
clearable
flat
solo-inverted
hide-details
prepend-inner-icon="search"
label="Search"
></v-text-field>
<template v-if="$vuetify.breakpoint.mdAndUp">
<v-spacer></v-spacer>
<v-select
v-model="sortBy"
flat
solo-inverted
hide-details
:items="keys"
prepend-inner-icon="search"
label="Sort by"
></v-select>
<v-spacer></v-spacer>
<v-btn-toggle v-model="sortDesc" mandatory>
<v-btn large depressed color="blue" :value="false">
<v-icon>mdi-arrow-up</v-icon>
</v-btn>
<v-btn large depressed color="blue" :value="true">
<v-icon>mdi-arrow-down</v-icon>
</v-btn>
</v-btn-toggle>
</template>
</v-toolbar>
</template>
<template v-slot:default="props">
<v-row>
<v-col v-for="item in props.items" :key="item.name" cols="12" sm="6" md="4" lg="3">
<v-card>
<v-card-title class="subheading font-weight-bold">{{ item.name }}</v-card-title>
<v-divider></v-divider>
<v-list dense>
<v-list-item v-for="(key, index) in filteredKeys" :key="index">
<v-list-item-content :class="{ 'blue--text': sortBy === key }">{{ key }}:</v-list-item-content>
<v-list-item-content
class="align-end"
:class="{ 'blue--text': sortBy === key }"
>{{ item[key.toLowerCase()] }}</v-list-item-content>
</v-list-item>
</v-list>
</v-card>
</v-col>
</v-row>
</template>
<template v-slot:footer>
<v-row class="mt-2" align="center" justify="center">
<span class="grey--text">Items per page</span>
<v-menu offset-y>
<template v-slot:activator="{ on, attrs }">
<v-btn dark text color="primary" class="ml-2" v-bind="attrs" v-on="on">
{{ itemsPerPage }}
<v-icon>mdi-chevron-down</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item
v-for="(number, index) in itemsPerPageArray"
:key="index"
@click="updateItemsPerPage(number)"
>
<v-list-item-title>{{ number }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-spacer></v-spacer>
<span class="mr-4 grey--text">Page {{ page }} of {{ numberOfPages }}</span>
<v-btn fab dark color="blue darken-3" class="mr-1" @click="formerPage">
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-btn fab dark color="blue darken-3" class="ml-1" @click="nextPage">
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</v-row>
</template>
</v-data-iterator>
</v-container>
</template>
<script>
export default {
name: "HelloWorld",
data: () => ({
itemsPerPageArray: [1, 2],
search: "",
filter: {},
sortDesc: false,
page: 1,
itemsPerPage: 1,
sortBy: "name",
keys: ["Name", "Calories", "Fat"],
items: [
{
name: "Yogurt",
calories: 159,
fat: 6.0,
},
{
name: "Ice cream sandwich",
calories: 237,
fat: 9.0,
},
{
name: "Donuts",
calories: 262,
fat: 16.0,
},
{
name: "Cupcake",
calories: 305,
fat: 3.7,
},
],
}),
computed: {
numberOfPages() {
return Math.ceil(this.items.length / this.itemsPerPage);
},
filteredKeys() {
return this.keys.filter((key) => key !== `Name`);
},
},
methods: {
nextPage() {
if (this.page + 1 <= this.numberOfPages) this.page += 1;
},
formerPage() {
if (this.page - 1 >= 1) this.page -= 1;
},
updateItemsPerPage(number) {
this.itemsPerPage = number;
},
},
};
</script>
We have the text field to search for items in the header
slot.
The search
state will be set if we type something into the text field in the header.
The v-btn-toggle
component lets us toggle the sorting between forward and reverse order.
When we click on the buttons, we toggle the sortDesc
state.
Also, we have a dropdown that sets the sortBy
state to let us sort items by the given key automatically.
All the states are used as prop values in the v-data-iterator
component.
Then all the sorting, pagination, and search logic will be taken care of by the v-data-iterator
.
We also have a v-select
to let us change the number of items per page with the updateItemsPerPage
method.
The content is populated in the default
slot.
The props.items
property has the items that we want to render into cards.
Conclusion
We can add sorting, pagination, and searching with the v-data-iterator
component with a few props.