Composables
useDataTable
Everything ITable does, with none of the markup: sorting, pagination, selection and expansion. Use it when the rows are laid out as cards, a list or a board rather than a <table>.
Options
rows and columns are getters rather than values, so the composable stays reactive to whatever holds them — a ref, a prop, or a store.
| Option | Type | What it does |
|---|---|---|
rows | () => T[] | The data. Required |
columns | () => TableColumn<T>[] | Column definitions. Required |
total | () => number | undefined | Row count on the server. Its presence switches modes |
rowKey | () => string | Field identifying a row, for selection and expansion |
sort | ModelRef<TableSort | null> | Controlled sort; omit for internal state |
page | ModelRef<number> | Controlled page |
perPage | ModelRef<number> | Controlled page size |
selection | ModelRef<(string | number)[]> | Controlled selection |
expanded | ModelRef<(string | number)[]> | Controlled expansion |
isRowSelectable | (row: T) => boolean | Rows the header checkbox skips |
Every model is optional. Bind one and you own that piece of state; leave it out and the composable keeps its own — so the page, sort and selection can live in a store, in the URL, or nowhere at all.
Client and server mode
Passing total is the switch. Without it the composable sorts and slices the rows you gave it. With it, the rows you passed are already the page the server returned, so sorting and paging only change state — watch sort and page, and fetch.
const table = useDataTable({
rows: () => data.value.rows,
columns: () => columns,
total: () => data.value.total, // server mode
})
watch([table.sort, table.page, table.perPage], fetchPage)isServerMode is exposed so your own UI can branch on it.
What you get back
| Returned | Type | |
|---|---|---|
sort / page / perPage | refs | The state, controlled or internal |
selection / expanded | refs | Arrays of row keys |
isServerMode | computed | true when total was supplied |
visibleColumns | computed | Columns left after hidden is applied |
pageRows | computed | The rows to render, sorted and sliced in client mode |
total | computed | Server total, or the row count |
pageCount | computed | Pages at the current size |
keyOf(row) | fn | The row's key, via rowKey |
toggleSort(column) | fn | Cycles ascending → descending → none |
sortOrderFor(column) | fn | 'asc', 'desc' or undefined |
headerSelection | computed | true, false or 'indeterminate' |
isSelected / toggleRow | fn | Per-row selection |
toggleAll | fn | Selects the selectable rows on the page |
isExpanded / toggleExpanded | fn | Per-row expansion |
getRowValue(row, path) | fn | Dot-notation accessor, exported separately too |
Columns
interface TableColumn<T = any> {
key: string
label?: string
sortable?: boolean
sortKey?: string
align?: 'start' | 'center' | 'end'
numeric?: boolean
width?: string
class?: string | ((row: T) => string)
hidden?: boolean | ((row: T) => boolean)
}sortKey covers the case where the field you display and the field the server sorts by have different names — customer.name against customer_name.
As with ITable, fetching and caching stay in your data layer.