Skip to content

DataTable

Data table component with pagination, filters, multi-selection, and built-in export.

Description

DataTable is a wrapper around vue3-easy-data-table that provides advanced features such as filters, server-side pagination, multi-selection, data export, and URL synchronization. It is designed to work with server data via an async load function.

Location: @/components/interface/Table/DataTable.vue

Props

PropTypeRequiredDefaultDescription
loadDataFunction✅ Yes-Async function that loads data from the server
columnsArray✅ Yes-Array of objects defining the table columns
optionsObject❌ No{}Table configuration options
filtersObject❌ No{}Basic filters configuration object
advanceFiltersObject❌ No{}Advanced filters configuration object
itemsSelectedBoolean❌ NoundefinedEnables multi-row selection
itemsSelectedFilterFunction❌ No() => trueFunction to filter which items can be selected
bulkButtonsObject❌ No{}Bulk action buttons for selected items
buttonsObject❌ No{}Additional table buttons
exportFormatString | Boolean❌ NofalseExport format: 'xlsx', 'csv' or false
exportNameString❌ No'exported-data'Export file name
disableUrlUpdateBoolean❌ NofalseDisables URL update with filters

Column Structure

The columns prop must be an array of objects, each defining a table column.

Column Properties

javascript
{
  text: 'Column Name',        // Column title (required)
  value: 'field_name',        // Data object field (required)
  sortable: true,              // Allow sorting (optional, default: true)
  exportable: true,            // Include in export (optional, default: true)
  visible: true,               // Show/hide column (optional, default: true)
  width: 100,                  // Column width in pixels (optional)
  exportFormat: 'field_name',  // Alternative field for export (optional)
  scape: false,                // Escape HTML in export (optional)
}

Column Example

javascript
const columns = computed(() => [
  {
    text: t('datatable.column.name'),
    value: 'name',
    sortable: true,
  },
  {
    text: t('datatable.column.email'),
    value: 'email',
    sortable: true,
  },
  {
    text: t('datatable.column.status'),
    value: 'status',
    sortable: false,
  },
  {
    text: t('datatable.column.actions'),
    value: 'actions',
    exportable: false,
    sortable: false,
    width: 100,
  },
]);

loadData Function

The loadData function is responsible for loading data from the server. It receives an object with pagination and filter options and must return a Promise that resolves with an object of the following shape:

javascript
{
  data: Array,           // Array of items to display in the table
  recordsTotal: Number  // Total number of records (for pagination)
}

loadData Example

javascript
const loadData = async (filters) => {
  const response = await api.users.get(filters);
  return {
    data: response.data,
    recordsTotal: response.recordsTotal,
  };
};

loadData Parameters

The filters object passed to loadData contains:

javascript
{
  page: 1,                    // Current page
  rowsPerPage: 100,            // Items per page
  sortBy: 'name',              // Sort field
  sortType: 'ASC',             // Sort type: 'ASC' | 'DESC'
  filterLocale: false,         // Filter by locale
  localeId: 1,                 // Locale ID (if filterLocale is true)
  // ... custom filters
  search: 'query',
  status: 'active',
  // ... other filters defined in filters and advanceFilters
}

Configuration Options

The options prop configures the table’s initial behavior:

javascript
const options = {
  rowsPerPage: 50,           // Items per page (default: 100)
  sortBy: 'name',             // Initial sort field
  sortType: 'ASC',            // Initial sort type: 'ASC' | 'DESC'
  filterLocale: true,         // Filter by user locale
  page: 1,                    // Initial page
};

Basic Filters

The filters prop defines the filters shown in the table’s top bar.

Filter Types

1. Input (el: 'input')

Simple text field.

javascript
const filters = computed(() => ({
  name: {
    className: 'col-12 col-lg-6',
    label: t('datatable.column.name'),
    el: 'input',
    placeholder: 'Enter name',
    value: '',  // Initial value (optional)
  },
}));

2. Select (el: 'select')

Simple dropdown.

javascript
const filters = computed(() => ({
  status: {
    className: 'col-12 col-lg-6',
    label: t('datatable.column.status'),
    el: 'select',
    options: [
      { value: 'active', label: 'Active' },
      { value: 'inactive', label: 'Inactive' },
    ],
    value: '',  // Initial value (optional)
  },
}));

3. Select2 (el: 'select2')

Advanced selector with multiple options.

javascript
const filters = computed(() => ({
  carrier: {
    className: 'col-12 col-lg-6',
    label: t('datatable.column.carrier'),
    el: 'select2',
    options: carriers.value,
    placeholder: t('header.all'),
    mode: 'tags',  // 'single' | 'tags' | 'multiple'
    value: null,    // Initial value (optional)
  },
}));

4. DatePicker (el: 'datePicker')

Date range selector.

javascript
const filters = computed(() => ({
  dateRange: {
    className: 'col-12 col-lg-6',
    label: t('filters.dateRange'),
    el: 'datePicker',
    mode: 'range',  // 'range' | 'month'
    value: {         // Initial value (optional)
      startDate: new Date(),
      endDate: new Date(),
    },
    names: {         // Query field names (optional)
      startDate: 'startDate',
      endDate: 'endDate',
    },
  },
}));

5. SearchMultiple (el: 'searchMultiple')

Multiple search with options.

javascript
const filters = computed(() => ({
  search: {
    className: 'col-12 col-lg-6',
    el: 'searchMultiple',
    options: [
      { key: 'tracking', label: t('filters.tracking') },
      { key: 'company', label: t('filters.companyId') },
      { key: 'ticket', label: t('filters.ticket') },
    ],
    value: '',  // Initial value (optional)
  },
}));

Common Filter Properties

  • className: Bootstrap CSS classes for layout (e.g. 'col-12 col-lg-6')
  • label: Filter label
  • el: Filter element type
  • value: Initial filter value
  • placeholder: Placeholder text (for inputs and selects)

Advanced Filters

The advanceFilters prop defines filters shown in a sliding panel (offcanvas). It uses the same structure as filters but is displayed in a separate panel.

javascript
const advanceFilters = computed(() => ({
  created_at: {
    className: 'col-12 col-lg-6',
    label: t('datatable.column.createdAt'),
    el: 'datePicker',
    value: {
      startDate: new Date(),
      endDate: new Date(),
    },
  },
  // ... more filters
}));

To show the advanced filters panel, emit the event:

javascript
eventBus.emit('showAdvanceFilters');

Multi-Selection

To enable multi-row selection, set itemsSelected to true:

javascript
<DataTable
  :itemsSelected="true"
  :itemsSelectedFilter="canSelectItem"
  :bulkButtons="bulkActions"
/>

itemsSelectedFilter

Function that determines whether an item can be selected:

javascript
const canSelectItem = (item) => {
  // Return true if the item can be selected
  return !item.deleted_at && item.status === 'active';
};

bulkButtons

Object with bulk action buttons shown when items are selected:

javascript
const bulkActions = {
  delete: {
    text: t('actions.delete'),
    icon: 'fa fa-trash',
    class: 'btn btn-danger',
    action: () => {
      // Action to run with selected items
      const selected = table.value.itemsSelected.value;
      console.log('Selected items:', selected);
    },
  },
  export: {
    text: t('actions.export'),
    icon: 'fa fa-download',
    class: 'btn btn-primary',
    action: () => {
      // Another action
    },
  },
};

Slots

Column Slots

You can customize any column’s content with the #item-{value} slot:

vue
<DataTable :columns="columns" :loadData="loadData">
  <template #item-name="item">
    <div class="d-flex align-items-center gap-2">
      <img :src="item.avatar" alt="" />
      <span>{{ item.name }}</span>
    </div>
  </template>

  <template #item-status="item">
    <span :class="`badge bg-${item.status === 'active' ? 'success' : 'danger'}`">
      {{ item.status }}
    </span>
  </template>

  <template #item-actions="item">
    <button @click="editItem(item)">Edit</button>
    <button @click="deleteItem(item)">Delete</button>
  </template>
</DataTable>

Checkbox Header Slot

Customize the selection checkbox header:

vue
<DataTable :itemsSelected="true">
  <template #header-check>
    <div class="custom-checkbox-header">
      <!-- Custom content -->
    </div>
  </template>
</DataTable>

Item Checkbox Slot

Customize each row’s checkbox:

vue
<DataTable :itemsSelected="true">
  <template #item-check="item">
    <div class="custom-checkbox">
      <!-- Custom content -->
    </div>
  </template>
</DataTable>

Loading Slot

Customize the loading indicator:

vue
<DataTable :loadData="loadData">
  <template #loading>
    <div class="custom-loader">
      <em class="fa-solid fa-spinner fa-spin" />
      <span>Loading data...</span>
    </div>
  </template>
</DataTable>

Empty Message Slot

Customize the message when there is no data:

vue
<DataTable :loadData="loadData">
  <template #empty-message>
    <div class="text-center">
      <h3>No data available</h3>
      <button @click="clearFilters">Clear Filters</button>
    </div>
  </template>
</DataTable>

Extra Fields Slot

Add extra content above the table:

vue
<DataTable :loadData="loadData">
  <template #extra-fields="{ items, loading }">
    <div class="mb-3">
      <button @click="refreshData">Refresh</button>
      <span>Total: {{ items.length }}</span>
    </div>
  </template>
</DataTable>

Exposed Methods

The component exposes the following methods via ref:

reload(callback)

Reloads the table data.

javascript
const table = ref(null);

// Reload without callback
table.value.reload();

// Reload with callback
table.value.reload(() => {
  console.log('Data reloaded');
});

itemsSelected

Reactive ref with the selected items.

javascript
const table = ref(null);

// Access selected items
const selected = table.value.itemsSelected.value;
console.log('Selected items:', selected);

cleanSelecteds()

Clears the item selection.

javascript
table.value.cleanSelecteds();

loading(status)

Sets the loading state manually.

javascript
table.value.loading(true);  // Enable loading
table.value.loading(false); // Disable loading

filters()

Returns an object with all active filters.

javascript
const activeFilters = table.value.filters();
console.log('Active filters:', activeFilters);

Data Export

To enable export, set exportFormat:

javascript
<DataTable
  :exportFormat="'xlsx'"
  :exportName="'users-report'"
  :loadData="loadData"
/>

Supported Formats

  • 'xlsx': Export to Excel
  • 'csv': Export to CSV
  • false: Disable export

Manual Export

To export manually, emit the event:

javascript
eventBus.emit('exportTable');

EventBus Events

The component listens for the following EventBus events:

changeLocale

Changes the data locale:

javascript
eventBus.emit('changeLocale', {
  detail: { action: 1 }  // Locale ID
});

exportTable

Exports the table with current filters:

javascript
eventBus.emit('exportTable');

showAdvanceFilters

Shows the advanced filters panel:

javascript
eventBus.emit('showAdvanceFilters');

onClearFilters

Clears all filters:

javascript
eventBus.emit('onClearFilters');

URL Synchronization

By default, the component syncs filters with the URL query parameters. This allows:

  • Sharing links with applied filters
  • Reloading the page while keeping filters
  • Back/forward navigation while preserving state

To disable this:

javascript
<DataTable
  :disableUrlUpdate="true"
  :loadData="loadData"
/>

Usage Examples

Basic Example

vue
<template>
  <DataTable
    ref="table"
    :columns="columns"
    :loadData="loadData"
  />
</template>

<script setup>
import { ref, computed } from 'vue';
import DataTable from '@/components/interface/Table/DataTable.vue';
import api from '@/services';

const { t } = useI18n();
const table = ref(null);

const columns = computed(() => [
  {
    text: t('datatable.column.name'),
    value: 'name',
  },
  {
    text: t('datatable.column.email'),
    value: 'email',
  },
  {
    text: t('datatable.column.status'),
    value: 'status',
  },
]);

const loadData = async (filters) => {
  const response = await api.users.get(filters);
  return {
    data: response.data,
    recordsTotal: response.recordsTotal,
  };
};
</script>

Example with Filters

vue
<template>
  <DataTable
    ref="table"
    :columns="columns"
    :filters="filtersTable"
    :loadData="loadData"
  />
</template>

<script setup>
import { ref, computed } from 'vue';
import DataTable from '@/components/interface/Table/DataTable.vue';
import api from '@/services';

const { t } = useI18n();
const table = ref(null);

const columns = computed(() => [
  {
    text: t('datatable.column.name'),
    value: 'name',
  },
  {
    text: t('datatable.column.email'),
    value: 'email',
  },
]);

const filtersTable = computed(() => ({
  name: {
    className: 'col-12 col-lg-6',
    label: t('datatable.column.name'),
    el: 'input',
    placeholder: 'Search by name',
  },
  status: {
    className: 'col-12 col-lg-6',
    label: t('datatable.column.status'),
    el: 'select',
    options: [
      { value: 'active', label: 'Active' },
      { value: 'inactive', label: 'Inactive' },
    ],
  },
}));

const loadData = async (filters) => {
  const response = await api.users.get(filters);
  return {
    data: response.data,
    recordsTotal: response.recordsTotal,
  };
};
</script>

Example with Multi-Selection

vue
<template>
  <DataTable
    ref="table"
    :columns="columns"
    :itemsSelected="true"
    :itemsSelectedFilter="canSelectItem"
    :bulkButtons="bulkActions"
    :loadData="loadData"
  />
</template>

<script setup>
import { ref, computed } from 'vue';
import DataTable from '@/components/interface/Table/DataTable.vue';
import api from '@/services';

const { t } = useI18n();
const table = ref(null);

const columns = computed(() => [
  {
    text: t('datatable.column.name'),
    value: 'name',
  },
  {
    text: t('datatable.column.email'),
    value: 'email',
  },
]);

const canSelectItem = (item) => {
  return !item.deleted_at;
};

const bulkActions = {
  delete: {
    text: t('actions.delete'),
    icon: 'fa fa-trash',
    class: 'btn btn-danger',
    action: async () => {
      const selected = table.value.itemsSelected.value;
      if (selected.length > 0) {
        await api.users.bulkDelete(selected.map(item => item.id));
        table.value.reload();
      }
    },
  },
};

const loadData = async (filters) => {
  const response = await api.users.get(filters);
  return {
    data: response.data,
    recordsTotal: response.recordsTotal,
  };
};
</script>

Example with Custom Slots

vue
<template>
  <DataTable
    ref="table"
    :columns="columns"
    :loadData="loadData"
  >
    <template #item-name="item">
      <div class="d-flex align-items-center gap-2">
        <img
          :src="item.avatar"
          :alt="item.name"
          class="rounded-circle"
          width="32"
          height="32"
        />
        <span>{{ item.name }}</span>
      </div>
    </template>

    <template #item-status="item">
      <span :class="`badge bg-${item.status === 'active' ? 'success' : 'danger'}`">
        {{ item.status }}
      </span>
    </template>

    <template #item-actions="item">
      <div class="d-flex gap-2">
        <button
          class="btn btn-sm btn-primary"
          @click="editItem(item)"
        >
          Edit
        </button>
        <button
          class="btn btn-sm btn-danger"
          @click="deleteItem(item)"
        >
          Delete
        </button>
      </div>
    </template>
  </DataTable>
</template>

<script setup>
import { ref, computed } from 'vue';
import DataTable from '@/components/interface/Table/DataTable.vue';
import api from '@/services';

const { t } = useI18n();
const table = ref(null);

const columns = computed(() => [
  {
    text: t('datatable.column.name'),
    value: 'name',
  },
  {
    text: t('datatable.column.status'),
    value: 'status',
  },
  {
    text: t('datatable.column.actions'),
    value: 'actions',
    exportable: false,
    width: 150,
  },
]);

const loadData = async (filters) => {
  const response = await api.users.get(filters);
  return {
    data: response.data,
    recordsTotal: response.recordsTotal,
  };
};

const editItem = (item) => {
  console.log('Edit item:', item);
};

const deleteItem = (item) => {
  console.log('Delete item:', item);
};
</script>

Example with Export

vue
<template>
  <DataTable
    ref="table"
    :columns="columns"
    :exportFormat="'xlsx'"
    :exportName="'users-report'"
    :loadData="loadData"
  />
</template>

<script setup>
import { ref, computed } from 'vue';
import DataTable from '@/components/interface/Table/DataTable.vue';
import api from '@/services';

const { t } = useI18n();
const table = ref(null);

const columns = computed(() => [
  {
    text: t('datatable.column.name'),
    value: 'name',
  },
  {
    text: t('datatable.column.email'),
    value: 'email',
  },
]);

const loadData = async (filters) => {
  const response = await api.users.get(filters);
  return {
    data: response.data,
    recordsTotal: response.recordsTotal,
  };
};

// Export manually
const exportData = () => {
  eventBus.emit('exportTable');
};
</script>

Important Notes

  1. loadData function: It must always return an object with data and recordsTotal, even on error.

  2. Server-side pagination: The component is designed for server-side pagination. Do not try to paginate data on the client.

  3. Reactive filters: Filters should be computed so they update reactively when options change.

  4. Translations: All text uses translation keys. Ensure translations are defined in your i18n files.

  5. Exportable columns: Columns with exportable: false are not included in the export.

  6. Disabled rows: Rows with deleted_at or utc_deleted_at are automatically shown with a disabled style.

  7. EventBus: The component uses EventBus for global communication. Ensure you import and use EventBus correctly.

  8. URL sync: URL synchronization can cause issues with multiple tables on the same page. Consider using disableUrlUpdate in that case.

Envia Admin