25 KiB
DataTable Component Documentation
Overview
A feature-rich data table component built with PrimeVue's DataTable. This component provides advanced functionality including server-side pagination, sorting, manual filtering with apply buttons, row selection, page data caching, and customizable column types with persistent state management.
Basic Usage
<template>
<DataTable
:columns="tableColumns"
:data="tableData"
:tableActions="tableActions"
table-name="my-table"
@row-click="handleRowClick"
/>
</template>
<script setup>
import { ref } from "vue";
import DataTable from "./components/common/DataTable.vue";
const tableColumns = ref([
{
fieldName: "name",
label: "Name",
sortable: true,
filterable: true,
},
{
fieldName: "status",
label: "Status",
type: "status",
sortable: true,
filterable: true,
},
]);
const tableData = ref([
{ id: 1, name: "John Doe", status: "completed" },
{ id: 2, name: "Jane Smith", status: "in progress" },
]);
const tableActions = ref([
{
label: "Add Item",
action: () => console.log("Add clicked"),
icon: "pi pi-plus",
style: "primary",
// Global action - always available
},
{
label: "View Details",
action: (rowData) => console.log("View:", rowData),
icon: "pi pi-eye",
style: "info",
requiresSelection: true,
// Single selection action - enabled when exactly one row selected
},
{
label: "Edit",
action: (rowData) => console.log("Edit:", rowData),
icon: "pi pi-pencil",
style: "secondary",
rowAction: true,
// Row action - appears in each row's actions column
},
]);
const handleRowClick = (event) => {
console.log("Row clicked:", event.data);
};
</script>
Props
columns (Array) - Required
- Description: Array of column configuration objects that define the table structure
- Type:
Array<Object> - Required:
true
data (Array) - Required
- Description: Array of data objects to display in the table
- Type:
Array<Object> - Required:
true
tableName (String) - Required
- Description: Unique identifier for the table, used for persistent filter state management
- Type:
String - Required:
true
totalRecords (Number)
- Description: Total number of records available on the server (for lazy loading)
- Type:
Number - Default:
0
onLazyLoad (Function)
- Description: Custom pagination event handler for server-side data loading
- Type:
Function - Default:
null
filters (Object)
- Description: Initial filter configuration object (used for non-lazy tables)
- Type:
Object - Default:
{ global: { value: null, matchMode: FilterMatchMode.CONTAINS } }
tableActions (Array)
- Description: Array of action objects that define interactive buttons for the table. Actions can be global (always available), single-selection (enabled when exactly one row is selected), row-specific (displayed per row), or bulk (for multiple selected rows).
- Type:
Array<Object> - Default:
[]
Server-Side Pagination & Lazy Loading
When lazy is set to true, the DataTable operates in server-side mode with the following features:
Automatic Caching
- Page Data Caching: Previously loaded pages are cached to prevent unnecessary API calls
- Cache Duration: 5 minutes default expiration time
- Cache Size: Maximum 50 pages per table with automatic cleanup
- Smart Cache Keys: Based on page, sorting, and filter combinations
Manual Filter Controls
- Apply Button: Filters are applied manually via button click to prevent excessive API calls
- Clear Button: Quick reset of all active filters
- Enter Key Support: Apply filters by pressing Enter in any filter field
- Visual Feedback: Shows active filters and pending changes
Quick Page Navigation
- Page Dropdown: Jump directly to any page number
- Page Info Display: Shows current record range and totals
- Persistent State: Page selection survives component re-mounts
Column Configuration
Each column object in the columns array supports the following properties:
Basic Properties
fieldName(String, required) - The field name in the data objectlabel(String, required) - Display label for the column headersortable(Boolean, default:false) - Enables sorting for this columnfilterable(Boolean, default:false) - Enables row-level filtering for this column
Column Types
type(String) - Defines special rendering behavior for the column
Available Types:
'status' Type
Renders values as colored tags/badges:
{
fieldName: 'status',
label: 'Status',
type: 'status',
sortable: true,
filterable: true
}
Status Colors:
'completed'→ Success (green)'in progress'→ Warning (yellow/orange)'not started'→ Danger (red)- Other values → Info (blue)
'button' Type
Renders values as clickable buttons:
{
fieldName: 'action',
label: 'Action',
type: 'button'
}
Table Actions Configuration
Table actions allow you to add interactive buttons to your DataTable. Actions can be either global (displayed above the table) or row-specific (displayed in an actions column).
Action Object Properties
Each action object in the tableActions array supports the following properties:
Basic Properties
label(String, required) - Display text for the buttonaction(Function, required) - Function to execute when button is clickedicon(String, optional) - PrimeVue icon class (e.g., 'pi pi-plus')style(String, optional) - Button severity: 'primary', 'secondary', 'success', 'info', 'warning', 'danger'size(String, optional) - Button size: 'small', 'normal', 'large'requiresSelection(Boolean, default: false) - When true, action appears above table but is only enabled when exactly one row is selectedrequiresMultipleSelection(Boolean, default: false) - Determines if action is for bulk operations on selected rowsrowAction(Boolean, default: false) - When true, action appears in each row's actions columnlayout(Object, optional) - Layout configuration for action positioning and styling
Layout Configuration
The layout property allows you to control where and how actions are displayed:
For Top-Level Actions (Global and Single Selection)
layout: {
position: "left" | "center" | "right", // Where to position in action bar
variant: "filled" | "outlined" | "text" // Visual style variant
}
For Row Actions
layout: {
priority: "primary" | "secondary" | "dropdown", // Display priority in row
variant: "outlined" | "text" | "compact" | "icon-only" // Visual style
}
For Bulk Actions
layout: {
position: "left" | "center" | "right", // Where to position in bulk action bar
variant: "filled" | "outlined" | "text" // Visual style variant
}
Action Types
Global Actions (default behavior)
Global actions are displayed above the table and are always available:
{
label: "Add New Item",
action: () => {
// Global action - no row data
console.log("Opening create modal");
},
icon: "pi pi-plus",
style: "primary"
// No requiresSelection, requiresMultipleSelection, or rowAction properties
}
Single Selection Actions (requiresSelection: true)
Single selection actions are displayed above the table but are only enabled when exactly one row is selected. They receive the selected row data as a parameter:
{
label: "View Details",
action: (rowData) => {
// Single selection action - receives selected row data
console.log("Viewing:", rowData.name);
router.push(`/items/${rowData.id}`);
},
icon: "pi pi-eye",
style: "info",
requiresSelection: true
}
Row Actions (rowAction: true)
Row actions are displayed in an "Actions" column for each row and receive that row's data as a parameter:
{
label: "Edit",
action: (rowData) => {
// Row action - receives individual row data
console.log("Editing:", rowData.name);
openEditModal(rowData);
},
icon: "pi pi-pencil",
style: "secondary",
rowAction: true
}
Bulk Actions (requiresMultipleSelection: true)
Bulk actions are displayed above the table when rows are selected and receive an array of selected row data:
{
label: "Delete Selected",
action: (selectedRows) => {
// Bulk action - receives array of selected row data
console.log("Deleting:", selectedRows.length, "items");
selectedRows.forEach(row => deleteItem(row.id));
},
icon: "pi pi-trash",
style: "danger",
requiresMultipleSelection: true
}
Example Table Actions Configuration
const tableActions = [
// Global action - shows above table, always available
{
label: "Add Client",
action: () => modalStore.openModal("createClient"),
icon: "pi pi-plus",
style: "primary",
},
// Single selection action - shows above table, enabled when exactly one row selected
{
label: "View Details",
action: (rowData) => router.push(`/clients/${rowData.id}`),
icon: "pi pi-eye",
style: "info",
requiresSelection: true,
},
// Bulk action - shows when rows selected
{
label: "Delete Selected",
action: (selectedRows) => {
if (confirm(`Delete ${selectedRows.length} clients?`)) {
selectedRows.forEach((row) => deleteClient(row.id));
}
},
icon: "pi pi-trash",
style: "danger",
requiresMultipleSelection: true,
},
// Row actions - show in each row's actions column
{
label: "Edit",
action: (rowData) => editClient(rowData),
icon: "pi pi-pencil",
style: "secondary",
rowAction: true,
},
{
label: "Quick View",
action: (rowData) => showQuickPreview(rowData),
icon: "pi pi-search",
style: "info",
rowAction: true,
},
];
Events
rowClick
- Description: Emitted when a button-type column is clicked
- Payload: PrimeVue slot properties object containing row data
- Usage:
@row-click="handleRowClick"
lazy-load
- Description: Emitted when lazy loading is triggered (pagination, sorting, filtering)
- Payload: Event object with page, sorting, and filter information
- Usage:
@lazy-load="handleLazyLoad"
page-change
- Description: Emitted when page changes
- Payload: PrimeVue page event object
sort-change
- Description: Emitted when sorting changes
- Payload: PrimeVue sort event object
filter-change
- Description: Emitted when filters are applied
- Payload: PrimeVue filter event object
const handleRowClick = (slotProps) => {
console.log("Clicked row data:", slotProps.data);
console.log("Row index:", slotProps.index);
};
const handleLazyLoad = async (event) => {
// event contains: page, rows, sortField, sortOrder, filters
console.log("Lazy load event:", event);
// Load data from API based on event parameters
const result = await Api.getData({
page: event.page,
pageSize: event.rows,
sortField: event.sortField,
sortOrder: event.sortOrder,
filters: event.filters,
});
// Update component data
tableData.value = result.data;
totalRecords.value = result.totalRecords;
};
Features
Pagination
- Rows per page options: 5, 10, 20, 50
- Default rows per page: 10
- Built-in pagination controls
Sorting
- Multiple column sorting support
- Removable sort - click to remove sort from a column
- Sort indicators in column headers
Filtering
- Manual filter application with Apply/Clear buttons
- Text-based search for filterable columns
- Persistent filter state across component re-renders and page navigation
- Visual filter feedback showing active filters and pending changes
- Enter key support for quick filter application
Selection
- Multiple row selection with checkboxes
- Meta key selection (Ctrl/Cmd + click for individual selection)
- Unique row identification using
dataKey="id"
Scrolling
- Vertical scrolling with fixed height (70vh)
- Horizontal scrolling for wide tables
- Fixed headers during scroll
State Management
- Persistent filters using Pinia store (
useFiltersStore) - Automatic filter initialization on component mount
- Cross-component filter synchronization
Table Actions
- Global actions displayed above the table for general operations
- Row-specific actions in dedicated actions column with row data access
- Bulk actions for selected rows with multi-selection support
- Customizable button styles with PrimeVue severity levels
- Icon support using PrimeVue icons
- Automatic action handling with error catching
- Disabled state during loading operations
- Dynamic bulk action visibility based on row selection
Usage Examples
Server-Side Paginated Table (Recommended for Large Datasets)
<script setup>
import { ref } from "vue";
import DataTable from "./components/common/DataTable.vue";
import Api from "./api.js";
const columns = [
{ fieldName: "id", label: "ID", sortable: true },
{ fieldName: "name", label: "Name", sortable: true, filterable: true },
{ fieldName: "email", label: "Email", filterable: true },
];
const tableData = ref([]);
const totalRecords = ref(0);
const isLoading = ref(false);
const handleLazyLoad = async (event) => {
try {
isLoading.value = true;
// Convert PrimeVue event to API parameters
const params = {
page: event.page,
pageSize: event.rows,
sortField: event.sortField,
sortOrder: event.sortOrder,
};
// Convert filters
const filters = {};
if (event.filters) {
Object.keys(event.filters).forEach((key) => {
if (event.filters[key]?.value) {
filters[key] = event.filters[key];
}
});
}
// API call with caching support
const result = await Api.getPaginatedData(params, filters);
tableData.value = result.data;
totalRecords.value = result.totalRecords;
} catch (error) {
console.error("Error loading data:", error);
tableData.value = [];
totalRecords.value = 0;
} finally {
isLoading.value = false;
}
};
</script>
<template>
<DataTable
:data="tableData"
:columns="columns"
tableName="myTable"
:lazy="true"
:totalRecords="totalRecords"
:loading="isLoading"
:onLazyLoad="handleLazyLoad"
@lazy-load="handleLazyLoad"
/>
</template>
Interactive Table with Actions
<script setup>
import { useRouter } from "vue-router";
import { useModalStore } from "./stores/modal";
const router = useRouter();
const modalStore = useModalStore();
const columns = [
{ fieldName: "name", label: "Name", sortable: true, filterable: true },
{ fieldName: "status", label: "Status", type: "status", sortable: true },
{ fieldName: "email", label: "Email", filterable: true },
];
const data = [
{ id: 1, name: "John Doe", status: "completed", email: "john@example.com" },
{
id: 2,
name: "Jane Smith",
status: "in progress",
email: "jane@example.com",
},
];
const tableActions = [
// Global action
{
label: "Add User",
action: () => {
modalStore.openModal("createUser");
},
icon: "pi pi-plus",
style: "primary",
},
// Single selection action
{
label: "View Details",
action: (rowData) => {
router.push(`/users/${rowData.id}`);
},
icon: "pi pi-eye",
style: "info",
requiresSelection: true,
},
// Bulk actions
{
label: "Export Selected",
action: (selectedRows) => {
exportUsers(selectedRows);
},
icon: "pi pi-download",
style: "success",
requiresMultipleSelection: true,
},
{
label: "Delete Selected",
action: (selectedRows) => {
if (confirm(`Delete ${selectedRows.length} users?`)) {
bulkDeleteUsers(selectedRows.map((row) => row.id));
}
},
icon: "pi pi-trash",
style: "danger",
requiresMultipleSelection: true,
},
// Row actions
{
label: "Edit",
action: (rowData) => {
modalStore.openModal("editUser", rowData);
},
icon: "pi pi-pencil",
style: "secondary",
rowAction: true,
},
{
label: "Quick Actions",
action: (rowData) => {
showQuickActionsMenu(rowData);
},
icon: "pi pi-ellipsis-v",
style: "info",
rowAction: true,
},
];
const deleteUser = async (userId) => {
// API call to delete user
await Api.deleteUser(userId);
};
const bulkDeleteUsers = async (userIds) => {
// API call to delete multiple users
await Api.bulkDeleteUsers(userIds);
};
const exportUsers = (users) => {
// Export selected users to CSV/Excel
const csv = generateCSV(users);
downloadFile(csv, "users.csv");
};
const refreshData = () => {
// Refresh table data
location.reload();
};
</script>
<template>
<DataTable
:columns="columns"
:data="data"
:tableActions="tableActions"
table-name="users-table"
/>
</template>
Basic Client-Side Table
<script setup>
const columns = [
{ fieldName: "id", label: "ID", sortable: true },
{ fieldName: "name", label: "Name", sortable: true, filterable: true },
{ fieldName: "email", label: "Email", filterable: true },
];
const data = [
{ id: 1, name: "John Doe", email: "john@example.com" },
{ id: 2, name: "Jane Smith", email: "jane@example.com" },
];
</script>
<template>
<DataTable :data="data" :columns="columns" tableName="basicTable" />
</template>
Status Table
<script setup>
const columns = [
{ fieldName: "task", label: "Task", sortable: true, filterable: true },
{
fieldName: "status",
label: "Status",
type: "status",
sortable: true,
filterable: true,
},
{ fieldName: "assignee", label: "Assignee", filterable: true },
];
const data = [
{ id: 1, task: "Setup project", status: "completed", assignee: "John" },
{ id: 2, task: "Write tests", status: "in progress", assignee: "Jane" },
{ id: 3, task: "Deploy app", status: "not started", assignee: "Bob" },
];
</script>
<template>
<DataTable :columns="columns" :data="data" table-name="tasks-table" />
</template>
Interactive Table with Buttons
<script setup>
const columns = [
{ fieldName: "name", label: "Name", sortable: true, filterable: true },
{ fieldName: "status", label: "Status", type: "status", sortable: true },
{ fieldName: "action", label: "Action", type: "button" },
];
const data = [
{ id: 1, name: "Project A", status: "completed", action: "View Details" },
{ id: 2, name: "Project B", status: "in progress", action: "Edit" },
];
const handleRowClick = (slotProps) => {
const { data, index } = slotProps;
console.log(`Action clicked for ${data.name} at row ${index}`);
// Handle the action (navigate, open modal, etc.)
};
</script>
<template>
<DataTable
:columns="columns"
:data="data"
table-name="projects-table"
@row-click="handleRowClick"
/>
</template>
Custom Filters
<script setup>
import { FilterMatchMode } from "@primevue/core";
const customFilters = {
global: { value: null, matchMode: FilterMatchMode.CONTAINS },
name: { value: "John", matchMode: FilterMatchMode.STARTS_WITH },
};
</script>
<template>
<DataTable
:columns="columns"
:data="data"
:filters="customFilters"
table-name="filtered-table"
/>
</template>
Layout-Aware Actions Example
<script setup>
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { useModalStore } from '@/stores/modal'
const router = useRouter()
const modalStore = useModalStore()
const columns = [
{ fieldName: 'name', label: 'Client Name', sortable: true, filterable: true },
{ fieldName: 'email', label: 'Email', filterable: true },
{ fieldName: 'status', label: 'Status', type: 'status', sortable: true }
]
const data = ref([
{ id: 1, name: 'Acme Corp', email: 'contact@acme.com', status: 'completed' },
{ id: 2, name: 'Tech Solutions', email: 'info@tech.com', status: 'in progress' }
])
const tableActions = [
// Left-positioned action with filled style
{
label: 'Quick Export',
icon: 'pi pi-download',
action: () => exportAllClients(),
severity: 'success',
layout: { position: 'left', variant: 'outlined' }
},
// Center-positioned bulk action
{
label: 'Archive Selected',
icon: 'pi pi-archive',
action: (selectedRows) => archiveClients(selectedRows),
severity: 'warning',
requiresMultipleSelection: true,
layout: { position: 'center', variant: 'outlined' }
},
// Right-positioned main action
{
label: 'Create Client',
icon: 'pi pi-plus',
action: () => modalStore.openModal('createClient'),
severity: 'info',
layout: { position: 'right', variant: 'filled' }
},
// Single selection action
{
label: 'Edit Details',
icon: 'pi pi-pencil',
action: (rowData) => router.push(`/clients/${rowData.id}/edit`),
severity: 'secondary',
requiresSelection: true,
layout: { position: 'left', variant: 'text' }
},
// Primary row action - most important
{
label: 'View',
icon: 'pi pi-eye',
action: (rowData) => router.push(`/clients/${rowData.id}`),
severity: 'info',
rowAction: true,
layout: { priority: 'primary', variant: 'outlined' }
},
// Secondary row action - less important
{
label: 'Contact',
icon: 'pi pi-phone',
action: (rowData) => initiateContact(rowData),
severity: 'success',
rowAction: true,
layout: { priority: 'secondary', variant: 'text' }
},
// Dropdown row action - additional options
{
label: 'More',
icon: 'pi pi-ellipsis-v',
action: (rowData) => showMoreOptions(rowData),
rowAction: true,
layout: { priority: 'dropdown', variant: 'icon-only' }
}
]
const exportAllClients = () => {
// Export logic
}
const archiveClients = (clients) => {
// Archive logic
}
const initiateContact = (client) => {
// Contact logic
}
const showMoreOptions = (client) => {
// More options logic
}
</script>
<template>
<DataTable
:columns="columns"
:data="data"
:tableActions="tableActions"
table-name="clients-with-layout"
/>
</template>
Store Integration
The component integrates with a Pinia store (useFiltersStore) for persistent filter state:
Store Methods Used
initializeTableFilters(tableName, columns)- Initialize filters for a tablegetTableFilters(tableName)- Get current filters for a tableupdateTableFilter(tableName, fieldName, value, matchMode)- Update a specific filter
Filter Persistence
- Filters are automatically saved when changed
- Filters persist across component re-mounts
- Each table maintains separate filter state based on
tableName
Styling
The component uses PrimeVue's default DataTable styling with:
- Scrollable layout with fixed 70vh height
- Responsive design that adapts to container width
- Consistent spacing and typography
- Accessible color schemes for status badges
Performance Considerations
Large Datasets
- Virtual scrolling is not implemented - consider for datasets > 1000 rows
- Client-side pagination may impact performance with very large datasets
- Debounced filtering helps with real-time search performance
Memory Management
- Filter state persistence may accumulate over time
- Consider implementing filter cleanup for unused tables
- Component re-rendering is optimized through computed properties
Best Practices
- Use unique
tableNamefor each table instance to avoid filter conflicts - Define clear column labels for better user experience
- Enable sorting and filtering on searchable/comparable columns
- Use appropriate column types (
status,button) for better UX - Handle
rowClickevents for interactive functionality - Consider data structure - ensure
idfield exists for selection - Test with various data sizes to ensure performance
- Use consistent status values for proper badge coloring
Accessibility
The component includes:
- Keyboard navigation support via PrimeVue
- Screen reader compatibility with proper ARIA labels
- High contrast status badges for visibility
- Focus management for interactive elements
- Semantic HTML structure for assistive technologies
Browser Support
Compatible with all modern browsers that support:
- Vue 3 Composition API
- ES6+ features
- CSS Grid and Flexbox
- PrimeVue components
Dependencies
- Vue 3 with Composition API
- PrimeVue DataTable, Column, Tag, Button, InputText components
- @primevue/core for FilterMatchMode
- Pinia store for state management (
useFiltersStore)