Row Actions Documentation
The setActions() method configures action buttons that appear for each row in your table. Actions can be simple navigation links or complex server-side operations with callbacks.
Quick Reference: Action Methods
| Method | Parameters | Description | Behavior |
|---|---|---|---|
addAction() |
string $key, array $config |
Adds a single row action without removing existing actions | Preserves existing |
setActions() |
array $actions |
Sets all row actions at once | Replaces all |
setDefaultActions() |
array $customActions = [] |
Adds default Edit and Delete actions, preserving any previously added actions | Preserves existing + Adds defaults |
addBulkAction() |
array $config |
Adds a single bulk action for multiple row selection | Preserves existing |
setBulkActions() |
array $actions |
Sets all bulk actions at once | Replaces all |
Method Chaining Order
Recommended pattern: Use addAction() to add custom actions first, then call setDefaultActions() to append Edit and Delete buttons.
$table->addAction('view', [...])
->addAction('duplicate', [...])
->setDefaultActions(); // Adds Edit and Delete after custom actions
Action Types
Link Actions
Link actions create clickable buttons that navigate to another page. Perfect for Edit, View, or Detail operations.
$table = \Builders\TableBuilder::create($model, 'table_id')
->setActions([
'edit' => [
'label' => 'Edit',
'link' => '?page=posts&action=edit&id=%id%'
],
'view' => [
'label' => 'View Details',
'link' => '?page=posts&action=view&id=%id%',
'target' => '_blank',
]
]);
Adding Actions Incrementally
Use addAction() to add individual actions without replacing existing ones. This is useful when you want to add custom actions and then append default actions:
// Add custom actions first
$table = \Builders\TableBuilder::create($model, 'table_id')
->addAction('comment', [
'label' => 'Comments',
'link' => '?page=posts&action=comments&id=%id%'
])
->addAction('preview', [
'label' => 'Preview',
'link' => '/post/%slug%',
'target' => '_blank'
])
->setDefaultActions(); // Adds Edit and Delete after custom actions
// Result: Actions appear in order: Comments, Preview, Edit, Delete
Best Practice: Use addAction() when you want to preserve action order and combine custom actions with defaults. Use setActions() when you want complete control over all actions.
Callback Actions
Callback actions execute server-side functions when clicked. Ideal for operations like Delete, Publish, or Archive.
$table = \Builders\TableBuilder::create($model, 'table_id')
->setActions([
'delete' => [
'label' => 'Delete',
'action' => [$this, 'actionDelete'],
'confirm' => 'Are you sure you want to delete this item?',
'class' => 'link-action-danger'
],
'publish' => [
'label' => 'Publish Now',
'action' => [$this, 'actionPublish'],
]
])
->getResponse();
// Action handler method
public function actionDelete($record, $request) {
if ($record->delete($record->id)) {
return ['success' => true, 'message' => 'Item deleted successfully'];
}
return ['success' => false, 'message' => 'Delete failed'];
}
Configuration Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
label |
string | Yes | Button text displayed to user |
link |
string | No* | URL pattern with placeholders (e.g., %id%, %field_name%) |
action |
callable | No* | Callback function: [$this, 'methodName'] |
class |
string | No | CSS classes for styling (Bootstrap classes supported) link-action-danger for danger link |
target |
string | No | Link target: _blank |
confirm |
string | No | Confirmation message shown before action execution |
icon |
string | No | Bootstrap icon class (e.g., bi-x-circle, bi-pencil) |
showIfFilter |
array | No | Table-level visibility based on active filters (see below) |
condition |
callable | No | Row-level visibility condition that receives $row object (see below) |
* Either link or action must be specified
URL Placeholders
Link actions support dynamic placeholders that are replaced with actual row data:
| Placeholder | Description | Example |
|---|---|---|
%id% |
Primary key value | ?page=posts&id=%id% → ?page=posts&id=123 |
%field_name% |
Any column value | /view/%slug% → /view/my-post-slug |
%primary% |
Explicit primary key reference | ?action=edit&pk=%primary% |
// Examples with multiple placeholders
->setActions([
'edit' => [
'label' => 'Edit',
'link' => '?page=%category%&action=edit&id=%id%'
],
'preview' => [
'label' => 'Preview',
'link' => '/blog/%created_at%/%slug%',
'target' => '_blank'
]
])
Action Callbacks
Callback Signature
Action callback functions receive two parameters:
public function actionMethodName($record, $request) {
// $record - Model instance of the selected row
// $request - Full $_REQUEST array with all parameters
// Perform your operation
// Return array with status and optional data
return ['success' => true, 'message' => 'Operation completed'];
}
Return Values
Callback functions must return an associative array. Common keys:
| Key | Type | Description |
|---|---|---|
success |
boolean | Whether the operation succeeded |
message |
string | User feedback message |
reload |
boolean | Whether to reload the table (default: true) |
redirect |
string | URL to redirect after completion |
| Custom keys | mixed | Any additional data to pass to the view |
Example Callbacks
// Simple delete action
public function actionDelete($record, $request) {
if ($record->delete($record->id)) {
return [
'success' => true,
'message' => 'Item deleted successfully'
];
}
return [
'success' => false,
'message' => 'Delete failed: ' . $record->getLastError()
];
}
// Status change action
public function actionPublish($record, $request) {
$record->status = 'published';
$record->published_at = date('Y-m-d H:i:s');
if ($record->save()) {
return [
'success' => true,
'message' => 'Post published successfully',
'reload' => true
];
}
return [
'success' => false,
'message' => 'Publish failed',
'errors' => $record->getErrors()
];
}
// Action with redirect
public function actionClone($record, $request) {
$record->id = 0;
if ($record->save()) {
return [
'success' => true,
'message' => 'Item cloned successfully',
'redirect' => '?page=items&action=edit&id=' . $record->id
];
}
return ['success' => false, 'message' => 'Clone failed'];
}
Conditional Visibility with showIfFilter
The showIfFilter parameter creates context-aware interfaces by showing or hiding actions based on active table filters.
Basic Concept
Actions configured with showIfFilter are only visible when specific filter conditions are met. This keeps the UI clean and prevents users from performing inappropriate actions.
Syntax
'showIfFilter' => ['filter_name' => 'expected_value']
Behavior:
- Action is visible only when the specified filter is active
- Filter value must exactly match the expected value
- Currently supports single filter condition per action
- Works seamlessly with table filter changes (real-time update)
Practical Example: Status-Based Actions
// Setup filter in SearchBuilder
$searchBuilder = \Builders\SearchBuilder::create('idTablePosts')
->actionList('status', 'Filter by Status:', [
'active' => 'Active Posts',
'deleted' => 'Deleted Posts'
], 'active');
// Configure table with conditional actions
$tableBuilder = \Builders\TableBuilder::create($postModel, 'idTablePosts')
->filter('status', function($query, $value) {
if ($value === 'deleted') {
$query->where('deleted_at IS NOT NULL');
} else {
$query->where('deleted_at IS NULL');
}
}, 'active')
->setActions([
// Always visible
'edit' => [
'label' => 'Edit',
'link' => '?page=posts&action=edit&id=%id%'
],
// Only visible when viewing active posts
'delete' => [
'label' => 'Move to Trash',
'action' => [$this, 'actionSoftDelete'],
'confirm' => 'Move this post to trash?',
'class' => 'link-action-warning',
'showIfFilter' => ['status' => 'active']
],
// Only visible when viewing deleted posts
'restore' => [
'label' => 'Restore',
'action' => [$this, 'actionRestore'],
'class' => 'link-action-success',
'showIfFilter' => ['status' => 'deleted']
]
]);
How it works
When viewing Active Posts: Users see "Edit" and "Move to Trash"
When viewing Deleted Posts: Users see "Edit" and "Restore"
Filter Change: Actions update instantly when filter is changed
Implementation of Action Handlers
public function actionSoftDelete($record, $request) {
$record->deleted_at = date('Y-m-d H:i:s');
if ($record->save()) {
return ['success' => true, 'message' => 'Post moved to trash'];
}
return ['success' => false, 'message' => 'Failed to move to trash'];
}
public function actionRestore($record, $request) {
$record->deleted_at = null;
if ($record->save()) {
return ['success' => true, 'message' => 'Post restored successfully'];
}
return ['success' => false, 'message' => 'Restore failed'];
}
Row-Level Conditional Visibility with condition
The condition parameter controls action visibility for individual rows based on row data. Unlike showIfFilter which checks global table filters, condition evaluates each row independently.
How It Works
The condition parameter accepts a callable (function or closure) that:
- Receives the current
$rowobject as parameter - Has access to all row field values via
$row->field_name - Returns
trueto show the action,falseto hide it - Is evaluated separately for each row in the table
Basic Example
$table = \Builders\TableBuilder::create($model, 'table_id')
->setActions([
'edit' => [
'label' => 'Modifica',
'link' => '?page=frequenze&action=edit&id=%id%'
],
'ritira' => [
'label' => 'Ritira',
'link' => '?page=frequenze&action=ritira&id=%id%',
'class' => 'btn-warning btn-sm',
'icon' => 'bi-x-circle',
'condition' => function($row) {
// Show only if DATA_FINE_FREQ is empty (active enrollment)
return empty($row->DATA_FINE_FREQ);
}
],
'restore' => [
'label' => 'Ripristina',
'link' => '?page=frequenze&action=restore&id=%id%',
'class' => 'btn-success btn-sm',
'condition' => function($row) {
// Show only if DATA_FINE_FREQ is NOT empty (withdrawn enrollment)
return !empty($row->DATA_FINE_FREQ);
}
]
]);
In this example, each row will show different actions based on its data:
- Active enrollments (empty DATA_FINE_FREQ): Show "Modifica" and "Ritira"
- Withdrawn enrollments (filled DATA_FINE_FREQ): Show "Modifica" and "Ripristina"
Complex Conditions
->setActions([
'approve' => [
'label' => 'Approve',
'action' => [$this, 'actionApprove'],
'class' => 'btn-success btn-sm',
'condition' => function($row) {
// Show only if status is pending AND user has permission
return $row->status === 'pending' &&
$row->created_by !== auth()->id();
}
],
'cancel' => [
'label' => 'Cancel Order',
'action' => [$this, 'actionCancel'],
'confirm' => 'Cancel this order?',
'condition' => function($row) {
// Show only if order is recent and not shipped
$order_date = strtotime($row->created_at);
$is_recent = (time() - $order_date) < 86400; // 24 hours
return $is_recent && $row->shipping_status !== 'shipped';
}
]
]);
Difference Between showIfFilter and condition
Both showIfFilter and condition control action visibility, but they work at different levels and serve different purposes:
| Feature | showIfFilter |
condition |
|---|---|---|
| Scope | Entire table (all rows) | Individual row |
| Evaluates | Global table filters | Row field values |
| When Evaluated | Once when table loads (in ActionManager) | For each row during rendering |
| Parameter Type | Array: ['filter' => 'value'] |
Callable: function($row) |
| Access To | Active filter values | All row data via $row->field |
| Use Case | "Show Archive when viewing Active items" | "Show Withdraw for this specific active enrollment" |
Practical Comparison Example
// Setup filter
$searchBuilder = \Builders\SearchBuilder::create('enrollments_table')
->actionList('year', 'Filter by Year:', [
'2024' => 'Year 2024',
'2023' => 'Year 2023'
], '2024');
$table = \Builders\TableBuilder::create($model, 'enrollments_table')
->filter('year', function($query, $value) {
$query->where('ANNO_SCOL_FRQ = ?', [$value]);
}, '2024')
->setActions([
// Always visible
'edit' => [
'label' => 'Edit',
'link' => '?page=enrollments&action=edit&id=%id%'
],
// showIfFilter: Shows for ALL rows when year filter is 2024
'close_year' => [
'label' => 'Close Academic Year',
'action' => [$this, 'actionCloseYear'],
'showIfFilter' => ['year' => '2024']
// Visible for ALL 2024 enrollments (table-level)
],
// condition: Shows only for specific active rows
'withdraw' => [
'label' => 'Withdraw',
'link' => '?page=enrollments&action=withdraw&id=%id%',
'class' => 'btn-warning btn-sm',
'condition' => function($row) {
return empty($row->DATA_FINE_FREQ);
}
// Visible only for THIS row if it's active (row-level)
],
// Combined: Both conditions must be true
'transfer_2024' => [
'label' => 'Transfer to 2025',
'action' => [$this, 'actionTransfer'],
'showIfFilter' => ['year' => '2024'], // Only when viewing 2024
'condition' => function($row) { // AND only for active enrollments
return empty($row->DATA_FINE_FREQ);
}
]
]);
When to Use Which
Use showIfFilter when:
- Action relevance depends on the current TABLE VIEW (filter context)
- You want to show/hide actions for ALL rows at once
- Example: "Archive All" button only visible when viewing active items
Use condition when:
- Action relevance depends on INDIVIDUAL ROW DATA
- Different rows should show different actions
- Example: "Withdraw" button only for active enrollments, "Restore" only for withdrawn ones
Use both together when: Action requires both global context AND row-specific validation
Default Actions Helper
For common CRUD operations, use setDefaultActions() to automatically generate Edit and Delete actions. This method preserves any actions you've already added, making it easy to combine custom actions with standard CRUD operations.
// Simple default actions
$table = \Builders\TableBuilder::create($model, 'table_id')
->setDefaultActions();
// Generates:
// - Edit: links to ?page=current_page&action=edit&id=%id%
// - Delete: calls built-in delete handler with confirmation
// With custom page
$table = \Builders\TableBuilder::create($model, 'table_id')
->setPage('posts')
->setDefaultActions();
// Combine custom actions with defaults (RECOMMENDED)
$table = \Builders\TableBuilder::create($model, 'table_id')
->addAction('comment', [
'label' => 'Comments',
'link' => '?page=posts&action=comments&id=%id%'
])
->setDefaultActions();
// Result: Comment, Edit, Delete (in that order)
// Add additional actions via parameter
$table = \Builders\TableBuilder::create($model, 'table_id')
->setDefaultActions([
'view' => [
'label' => 'View',
'link' => '?page=posts&action=view&id=%id%',
'target' => '_blank'
]
]);
// Result: Edit, Delete, View (defaults first, then custom)
Important: Action Order
When you call setDefaultActions(), it adds Edit and Delete actions after any actions you've already added with addAction(). Actions passed as parameter to setDefaultActions() are added last.
Opening Offcanvas from Row Actions
You can open an offcanvas sidebar from a row action without writing any JavaScript. Simply return an offcanvas_end array in your action callback.
No JavaScript Required!
The framework automatically handles opening the offcanvas when your callback returns the special offcanvas_end key. This works for both row actions and bulk actions.
Basic Example: View Details in Offcanvas
// Controller
class TasksController extends AbstractController {
#[RequestAction('home')]
public function tasksList() {
$response = TableBuilder::create($this->model, 'tasks_table')
->setActions([
'view' => [
'label' => 'View Details',
'action' => [$this, 'actionViewDetails'],
'class' => 'btn btn-sm btn-info'
],
'edit' => [
'label' => 'Edit',
'link' => '?page=tasks&action=edit&id=%id%'
]
])
->getResponse(); // IMPORTANT: Use getResponse() with action callbacks!
Response::render(__DIR__ . '/Views/list_page.php', $response);
}
// Action callback - opens offcanvas automatically
public function actionViewDetails($record, $request) {
// Build HTML content for offcanvas
$html = '
' . _esc_html($record->title) . '
Status: ' . $record->status . '
Priority: ' . $record->priority . '
' . $record->description . '
';
// Return with offcanvas_end key
return [
'success' => true,
'offcanvas_end' => [
'title' => 'Task Details',
'body' => $html,
'size' => '' // Optional: 'xl' for extra large
]
];
}
}
Offcanvas Configuration Options
| Key | Type | Required | Description |
|---|---|---|---|
title |
string | Yes | Offcanvas header title |
body |
string (HTML) | Yes | HTML content to display in offcanvas body |
size |
string | No | Offcanvas width: '' (default) or 'xl' (extra large) |
Advanced Example: Edit Form in Offcanvas
// Controller
public function actionQuickEdit($record, $request) {
// Get record for editing
$data = $this->model->getByIdForEdit($record->id);
// Build form using FormBuilder
$form = FormBuilder::create($this->model)
->addFieldsFromObject($data, 'edit')
->removeField('created_at')
->removeField('updated_at')
->setActions([
'save' => [
'label' => 'Save',
'class' => 'btn btn-primary',
'action' => FormBuilder::saveAction('?page='.$this->page)
],
'cancel' => [
'label' => 'Cancel',
'type' => 'link',
'class' => 'btn btn-secondary',
'link' => 'javascript:window.offcanvasEnd.hide()'
]
])
->getForm();
return [
'success' => true,
'offcanvas_end' => [
'title' => 'Quick Edit: ' . $record->title,
'body' => $form,
'size' => 'xl' // Larger size for forms
]
];
}
Combining with Other Actions
You can mix offcanvas actions with regular actions and links:
->setActions([
'view' => [
'label' => 'Quick View',
'action' => [$this, 'actionViewInOffcanvas']
// Opens offcanvas when callback returns offcanvas_end
],
'edit' => [
'label' => 'Full Edit',
'link' => '?page=tasks&action=edit&id=%id%'
// Regular page navigation
],
'delete' => [
'label' => 'Delete',
'action' => [$this, 'actionDelete'],
'confirm' => 'Are you sure?'
// Regular callback with reload
]
])
How It Works
When a row action callback returns an array containing offcanvas_end, the framework:
- Automatically calls
window.offcanvasEnd.show() - Sets the title using
window.offcanvasEnd.title() - Sets the body HTML using
window.offcanvasEnd.body() - Optionally sets the size using
window.offcanvasEnd.size()
No custom JavaScript needed! The table component handles everything automatically.
Related Documentation
- TableBuilder Overview - Main table builder documentation
- Bulk Actions - Working with multiple rows at once (includes more offcanvas examples)
- Offcanvas Component - Manual offcanvas control with JavaScript
- SearchBuilder - Creating filters and search interfaces