WeDataTable
A powerful data table component that provides advanced features such as server-side pagination, filtering, sorting, bulk actions, and customizable formatting. It's designed to work seamlessly with API endpoints that follow Laravel API resource conventions.
Usage
<template>
<!-- Basic usage -->
<WeDataTable resource="users" />
<!-- With customization -->
<WeDataTable
resource="orders"
v-model:filters="filters"
:extra-headers="extraHeaders"
@action="handleAction"
/>
</template>
Props
Prop | Type | Default | Description |
---|---|---|---|
resource | String | Required | Name of the resource on the server |
addButton | Boolean | String | true | Show the add button (true) or custom label (string) |
filters | Object | {} | Filter values for the table |
requestParams | Object | {} | Additional parameters sent to the API |
actionsHeader | Boolean | Object | true | Show actions column |
extraHeaders | Array | [] | Additional headers to display in the table |
persistKey | String | resource | Custom key for persisting table state |
persist | String | Boolean | Config or 'state' | State persistence mode: 'state' (Nuxt state), 'local' (localStorage), 'session' (sessionStorage), or false to disable |
v-model Bindings
The component provides several v-model bindings for controlling its state:
Binding | Type | Description |
---|---|---|
v-model:search | String | Search query string |
v-model:page | Number | Current page number |
v-model:itemsPerPage | Number | Items per page |
v-model:sortBy | Array | Sort configuration |
v-model:filters | Object | Filter configuration |
Configuration
You can configure global defaults for WeDataTable in your app.config.ts file:
// app.config.ts
import EmailFormatter from '~/components/formatters/EmailFormatter.vue'
import BooleanFormatter from '~/components/formatters/BooleanFormatter.vue'
import DateFormatter from '~/components/formatters/DateFormatter.vue'
export default defineAppConfig({
we: {
datatables: {
// Default persistence mode
persist: 'state', // 'state', 'local', 'session', or false
// Register custom formatters
formatters: {
// Map formatter types to components
email: EmailFormatter,
boolean: BooleanFormatter,
date: DateFormatter,
},
},
},
})
Events
Event | Payload | Description |
---|---|---|
action | { id: string, selection: Array } | Emitted when an action is triggered (including bulk actions) |
new | -- | Emitted when the add button is clicked |
update:filters | Object | Emitted when filters change |
update:search | String | Emitted when search text changes |
update:page | Number | Emitted when current page changes |
update:itemsPerPage | Number | Emitted when items per page changes |
update:sortBy | Array | Emitted when sort configuration changes |
Slots
Name | Props | Description |
---|---|---|
action-buttons | -- | Additional action buttons in the header |
item.[fieldName] | { item, index } | Custom cell renderer for specific fields |
item.actions | { item } | Actions column content |
Methods
Method | Description |
---|---|
reloadData() | Refresh the data without changing pagination |
clearSelection() | Clear selected items |
executeAction(actionId) | Execute a specific action |
reset() | Reset all filters, sorting, and pagination |
Examples
Basic Implementation
<script setup>
import { ref } from 'vue'
const filters = ref({})
</script>
<template>
<WeDataTable
resource="users"
v-model:filters="filters"
/>
</template>
Custom Headers
<script setup>
import { ref } from 'vue'
const filters = ref({})
const extraHeaders = ref([
{ key: 'status', title: 'Status', sortable: true, width: '120px' },
{ key: 'actions', title: 'Actions', sortable: false, align: 'end', width: '120px' }
])
</script>
<template>
<WeDataTable
resource="orders"
v-model:filters="filters"
:extra-headers="extraHeaders"
>
<!-- Custom status column -->
<template #[`item.status`]="{ item }">
<v-chip
:color="item.status === 'active' ? 'success' : 'error'"
size="small"
>
{{ item.status }}
</v-chip>
</template>
<!-- Custom actions column -->
<template #[`item.actions`]="{ item }">
<v-btn
icon
size="small"
@click="editItem(item)"
>
<v-icon>mdi-pencil</v-icon>
</v-btn>
<v-btn
icon
size="small"
color="error"
@click="deleteItem(item)"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
</template>
</WeDataTable>
</template>
With Custom Filters
<script setup>
import { ref } from 'vue'
const filters = ref({
status: { operator: '=', value: 'active' },
created_at: { operator: 'between', value: [
new Date().toISOString().split('T')[0], // Today
new Date().toISOString().split('T')[0] // Today
]}
})
const dataTable = ref(null)
function resetFilters() {
dataTable.value.reset()
}
</script>
<template>
<WeDataTable
ref="dataTable"
resource="orders"
v-model:filters="filters"
/>
</template>
With State Persistence
The WeDataTable component can automatically persist its state across page reloads:
<script setup>
import { ref } from 'vue'
</script>
<template>
<!-- Using default persistence (Nuxt state) -->
<WeDataTable
resource="users"
/>
<!-- Using localStorage persistence with custom key -->
<WeDataTable
resource="orders"
persist="local"
persistKey="my_orders_table"
/>
<!-- Using sessionStorage persistence -->
<WeDataTable
resource="products"
persist="session"
/>
<!-- Disable persistence -->
<WeDataTable
resource="temp_data"
:persist="false"
/>
</template>
Persisted state includes:
- Search query
- Current page
- Filters
- Items per page
- Sort configuration
With Request Parameters
The requestParams
prop allows you to send additional parameters to the API with each request. This is useful for including related data, adding special filters, or passing other API-specific parameters:
<script setup>
import { ref } from 'vue'
const requestParams = ref({
// Include related entities in the response
include: 'author,comments,categories',
// Add additional context parameters
withTrashed: true,
// Custom API-specific parameters
filterByTeam: 5,
orderByLatest: true
})
</script>
<template>
<WeDataTable
resource="articles"
:request-params="requestParams"
>
<!-- The data will include related author, comments, categories and other parameters -->
<template #[`item.author.name`]="{ item }">
<v-avatar size="24" class="mr-2">
<v-img :src="item.author.avatar"></v-img>
</v-avatar>
{{ item.author.name }}
</template>
<template #[`item.comments`]="{ item }">
<v-chip size="small">
{{ item.comments.length }} comments
</v-chip>
</template>
</WeDataTable>
</template>
These parameters are sent with every request to the API endpoint, and you can update them reactively to change what data is fetched.
Customizing the Actions Header
The actionsHeader
prop can be used to customize or disable the actions column:
<script setup>
import { ref } from 'vue'
// Default behavior - actions column is shown (actionsHeader=true)
const defaultTable = ref(null)
// Custom actions header
const customActionsHeader = ref({
title: 'Operations',
align: 'center',
sortable: false,
width: '150px',
class: 'bg-grey-lighten-4'
})
// Function to handle editing an item
function editItem(item) {
console.log('Editing item:', item.id)
}
// Function to handle deleting an item
function deleteItem(item) {
console.log('Deleting item:', item.id)
}
</script>
<template>
<!-- Default actions header -->
<WeDataTable
ref="defaultTable"
resource="users"
>
<template #[`item.actions`]="{ item }">
<v-btn icon size="small" @click="editItem(item)">
<v-icon>mdi-pencil</v-icon>
</v-btn>
</template>
</WeDataTable>
<!-- Custom actions header -->
<WeDataTable
resource="products"
:actions-header="customActionsHeader"
>
<template #[`item.actions`]="{ item }">
<div class="d-flex justify-center">
<v-btn icon size="small" class="mr-2" @click="editItem(item)">
<v-icon>mdi-pencil</v-icon>
</v-btn>
<v-btn icon size="small" color="error" @click="deleteItem(item)">
<v-icon>mdi-delete</v-icon>
</v-btn>
</div>
</template>
</WeDataTable>
<!-- Disabled actions header -->
<WeDataTable
resource="categories"
:actions-header="false"
/>
</template>
When actionsHeader
is set to false
, no actions column will be shown. When it's an object, you can customize properties like title, alignment, width, and CSS classes.
Handling Extra Actions
You can add custom actions that can be applied to selected items. The DataTable component provides a way to handle these actions through the @action
event and executeAction
method:
<script setup>
import { ref } from 'vue'
const dataTable = ref(null)
const selectedItems = ref([])
// Handle action events from the datatable
async function handleAction(action) {
const { id, selection } = action
switch (id) {
case 'approve':
try {
// This will send a POST to your API with the selection and filters
await dataTable.value.executeAction('approve')
// Handle success
} catch (error) {
console.error('Failed to approve items', error)
}
break
case 'export':
// Custom handling for export (bypassing executeAction)
const format = await selectExportFormat() // Your custom function
if (format) {
// Using executeAction with extra parameters
await dataTable.value.executeAction('export', { format })
}
break
case 'new':
// Handle new item creation
openCreateForm()
break
}
}
// You can also trigger actions programmatically
function approveAllVisible() {
dataTable.value.executeAction('approve')
}
</script>
<template>
<div>
<!-- Action buttons outside the datatable -->
<v-btn color="success" @click="approveAllVisible">
Approve All Visible
</v-btn>
<WeDataTable
ref="dataTable"
v-model="selectedItems"
resource="posts"
@action="handleAction"
>
<!-- You can add custom action buttons in the datatable header -->
<template #action-buttons>
<v-btn
prepend-icon="mdi-refresh"
@click="dataTable.reloadData()"
>
Refresh
</v-btn>
</template>
</WeDataTable>
</div>
</template>
When using executeAction
, the component will make a POST request to your API at {resource}/datatable
with a payload containing:
- The action ID
- Selected item IDs
- Current filters
- Any extra parameters you provide
Your backend can then process these actions accordingly.
The laravel action must look like this:
class ApproveAction extends Action
{
// ...
public string $id = "approve";
public bool $extra = true;
// ...
}
Custom Data Formatters
The WeDataTable
component supports custom cell formatters for different data types. Formatters are Vue components that are registered in the app configuration and automatically applied to specific data types or fields.
First, create a formatter component:
<!-- EmailFormatter.vue -->
<script setup>
// EmailFormatter displays a clickable email link
const props = defineProps({
value: {
type: [Array, String],
default: () => '',
},
field: {
type: Object,
default: () => ({}),
},
item: {
type: Object,
required: true,
},
})
const color = computed(() => {
return props.field.formatter?.color
})
</script>
<template>
<a :href="`mailto:${props.value}`" v-if="props.value">
{{ props.value }}
</a>
</template>
Then register your formatters in your App config:
// app.config.ts
import EmailFormatter from '~/components/EmailFormatter.vue'
export default defineAppConfig({
we: {
datatables: {
persist: 'state',
formatters: {
email: EmailFormatter,
},
},
},
})
You can define the formatter in Laravel like this:
TextField::make('email', 'Email')->formatter("email", "color" => "primary")