Action Column
UX DataTables builds row actions from an Actions collection. The recommended entry point is AbstractDataTable::configureActions().
Recommended Usage with AbstractDataTable
use App\Entity\User;
use Pentiminax\UX\DataTables\Attribute\AsDataTable;
use Pentiminax\UX\DataTables\Column\NumberColumn;
use Pentiminax\UX\DataTables\Column\TextColumn;
use Pentiminax\UX\DataTables\Model\AbstractDataTable;
use Pentiminax\UX\DataTables\Model\Action;
use Pentiminax\UX\DataTables\Model\Actions;
#[AsDataTable(User::class)]
final class UsersDataTable extends AbstractDataTable
{
public function configureColumns(): iterable
{
yield NumberColumn::new('id', 'ID');
yield TextColumn::new('email', 'Email');
yield TextColumn::new('status', 'Status');
}
public function configureActions(Actions $actions): Actions
{
return $actions
->setColumnLabel('Actions')
->add(
Action::edit('Edit')
->setClassName('btn btn-sm btn-warning')
->icon('bi bi-pencil')
)
->add(
Action::delete('Delete')
->setClassName('btn btn-sm btn-danger')
->icon('bi bi-trash')
->askConfirmation('Delete this user?')
->displayIf('status', 'draft')
);
}
protected function mapRow(mixed $item): array
{
/** @var User $item */
return [
'id' => $item->getId(),
'email' => $item->getEmail(),
'status' => $item->getStatus(),
];
}
}
When configureActions() returns at least one action, AbstractDataTable automatically appends an ActionColumn named actions.
Action API
Factory Methods
| Method | Description |
|---|---|
| ``Action::delete($label = 'Delete', $className = 'btn btn-danger')`` | Create the built-in delete action |
| ``Action::detail($label = 'Detail', $className = 'btn btn-primary')`` | Create a link action to a detail page |
| ``Action::edit($label = 'Edit', $className = 'btn btn-warning')`` | Create an inline edit action (opens the configured edit modal) |
Fluent Configuration
| Method | Description |
|---|---|
| `label(string $label)` | Override the button label |
| `setClassName(string $className)` | Customize button classes |
| `icon(string $icon)` | Render an icon before the label |
| `htmlAttributes(array $htmlAttributes)` | Set extra HTML attributes on the rendered action button/link |
| `askConfirmation(string $message)` | Display a browser confirmation prompt before the action |
| `displayIf(string $field, mixed $value)` | Show the action only when `row[field] === value` |
| `setIdField(string $idField)` | Use another row key instead of `id` |
| `setEntityClass(string $entityClass)` | Set the Doctrine entity class explicitly |
| `linkToUrl(string|callable $url)` | Set the URL for detail actions (static string or per-row callable) |
| `collapsible(string $template, array $parameters = [])` | Render a `detail` action as an expand/collapse control that lazily loads a Twig child row (the template receives the row as `entity`, plus any extra `$parameters`). Only meaningful for `Action::detail()` |
| `permission(string $attribute, ?callable $subjectResolver = null)` | Restrict the action with a Symfony security attribute (role/voter/expression). Without a resolver it is evaluated once before serialization; with a resolver it is evaluated per row (the resolver receives the raw source row). Denied actions are removed and never serialized to the client |
| `position(?ActionsPosition $position)` | Override the column placement for this action only (`null` inherits the `Actions` collection position). See [Column Position & Alignment](#column-position--alignment) |
When linkToUrl() receives a callable, the argument depends on how the row was produced:
AbstractDataTable+ provider orsetData($objects): the callable receives the original source object- manual
DataTable::data([...])inline rows: the callable receives the row array
Actions API
| Method | Description |
|---|---|
| `add(Action $action)` | Register an action for the column |
| `remove(ActionType $type)` | Remove a previously registered action |
| `setColumnLabel(string $label)` | Customize the action column header |
| `position(ActionsPosition $position)` | Place the actions column before or after the data columns (default: `AfterColumns`) |
| `alignment(ActionsAlignment $alignment)` | Horizontally align the action cell. See [Column Position & Alignment](#column-position--alignment) |
Actions stores one action per ActionType. The public enum values are:
use Pentiminax\UX\DataTables\Enum\ActionType;
ActionType::Delete; // value: DELETE
ActionType::Detail; // value: DETAIL
ActionType::Edit; // value: EDIT
Column Position & Alignment
By default, the action column is appended after the data columns. You can move it, align its content, and even split actions across two dedicated columns.
Collection-level position
Use Actions::position() to place the whole actions column before or after the data
columns:
use Pentiminax\UX\DataTables\Enum\ActionsPosition;
use Pentiminax\UX\DataTables\Model\Actions;
public function configureActions(Actions $actions): Actions
{
return $actions
->position(ActionsPosition::BeforeColumns)
->add(Action::edit('Edit'))
->add(Action::delete('Delete'));
}
The ActionsPosition enum exposes:
use Pentiminax\UX\DataTables\Enum\ActionsPosition;
ActionsPosition::BeforeColumns; // value: before
ActionsPosition::AfterColumns; // value: after (default)
Alignment
Use Actions::alignment() to horizontally align the content of the action cell:
use Pentiminax\UX\DataTables\Enum\ActionsAlignment;
$actions->alignment(ActionsAlignment::Center);
The alignment is applied as a dt-{value} CSS class on the column (for example,
ActionsAlignment::Center adds dt-center).
use Pentiminax\UX\DataTables\Enum\ActionsAlignment;
ActionsAlignment::Left; // value: left
ActionsAlignment::Center; // value: center
ActionsAlignment::Right; // value: right
Per-action position
Action::position() overrides the placement for a single action, independently of the
collection-level position. Pass null (the default) to inherit the Actions collection
position, or an explicit ActionsPosition to pin that action.
When all actions resolve to the same position, a single ActionColumn named actions is
produced (the default behavior). When actions are split across both positions, two action
columns are produced:
- the
BeforeColumnsgroup is prepended as a column namedactions_before; - the
AfterColumnsgroup is appended as a column namedactions.
A typical use case is pinning a collapsible detail toggle before the data while keeping edit/delete after it:
use Pentiminax\UX\DataTables\Enum\ActionsPosition;
use Pentiminax\UX\DataTables\Model\Action;
use Pentiminax\UX\DataTables\Model\Actions;
public function configureActions(Actions $actions): Actions
{
return $actions
->add(
Action::detail('Details')
->collapsible('data_tables/details.html.twig')
->position(ActionsPosition::BeforeColumns)
)
->add(Action::edit('Edit'))
->add(Action::delete('Delete'));
}
This renders the detail toggle in an actions_before column placed before the data, and the
edit/delete buttons in the trailing actions column.
Manual Usage with DataTableBuilderInterface
If you build tables manually, create an Actions collection and wrap it with ActionColumn::fromActions():
use App\Entity\User;
use Pentiminax\UX\DataTables\Column\ActionColumn;
use Pentiminax\UX\DataTables\Column\TextColumn;
use Pentiminax\UX\DataTables\Model\Action;
use Pentiminax\UX\DataTables\Model\Actions;
$actions = (new Actions())
->setColumnLabel('Operations')
->add(
Action::delete()
->setEntityClass(User::class)
->setIdField('uuid')
->askConfirmation('Delete this user?')
);
$table = $builder
->createDataTable('users')
->columns([
TextColumn::new('email', 'Email'),
ActionColumn::fromActions('actions', 'Operations', $actions),
])
->data([
['uuid' => '8f9f0c31', 'email' => 'john@example.com'],
]);
Manual tables must provide the entity class themselves because there is no #[AsDataTable] attribute to infer it from.
Inline Edit (Modal)
Action::edit() opens a configured edit modal with an auto-generated Symfony Form. See the dedicated Edit Modal page for full documentation: template overrides, column-to-form mapping, primary key handling, and examples.
Ajax Endpoints
All action endpoints are handled by built-in Stimulus controllers. Import the bundle routes:
// config/routes/ux_datatables.php
use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator;
return static function (RoutingConfigurator $routes): void {
$routes->import('@DataTablesBundle/config/routes.php');
};
Delete Endpoint
DELETE /datatables/ajax/delete — payload: entity, id, topics (optional Mercure topics).
Edit Form Endpoints
GET /datatables/ajax/edit-form— returns the rendered modal HTMLPOST /datatables/ajax/edit-form— validates and persists the form data
These endpoints are only available when symfony/form is installed.