Skip to content

Table Components ​

FancyCRUD provides several components to build and customize tables. These components can be imported from @fancy-crud/vue and used individually or together.

vue
<script lang="ts" setup>
import { 
  FTable, 
  FTableBody, 
  FTableFooter, 
  FTableHeaderActions,
  FTableRowActions,
  FDeleteConfirmationModal 
} from '@fancy-crud/vue'
</script>

FTable ​

The main table container that orchestrates all table functionality. This is the primary component you'll use in most cases.

Props ​

NameDescriptionTypeRequired
idTable ID as symbol valuesymbolYes
columnsColumn definitionsObjectWithNormalizedColumnsYes
formForm configuration for create/edit operationsBaseTableFormYes
settingsTable settings (URL, lookup field, etc.)NormalizedTableSettingsYes
paginationPagination configurationNormalizedTablePaginationYes
filterParamsFilter parameters for data fetchingNormalizedTableFiltersYes
buttonsButton configurations (add, edit, remove, etc.)NormalizedTableButtonsYes
listList data and fetching methodsNormalizedTableListYes

Usage ​

vue
<template>
  <!-- Expanded way to pass props -->
  <f-table
    :id="table.id"
    :columns="table.columns"
    :form="table.form"
    :settings="table.settings"
    :pagination="table.pagination"
    :filterParams="table.filterParams"
    :buttons="table.buttons"
    :list="table.list"
  />

  <!-- Shortcut to pass all props -->
  <f-table v-bind="table" />
</template>

<script lang="ts" setup>
import { useForm, useTable } from '@fancy-crud/vue'

const form = useForm({
  fields: {
    name: { type: 'text', label: 'Name' },
    email: { type: 'text', label: 'Email' },
  },
  settings: { url: 'users/' }
})

const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
    email: { label: 'Email' },
  },
  settings: { url: 'users/' }
})
</script>

Slots ​

NameDescriptionScope
table-headerCustomize the entire header section{ onCreate, onExport, add, dump }
table-bodyReplace the entire table body{ items, headers, loading }
table-footerCustomize pagination and footer{ pagination }
table-formCustomize the form dialog{ form, id, onSuccess }
delete-confirmation-modalCustomize delete confirmation dialog{ accept, cancel }
column-{columnName}Customize individual column display{ row, item, index }
column-actionsReplace the entire actions column{ row, item }
column-actions-prependAdd actions before edit/delete buttons{ row, item }
column-actions-appendAdd actions after edit/delete buttons{ row, item }
create-buttonCustomize the create button in header-
export-buttonCustomize the export button in header-
prependAdd content before header buttons-
appendAdd content after header buttons-
edit-buttonCustomize the edit button in row actions-
delete-buttonCustomize the delete button in row actions-

Slot Examples ​

Custom Column Rendering ​

vue
<template>
  <f-table v-bind="table">
    <template #column-status="{ row }">
      <v-chip :color="row.status === 'active' ? 'success' : 'error'">
        {{ row.status }}
      </v-chip>
    </template>
    
    <template #column-email="{ row }">
      <a :href="`mailto:${row.email}`">{{ row.email }}</a>
    </template>
  </f-table>
</template>

Custom Actions ​

vue
<template>
  <f-table v-bind="table">
    <template #column-actions-prepend="{ row }">
      <v-btn
        icon="mdi-eye"
        size="small"
        @click="viewDetails(row)"
      />
    </template>
    
    <template #column-actions-append="{ row }">
      <v-btn
        icon="mdi-download"
        size="small"
        @click="downloadReport(row)"
      />
    </template>
  </f-table>
</template>

Custom Header ​

vue
<template>
  <f-table v-bind="table">
    <template #table-header="{ onCreate, add }">
      <div class="custom-header">
        <h2>User Management</h2>
        <v-btn @click="onCreate" v-bind="add">
          Add New User
        </v-btn>
      </div>
    </template>
  </f-table>
</template>

FTableHeaderActions ​

Component that renders the header action buttons (Create and Export).

Props ​

NameDescriptionTypeRequired
addAdd button configNormalizedTableButtonNo
dumpExport button configNormalizedTableButtonNo

Events ​

NameDescriptionType
@createEmitted when create is clicked() => void
@exportEmitted when export is clicked() => void

Slots ​

NameDescription
prependContent before create/export buttons
create-buttonCustom create button
export-buttonCustom export button
appendContent after create/export buttons

Usage ​

vue
<template>
  <f-table-header-actions
    :add="table.buttons.add"
    :dump="table.buttons.dump"
    @create="handleCreate"
    @export="handleExport"
  >
    <template #prepend>
      <v-btn icon="mdi-filter">Filters</v-btn>
    </template>
    
    <template #append>
      <v-btn icon="mdi-refresh" @click="refresh">Refresh</v-btn>
    </template>
  </f-table-header-actions>
</template>

FTableRowActions ​

Component that renders row action buttons (Edit and Delete).

Props ​

NameDescriptionTypeRequired
editEdit button configNormalizedTableButtonNo
deleteDelete button configNormalizedTableButtonNo

Events ​

NameDescriptionType
@editEmitted when edit is clicked() => void
@deleteEmitted when delete is clicked() => void

Slots ​

NameDescription
edit-buttonCustom edit button
delete-buttonCustom delete button

Usage ​

vue
<template>
  <f-table-row-actions
    :edit="table.buttons.edit"
    :delete="table.buttons.remove"
    @edit="handleEdit(row)"
    @delete="handleDelete(row)"
  >
    <template #edit-button>
      <v-btn icon="mdi-pencil" size="small" color="primary" />
    </template>
    
    <template #delete-button>
      <v-btn icon="mdi-trash-can" size="small" color="error" />
    </template>
  </f-table-row-actions>
</template>

FTableBody ​

Component that renders the table body with data rows.

Props ​

NameDescriptionTypeRequired
itemsArray of data rowsany[]Yes
headersColumn definitionsNormalizedColumn[]Yes
loadingLoading statebooleanYes
buttonsButton configurationsNormalizedTableButtonsYes

Events ​

NameDescriptionType
@editEmitted when edit is clicked(row: any) => void
@deleteEmitted when delete is clicked(row: any) => void

Slots ​

NameDescriptionScope
column-{columnName}Customize individual column{ row, item, index }
column-actionsReplace entire actions column{ row, item }
column-actions-prependAdd actions before edit/delete{ row, item }
column-actions-appendAdd actions after edit/delete{ row, item }

Usage ​

vue
<template>
  <f-table-body
    :items="table.list.data"
    :headers="headers"
    :loading="table.list.isFetching"
    :buttons="table.buttons"
    @edit="handleEdit"
    @delete="handleDelete"
  >
    <template #column-name="{ row }">
      <strong>{{ row.name }}</strong>
    </template>
  </f-table-body>
</template>

FTableFooter ​

Component that renders pagination controls.

Props ​

NameDescriptionTypeRequired
paginationPagination configurationNormalizedTablePaginationYes

Events ​

NameDescriptionType
@update:paginationEmitted when pagination changes(pagination: Pagination) => void

Usage ​

vue
<template>
  <f-table-footer
    :pagination="table.pagination"
    @update:pagination="handlePaginationUpdate"
  />
</template>

<script setup lang="ts">
const handlePaginationUpdate = (pagination: Pagination) => {
  console.log('Page:', pagination.page)
  console.log('Rows per page:', pagination.rowsPerPage)
}
</script>

FDeleteConfirmationModal ​

Component that displays a confirmation dialog before deleting records.

Props ​

NameDescriptionTypeRequired
modelValueControls modal visibilitybooleanYes

Events ​

NameDescriptionType
@update:modelValueEmitted when modal is closed(value: boolean) => void
@acceptEmitted when user confirms delete() => void
@cancelEmitted when user cancels() => void

Slots ​

NameDescriptionScope
defaultCustom modal content{ accept, cancel }

Usage ​

vue
<template>
  <f-delete-confirmation-modal
    v-model="showDeleteDialog"
    @accept="confirmDelete"
    @cancel="cancelDelete"
  >
    <template #default="{ accept, cancel }">
      <v-card>
        <v-card-title>Confirm Delete</v-card-title>
        <v-card-text>
          Are you sure you want to delete this record?
        </v-card-text>
        <v-card-actions>
          <v-btn @click="cancel">Cancel</v-btn>
          <v-btn color="error" @click="accept">Delete</v-btn>
        </v-card-actions>
      </v-card>
    </template>
  </f-delete-confirmation-modal>
</template>

<script setup lang="ts">
const showDeleteDialog = ref(false)

const confirmDelete = () => {
  // Perform delete operation
  showDeleteDialog.value = false
}

const cancelDelete = () => {
  showDeleteDialog.value = false
}
</script>

Complete Custom Example ​

Here's an example using multiple components together with customization:

vue
<template>
  <div class="user-management">
    <!-- Custom Header -->
    <f-table-header-actions
      :add="table.buttons.add"
      :dump="table.buttons.dump"
      @create="table.buttons.add.onClick"
      @export="table.buttons.dump.onClick"
    >
      <template #prepend>
        <h2>Users</h2>
      </template>
      
      <template #append>
        <v-btn
          icon="mdi-refresh"
          @click="table.list.fetchData()"
        />
      </template>
    </f-table-header-actions>

    <!-- Custom Table Body -->
    <f-table-body
      :items="table.list.data"
      :headers="headers"
      :loading="table.list.isFetching"
      :buttons="table.buttons"
      @edit="table.buttons.edit.onClick"
      @delete="table.buttons.remove.onClick"
    >
      <!-- Custom columns -->
      <template #column-status="{ row }">
        <v-chip :color="getStatusColor(row.status)">
          {{ row.status }}
        </v-chip>
      </template>
      
      <!-- Custom actions -->
      <template #column-actions-prepend="{ row }">
        <v-btn
          icon="mdi-email"
          size="small"
          @click="sendEmail(row)"
        />
      </template>
    </f-table-body>

    <!-- Custom Footer -->
    <f-table-footer
      :pagination="table.pagination"
      @update:pagination="handlePagination"
    />

    <!-- Form Dialog -->
    <v-dialog v-model="table.settings.displayFormDialog">
      <f-form
        v-bind="table.form"
        @success="handleFormSuccess"
      />
    </v-dialog>

    <!-- Delete Confirmation -->
    <f-delete-confirmation-modal
      v-model="table.settings.displayConfirmationDialog"
      @accept="confirmDelete"
      @cancel="cancelDelete"
    />
  </div>
</template>

<script setup lang="ts">
import { useForm, useTable } from '@fancy-crud/vue'

const form = useForm({
  fields: {
    name: { type: 'text', label: 'Name' },
    email: { type: 'text', label: 'Email' },
    status: { type: 'select', label: 'Status' },
  },
  settings: { url: 'users/' }
})

const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
    email: { label: 'Email' },
    status: { label: 'Status' },
  },
  settings: { url: 'users/' }
})

const headers = computed(() => 
  Object.values(table.columns).filter(col => !col.exclude)
)

const getStatusColor = (status: string) => {
  const colors: Record<string, string> = {
    active: 'success',
    inactive: 'error',
    pending: 'warning',
  }
  return colors[status] || 'default'
}

const sendEmail = (row: any) => {
  console.log('Sending email to:', row.email)
}

const handleFormSuccess = () => {
  table.list.fetchData()
  table.settings.displayFormDialog = false
}

const handlePagination = (pagination: Pagination) => {
  Object.assign(table.pagination, pagination)
}

const confirmDelete = () => {
  table.buttons.remove.onClick()
}

const cancelDelete = () => {
  table.settings.displayConfirmationDialog = false
  table.settings.rowToDelete = null
}
</script>

Best Practices ​

1. Use FTable for Most Cases ​

The FTable component handles everything automatically. Use individual components only when you need fine-grained control.

vue
<!-- ✅ Good - Simple and complete -->
<f-table v-bind="table" />

<!-- ❌ Only if you need custom layout -->
<f-table-header-actions ... />
<f-table-body ... />
<f-table-footer ... />

2. Use Slots for Customization ​

Instead of using individual components, use slots within FTable:

vue
<!-- ✅ Good - Use slots -->
<f-table v-bind="table">
  <template #column-status="{ row }">
    <v-chip>{{ row.status }}</v-chip>
  </template>
</f-table>

3. Keep Component Props Reactive ​

Always bind reactive data to component props:

vue
<!-- ✅ Good -->
<f-table-body
  :items="table.list.data"
  :loading="table.list.isFetching"
/>

Next Steps ​