npx volt-vue add DataTable
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import ColumnGroup from 'primevue/columngroup'; // optional
import Row from 'primevue/row'; // optional
DataTable requires a value as data to display and Column components as children for the representation.
<template>
<div class="card">
<DataTable :value="products" pt:table="min-w-200">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
Columns can be created programmatically.
<template>
<div class="card">
<DataTable :value="products" pt:table="min-w-200">
<Column v-for="col of columns" :key="col.field" :field="col.field" :header="col.header"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
const columns = ref([
{ field: 'code', header: 'Code' },
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
]);
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
Custom content at header and footer sections are supported via templating.
<template>
<div class="card">
<DataTable :value="products" pt:table="min-w-200">
<template #header>
<div class="flex flex-wrap items-center justify-between gap-2">
<span class="text-xl font-bold">Products</span>
<Button icon="pi pi-refresh" rounded raised />
</div>
</template>
<Column field="name" header="Name"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.data.image}`" :alt="slotProps.data.image" class="w-24 rounded" />
</template>
</Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category"></Column>
<Column field="rating" header="Reviews">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" readonly />
</template>
</Column>
<Column header="Status">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getSeverity(slotProps.data)" />
</template>
</Column>
<template #footer> In total there are {{ products ? products.length : 0 }} products. </template>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import Button from '@/volt/Button.vue';
import DataTable from '@/volt/DataTable.vue';
import Rating from '@/volt/Rating.vue';
import Tag from '@/volt/Tag.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
const getSeverity = (product) => {
switch (product.inventoryStatus) {
case 'INSTOCK':
return 'success';
case 'LOWSTOCK':
return 'warn';
case 'OUTOFSTOCK':
return 'danger';
default:
return null;
}
};
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
Pagination is enabled by adding paginator property and defining rows per page. Refer to the Paginator for more information about customizing the paginator.
<template>
<div class="card">
<DataTable :value="customers" paginator :rows="5" pt:table="min-w-200">
<Column field="name" header="Name" style="width: 25%"></Column>
<Column field="country.name" header="Country" style="width: 25%">
<template #body="{ data }">
<div class="flex items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="`flag flag-${data.country.code}`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
</Column>
<Column field="company" header="Company" style="width: 25%"></Column>
<Column field="representative.name" header="Representative" style="width: 25%">
<template #body="{ data }">
<div class="flex items-center gap-2">
<img :alt="data.representative.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${data.representative.image}`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
</Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { CustomerService } from '@/service/CustomerService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const customers = ref(null);
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => (customers.value = data));
});
</script>
Sorting on a column is enabled by adding the sortable property.
<template>
<div class="card">
<DataTable :value="products" pt:table="min-w-200">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
Multiple columns can be sorted by defining sortMode as multiple. This mode requires metaKey (e.g. ⌘) to be pressed when clicking a header.
<template>
<div class="card">
<DataTable :value="products" sortMode="multiple" pt:table="min-w-200">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
Defining a default sortField and sortOrder displays data as sorted initially in single column sorting. In multiple sort mode, multiSortMeta should be used instead by providing an array of DataTableSortMeta objects.
<template>
<div class="card">
<DataTable :value="products" sortField="price" :sortOrder="-1" pt:table="min-w-200">
<Column field="code" header="Code" sortable style="width: 20%"></Column>
<Column field="name" header="Name" sortable style="width: 20%"></Column>
<Column field="price" header="Price" :sortable="true">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category" sortable style="width: 20%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 20%"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
const formatCurrency = (value) => {
return value.toLocaleString('en-US', {style: 'currency', currency: 'USD'});
}
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
When removableSort is present, the third click removes the sorting from the column.
<template>
<div class="card">
<DataTable :value="products" removableSort pt:table="min-w-200">
<Column field="code" header="Code" sortable style="width: 25%"></Column>
<Column field="name" header="Name" sortable style="width: 25%"></Column>
<Column field="category" header="Category" sortable style="width: 25%"></Column>
<Column field="quantity" header="Quantity" sortable style="width: 25%"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
The global filtering searches the data against a single value that is bound to the global key of the filters object. The fields to search against are defined with the globalFilterFields.
<template>
<div class="card">
<DataTable v-model:filters="filters" :value="customers" paginator :rows="10" dataKey="id" :globalFilterFields="['name', 'country.name', 'representative.name', 'status']">
<template #header>
<div class="flex justify-end">
<div class="relative">
<i class="pi pi-search absolute top-1/2 -mt-2 text-surface-400 leading-none end-3 z-1" />
<InputText v-model="filters['global'].value" placeholder="Search" />
</div>
</div>
</template>
<template #empty>
<div class="p-4">No customers found.</div>
</template>
<Column field="name" header="Name" style="min-width: 12rem">
<template #body="{ data }">
{{ data.name }}
</template>
</Column>
<Column header="Country" filterField="country.name" style="min-width: 12rem">
<template #body="{ data }">
<div class="flex items-center gap-2">
<img alt="flag" src="https://primefaces.org/cdn/primevue/images/flag/flag_placeholder.png" :class="`flag flag-${data.country.code}`" style="width: 24px" />
<span>{{ data.country.name }}</span>
</div>
</template>
</Column>
<Column header="Agent" filterField="representative" :showFilterMenu="false" style="min-width: 14rem">
<template #body="{ data }">
<div class="flex items-center gap-2">
<img :alt="data.representative.name" :src="`https://primefaces.org/cdn/primevue/images/avatar/${data.representative.image}`" style="width: 32px" />
<span>{{ data.representative.name }}</span>
</div>
</template>
</Column>
<Column field="status" header="Status" :showFilterMenu="false" style="min-width: 12rem">
<template #body="{ data }">
<Tag :value="data.status" :severity="getSeverity(data.status)" />
</template>
</Column>
<Column field="verified" header="Verified" dataType="boolean" style="min-width: 6rem">
<template #body="{ data }">
<i class="pi" :class="{ 'pi-check-circle text-green-500': data.verified, 'pi-times-circle text-red-400': !data.verified }"></i>
</template>
</Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { CustomerService } from '@/service/CustomerService';
import DataTable from '@/volt/DataTable.vue';
import InputText from '@/volt/InputText.vue';
import Tag from '@/volt/Tag.vue';
import { FilterMatchMode } from '@primevue/core/api';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const customers = ref(null);
const filters = ref({
global: { value: null, matchMode: FilterMatchMode.CONTAINS }
});
const getSeverity = (status) => {
switch (status) {
case 'unqualified':
return 'danger';
case 'qualified':
return 'success';
case 'new':
return 'info';
case 'negotiation':
return 'warn';
case 'renewal':
return null;
}
};
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => (customers.value = data));
});
</script>
Single row selection is enabled by defining selectionMode as single along with a value binding using selection property. When available, it is suggested to provide a unique identifier of a row with dataKey to optimize performance.
By default, metaKey press (e.g. ⌘) is necessary to unselect a row however this can be configured with disabling the metaKeySelection property. In touch enabled devices this option has no effect and behavior is same as setting it to false.
<template>
<div class="card">
<div class="flex justify-center items-center mb-6 gap-2">
<ToggleSwitch v-model="metaKey" inputId="input-metakey" />
<label for="input-metakey">MetaKey</label>
</div>
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" :metaKeySelection="metaKey" dataKey="id" pt:table="min-w-200">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import ToggleSwitch from '@/volt/ToggleSwitch.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
const selectedProduct = ref();
const metaKey = ref(true);
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
More than one row is selectable by setting selectionMode to multiple. By default in multiple selection mode, metaKey press (e.g. ⌘) is not necessary to add to existing selections. When the optional metaKeySelection is present, behavior is changed in a way that selecting a new row requires meta key to be present. Note that in touch enabled devices, DataTable always ignores metaKey.
<template>
<div class="card">
<div class="flex justify-center items-center mb-6 gap-2">
<ToggleSwitch v-model="metaKey" inputId="input-metakey" />
<label for="input-metakey">MetaKey</label>
</div>
<DataTable v-model:selection="selectedProducts" :value="products" selectionMode="multiple" :metaKeySelection="metaKey" dataKey="id" pt:table="min-w-200">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import ToggleSwitch from '@/volt/ToggleSwitch.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
const selectedProducts = ref(null);
const metaKey = ref(true);
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
Specifying selectionMode as single on a Column, displays a radio button inside that column for selection. By default, row clicks also trigger selection, set selectionMode of DataTable to radiobutton to only trigger selection using the radio buttons.
<template>
<div class="card">
<DataTable v-model:selection="selectedProduct" :value="products" dataKey="id" tableStyle="min-width: 50rem">
<Column selectionMode="single" headerStyle="width: 3rem"></Column>
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
const selectedProduct = ref(null);
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
Specifying selectionMode as multiple on a Column, displays a checkbox inside that column for selection.
The header checkbox toggles the selection state of the whole dataset by default, when paginator is enabled you may add selectAll property and select-all-change event to only control the selection of visible rows.
<template>
<div class="card">
<DataTable v-model:selection="selectedProducts" :value="products" dataKey="id" pt:table="min-w-200">
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
const selectedProducts = ref(null);
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
Row selection with an element inside a column is implemented with templating.
<template>
<div class="card">
<DataTable :value="products" pt:table="min-w-200">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
<Column class="w-24">
<template #body="{ data }">
<Button icon="pi pi-search" @click="selectRow(data)" severity="secondary" rounded></Button>
</template>
</Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import Button from '@/volt/Button.vue';
import Column from 'primevue/column';
import { useToast } from 'primevue/usetoast';
import { ref, onMounted } from 'vue';
const products = ref(null);
const toast = useToast();
const selectRow = (data) => {
toast.add({ severity: 'info', summary: data.name, detail: data.inventoryStatus + ' | $' + data.price, life: 3000 });
};
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
DataTable provides row-select and row-unselect events to listen selection events.
<template>
<div class="card">
<DataTable v-model:selection="selectedProduct" :value="products" selectionMode="single" dataKey="id" :metaKeySelection="false" @rowSelect="onRowSelect" @rowUnselect="onRowUnselect" pt:table="min-w-200">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { useToast } from 'primevue/usetoast';
import { ref, onMounted } from 'vue';
const products = ref(null);
const selectedProduct = ref(null);
const toast = useToast();
const onRowSelect = (event) => {
toast.add({ severity: 'info', summary: 'Product Selected', detail: 'Name: ' + event.data.name, life: 3000 });
};
const onRowUnselect = (event) => {
toast.add({ severity: 'warn', summary: 'Product Unselected', detail: 'Name: ' + event.data.name, life: 3000 });
};
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
Row expansion is controlled with expandedRows property. The column that has the expander element requires expander property to be enabled. Optional rowExpand and rowCollapse events are available as callbacks.
Expanded rows can either be an array of row data or when dataKey is present, an object whose keys are strings referring to the identifier of the row data and values are booleans to represent the expansion state e.g. {'1004': true}. The dataKey alternative is more performant for large amounts of data.
<template>
<div class="card">
<DataTable v-model:expandedRows="expandedRows" :value="products" dataKey="id" @rowExpand="onRowExpand" @rowCollapse="onRowCollapse" tableStyle="min-width: 60rem">
<template #header>
<div class="flex flex-wrap justify-end gap-2">
<Button text icon="pi pi-plus" label="Expand All" @click="expandAll" />
<Button text icon="pi pi-minus" label="Collapse All" @click="collapseAll" />
</div>
</template>
<Column expander style="width: 5rem" />
<Column field="name" header="Name"></Column>
<Column header="Image">
<template #body="slotProps">
<img :src="`https://primefaces.org/cdn/primevue/images/product/${slotProps.data.image}`" :alt="slotProps.data.image" class="shadow-lg" width="64" />
</template>
</Column>
<Column field="price" header="Price">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.price) }}
</template>
</Column>
<Column field="category" header="Category"></Column>
<Column field="rating" header="Reviews">
<template #body="slotProps">
<Rating :modelValue="slotProps.data.rating" readonly />
</template>
</Column>
<Column header="Status">
<template #body="slotProps">
<Tag :value="slotProps.data.inventoryStatus" :severity="getSeverity(slotProps.data)" />
</template>
</Column>
<template #expansion="slotProps">
<div class="p-4">
<h5>Orders for {{ slotProps.data.name }}</h5>
<DataTable :value="slotProps.data.orders">
<Column field="id" header="Id" sortable></Column>
<Column field="customer" header="Customer" sortable></Column>
<Column field="date" header="Date" sortable></Column>
<Column field="amount" header="Amount" sortable>
<template #body="slotProps">
{{ formatCurrency(slotProps.data.amount) }}
</template>
</Column>
<Column field="status" header="Status" sortable>
<template #body="slotProps">
<Tag :value="slotProps.data.status.toLowerCase()" :severity="getOrderSeverity(slotProps.data)" />
</template>
</Column>
<Column headerStyle="width:4rem">
<template #body>
<Button icon="pi pi-search" />
</template>
</Column>
</DataTable>
</div>
</template>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import Button from '@/volt/Button.vue';
import DataTable from '@/volt/DataTable.vue';
import Rating from '@/volt/Rating.vue';
import Tag from '@/volt/Tag.vue';
import Column from 'primevue/column';
import { useToast } from 'primevue/usetoast';
import { ref, onMounted } from 'vue';
const products = ref(null);
const expandedRows = ref({});
const toast = useToast();
const onRowExpand = (event) => {
toast.add({ severity: 'info', summary: 'Product Expanded', detail: event.data.name, life: 3000 });
};
const onRowCollapse = (event) => {
toast.add({ severity: 'success', summary: 'Product Collapsed', detail: event.data.name, life: 3000 });
};
const expandAll = () => {
expandedRows.value = products.value.reduce((acc, p) => (acc[p.id] = true) && acc, {});
};
const collapseAll = () => {
expandedRows.value = null;
};
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
const getSeverity = (product) => {
switch (product.inventoryStatus) {
case 'INSTOCK':
return 'success';
case 'LOWSTOCK':
return 'warn';
case 'OUTOFSTOCK':
return 'danger';
default:
return null;
}
};
const getOrderSeverity = (order) => {
switch (order.status) {
case 'DELIVERED':
return 'success';
case 'CANCELLED':
return 'danger';
case 'PENDING':
return 'warn';
case 'RETURNED':
return 'info';
default:
return null;
}
};
onMounted(() => {
ProductService.getProductsWithOrdersSmall().then((data) => (products.value = data));
});
</script>
Adding scrollable property along with a scrollHeight for the data viewport enables vertical scrolling with fixed headers.
<template>
<div class="card">
<DataTable :value="customers" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="company" header="Company"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { CustomerService } from '@/service/CustomerService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref } from 'vue';
const customers = ref(null);
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => (customers.value = data));
});
</script>
Flex scroll feature makes the scrollable viewport section dynamic instead of a fixed value so that it can grow or shrink relative to the parent size of the table. Click the button below to display a maximizable Dialog where data viewport adjusts itself according to the size changes.
<template>
<div class="card flex justify-center">
<Button label="Show" icon="pi pi-external-link" @click="dialogVisible = true" />
<Dialog v-model:visible="dialogVisible" header="Flex Scroll" class="md:w-2/4 w-9/10" maximizable modal pt:content:class="h-80" @show="onShow">
<DataTable :value="customers" scrollable scrollHeight="flex" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="company" header="Company"></Column>
</DataTable>
<template #footer>
<Button label="Ok" icon="pi pi-check" @click="dialogVisible = false" />
</template>
</Dialog>
</div>
</template>
<script setup lang="ts">
import { CustomerService } from '@/service/CustomerService';
import Button from '@/volt/Button.vue';
import DataTable from '@/volt/DataTable.vue';
import Dialog from '@/volt/Dialog.vue';
import Column from 'primevue/column';
import { ref } from 'vue';
const customers = ref(null);
const dialogVisible = ref(false);
const onShow = () => {
CustomerService.getCustomersMedium().then((data) => (customers.value = data));
};
</script>
Horizontal scrollbar is displayed when table width exceeds the parent width.
<template>
<div class="card">
<DataTable :value="customers" scrollable scrollHeight="400px">
<Column field="id" header="Id" footer="Id" style="min-width: 100px"></Column>
<Column field="name" header="Name" footer="Name" style="min-width: 200px"></Column>
<Column field="country.name" header="Country" footer="Country" style="min-width: 200px"></Column>
<Column field="date" header="Date" footer="Date" style="min-width: 200px"></Column>
<Column field="balance" header="Balance" footer="Balance" style="min-width: 200px">
<template #body="{ data }">
{{ formatCurrency(data.balance) }}
</template>
</Column>
<Column field="company" header="Company" footer="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" footer="Status" style="min-width: 200px"></Column>
<Column field="activity" header="Activity" footer="Activity" style="min-width: 200px"></Column>
<Column field="representative.name" header="Representative" footer="Representative" style="min-width: 200px"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { CustomerService } from '@/service/CustomerService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref } from 'vue';
const customers = ref(null);
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => (customers.value = data));
});
</script>
Rows can be fixed during scrolling by enabling the frozenValue property.
<template>
<div class="card">
<DataTable :value="customers" :frozenValue="lockedCustomers" scrollable scrollHeight="400px" tableStyle="min-width: 50rem">
<Column field="name" header="Name"></Column>
<Column field="country.name" header="Country"></Column>
<Column field="representative.name" header="Representative"></Column>
<Column field="status" header="Status"></Column>
<Column style="flex: 0 0 4rem">
<template #body="{ data, frozenRow, index }">
<button type="button" :disabled="frozenRow ? null : lockedCustomers.length >= 2" @click="toggleLock(data, frozenRow, index)" class="disabled:opacity-50 enabled:cursor-pointer">
<i :class="frozenRow ? 'pi pi-lock-open' : 'pi pi-lock'" />
</button>
</template>
</Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { CustomerService } from '@/service/CustomerService';
import Button from '@/volt/Button.vue';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref } from 'vue';
const customers = ref(null);
const lockedCustomers = ref([
{
id: 5135,
name: 'Geraldine Bisset',
country: {
name: 'France',
code: 'fr'
},
company: 'Bisset Group',
status: 'proposal',
date: '2019-05-05',
activity: 0,
representative: {
name: 'Amy Elsner',
image: 'amyelsner.png'
}
}
]);
const toggleLock = (data, frozen, index) => {
if (frozen) {
lockedCustomers.value = lockedCustomers.value.filter((c, i) => i !== index);
customers.value.push(data);
} else {
customers.value = customers.value.filter((c, i) => i !== index);
lockedCustomers.value.push(data);
}
customers.value.sort((val1, val2) => {
return val1.id < val2.id ? -1 : 1;
});
};
onMounted(() => {
CustomerService.getCustomersMedium().then((data) => (customers.value = data));
});
</script>
A column can be fixed during horizontal scrolling by enabling the frozen property. The location is defined with the alignFrozen that can be left or right.
<template>
<div class="card">
<ToggleButton v-model="balanceFrozen" onIcon="pi pi-lock" offIcon="pi pi-lock-open" onLabel="Balance" offLabel="Balance" />
<DataTable :value="customers" scrollable scrollHeight="400px" class="mt-6">
<Column field="name" header="Name" style="min-width: 200px" frozen class="font-bold"></Column>
<Column field="id" header="Id" style="min-width: 100px"></Column>
<Column field="name" header="Name" style="min-width: 200px"></Column>
<Column field="country.name" header="Country" style="min-width: 200px"></Column>
<Column field="date" header="Date" style="min-width: 200px"></Column>
<Column field="company" header="Company" style="min-width: 200px"></Column>
<Column field="status" header="Status" style="min-width: 200px"></Column>
<Column field="activity" header="Activity" style="min-width: 200px"></Column>
<Column field="representative.name" header="Representative" style="min-width: 200px"></Column>
<Column field="balance" header="Balance" style="min-width: 200px" alignFrozen="right" :frozen="balanceFrozen">
<template #body="{ data }">
<span class="font-bold">{{ formatCurrency(data.balance) }}</span>
</template>
</Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { CustomerService } from '@/service/CustomerService';
import DataTable from '@/volt/DataTable.vue';
import ToggleButton from '@/volt/ToggleButton.vue';
import Column from 'primevue/column';
import { ref } from 'vue';
const customers = ref(null);
const balanceFrozen = ref(false);
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
onMounted(() => {
CustomerService.getCustomersLarge().then((data) => (customers.value = data));
});
</script>
Adding scrollable property along with a scrollHeight for the data viewport enables vertical scrolling with fixed headers.
Virtual Scrolling is an efficient way to render large amount data. Usage is similar to regular scrolling with the addition of virtualScrollerOptions property to define a fixed itemSize. Internally, VirtualScroller component is utilized so refer to the API of VirtualScroller for more information about the available options.
In this example, 100000 preloaded records are rendered by the Table.
<template>
<div class="card">
<DataTable :value="cars" scrollable scrollHeight="400px" :virtualScrollerOptions="{ itemSize: 44 }" tableStyle="min-width: 50rem">
<Column field="id" header="Id" style="width: 20%; height: 44px"></Column>
<Column field="vin" header="Vin" style="width: 20%; height: 44px"></Column>
<Column field="year" header="Year" style="width: 20%; height: 44px"></Column>
<Column field="brand" header="Brand" style="width: 20%; height: 44px"></Column>
<Column field="color" header="Color" style="width: 20%; height: 44px"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { CarService } from '@/service/CarService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref } from 'vue';
const cars = ref(null);
onMounted(() => {
cars.value = Array.from({ length: 100000 }).map((_, i) => CarService.generateCar(i + 1));
});
</script>
When lazy loading is enabled via the virtualScrollerOptions, data is fetched on demand during scrolling instead of preload.
In sample below, an in-memory list and timeout is used to mimic fetching from a remote datasource. The virtualCars is an empty array that is populated on scroll.
<template>
<div class="card">
<DataTable
:value="virtualCars"
scrollable
scrollHeight="400px"
:virtualScrollerOptions="{ lazy: true, onLazyLoad: loadCarsLazy, itemSize: 44, delay: 200, showLoader: true, loading: lazyLoading, numToleratedItems: 10 }"
tableStyle="min-width: 50rem"
>
<Column field="id" header="Id" style="width: 20%; height: 44px">
<template #loading>
<div class="flex items-center h-[17px] grow overflow-hidden">
<Skeleton width="60%" height="1rem" />
</div>
</template>
</Column>
<Column field="vin" header="Vin" style="width: 20%; height: 44px">
<template #loading>
<div class="flex items-center h-[17px] grow overflow-hidden">
<Skeleton width="40%" height="1rem" />
</div>
</template>
</Column>
<Column field="year" header="Year" style="width: 20%; height: 44px">
<template #loading>
<div class="flex items-center h-[17px] grow overflow-hidden">
<Skeleton width="30%" height="1rem" />
</div>
</template>
</Column>
<Column field="brand" header="Brand" style="width: 20%; height: 44px">
<template #loading>
<div class="flex items-center h-[17px] grow overflow-hidden">
<Skeleton width="40%" height="1rem" />
</div>
</template>
</Column>
<Column field="color" header="Color" style="width: 20%; height: 44px">
<template #loading>
<div class="flex items-center h-[17px] grow overflow-hidden">
<Skeleton width="60%" height="1rem" />
</div>
</template>
</Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { CarService } from '@/service/CarService';
import DataTable from '@/volt/DataTable.vue';
import Skeleton from '@/volt/Skeleton.vue';
import Column from 'primevue/column';
import { ref } from 'vue';
const cars = ref(null);
const virtualCars = ref(Array.from({ length: 100000 }));
const lazyLoading = ref(false);
const loadLazyTimeout = ref(null);
const loadCarsLazy = (event) => {
!lazyLoading.value && (lazyLoading.value = true);
if (loadLazyTimeout.value) {
clearTimeout(loadLazyTimeout.value);
}
//simulate remote connection with a timeout
loadLazyTimeout.value = setTimeout(
() => {
let _virtualCars = [...virtualCars.value];
let { first, last } = event;
//load data of required page
const loadedCars = cars.value.slice(first, last);
//populate page of virtual cars
Array.prototype.splice.apply(_virtualCars, [...[first, last - first], ...loadedCars]);
virtualCars.value = _virtualCars;
lazyLoading.value = false;
},
Math.random() * 1000 + 250
);
};
onMounted(() => {
cars.value = Array.from({ length: 100000 }).map((_, i) => CarService.generateCar(i + 1));
});
</script>
Column visibility based on a condition can be implemented with dynamic columns, in this sample a MultiSelect is used to manage the visible columns.
<template>
<div class="card">
<DataTable :value="products" pt:table="min-w-200">
<template #header>
<div style="text-align: left">
<MultiSelect :modelValue="selectedColumns" :options="columns" optionLabel="header" @update:modelValue="onToggle" display="chip" placeholder="Select Columns" />
</div>
</template>
<Column field="code" header="Code" />
<Column v-for="(col, index) of selectedColumns" :key="col.field + '_' + index" :field="col.field" :header="col.header"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import MultiSelect from '@/volt/MultiSelect.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const columns = ref([
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
]);
const selectedColumns = ref(columns.value);
const products = ref(null);
const onToggle = (val) => {
selectedColumns.value = columns.value.filter((col) => val.includes(col));
};
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data))
});
</script>
Columns can be grouped within a Row component and groups can be displayed within a ColumnGroup component. These groups can be displayed using type property that can be header or footer. Number of cells and rows to span are defined with the colspan and rowspan properties of a Column.
<template>
<div class="card">
<DataTable :value="sales" pt:table="min-w-200">
<ColumnGroup type="header">
<Row>
<Column header="Product" :rowspan="3" />
<Column header="Sale Rate" :colspan="4" />
</Row>
<Row>
<Column header="Sales" :colspan="2" />
<Column header="Profits" :colspan="2" />
</Row>
<Row>
<Column header="Last Year" sortable field="lastYearSale" />
<Column header="This Year" sortable field="thisYearSale" />
<Column header="Last Year" sortable field="lastYearProfit" />
<Column header="This Year" sortable field="thisYearProfit" />
</Row>
</ColumnGroup>
<Column field="product" />
<Column field="lastYearSale">
<template #body="slotProps"> {{ slotProps.data.lastYearSale }}% </template>
</Column>
<Column field="thisYearSale">
<template #body="slotProps"> {{ slotProps.data.thisYearSale }}% </template>
</Column>
<Column field="lastYearProfit">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.lastYearProfit) }}
</template>
</Column>
<Column field="thisYearProfit">
<template #body="slotProps">
{{ formatCurrency(slotProps.data.thisYearProfit) }}
</template>
</Column>
<ColumnGroup type="footer">
<Row>
<Column footer="Totals:" :colspan="3" footerStyle="text-align:right" />
<Column :footer="lastYearTotal" />
<Column :footer="thisYearTotal" />
</Row>
</ColumnGroup>
</DataTable>
</div>
</template>
<script setup lang="ts">
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import ColumnGroup from 'primevue/columngroup';
import Row from 'primevue/row';
import { ref } from 'vue';
const sales = ref([
{ product: 'Bamboo Watch', lastYearSale: 51, thisYearSale: 40, lastYearProfit: 54406, thisYearProfit: 43342 },
{ product: 'Black Watch', lastYearSale: 83, thisYearSale: 9, lastYearProfit: 423132, thisYearProfit: 312122 },
{ product: 'Blue Band', lastYearSale: 38, thisYearSale: 5, lastYearProfit: 12321, thisYearProfit: 8500 },
{ product: 'Blue T-Shirt', lastYearSale: 49, thisYearSale: 22, lastYearProfit: 745232, thisYearProfit: 65323 },
{ product: 'Brown Purse', lastYearSale: 17, thisYearSale: 79, lastYearProfit: 643242, thisYearProfit: 500332 },
{ product: 'Chakra Bracelet', lastYearSale: 52, thisYearSale: 65, lastYearProfit: 421132, thisYearProfit: 150005 },
{ product: 'Galaxy Earrings', lastYearSale: 82, thisYearSale: 12, lastYearProfit: 131211, thisYearProfit: 100214 },
{ product: 'Game Controller', lastYearSale: 44, thisYearSale: 45, lastYearProfit: 66442, thisYearProfit: 53322 },
{ product: 'Gaming Set', lastYearSale: 90, thisYearSale: 56, lastYearProfit: 765442, thisYearProfit: 296232 },
{ product: 'Gold Phone Case', lastYearSale: 75, thisYearSale: 54, lastYearProfit: 21212, thisYearProfit: 12533 }
]);
const formatCurrency = (value) => {
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
};
const lastYearTotal = computed(() => {
let total = 0;
for (let sale of sales.value) {
total += sale.lastYearProfit;
}
return formatCurrency(total);
});
const thisYearTotal = computed(() => {
let total = 0;
for (let sale of sales.value) {
total += sale.thisYearProfit;
}
return formatCurrency(total);
});
</script>
Particular rows and cells can be styled based on conditions. The rowClass receives a row data as a parameter to return a style class for a row whereas cells are customized using the body template.
<template>
<div class="card">
<DataTable :value="products" :rowClass="rowClass" :rowStyle="rowStyle" pt:table="min-w-200">
<Column field="code" header="Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
const rowClass = (data) => {
return [{ '!bg-primary !text-primary-contrast': data.category === 'Fitness' }];
};
const rowStyle = (data) => {
if (data.quantity === 0) {
return { fontWeight: 'bold', fontStyle: 'italic' };
}
};
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
DataTable can export its data to CSV format.
<template>
<div class="card">
<DataTable ref="dt" :value="products" pt:table="min-w-200">
<template #header>
<div class="text-end pb-4">
<Button icon="pi pi-external-link" label="Export" @click="exportCSV()" />
</div>
</template>
<Column field="code" header="Code" exportHeader="Product Code"></Column>
<Column field="name" header="Name"></Column>
<Column field="category" header="Category"></Column>
<Column field="quantity" header="Quantity"></Column>
</DataTable>
</div>
</template>
<script setup lang="ts">
import { ProductService } from '@/service/ProductService';
import Button from '@/volt/Button.vue';
import DataTable from '@/volt/DataTable.vue';
import Column from 'primevue/column';
import { ref, onMounted } from 'vue';
const products = ref(null);
const dt = ref();
const exportCSV = () => {
dt.value.exportCSV();
};
onMounted(() => {
ProductService.getProductsMini().then((data) => (products.value = data));
});
</script>
Key distinctions to notice between the Volt DataTable and the PrimeVue DataTable.