Skip to content

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

dart
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

PropertyTypeDescription
iconIconDataIcon for the action button
titleStringStatic title / tooltip
handlerFuture<void> Function(BuildContext, T entity)The thing the action does
titleResolverFuture<String> Function(T entity)?Async dynamic title (overrides title)
isVisibleFuture<bool> Function(T entity)?State-based visibility (e.g. only show "Deactivate" if entity.isActive)
isEnabledFuture<bool> Function(T entity)?Disable (button still visible, greyed out)
disabledTooltipFuture<String> Function(T entity)?Dynamic tooltip when disabled (falls back to title)
styleButtonStyle?Optional button styling
iconColorColor?Icon colour override
authorizeAuthorize?Authorization expression — combined with isVisible (both must pass)

Methods

MethodSignatureDescription
checkAvailabilityFuture<bool> checkAvailability(T entity)isVisible AND authorize (short-circuits visibility first)
checkEnabledFuture<bool> checkEnabled(T entity)Evaluates isEnabled (defaults to true)
checkAuthorizationFuture<bool> checkAuthorization()Evaluates authorize against the registered AuthorizationProvider (returns true on no provider, false on error)
resolveTitleFuture<String> resolveTitle(T entity)Resolves titleResolver or falls back to title
copyWithEntityAction<T> copyWith({...})Returns a copy with overrides

Example

dart
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

dart
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

PropertyTypeDescription
iconIconDataIcon
titleStringTitle / tooltip
handlerFuture<void> Function(BuildContext)Action handler
isAvailableFuture<bool> Function(BuildContext)?Custom availability check (mutually exclusive with authorize)
authorizeAuthorize?Authorization gate (mutually exclusive with isAvailable)
styleButtonStyle?Button styling
iconColorColor?Icon colour
isDestructiveboolRender with destructive styling
isPrimaryboolRender as a filled / primary button (used for header "Create")
splitMenuItemsList<CollectionAction<T>>When non-empty on a primary header action, renders as a split button with these as the dropdown menu
widgetBuilderWidget 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

MethodSignatureDescription
checkAvailabilityFuture<bool> checkAvailability(BuildContext)authorize if set, else isAvailable
checkAuthorizationFuture<bool> checkAuthorization()Evaluates authorize
copyWithCollectionAction<T> copyWith({...})Returns a copy with overrides

Example

dart
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

dart
const ActionGroup({
  required this.identifier,
  required this.title,
  required this.actions,
  this.isAvailable,
});
PropertyTypeDescription
identifierStringUnique identifier
titleStringGroup title
actionsList<EntityAction<T>>Actions in this group
isAvailableFuture<bool> Function(T entity)?Optional gate for the entire group

Methods

MethodSignatureDescription
checkAvailabilityFuture<bool> checkAvailability(T entity)Evaluates isAvailable
getAvailableActionsFuture<List<EntityAction<T>>> getAvailableActions(T entity)Returns actions that pass checkAvailability
hasAvailableActionsFuture<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.

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

dart
enum EditorActionType { save, draft, cancel }

Used for button styling only (FilledButton / OutlinedButton / TextButton) — never for behavioural branching. The controller calls executor polymorphically.

SaveCallback

dart
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>()

dart
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>()

dart
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

dart
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.

dart
static List<EntityAction<T>> defaults<T extends Versionable>({
  Authorize? editAuthorize,
  Authorize? statusChangeAuthorize,
  LifecycleResolver? lifecycleResolver,
});

Returns:

  1. Edit — visible for draft entities in draft status, 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.
  2. Deactivate — shown when entity.isActive == true.
  3. 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:

  1. If the action is gated by an Authorize expression, evaluate it via vyuh.entity?.authorizationProvider. Returns true when no provider is registered (so standalone tests / pre-init renders don't silently hide actions); returns false on evaluation error.
  2. Otherwise, fall back to the per-action callback (isVisible for EntityAction, isAvailable for CollectionAction). Returns true when 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
  • EditorsEditorAction, SignatureDrivenEditor, the save flow
  • PermissionsAuthorize DSL and the AuthorizationProvider