Skip to content
IzzyBlazor 0.1.0-alpha.20

Enter all results ↑ ↓ move Esc close

Selection

Checkboxes that post with a form, totals, and actions on the selected rows.

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

Recent orders
SelectOrderCustomerTotal
1001Margaret Hamilton$66.25
1002Katherine Johnson$108.00
1003Grace Hopper$149.75
1004Barbara Liskov$191.50
1005Linus Torvalds$233.25
Total$748.75

Code

SelectRows.razor
@* 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>

@code {
    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

Orders to archive
SelectOrder
Customer
Status
Total
1001Margaret HamiltonFailed$66.25
1002Katherine JohnsonRefunded$108.00
1003Grace HopperPending$149.75
1004Barbara LiskovPaid$191.50
1005Linus TorvaldsFailed$233.25

Code

BulkActions.razor
@* 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>

@code {
    // 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.

Connection lost

Rejoining the server…