Selection
Static server-side rendering: no circuit, no WebAssembly.
Selecting rows
Use SelectionMode="SelectionMode.Multiple" with an ItemKey for a checkbox per row (Single for a radio button). In a form, the checked rows post their keys, so this works with JavaScript off.
Preview
Code
@* Selection in a form: each checked row posts its ItemKey, so Archive works with JavaScript off. A
footer row shows the total. *@
<form method="post" @onsubmit="Archive" @formname="archive" style="width: 100%">
<AntiforgeryToken />
<Stack Gap="3">
<Table Items="Recent" Caption="Recent orders" SelectionMode="SelectionMode.Multiple" ItemKey="o => o.Number.ToString()"
RowLabel='o => $"order {o.Number}"' @bind-Selection="_selected">
<PropertyColumn Property="o => o.Number" Title="Order" Align="CellAlign.Start" FooterText="Total" />
<PropertyColumn Property="o => o.Customer" />
<PropertyColumn Property="o => o.Total" Format="C" FooterText="Recent.Sum(o => o.Total).ToString("C")" />
</Table>
<Row Gap="3" Align="Align.Center" Wrap>
<Button Submit Variant="ButtonVariant.Outline" Accent="Accent.Neutral" StartIcon="Lucide.Archive">Archive selected</Button>
if (_archived is not null)
{
<TextBlock Size="TextSize.Sm" Color="Color.Muted">
Archived: (_archived.Length == 0 ? "nothing selected" : string.Join(", ", _archived.Select(k => "#" + k)))
</TextBlock>
}
</Row>
</Stack>
</form>
{
private static readonly IReadOnlyList<Order> Recent = Orders.All.Take(5).ToList();
// Static pages: the checked rows post as "selected".
[SupplyParameterFromForm(FormName = "archive", Name = "selected")]
private Selection? Posted { get; set; }
// Interactive pages: bound to the checkboxes.
private Selection? _selected;
private string[]? _archived;
protected override void OnInitialized() => _selected = Posted;
private void Archive()
{
var selection = Posted ?? _selected ?? Selection.None;
_archived = [.. selection.Apply(Recent.AsQueryable(), o => o.Number.ToString()).Select(o => o.Number.ToString())];
}
}Static: The header checkbox selects every row on the page.
Read them as a Selection with [SupplyParameterFromForm(Name = "selected")] on a static page, or @bind-Selection on an interactive one; Apply gives the selected items from a query. Give columns FooterText for a totals row.
Bulk actions
While rows are selected, the footer shows how many, with Clear selection. The selection stays as you page, sort and filter. Use SelectionActions for buttons on the selection, in a bar above the rows, disabled until something is selected (a link can't be disabled, so it stays live); it's always there, so selecting doesn't move the rows. SelectAllMode says what the header checkbox selects: the rows on this page, or, with SelectAllMode.All, every row the filters match, which aren't loaded, so Apply finds them, and a change of filter clears it. Shift-click a checkbox to select or clear the rows from the last one clicked. With JavaScript off, only the rows checked on the page post.
Preview
Code
@* Bulk actions: select rows on any page, or, with the header checkbox, every order the filters
match. The bar above the rows holds Archive, which posts the selection; Selection.Apply finds
the orders it means. *@
<form method="post" @onsubmit="Archive" @formname="bulk-archive" style="width: 100%">
<AntiforgeryToken />
<Stack Gap="3">
<Table Items="Orders.All" PageSize="5" Caption="Orders to archive" QueryPrefix="bulk-" Grow
ItemKey="o => o.Number.ToString()" RowLabel='o => $"order {o.Number}"'
SelectionMode="SelectionMode.Multiple" SelectAllMode="SelectAllMode.All" @bind-Selection="_selection">
<SelectionActions>
<Button Submit Size="ControlSize.Sm" Variant="ButtonVariant.Outline" Accent="Accent.Neutral" StartIcon="Lucide.Archive">Archive</Button>
</SelectionActions>
<ChildContent>
<PropertyColumn Property="o => o.Number" Title="Order" Align="CellAlign.Start" />
<PropertyColumn Property="o => o.Customer" Filterable />
<PropertyColumn Property="o => o.Status" Filterable />
<PropertyColumn Property="o => o.Total" Format="C" />
</ChildContent>
</Table>
<TextBlock Size="TextSize.Sm" Color="Color.Muted" role="status">_status</TextBlock>
</Stack>
</form>
{
// Static pages: the selection posts under the table's SelectionName ("{QueryPrefix}selected").
[SupplyParameterFromForm(FormName = "bulk-archive", Name = "bulk-selected")]
private Selection? Posted { get; set; }
// Interactive pages: bound to the table.
private Selection? _selection;
private string? _status;
protected override void OnInitialized() => _selection = Posted;
private void Archive()
{
var orders = (Posted ?? _selection ?? Selection.None).Apply(Orders.All.AsQueryable(), o => o.Number.ToString()).ToList();
_status = orders.Count switch
{
0 => "Select the orders to archive first.",
1 => $"Archived order #{orders[0].Number}.",
_ => $"Archived {orders.Count} orders.",
};
_selection = Selection.None;
}
}Static: The selection stays as you page and filter, and Archive posts all of it, or every match after Select all.