BaseDataTable โ
BaseDataTable is the core orchestrator table shell built on Vuetify v-data-table. Use it in plugin and workbench list widgets for a consistent toolbar, filters, export/import hooks, and row-action layout.
import BaseDataTable from "@raclettejs/core/orchestrator/components/dataTable/BaseDataTable.vue"Separation of concerns
Core (BaseDataTable) provides table chrome: toolbar, filter drawer, export/import wiring, row selection, delete-confirm plumbing, and built-in filter UIs/matchers.
Your widget owns data fetching ($data), CRUD, routing, deleted-items toggles, restore actions, and column filter metadata on headers. Do not put plugin/domain logic into core.
Quick start โ
<template>
<BaseDataTable
:items="rows"
:headers="headers"
:data-name="$t('myPlugin.list.dataName')"
:show-filters="true"
:row-click-handler="(item) => goToEditById('myEditLink', item._id)"
>
<template #toolbar-end>
<v-btn
prepend-icon="mdi-plus"
variant="flat"
color="success"
:text="$t('core.baseDataTable.createDataButton', { dataName: $t('myPlugin.list.dataName') })"
@click="goToCreate('myCreateLink')"
/>
</template>
<template #row-actions="{ item }">
<BaseDataTableConfirmDeleteBtn :item="item" @delete="deleteRow(item)" />
</template>
</BaseDataTable>
</template>For Workbench plugins, keep navigation in useWorkbenchTableActions (workbench app composable) rather than encoding it in BaseDataTable.
Props โ
Data and display โ
| Prop | Type | Default | Description |
|---|---|---|---|
items | T[] | [] | Rows shown in the table |
loading | boolean | โ | Loading state |
headers | Array<{ title, key, columnFilter?, โฆ }> | required | Column definitions (see Headers) |
dataName | string | required | Entity label (search placeholder, toolbar title) |
itemValue | string | "_id" | Unique row key for selection and export |
itemsPerPage | number | 10 | Pagination page size |
sortBy | { key, order }[] | โ | Initial column sort |
groupBy | string | string[] | โ | Vuetify group-by key(s); see Grouping |
showSearch | boolean | true | Search field above the table |
showLoading | boolean | true | Pass loading through to v-data-table |
showActionsColumn | boolean | true | Append an actions column |
actionsHeaderTitle | string | โ | Overrides core.baseDataTable.actions |
Row interaction โ
| Prop | Type | Description |
|---|---|---|
rowClickHandler | (item, context?) => void | Called on row click (skips interactive controls) |
isRowClickable | (item) => boolean | When set with rowClickHandler, limits pointer cursor and clicks |
Deleted / soft-delete behaviour โ
| Prop | Type | Description |
|---|---|---|
itemsDeleted | boolean | Tints rows and switches hard-delete confirm copy |
confirmDelete | boolean | Show confirm dialog for requestDelete (default true) |
getDeleteConfirmMessage | (item) => string | Soft-delete message |
getHardDeleteConfirmMessage | (item) => string | Used when itemsDeleted is true |
Filters โ
| Prop | Type | Default | Description |
|---|---|---|---|
showFilters | boolean | false | Show Filters button and drawer |
filterMaxSelectOptions | number | 300 | Max distinct values per enum column before dropdown suggestions are omitted (custom chips still work) |
filterDrawerAttach | "auto" | "viewport" | "parent" | "auto" | Drawer attachment; see Drawer attachment |
filterDrawerCloseOnOutsideClick | boolean | true | Close the filter drawer when clicking the scrim / outside; false keeps it open until the close button |
onFilterStateChange | (filters, context) => void | โ | Draft filter edits in the drawer |
onFilterApply | (filters, context) => void | โ | After Apply |
onFilterReset | (filters, context) => void | โ | After Reset |
Filter callback payloads are Record<string, ColumnFilterClause> (see Filter clause shape).
Export and import โ
See also DataExporter.
| Prop | Type | Default | Description |
|---|---|---|---|
showExport | boolean | true | Export mode in the actions menu |
showSelect | boolean | false | Checkbox column independent of export |
exportableItems | Record[] | โ | Canonical records for export (matched by itemValue) |
exportMeta | object | โ | Optional meta in export payload |
exportRowShape | "visible-columns" | "full-records" | "visible-columns" | Project export to header keys or full rows |
exportShowDataScopeSwitch | boolean | true | Items/meta/combined toggle in exporter |
exportFormats | FormatEntry[] | ["json"] | Export formats |
exportDefaultFormat | string | "json" | Default format |
onFileLoaded | (content, file) => void | โ | Enables import in the actions menu |
importAccept | string | ".json" | File input accept |
importReadAs | "text" | "arrayBuffer" | "text" | File read mode |
Toolbar indicators โ
| Prop | Type | Default | Description |
|---|---|---|---|
toolbarIndicators | { id, label, color?, icon? }[] | [] | Generic active-mode chips and โฎ menu badge |
Core does not know what each indicator means โ widgets pass labels and colours. Example for deleted-items mode:
:toolbar-indicators="
showDeleted
? [{ id: 'deleted', label: $t('core.showDeleted'), color: 'error', icon: 'mdi-delete-outline' }]
: []
"When any indicator is present:
- A dot (or count) badge appears on the โฎ actions menu button
- Default chips render in the toolbar (override with
#toolbar-indicators)
Headers โ
Each header is:
{
title: string
key: string
align?: "start" | "center" | "end"
sortable?: boolean
width?: string | number
columnFilter?: false | {
type?: "enum" | "number" | "date" | "datetime" // default "enum"
inputFactor?: number // number filters only
units?: Array<{ id: string; label: string; factor: number }>
defaultUnit?: string
match?: (cellValue, clause, row) => boolean
}
// Optional Vuetify custom filter *function* (unrelated to columnFilter)
filter?: (value, query, item?) => boolean
}columnFilter | Behaviour |
|---|---|
| omitted | Default enum multi-select combobox |
false | Column is not shown in the filter drawer |
{ type: "enum" } | Multi-select combobox + custom strings |
{ type: "number" } | Comparison operators + numeric inputs |
{ type: "date" } | Comparison operators + date inputs |
{ type: "datetime" } | Comparison operators + date and time inputs |
{ type: "number", inputFactor: 100 } | User enters display units; core multiplies before compare (e.g. euros โ cents) |
{ type: "number", units, defaultUnit } | Number filter with unit select (see DURATION_HOURS_COLUMN_FILTER) |
{ match } | Custom predicate replaces the built-in matcher for that column |
Name collision with Vuetify
Vuetify headers already use filter as an optional function. Raclette column-filter config must use columnFilter. BaseDataTable strips columnFilter (and any non-function filter) before headers reach v-data-table, so Vuetify never tries to call a config object.
Prefer typed filters for dates and numbers
High-cardinality columns (unique ISO timestamps, continuous costs) produce empty enum suggestion lists once they exceed filterMaxSelectOptions. Declare columnFilter: { type: "date" }, { type: "datetime" }, or { type: "number" } instead of relying on enum chips.
Display formatting vs filter values
Keep raw values on the row for filtering/sorting (ISO dates, cents, hours). Render friendly text with #item.<key> slots. That is the supported pattern โ not replacing the cell value with a formatted string.
Slots โ
Toolbar โ
| Slot | Purpose |
|---|---|
toolbar-start | Left side (default: dataName title) |
toolbar-leading | Controls before actions (e.g. DB selector) |
toolbar-indicators | Override default mode chips; props: { indicators } |
toolbar-end | Primary actions (e.g. Create) |
toolbar-filters-trigger | Override Filters button; props: { openFilters, toggleFilterDrawer, isFilterDrawerOpen } |
toolbar-actions-trigger | Override โฎ menu activator; props: { isOpen, toggle } |
toolbar-actions-menu | Extra entries in the โฎ menu (e.g. show-deleted switch) |
toolbar-search | Override search field |
Row actions โ
| Slot | Purpose |
|---|---|
prepend-row-actions | Icons before row actions (e.g. restore) |
row-actions | Per-row actions (delete, edit, โฆ) |
append-row-actions | Trailing row actions |
Filters drawer โ
| Slot | Purpose |
|---|---|
filters.summary | Above drawer header; props: { filters, activeCount } |
filters.drawer.header | Drawer title row |
filters.drawer.body | Replace all filter fields (escape hatch) |
filters.drawer.footer | Apply / Reset buttons |
Grouping โ
| Slot | Purpose |
|---|---|
group-header | Custom group row; receives Vuetify group props plus selectionActive |
| โ | Use BaseDataTableGroupSelectCheckbox inside a custom group header for group selection |
Export / import โ
| Slot | Purpose |
|---|---|
exporter | Replace DataExporter |
importer | Replace import UI |
action.export-mode | Custom export-mode menu item |
action.import | Custom import menu item |
Columns and passthrough โ
| Slot | Purpose |
|---|---|
item.<key> | Custom cell renderer for a column |
body.prepend, body.append, tfoot | Summary rows โ prefer BaseDataTableSummaryRow (see below) |
bottom, thead, โฆ | Other v-data-table slots forwarded automatically |
dialogs | Sibling content below the table |
Built-in column slots: item.color, item.tags (when not overridden).
Summary / totals rows โ
Hand-rolled <td> cells in #body.append / #tfoot skip Vuetifyโs column align classes, so totals drift from body values. Use BaseDataTableSummaryRow and set align: 'end' on numeric headers:
import BaseDataTableSummaryRow from "@raclettejs/core/orchestrator/components/dataTable/BaseDataTableSummaryRow.vue"
const headers = [
{ title: "Name", key: "name" },
{ title: "Cost", key: "cost", align: "end" },
]<template #body.append="{ columns, items }">
<BaseDataTableSummaryRow :columns="columns">
<template #default="{ column }">
<strong v-if="column.key === 'name'">Total</strong>
<strong v-else-if="column.key === 'cost'">
{{ formatTotal(items) }}
</strong>
</template>
</BaseDataTableSummaryRow>
</template>BaseDataTableSummaryRow mirrors VDataTableColumn classes (especially align) and sticky bottom styling. Pass :sticky="false" if you do not want a pinned totals row.
Patterns โ
Delete with confirmation โ
Use BaseDataTableConfirmDeleteBtn inside #row-actions. It calls requestDelete from useBaseDataTableDeleteConfirm() (provided by BaseDataTable).
<template #row-actions="{ item }">
<BaseDataTableConfirmDeleteBtn :item="item" @delete="deleteRow(item)" />
</template>Show deleted / hard delete โ
Keep showDeleted in the widget. Wire:
:items-deleted="showDeleted"โ row tint and hard-delete messages#toolbar-actions-menuโ switch to toggle the mode:toolbar-indicatorsโ visible badge/chip when the mode is active (even with zero rows)#prepend-row-actionsโ restore icon whenshowDeleted
Fetch with isDeleted: showDeleted.value on your $data calls.
Typed column filters โ
Declare filter behaviour next to each column. Core owns the UI and matchers; the widget only annotates headers.
import { DURATION_HOURS_COLUMN_FILTER } from "@raclettejs/core/orchestrator/components"
const headers = computed(() => [
{ title: $t("project"), key: "project" }, // default enum
{ title: $t("billingStart"), key: "startDate", columnFilter: { type: "datetime" } },
{
title: $t("usageInTimespan"),
key: "upTimeInHours",
columnFilter: DURATION_HOURS_COLUMN_FILTER,
},
{
title: $t("costInTimespan"),
key: "costInTimeSpan",
// User enters major currency units; row stores cents. Unit label from locale.
columnFilter: {
type: "number",
inputFactor: 100,
units: [{ id: "EUR", label: "โฌ", factor: 1 }],
defaultUnit: "EUR",
},
},
{
title: $t("updatedAt"),
key: "updatedAt",
columnFilter: { type: "date" },
},
])Keep raw dates on the row and format in a slot:
<template #item.updatedAt="{ item }">
{{ item.updatedAt ? formatDistance(new Date(item.updatedAt), new Date()) : "" }}
</template>Custom match escape hatch (keep core UI, override compare):
{
title: $t("status"),
key: "status",
columnFilter: {
type: "enum",
match: (cell, clause, row) => {
// your domain logic
return true
},
},
}Export round-trip (Workbench) โ
See DataExporter โ Use with BaseDataTable.
Filters โ
- Click Filters to open a right-side drawer.
- Edits are draft until Apply; Reset clears applied filters.
- Columns with active filters get a highlighted header and cell background.
- Across columns, filters are combined with AND.
Built-in filter types โ
| Type | UI | Matching |
|---|---|---|
enum | Multi-select combobox with chips; allows custom strings | OR across values; exact match for known options, substring for custom values |
date | Operator select + date picker chips | Same operators; compares calendar days against cell timestamps |
datetime | Operator select + date and time pickers | Same operators; compares timestamps (minute precision for eq) |
number | Operator select + numeric fields (+ optional unit select) | eq, gt, gte, lt, lte, between; inputFactor and unit factor applied to user values |
Filter clause shape โ
Applied / draft filter maps use:
type ColumnFilterClause =
| { type: "enum"; values: string[] }
| {
type: "number" | "date" | "datetime"
op: "eq" | "gt" | "gte" | "lt" | "lte" | "between"
value?: number | string | null
valueTo?: number | string | null // for "between"
unit?: string | null // when columnFilter.units is set
}Drawer attachment โ
filterDrawerAttach | Behaviour |
|---|---|
auto (default) | Full-viewport slide-over (teleported to body) on page and inline; custom slide-over panel inside the table host when slotType is modal |
viewport | Full-height drawer teleported to the viewport (body) with a fixed scrim |
parent | Drawer contained to the table host element |
Modal detection uses provide / inject from WidgetsLayoutLoader (slotType: page | modal | inline) โ the same value every *Widget.vue receives. No extra widget prop is required.
Set filterDrawerCloseOnOutsideClick to false if the drawer should stay open until the user hits the close button (or a custom #filters.drawer.header close()). Default is true (scrim / outside click dismisses).
On full-page tables with the raclette footer (โCooked with racletteโ), the drawer footer gets extra bottom padding so Apply/Reset stay above the app footer.
Grouping โ
Pass group-by="project" (or an array of keys) to group rows.
- Grouping is resolved from filtered rows.
- If a group key has โค 1 distinct non-empty value in the filtered set, that key is dropped from grouping (flat list). This covers single-project permissions and filtering down to one project.
- Custom
#group-headerslots still work when grouping is active; when grouping collapses they are simply unused. - For selectable group headers, use
BaseDataTableGroupSelectCheckbox.
<BaseDataTable
:items="rows"
:headers="headers"
group-by="project"
:show-filters="true"
data-name="Projects"
>
<template #group-header="{ item, toggleGroup, isGroupOpen, selectionActive }">
<!-- custom group row -->
</template>
</BaseDataTable>