Skip to content

Table Buttons ​

FancyCRUD tables provide built-in buttons for common operations and allow you to customize their behavior and appearance. You can configure four default buttons (add, edit, remove, dump) and add custom buttons.

Default Buttons ​

Tables come with four built-in button types:

  • add - Opens the form dialog to create a new record
  • edit - Opens the form dialog to edit an existing record
  • remove - Deletes a record (with confirmation dialog by default)
  • dump - Exports/dumps table data
vue
<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/' },
  buttons: {
    add: {
      label: 'Create User',
    },
    edit: {
      label: 'Edit',
    },
    remove: {
      label: 'Delete',
    },
    dump: {
      label: 'Export',
    },
  },
})
</script>

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

Button Properties ​

Label ​

ButtonNameTypeDefault
addlabelstring"Create"
editlabelstring"Edit"
removelabelstring"Delete"
dumplabelstring"Export"

The button label is the text displayed on the button:

vue
<script lang="ts" setup>
const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
  },
  settings: { url: 'users/' },
  buttons: {
    add: {
      label: 'Add New User',
    },
    edit: {
      label: 'Modify',
    },
    remove: {
      label: 'Remove',
    },
  },
})
</script>

Class ​

NameTypeDefault
classstring(Depends on the selected UI wrapper)

Add custom CSS classes to buttons:

vue
<script lang="ts" setup>
const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
  },
  settings: { url: 'users/' },
  buttons: {
    add: {
      class: 'bg-blue-500 hover:bg-blue-700',
    },
    remove: {
      class: 'bg-red-500 hover:bg-red-700',
    },
  },
})
</script>

Loading State ​

NameTypeDefault
isLoadingbooleanfalse

Show a loading indicator on buttons during async operations:

vue
<script lang="ts" setup>
const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
  },
  settings: { url: 'users/' },
  buttons: {
    add: {
      isLoading: false, // Set to true to show loading state
    },
  },
})

// Control loading state programmatically
const exportData = async () => {
  table.buttons.dump.isLoading = true
  try {
    await performExport()
  } finally {
    table.buttons.dump.isLoading = false
  }
}
</script>

Hidden ​

NameTypeDefault
hiddenbooleanfalse

Hide buttons from the UI:

vue
<script lang="ts" setup>
const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
  },
  settings: { url: 'users/' },
  buttons: {
    add: {
      hidden: false, // Show add button
    },
    edit: {
      hidden: false, // Show edit button
    },
    remove: {
      hidden: true, // Hide delete button
    },
    dump: {
      hidden: true, // Hide export button
    },
  },
})
</script>

TIP

You can conditionally hide buttons based on user permissions or other conditions:

ts
buttons: {
  remove: {
    hidden: !currentUser.isAdmin
  }
}

Button Events ​

Add Button onClick ​

The add button opens the form dialog in create mode:

vue
<script lang="ts" setup>
const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
  },
  settings: { url: 'users/' },
  buttons: {
    add: {
      onClick: () => {
        console.log('Creating new record')
        // Custom logic before opening form
        // The default behavior (opening form dialog) still executes
      },
    },
  },
})
</script>

Edit Button onClick ​

The edit button receives the row data as a parameter:

vue
<script lang="ts" setup>
const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
  },
  settings: { url: 'users/' },
  buttons: {
    edit: {
      onClick: (row) => {
        console.log('Editing:', row)
        // Custom logic before opening edit form
      },
    },
  },
})
</script>

Remove Button onClick ​

The remove button receives the row data and triggers deletion:

vue
<script lang="ts" setup>
const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
  },
  settings: { url: 'users/' },
  buttons: {
    remove: {
      onClick: (row) => {
        console.log('Deleting:', row)
        // Custom logic before/instead of deletion
      },
    },
  },
})
</script>

Dump Button onClick ​

The dump button can be used for exporting data:

vue
<script lang="ts" setup>
const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
  },
  settings: { url: 'users/' },
  buttons: {
    dump: {
      onClick: async () => {
        table.buttons.dump.isLoading = true
        try {
          const response = await axios.get('users/export')
          downloadFile(response.data)
        } finally {
          table.buttons.dump.isLoading = false
        }
      },
    },
  },
})
</script>

Custom Buttons ​

You can add custom buttons beyond the default ones:

vue
<script lang="ts" setup>
const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
  },
  settings: { url: 'users/' },
  buttons: {
    // Default buttons
    add: { label: 'Create' },
    edit: { label: 'Edit' },
    remove: { label: 'Delete' },
    
    // Custom buttons
    archive: {
      label: 'Archive',
      class: 'btn-warning',
      onClick: (row) => {
        console.log('Archiving:', row)
      },
    },
    duplicate: {
      label: 'Duplicate',
      onClick: (row) => {
        console.log('Duplicating:', row)
      },
    },
  },
})
</script>

Then use them in your template with custom slots:

vue
<template>
  <f-table v-bind="table">
    <template #column-actions-append="{ row }">
      <v-btn
        v-bind="table.buttons.archive"
        @click="table.buttons.archive.onClick(row)"
      >
        {{ table.buttons.archive.label }}
      </v-btn>
      
      <v-btn
        v-bind="table.buttons.duplicate"
        @click="table.buttons.duplicate.onClick(row)"
      >
        {{ table.buttons.duplicate.label }}
      </v-btn>
    </template>
  </f-table>
</template>

Programmatic Button Control ​

You can control buttons programmatically:

vue
<script lang="ts" setup>
const table = useTable({
  form,
  columns: {
    name: { label: 'Name' },
  },
  settings: { url: 'users/' },
})

// Call button actions programmatically
const createNewUser = () => {
  table.buttons.add.onClick()
}

const editUser = (user: any) => {
  table.buttons.edit.onClick(user)
}

const deleteUser = (user: any) => {
  table.buttons.remove.onClick(user)
}

// Toggle button visibility
const toggleDeleteButton = () => {
  table.buttons.remove.hidden = !table.buttons.remove.hidden
}

// Change button labels dynamically
const changeButtonLabel = () => {
  table.buttons.add.label = 'Add New Record'
}
</script>

<template>
  <div>
    <v-btn @click="createNewUser">Trigger Add</v-btn>
    <v-btn @click="toggleDeleteButton">Toggle Delete Button</v-btn>
    <v-btn @click="changeButtonLabel">Change Add Label</v-btn>
    <f-table v-bind="table" />
  </div>
</template>

Complete Example ​

Here's a complete example with all button configurations:

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

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/' },
  buttons: {
    add: {
      label: 'Create User',
      class: 'btn-primary',
      hidden: false,
      onClick: () => {
        console.log('Opening create form')
      },
    },
    edit: {
      label: 'Edit User',
      class: 'btn-secondary',
      hidden: false,
      onClick: (row) => {
        console.log('Editing user:', row)
      },
    },
    remove: {
      label: 'Delete User',
      class: 'btn-danger',
      hidden: false,
      onClick: (row) => {
        console.log('Deleting user:', row)
      },
    },
    dump: {
      label: 'Export Users',
      class: 'btn-info',
      hidden: false,
      isLoading: false,
      onClick: async () => {
        table.buttons.dump.isLoading = true
        try {
          const response = await axios.get('users/export')
          // Handle export
          console.log('Exported:', response.data)
        } finally {
          table.buttons.dump.isLoading = false
        }
      },
    },
    // Custom button
    sendEmail: {
      label: 'Send Email',
      class: 'btn-success',
      hidden: false,
      onClick: (row) => {
        console.log('Sending email to:', row.email)
      },
    },
  },
})
</script>

<template>
  <f-table v-bind="table">
    <!-- Custom button in actions column -->
    <template #column-actions-append="{ row }">
      <v-btn
        v-if="!table.buttons.sendEmail.hidden"
        :class="table.buttons.sendEmail.class"
        @click="table.buttons.sendEmail.onClick(row)"
      >
        {{ table.buttons.sendEmail.label }}
      </v-btn>
    </template>
  </f-table>
</template>

Default Button Behavior ​

Add Button ​

  • Opens the form dialog
  • Sets form mode to FORM_MODE.create
  • Clears any existing form data
  • Sets table.settings.displayFormDialog = true

Edit Button ​

  • Opens the form dialog
  • Sets form mode to FORM_MODE.update
  • Loads the selected row data into the form
  • Sets form.settings.lookupValue to the row's lookup field value
  • Sets table.settings.displayFormDialog = true

Remove Button ​

  • Shows confirmation dialog (unless skipDeleteConfirmation is true)
  • Sets table.settings.rowToDelete to the selected row
  • Executes DeleteRecordCommand on confirmation
  • Refreshes table data after successful deletion

Dump Button ​

  • No default behavior (requires custom implementation)
  • Typically used for exporting data

Best Practices ​

1. Use Hidden Instead of Conditional Rendering ​

ts
// ✅ Good - Dynamic visibility
buttons: {
  remove: {
    hidden: !userHasPermission('delete')
  }
}

// ❌ Avoid - Causes issues with FancyCRUD
// Don't conditionally define buttons

2. Handle Loading States ​

ts
buttons: {
  dump: {
    onClick: async () => {
      table.buttons.dump.isLoading = true
      try {
        await performExport()
      } finally {
        table.buttons.dump.isLoading = false
      }
    }
  }
}

3. Use Custom Buttons for Special Actions ​

ts
buttons: {
  // Keep default buttons
  add: { label: 'Create' },
  edit: { label: 'Edit' },
  remove: { label: 'Delete' },
  
  // Add custom buttons for specific needs
  approve: {
    label: 'Approve',
    onClick: (row) => approveRecord(row)
  }
}

4. Preserve Default Behavior ​

When overriding onClick, the default behavior still executes. To prevent this, you need to handle the complete flow:

ts
buttons: {
  edit: {
    onClick: (row) => {
      // Your custom logic here
      // Default behavior (opening form) will still execute after this
      console.log('Custom logic before edit')
    }
  }
}

Next Steps ​