Skip to main content

Context Menus

Context menus are a good place for commands that belong to the current record or cell. FDC builds them from FdcMenuEntry objects and gives the builder a rich FdcGridMenuContext describing the row, column, value, selection state, edit state, and record commands that are currently valid.

Customer actions
Loading FDC sample…
FdcGrid(
dataSet: customers,
options: const FdcGridOptions(autoEdit: true),
menuBuilder: (context) {
final company = '${context.row['company_name'] ?? 'Customer'}';
final status = '${context.row['status'] ?? ''}';
final isActive = context.row['active'] == true;

return <FdcMenuEntry>[
FdcMenuTitle(text: company),
FdcMenuAction(
text: 'Open customer details',
icon: Icons.open_in_new,
onPressed: () => openCustomer(context.recordId),
),
const FdcMenuSeparator(),
FdcSubMenu(
text: 'Set status',
icon: Icons.flag_outlined,
children: <FdcMenuEntry>[
for (final nextStatus in const <String>[
'Lead',
'Active',
'On Hold',
'Inactive',
])
FdcMenuCheckAction(
text: nextStatus,
checked: status == nextStatus,
onPressed: () => setCustomerStatus(
context,
nextStatus,
),
),
],
),
FdcMenuCheckAction(
text: 'Active account',
icon: Icons.verified_outlined,
checked: isActive,
onPressed: () => setAccountActive(
context,
!isActive,
),
),
const FdcMenuSeparator(),
FdcMenuAction(
text: 'Insert customer here',
icon: Icons.add_box_outlined,
enabled: context.canInsertRecord,
onPressed: context.insertRecord,
),
FdcMenuAction(
text: 'Append customer',
icon: Icons.playlist_add,
enabled: context.canAppendRecord,
onPressed: context.appendRecord,
),
FdcMenuAction(
text: 'Cancel current edit',
icon: Icons.undo,
enabled: context.canCancelEdit,
onPressed: context.cancelEdit,
),
];
},
columns: customerColumns,
);

Right-click any customer row to open the sample menu. The menu combines record-specific actions, a status submenu, a checked account-state action, a business operation over the credit limit, and dataset commands whose enabled state follows the current grid lifecycle.

Build a grid-level menu

Use menuBuilder on FdcGrid when most columns should share the same record-oriented menu:

FdcGrid(
dataSet: customers,
menuBuilder: (context) {
final company = '${context.row['company_name']}';
final status = '${context.row['status']}';
final isActive = context.row['active'] == true;

return [
FdcMenuTitle(text: company),
FdcMenuAction(
text: 'Open customer details',
icon: Icons.open_in_new,
onPressed: () => openCustomer(context.recordId),
),
const FdcMenuSeparator(),
FdcSubMenu(
text: 'Set status',
icon: Icons.flag_outlined,
children: [
for (final nextStatus in const [
'Lead',
'Active',
'On Hold',
'Inactive',
])
FdcMenuCheckAction(
text: nextStatus,
checked: status == nextStatus,
onPressed: () => setCustomerStatus(
context,
nextStatus,
),
),
],
),
FdcMenuCheckAction(
text: 'Active account',
checked: isActive,
onPressed: () => setAccountActive(
context,
!isActive,
),
),
const FdcMenuSeparator(),
FdcMenuAction(
text: 'Insert customer here',
enabled: context.canInsertRecord,
onPressed: context.insertRecord,
),
FdcMenuAction(
text: 'Append customer',
enabled: context.canAppendRecord,
onPressed: context.appendRecord,
),
FdcMenuAction(
text: 'Cancel current edit',
enabled: context.canCancelEdit,
onPressed: context.cancelEdit,
),
];
},
columns: customerColumns,
)

The builder runs for the cell that opened the menu, so menu labels, checked states, enabled states, and actions can all be derived from that exact row.

What the context provides

FdcGridMenuContext exposes the information needed for cell- and record-oriented commands:

  • dataSet — the dataset connected to the grid;
  • row — read-only row access through row['field_name'] or valueOf();
  • rowIndex and sourceRowIndex — view and adapter positions when available;
  • recordId — internal record identifier;
  • column, columnIndex, and value — the cell that opened the menu;
  • isEditing — whether an edit buffer is active;
  • isCellSelected and isRowSelected — current selection state;
  • canInsertRecord, canAppendRecord, and canCancelEdit — command availability;
  • insertRecord, appendRecord, and cancelEdit — grid-owned record commands.

Use row for decisions and labels. Use dataSet or application services when the action needs to change business data.

Menu entry types

The shared menu model includes:

  • FdcMenuAction for immediate commands;
  • FdcMenuCheckAction for checked or toggle-style commands;
  • FdcMenuTitle for a non-interactive group caption;
  • FdcMenuSeparator for visual grouping;
  • FdcSubMenu for nested commands;
  • FdcMenuWidgetEntry for custom widget content.

The sample deliberately uses several entry types together. The customer name is a title, status choices live in a submenu, account state is a check action, and record commands are separated from business actions.

Context-sensitive enabled states

Do not duplicate grid lifecycle rules in application code when the context already exposes them. For example:

FdcMenuAction(
text: 'Append customer',
enabled: context.canAppendRecord,
onPressed: context.appendRecord,
)

The grid decides whether the operation is currently valid. The menu only reflects that state.

This is especially useful around active edits and inserts, where a command that is normally available may temporarily need to be disabled or replaced by cancelEdit.

Override a specific column

A column-level menuBuilder takes precedence over the grid-level builder for cells in that column. Use it when one field has specialized commands:

FdcTextColumn(
fieldName: 'email',
label: 'Email',
menuBuilder: (context) => [
FdcMenuAction(
text: 'Compose email',
icon: Icons.mail_outline,
onPressed: () => composeEmail('${context.value ?? ''}'),
),
FdcMenuAction(
text: 'Copy email address',
icon: Icons.copy,
onPressed: () => copyText('${context.value ?? ''}'),
),
],
)

Use grid-level menus for record-wide workflows and column overrides for value-specific actions. Keeping that distinction usually produces shorter, more predictable menus.

Keep business work outside the builder

The menu builder should stay lightweight. It is appropriate to inspect context and construct entries, but long-running workflows should hand off to application services:

FdcMenuAction(
text: 'Generate statement',
onPressed: () => statementService.generate(
customerId: context.row['customer_id'] as int,
),
)

This keeps menu construction synchronous and avoids embedding persistence, networking, or navigation orchestration inside the menu definition itself.

Range-selection context menu

PRO

Range Selection has its own copy/paste context-menu behavior. Configure it through FdcGridRangeSelection rather than the body-cell menuBuilder.