Skip to content

Tables ​

Tables in FancyCRUD provide a powerful and flexible way to display, manage, and interact with data. With built-in support for pagination, filtering, sorting, CRUD operations, and custom actions, FancyCRUD tables handle the complexity of data management while giving you complete control over appearance and behavior.

Overview ​

FancyCRUD tables offer:

  • Automatic Column Generation: Columns are inferred from your form fields
  • Built-in CRUD Operations: Create, read, update, and delete records with minimal configuration
  • Integrated Forms: Seamlessly connect forms for create/edit operations
  • Pagination: Built-in pagination with customizable page sizes
  • Filtering: Server-side and client-side filtering support
  • Sorting: Column-based sorting capabilities
  • Actions Column: Customizable action buttons (edit, delete, custom actions)
  • Custom Buttons: Add toolbar buttons for exports, bulk operations, etc.
  • Responsive: Works with any UI framework (Vuetify, Element Plus, Quasar, Oruga)

Quick Start ​

The simplest way to create a table is by using the useTable composable with a form:

vue
<template>
  <f-table v-bind="table" />
</template>
  
<script setup lang='ts'>
import { FieldType, useForm, useTable } from '@fancy-crud/vue';

// The next form will be used to create/edit records
const form = useForm({
  fields: {
    firstName: {
      type: FieldType.text,
      label: 'First Name'
    },
    lastName: {
      type: FieldType.text,
      label: 'Last Name' 
    },
    age: {
      type: FieldType.text,
      label: 'Age'
    }
  },
  settings: {
    url: '/api/users/',
  }
})

// The next table will be used to display the records
// and to perform actions on them
const table = useTable({
  // The form to create/edit records
  form,

  // Columns are going to be automatically inferred from the form
  // but you can override them here and add more
  columns: {
    // You need to specify the actions column
    // if you want to perform actions on the records
    actions: {}
  },
  settings: {
    // The url to fetch the records and/or delete them
    url: '/api/users/',
  },
  buttons: {
    // The button to export the records.
    // This functionality is not implemented by default
    // but you can add it here
    dump: {
      onClick() {
        alert('Exporting data...')
      }
    }
  },
})
</script>
First Name
Last Name
Age

TIP

The table automatically performs a GET request to fetch data and maps columns to object keys in the response data array.

How It Works ​

1. API Request ​

The table automatically sends a GET request to the endpoint specified in settings.url:

GET /api/users/

2. Expected Response Format ​

Your API should return data in this format:

json
{
  "data": [
    { 
      "id": "84e1ea10-f482-4fd4-a2bf-ca075e27ad6b", 
      "firstName": "John", 
      "lastName": "Doe", 
      "age": 25 
    },
    { 
      "id": "2b1f0f58-f207-4b2f-9ddb-4f3f4feb6e07", 
      "firstName": "Jane", 
      "lastName": "Doe", 
      "age": 24 
    },
    { 
      "id": "eeff9587-27a3-40e5-a01d-4042b0d05455", 
      "firstName": "Jim", 
      "lastName": "Doe", 
      "age": 23 
    },
    { 
      "id": "906aef41-0d5d-4579-9050-27c4121ac6df", 
      "firstName": "Jill", 
      "lastName": "Doe", 
      "age": 22 
    }
  ]
}

Response Format

The response must have a data property containing an array of records. Each record should be an object with properties matching your column keys.

3. Column Mapping ​

FancyCRUD automatically maps columns to object keys:

  • Form field firstName → Table column displays record.firstName
  • Form field lastName → Table column displays record.lastName
  • Form field age → Table column displays record.age

Core Concepts ​

useTable Composable ​

The useTable composable is the primary way to create tables:

typescript
const table = useTable({
  form,        // Required: Form instance for create/edit
  columns,     // Optional: Column definitions (inferred from form)
  settings,    // Required: Table settings (URL, pagination, etc.)
  buttons,     // Optional: Toolbar buttons
  filters      // Optional: Filter definitions
})

Form Integration ​

Tables are tightly integrated with forms:

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

// 1. Define the form
const form = useForm({
  fields: {
    name: { type: FieldType.text, label: 'Name' },
    email: { type: FieldType.text, label: 'Email' },
    status: { type: FieldType.select, label: 'Status', options: ['Active', 'Inactive'] }
  },
  settings: {
    url: 'users/'
  }
})

// 2. Create the table with the form
const table = useTable({
  form,
  columns: {
    actions: {}  // Add actions column for edit/delete buttons
  },
  settings: {
    url: 'users/'
  }
})
</script>

Automatic Column Inference ​

Columns are automatically inferred from form fields:

typescript
// Form has fields: name, email, status
const form = useForm({
  fields: {
    name: { type: FieldType.text, label: 'Name' },
    email: { type: FieldType.text, label: 'Email' },
    status: { type: FieldType.select, label: 'Status' }
  }
})

// Table automatically creates columns for name, email, and status
const table = useTable({
  form,
  columns: {
    // These columns are automatically created:
    // - name (from form.fields.name)
    // - email (from form.fields.email)
    // - status (from form.fields.status)
    
    // You only need to add columns that aren't in the form:
    actions: {}
  }
})

Complete Example ​

Here's a comprehensive example with all common features:

vue
<template>
  <div class="page-container">
    <h1>User Management</h1>
    
    <f-table v-bind="table" />
  </div>
</template>

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

const router = useRouter()

// Define the form for create/edit operations
const form = useForm({
  fields: {
    firstName: {
      type: FieldType.text,
      label: 'First Name',
      required: true,
      rules: (value) => !!value || 'First name is required'
    },
    lastName: {
      type: FieldType.text,
      label: 'Last Name',
      required: true,
      rules: (value) => !!value || 'Last name is required'
    },
    email: {
      type: FieldType.text,
      label: 'Email',
      required: true,
      rules: (value) => {
        if (!value) return 'Email is required'
        if (!/\S+@\S+\.\S+/.test(value)) return 'Invalid email format'
        return true
      }
    },
    age: {
      type: FieldType.text,
      label: 'Age',
      rules: (value) => {
        if (!value) return true
        const age = parseInt(value)
        if (isNaN(age)) return 'Age must be a number'
        if (age < 0 || age > 120) return 'Invalid age'
        return true
      }
    },
    status: {
      type: FieldType.select,
      label: 'Status',
      options: ['Active', 'Inactive', 'Pending'],
      required: true
    }
  },
  settings: {
    url: 'users/',
    mode: FORM_MODE.create,
    title: '{{ Create User | Edit User }}'
  }
})

// Create the table
const table = useTable({
  form,
  
  // Define columns
  columns: {
    // Inherited from form: firstName, lastName, email, age, status
    
    // Add ID column (not in form)
    id: {
      label: 'ID',
      sortable: true
    },
    
    // Customize inherited columns
    email: {
      label: 'Email Address',
      sortable: true
    },
    
    age: {
      label: 'Age',
      align: 'center',
      sortable: true
    },
    
    // Add actions column
    actions: {
      label: 'Actions',
      align: 'center'
    }
  },
  
  // Table settings
  settings: {
    url: 'users/',
    lookupField: 'id'
  },
  
  // Custom toolbar buttons
  buttons: {
    // Export button
    export: {
      label: 'Export CSV',
      onClick: () => {
        console.log('Exporting data...')
        // Implement export logic
      }
    },
    
    // Refresh button
    refresh: {
      label: 'Refresh',
      onClick: () => {
        table.list.fetch()
      }
    }
  }
})

// Watch for successful form submission
watch(() => form.settings.mode, (newMode) => {
  if (newMode === FORM_MODE.create) {
    console.log('Switched to create mode')
  }
})
</script>

<style scoped>
.page-container {
  padding: 2rem;
  max-width: 1200px;
  margin: 0 auto;
}

h1 {
  margin-bottom: 2rem;
  color: #333;
}
</style>

Common Patterns ​

Basic CRUD Table ​

Minimal setup for standard CRUD operations:

vue
<template>
  <f-table v-bind="table" />
</template>

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

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

const table = useTable({
  form,
  columns: { actions: {} },
  settings: { url: 'users/' }
})
</script>

Read-Only Table ​

Display data without edit/delete actions:

vue
<script lang="ts" setup>
const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
    email: { label: 'Email' },
    createdAt: { label: 'Created At' }
    // No actions column = read-only
  },
  settings: { url: 'users/' }
})
</script>

Table with Custom Actions ​

Add custom action buttons:

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

<script lang="ts" setup>
const viewDetails = (row: any) => {
  router.push(`/users/${row.id}`)
}
</script>

Table with Filters ​

Add filtering capabilities:

vue
<script lang="ts" setup>
const table = useTable({
  form,
  columns: { actions: {} },
  settings: { url: 'users/' },
  filters: {
    status: {
      type: FieldType.select,
      label: 'Status',
      options: ['Active', 'Inactive']
    },
    search: {
      type: FieldType.text,
      label: 'Search',
      placeholder: 'Search by name or email'
    }
  }
})
</script>

Key Features Explained ​

1. Automatic Data Fetching ​

Tables automatically fetch data on mount and refresh when needed:

typescript
// Fetch happens automatically
const table = useTable({ form, settings: { url: 'users/' } })

// Manual refresh
table.list.fetch()

2. Integrated Create/Edit ​

Clicking "Create" or "Edit" buttons automatically:

  • Opens a form dialog
  • Switches form mode (create/edit)
  • Loads record data (edit mode)
  • Refreshes table after save

3. Delete Confirmation ​

Delete button shows a confirmation dialog by default:

typescript
settings: {
  url: 'users/',
  skipDeleteConfirmation: false  // Shows confirmation (default)
}

4. Pagination ​

Built-in pagination with customizable options:

typescript
// Pagination is automatic
// Configure page size in table settings

5. Responsive Actions ​

Action buttons adapt to your UI framework:

  • Vuetify: Icon buttons with tooltips
  • Element Plus: Compact button group
  • Quasar: Q-btn with icons
  • Oruga: Icon buttons

API Integration ​

Backend Requirements ​

Your API should support these operations:

OperationMethodEndpointPurpose
ListGET/users/Fetch records with pagination
CreatePOST/users/Create a new record
ReadGET/users/:id/Fetch single record for editing
UpdatePATCH/users/:id/Update existing record
DeleteDELETE/users/:id/Delete a record

Request/Response Examples ​

List (GET /users/)

json
// Response
{
  "data": [
    { "id": 1, "name": "John", "email": "john@example.com" },
    { "id": 2, "name": "Jane", "email": "jane@example.com" }
  ],
  "total": 100,
  "page": 1,
  "pageSize": 10
}

Create (POST /users/)

json
// Request
{ "name": "John", "email": "john@example.com" }

// Response
{
  "data": { "id": 3, "name": "John", "email": "john@example.com" }
}

Update (PATCH /users/3/)

json
// Request
{ "name": "John Doe", "email": "john.doe@example.com" }

// Response
{
  "data": { "id": 3, "name": "John Doe", "email": "john.doe@example.com" }
}

Delete (DELETE /users/3/)

json
// Response
{ "success": true }

Best Practices ​

Table Best Practices

  1. Keep URLs Consistent: Use the same base URL for form and table
  2. Add Actions Column: Always include actions: {} for CRUD operations
  3. Validate Data: Add validation rules to form fields
  4. Handle Errors: Implement response interceptors for error handling
  5. Use Lookup Fields: Configure lookupField if your ID field isn't named id
  6. Customize Columns: Override inferred columns to add sorting, alignment, etc.
  7. Provide Feedback: Use toast notifications for user actions

Common Pitfalls

  • Missing Actions Column: Without it, users can't edit or delete records
  • URL Mismatch: Form and table URLs should match for proper CRUD flow
  • No Error Handling: Always handle API errors gracefully
  • Wrong Response Format: Ensure API returns { data: [...] } format
  • Missing Lookup Field: Configure if using custom ID field names

Next Steps

Now that you understand the basics, explore advanced features:

Troubleshooting ​

Table Not Showing Data ​

Check:

  1. API endpoint is correct in settings.url
  2. API returns { data: [...] } format
  3. Column keys match object properties in data
  4. No console errors in browser DevTools

Edit/Delete Not Working ​

Check:

  1. actions column is defined in columns
  2. lookupField matches your ID field name
  3. Form settings.url matches table settings.url
  4. API supports PATCH (update) and DELETE methods

Form Not Opening ​

Check:

  1. Form is properly defined and passed to useTable
  2. form.settings.url is configured
  3. No JavaScript errors in console

TypeScript Support ​

FancyCRUD provides full TypeScript support:

typescript
import type { TableInstance, FormInstance } from '@fancy-crud/vue'

// Typed table instance
const table: TableInstance = useTable({
  form,
  columns: { actions: {} },
  settings: { url: 'users/' }
})

// Typed row data
interface User {
  id: string
  firstName: string
  lastName: string
  email: string
  age: number
}

// Use in custom actions
const viewUser = (row: User) => {
  console.log('Viewing user:', row.firstName)
}

Next Steps ​

Continue learning about FancyCRUD tables:

  • Columns: Learn how to define and customize table columns
  • Buttons: Add custom toolbar buttons for exports and actions
  • Settings: Configure table behavior and appearance
  • Filters: Implement filtering and search functionality
  • Pagination: Customize pagination behavior
  • List Management: Understand data fetching and management
  • Custom Components: Build custom table layouts