Skip to content

useDataTable

Composable for schema-driven data tables with pagination, sorting, filtering, and selection.

Basic Usage

vue
<script setup lang="ts">
import { useDataTable } from '@anhanga/vue'
import { Scope } from '@anhanga/core'

const table = useDataTable({
  schema: PersonSchema.provide(),
  scope: Scope.index,
  handlers: personHandlers,
  hooks: personHooks,
  component: componentContract,
  translate: t,
})
</script>

<template>
  <div v-if="table.loading">Loading...</div>
  <div v-else-if="table.empty">No records found</div>
  <div v-else>
    <table>
      <thead>
        <tr>
          <th
            v-for="col in table.columns"
            :key="col.name"
            @click="table.setSort(col.name)"
          >
            {{ t(`person.fields.${col.name}`) }}
          </th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="row in table.rows" :key="table.getIdentity(row)">
          <td v-for="col in table.columns" :key="col.name">
            {{ table.formatValue(col.name, row[col.name], row) }}
          </td>
        </tr>
      </tbody>
    </table>

    <div>
      <button :disabled="table.page <= 1" @click="table.setPage(table.page - 1)">Previous</button>
      <span>Page {{ table.page }} of {{ table.totalPages }}</span>
      <button :disabled="table.page >= table.totalPages" @click="table.setPage(table.page + 1)">Next</button>
    </div>
  </div>
</template>

Options

typescript
interface UseDataTableOptions {
  schema: SchemaProvide
  scope: ScopeValue
  handlers?: Record<string, HandlerFn>
  hooks?: SchemaHooks
  context?: Record<string, unknown>
  component: ComponentContract
  pageSize?: number
  translate?: TranslateContract
}
OptionRequiredDescription
schemaYesOutput of SchemaDefinition.provide()
scopeYesCurrent scope (typically Scope.index)
handlersNoAction handlers
hooksNoLifecycle hooks (fetch hooks provide data)
componentYesComponentContract implementation
pageSizeNoItems per page (default varies)
translateNoTranslation function

Return Value

Data

PropertyTypeDescription
rowsRecord<string, unknown>[]Current page data
loadingbooleanTrue while fetching data
emptybooleanTrue if no rows

Columns

PropertyTypeDescription
columnsResolvedColumn[]Visible columns
availableColumnsResolvedColumn[]All available columns
visibleColumnsstring[]Names of visible columns
toggleColumn(name)functionToggle column visibility

Pagination

PropertyTypeDescription
pagenumberCurrent page
limitnumberItems per page
totalnumberTotal record count
totalPagesnumberTotal page count
setPage(page)functionNavigate to page
setLimit(limit)functionChange page size

Sorting

PropertyTypeDescription
sortFieldstring | undefinedCurrent sort field
sortOrder'asc' | 'desc' | undefinedSort direction
setSort(field)functionToggle sort on field

Filtering

PropertyTypeDescription
filtersRecord<string, unknown>Active filters
setFilter(field, value)functionSet a filter value
clearFilters()functionClear all filters

Selection

PropertyTypeDescription
selectedRecord<string, unknown>[]Selected rows
isSelected(record)functionCheck if row is selected
toggleSelect(record)functionToggle row selection
selectAll()functionSelect all rows
clearSelection()functionClear selection

Actions

PropertyTypeDescription
actionsResolvedAction[]Top-level actions (e.g., "Add")
getRowActions(record)functionGet actions for a specific row

Utilities

MethodDescription
reload()Re-fetch current page data
formatValue(name, value, record)Format a cell value using column config
getIdentity(record)Get the identity key for a record

Column Configuration

Fields become table columns when marked with .column():

typescript
text().column()               // basic column
text().column().sortable()    // sortable
text().column().filterable()  // filterable

Fetch Hooks

Table data is loaded through fetch hooks:

typescript
const personHooks = PersonSchema.hooks({
  fetch: {
    [Scope.index]: async ({ params }) => {
      return personService.paginate(params)
    },
  },
})

The params object contains pagination, sorting, and filter state:

typescript
interface PaginateParams {
  page: number
  limit: number
  sort?: string
  order?: 'asc' | 'desc'
  filters?: Record<string, unknown>
}

The hook expects a PaginatedResult:

typescript
interface PaginatedResult<T> {
  data: T[]
  total: number
  page: number
  limit: number
}

ResolvedColumn

typescript
interface ResolvedColumn {
  name: string
  config: FieldConfig
  table: TableConfig
}

Released under the MIT License.