Actions
Entity actions are the unit of "something the user can do" attached to an entity or a collection of entities. The system distinguishes single-entity actions (EntityAction<T>) from collection actions (CollectionAction<T>), groups them in EntityActions<T>, and provides standard helpers for the common cases.
All action classes live in package:vyuh_entity_system/vyuh_entity_system.dart. The StandardEntityActions and VersionableActions helpers — which depend on Flutter widgets — live in package:vyuh_entity_system_ui/vyuh_entity_system_ui.dart.
EntityAction
A single action performed on one entity instance — used in detail views, inline buttons, dropdown menus.
Constructor
EntityAction({
required this.icon,
required this.title,
required this.handler,
this.titleResolver,
this.isVisible,
this.isEnabled,
this.disabledTooltip,
this.style,
this.iconColor,
this.authorize,
});Properties
| Property | Type | Description |
|---|---|---|
icon | IconData | Icon for the action button |
title | String | Static title / tooltip |
handler | Future<void> Function(BuildContext, T entity) | The thing the action does |
titleResolver | Future<String> Function(T entity)? | Async dynamic title (overrides title) |
isVisible | Future<bool> Function(T entity)? | State-based visibility (e.g. only show "Deactivate" if entity.isActive) |
isEnabled | Future<bool> Function(T entity)? | Disable (button still visible, greyed out) |
disabledTooltip | Future<String> Function(T entity)? | Dynamic tooltip when disabled (falls back to title) |
style | ButtonStyle? | Optional button styling |
iconColor | Color? | Icon colour override |
authorize | Authorize? | Authorization expression — combined with isVisible (both must pass) |
Methods
| Method | Signature | Description |
|---|---|---|
checkAvailability | Future<bool> checkAvailability(T entity) | isVisible AND authorize (short-circuits visibility first) |
checkEnabled | Future<bool> checkEnabled(T entity) | Evaluates isEnabled (defaults to true) |
checkAuthorization | Future<bool> checkAuthorization() | Evaluates authorize against the registered AuthorizationProvider (returns true on no provider, false on error) |
resolveTitle | Future<String> resolveTitle(T entity) | Resolves titleResolver or falls back to title |
copyWith | EntityAction<T> copyWith({...}) | Returns a copy with overrides |
Example
final editAction = EntityAction<MyEntity>(
icon: FluentIcons.edit_24_regular,
title: 'Edit',
authorize: Authorize.permission('my_entity.edit'),
handler: (context, entity) async {
final route = vyuh.entity!.getConfig<MyEntity>()!.route;
context.go(route.edit(entity.id));
},
);
// State-based visibility plus authorization:
final deactivateAction = EntityAction<MyEntity>(
icon: FluentIcons.prohibited_24_regular,
title: 'Deactivate',
isVisible: (e) async => e.isActive,
authorize: Authorize.permission('my_entity.deactivate'),
handler: (context, entity) async { /* … */ },
);CollectionAction
An action that operates on a list selection or appears in the list-view header. Takes only a BuildContext (not an entity) — selection state is fetched from the surrounding scope when needed.
Constructor
CollectionAction({
required this.icon,
required this.title,
required this.handler,
this.isAvailable,
this.style,
this.iconColor,
this.authorize,
this.isDestructive = false,
this.isPrimary = false,
this.splitMenuItems = const [],
this.widgetBuilder,
});Properties
| Property | Type | Description |
|---|---|---|
icon | IconData | Icon |
title | String | Title / tooltip |
handler | Future<void> Function(BuildContext) | Action handler |
isAvailable | Future<bool> Function(BuildContext)? | Custom availability check (mutually exclusive with authorize) |
authorize | Authorize? | Authorization gate (mutually exclusive with isAvailable) |
style | ButtonStyle? | Button styling |
iconColor | Color? | Icon colour |
isDestructive | bool | Render with destructive styling |
isPrimary | bool | Render as a filled / primary button (used for header "Create") |
splitMenuItems | List<CollectionAction<T>> | When non-empty on a primary header action, renders as a split button with these as the dropdown menu |
widgetBuilder | Widget Function(BuildContext)? | Escape hatch — renders a custom widget verbatim instead of a default button |
Mutual exclusion
Specifying both authorize and isAvailable throws an AssertionError. Use authorize for declarative permission gating; use isAvailable for custom logic.
Methods
| Method | Signature | Description |
|---|---|---|
checkAvailability | Future<bool> checkAvailability(BuildContext) | authorize if set, else isAvailable |
checkAuthorization | Future<bool> checkAuthorization() | Evaluates authorize |
copyWith | CollectionAction<T> copyWith({...}) | Returns a copy with overrides |
Example
final exportAction = CollectionAction<MyEntity>(
icon: FluentIcons.arrow_download_24_regular,
title: 'Export All',
authorize: Authorize.permission('my_entity.export'),
handler: (context) async { /* … */ },
);ActionGroup
Groups related EntityActions into a dropdown menu (rendered via the ⋮ icon in the detail view).
Constructor
const ActionGroup({
required this.identifier,
required this.title,
required this.actions,
this.isAvailable,
});| Property | Type | Description |
|---|---|---|
identifier | String | Unique identifier |
title | String | Group title |
actions | List<EntityAction<T>> | Actions in this group |
isAvailable | Future<bool> Function(T entity)? | Optional gate for the entire group |
Methods
| Method | Signature | Description |
|---|---|---|
checkAvailability | Future<bool> checkAvailability(T entity) | Evaluates isAvailable |
getAvailableActions | Future<List<EntityAction<T>>> getAvailableActions(T entity) | Returns actions that pass checkAvailability |
hasAvailableActions | Future<bool> hasAvailableActions(T entity) | True if group is available AND has any visible actions |
The default rendering uses ActionGroupButton<T> from vyuh_entity_system_ui, which wires the group into a CDX ActionMenu.
EditorAction
A semantic subclass of EntityAction used by the editor for save / cancel / draft buttons. Lives in forms/core/editor_action.dart.
class EditorAction<T extends EntityBase> extends EntityAction<T> {
final EditorActionType actionType;
final Future<T?> Function(
BuildContext context,
T entity, {
required SaveCallback<T> save,
})? executor;
EditorAction({
required super.icon,
required super.title,
required this.actionType,
this.executor,
super.isVisible,
super.isEnabled,
super.authorize,
});
bool get hasExecutor;
Future<EntityBase?> executeWith(
BuildContext context,
EntityBase entity, {
required Future<EntityBase> Function(EntityBase, {String? remarks}) save,
});
}EditorActionType
enum EditorActionType { save, draft, cancel }Used for button styling only (FilledButton / OutlinedButton / TextButton) — never for behavioural branching. The controller calls executor polymorphically.
SaveCallback
typedef SaveCallback<T> = Future<T> Function(T entity, {String? remarks});The controller passes its own draft-aware save implementation as a SaveCallback to the action's executor. This lets the executor wrap the save in a verification dialog and feed remarks back through the same path (see SignatureDrivenEditor).
StandardEntityActions
final class StandardEntityActions (in vyuh_entity_system_ui) — ready-made action factories.
inline<T>()
static List<EntityAction<T>> inline<T extends EntityBase>({
Authorize? editAuthorize,
Authorize? deleteAuthorize,
});Returns Edit + Deactivate. Use this for non-versioned entities. The Deactivate handler shows a confirmation dialog and calls the API's delete() method.
header<T>()
static List<CollectionAction<T>> header<T extends EntityBase>({
Authorize? createAuthorize,
});Returns a single primary "Create" CollectionAction. When createAuthorize is null, it falls back to Authorize.anyPermission(routing.permissions.create) from the registered config — the canonical "who can create this entity" gate.
The handler clears the active list selection (mutually exclusive with create) before navigating to route.create().
none<T>()
Returns an empty list — explicit "no actions" sentinel.
Example
actions: EntityActions<Equipment>(
inline: StandardEntityActions.inline<Equipment>(
editAuthorize: Authorize.permission('equipment.edit'),
deleteAuthorize: Authorize.permission('equipment.delete'),
),
header: StandardEntityActions.header<Equipment>(),
),VersionableActions
class VersionableActions (in vyuh_entity_system_ui, versioning/versioned_entity_actions.dart) — defaults for entities that mix in Versionable.
static List<EntityAction<T>> defaults<T extends Versionable>({
Authorize? editAuthorize,
Authorize? statusChangeAuthorize,
LifecycleResolver? lifecycleResolver,
});Returns:
- Edit — visible for draft entities in
draftstatus, and for approved active entities without pending modification drafts. Hidden for entities under review, in terminal states, or already deactivated. Title resolves dynamically to'Edit Draft'for entities with a draft operation. - Deactivate — shown when
entity.isActive == true. - Activate — shown when
entity.isActive == false.
The Deactivate / Activate handlers call the matching methods on the versioning API mixin and route through the signature verification dialog when lifecycleResolver reports verification is required.
Action evaluation pipeline
Both EntityAction.checkAvailability and CollectionAction.checkAvailability follow the same contract:
- If the action is gated by an
Authorizeexpression, evaluate it viavyuh.entity?.authorizationProvider. Returnstruewhen no provider is registered (so standalone tests / pre-init renders don't silently hide actions); returnsfalseon evaluation error. - Otherwise, fall back to the per-action callback (
isVisibleforEntityAction,isAvailableforCollectionAction). Returnstruewhen neither is set.
For EntityAction, isVisible is checked first (cheap state-based predicate) and short-circuits before the authorization round-trip.
See Also
- EntityConfiguration — Where actions are wired into the entity config
- Editors —
EditorAction,SignatureDrivenEditor, the save flow - Permissions —
AuthorizeDSL and theAuthorizationProvider