Skip to content

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

vue
<template>
  <!-- Basic usage -->
  <WeDataTable resource="users" />
  
  <!-- With customization -->
  <WeDataTable 
    resource="orders"
    v-model:filters="filters"
    :extra-headers="extraHeaders"
    @action="handleAction"
  />
</template>

Props

PropTypeDefaultDescription
resourceStringRequiredName of the resource on the server
addButtonBoolean | StringtrueShow the add button (true) or custom label (string)
filtersObject{}Filter values for the table
requestParamsObject{}Additional parameters sent to the API
actionsHeaderBoolean | ObjecttrueShow actions column
extraHeadersArray[]Additional headers to display in the table
persistKeyStringresourceCustom key for persisting table state
persistString | BooleanConfig 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:

BindingTypeDescription
v-model:searchStringSearch query string
v-model:pageNumberCurrent page number
v-model:itemsPerPageNumberItems per page
v-model:sortByArraySort configuration
v-model:filtersObjectFilter configuration

Configuration

You can configure global defaults for WeDataTable in your app.config.ts file:

ts
// 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

EventPayloadDescription
action{ id: string, selection: Array }Emitted when an action is triggered (including bulk actions)
new--Emitted when the add button is clicked
update:filtersObjectEmitted when filters change
update:searchStringEmitted when search text changes
update:pageNumberEmitted when current page changes
update:itemsPerPageNumberEmitted when items per page changes
update:sortByArrayEmitted when sort configuration changes

Slots

NamePropsDescription
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

MethodDescription
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

vue
<script setup>
import { ref } from 'vue'

const filters = ref({})
</script>

<template>
  <WeDataTable 
    resource="users" 
    v-model:filters="filters"
  />
</template>

Custom Headers

vue
<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

vue
<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:

vue
<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:

vue
<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:

vue
<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:

vue
<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:

php
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:

vue
<!-- 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:

ts
// 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:

php
TextField::make('email', 'Email')->formatter("email", "color" => "primary")

Source Code