Skip to content

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.

vue
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 โ€‹

vue
<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 โ€‹

PropTypeDefaultDescription
itemsT[][]Rows shown in the table
loadingbooleanโ€”Loading state
headersArray<{ title, key, columnFilter?, โ€ฆ }>requiredColumn definitions (see Headers)
dataNamestringrequiredEntity label (search placeholder, toolbar title)
itemValuestring"_id"Unique row key for selection and export
itemsPerPagenumber10Pagination page size
sortBy{ key, order }[]โ€”Initial column sort
groupBystring | string[]โ€”Vuetify group-by key(s); see Grouping
showSearchbooleantrueSearch field above the table
showLoadingbooleantruePass loading through to v-data-table
showActionsColumnbooleantrueAppend an actions column
actionsHeaderTitlestringโ€”Overrides core.baseDataTable.actions

Row interaction โ€‹

PropTypeDescription
rowClickHandler(item, context?) => voidCalled on row click (skips interactive controls)
isRowClickable(item) => booleanWhen set with rowClickHandler, limits pointer cursor and clicks

Deleted / soft-delete behaviour โ€‹

PropTypeDescription
itemsDeletedbooleanTints rows and switches hard-delete confirm copy
confirmDeletebooleanShow confirm dialog for requestDelete (default true)
getDeleteConfirmMessage(item) => stringSoft-delete message
getHardDeleteConfirmMessage(item) => stringUsed when itemsDeleted is true

Filters โ€‹

PropTypeDefaultDescription
showFiltersbooleanfalseShow Filters button and drawer
filterMaxSelectOptionsnumber300Max distinct values per enum column before dropdown suggestions are omitted (custom chips still work)
filterDrawerAttach"auto" | "viewport" | "parent""auto"Drawer attachment; see Drawer attachment
filterDrawerCloseOnOutsideClickbooleantrueClose 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.

PropTypeDefaultDescription
showExportbooleantrueExport mode in the actions menu
showSelectbooleanfalseCheckbox column independent of export
exportableItemsRecord[]โ€”Canonical records for export (matched by itemValue)
exportMetaobjectโ€”Optional meta in export payload
exportRowShape"visible-columns" | "full-records""visible-columns"Project export to header keys or full rows
exportShowDataScopeSwitchbooleantrueItems/meta/combined toggle in exporter
exportFormatsFormatEntry[]["json"]Export formats
exportDefaultFormatstring"json"Default format
onFileLoaded(content, file) => voidโ€”Enables import in the actions menu
importAcceptstring".json"File input accept
importReadAs"text" | "arrayBuffer""text"File read mode

Toolbar indicators โ€‹

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

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

ts
{
  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
}
columnFilterBehaviour
omittedDefault enum multi-select combobox
falseColumn 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 โ€‹

SlotPurpose
toolbar-startLeft side (default: dataName title)
toolbar-leadingControls before actions (e.g. DB selector)
toolbar-indicatorsOverride default mode chips; props: { indicators }
toolbar-endPrimary actions (e.g. Create)
toolbar-filters-triggerOverride Filters button; props: { openFilters, toggleFilterDrawer, isFilterDrawerOpen }
toolbar-actions-triggerOverride โ‹ฎ menu activator; props: { isOpen, toggle }
toolbar-actions-menuExtra entries in the โ‹ฎ menu (e.g. show-deleted switch)
toolbar-searchOverride search field

Row actions โ€‹

SlotPurpose
prepend-row-actionsIcons before row actions (e.g. restore)
row-actionsPer-row actions (delete, edit, โ€ฆ)
append-row-actionsTrailing row actions

Filters drawer โ€‹

SlotPurpose
filters.summaryAbove drawer header; props: { filters, activeCount }
filters.drawer.headerDrawer title row
filters.drawer.bodyReplace all filter fields (escape hatch)
filters.drawer.footerApply / Reset buttons

Grouping โ€‹

SlotPurpose
group-headerCustom group row; receives Vuetify group props plus selectionActive
โ€”Use BaseDataTableGroupSelectCheckbox inside a custom group header for group selection

Export / import โ€‹

SlotPurpose
exporterReplace DataExporter
importerReplace import UI
action.export-modeCustom export-mode menu item
action.importCustom import menu item

Columns and passthrough โ€‹

SlotPurpose
item.<key>Custom cell renderer for a column
body.prepend, body.append, tfootSummary rows โ€” prefer BaseDataTableSummaryRow (see below)
bottom, thead, โ€ฆOther v-data-table slots forwarded automatically
dialogsSibling 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:

vue
import BaseDataTableSummaryRow from "@raclettejs/core/orchestrator/components/dataTable/BaseDataTableSummaryRow.vue"

const headers = [
  { title: "Name", key: "name" },
  { title: "Cost", key: "cost", align: "end" },
]
vue
<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).

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

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.

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

vue
<template #item.updatedAt="{ item }">
  {{ item.updatedAt ? formatDistance(new Date(item.updatedAt), new Date()) : "" }}
</template>

Custom match escape hatch (keep core UI, override compare):

ts
{
  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 โ€‹

TypeUIMatching
enumMulti-select combobox with chips; allows custom stringsOR across values; exact match for known options, substring for custom values
dateOperator select + date picker chipsSame operators; compares calendar days against cell timestamps
datetimeOperator select + date and time pickersSame operators; compares timestamps (minute precision for eq)
numberOperator 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:

ts
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 โ€‹

filterDrawerAttachBehaviour
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
viewportFull-height drawer teleported to the viewport (body) with a fixed scrim
parentDrawer 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-header slots still work when grouping is active; when grouping collapses they are simply unused.
  • For selectable group headers, use BaseDataTableGroupSelectCheckbox.
vue
<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>

See also โ€‹