Skip to content
IzzyBlazor 0.1.0-alpha.20

Enter all results ↑ ↓ move Esc close

Editing

Since 0.1.0-alpha.20
Edit in a dialog, in the row, or a cell at a time.

Static server-side rendering: no circuit, no WebAssembly.

Choose a mode by what there is to edit. DataGridEditMode.Dialog has room for many fields, including ones the table doesn't show, and works with rows that load as they're needed. DataGridEditMode.Row keeps the row in place, for a few short fields. DataGridEditMode.Cell is the quickest way to change one value at a time, on an interactive page. Dialog and Row work on static pages with no script.

A field shows the value itself, not what the column's Format makes of it: a number has no currency sign, and a date follows the browser's language, which can differ from the server's culture the column formats with.

Editing rows

Give the grid an EditMode and an OnSave: each column gets a field chosen by its type, shown or not. Set Editable to false on what can't change, such as the key. A save is a form post, and only editable columns take a value from it. OnSave either finishes, or calls AddError, and the dialog stays open with the error on its field. NewItem adds an Add button and OnDelete a Delete action that asks first, with ItemEditable and ItemDeletable to limit them by row; write an ActionColumn to set their order or add your own. A row with no Delete is one ItemDeletable turned off: here, a paid order. The grid needs an explicit TItem, and on a static page, a row type with a parameterless constructor.

Preview

Orders you can edit
OrderCustomerStatusTotalActions
1001Margaret HamiltonFailed$66.25
1002Katherine JohnsonRefunded$108.00
1003Grace HopperPending$149.75
1004Barbara LiskovPaid$191.50
1005Linus TorvaldsFailed$233.25

Code

Editing.razor

razor
@using Rows = IzzyBlazor.Site.Examples.Table.Orders
@* Edit a row with the pencil: a dialog, with no script needed, that saves by a form post. Only the
   editable columns take a value from it: all of them but the key here, including the two that
   aren't shown. A total over 10,000 comes back as an error on its field. Add and Delete come
   from NewItem and OnDelete, and a paid order can't be deleted. *@
<DataGrid TItem="EditableOrder" Items="_orders" PageSize="5" Caption="Orders you can edit" ItemKey="o => o.Number.ToString()" QueryPrefix="edits-"
          EditMode="DataGridEditMode.Dialog" OnSave="Save" NewItem="NewOrder" OnDelete="Delete" ItemDeletable="o => o.Status != OrderStatus.Paid" Grow>
    <PropertyColumn Property="o => o.Number" Title="Order" Align="CellAlign.Start" Width="6.Rem" Editable="false" />
    <PropertyColumn Property="o => o.Customer" />
    <PropertyColumn Property="o => o.Status" />
    <PropertyColumn Property="o => o.Total" Format="C" Width="8.Rem" />
    <PropertyColumn Property="o => o.Date" Visible="false" />
    <PropertyColumn Property="o => o.Notes" Visible="false" />
</DataGrid>

@code {
    [CascadingParameter] private HttpContext? Http { get; set; }

    private List<EditableOrder> _orders = [];

    // Stands in for your database, with a copy of the rows for each visitor to this site.
    protected override void OnInitialized() => _orders = ExampleRows.For(Http, "editing", () => Rows.All
        .Select(o => new EditableOrder { Number = o.Number, Customer = o.Customer, Date = o.Date, Status = (OrderStatus)o.Status, Total = o.Total })
        .ToList());

    private void Save(DataGridSave<EditableOrder> save)
    {
        if (save.Item.Total > 10_000)
        {
            save.AddError(o => o.Total, "An order can't be over 10,000.");
            return;
        }

        // A real page would read the row from its database here.
        if (save.IsNew)
        {
            _orders.Insert(0, save.Item);
            return;
        }

        save.CopyTo(_orders.First(o => o.Number == save.Item.Number));
    }

    private EditableOrder NewOrder() => new() { Number = _orders.Max(o => o.Number) + 1, Date = DateOnly.FromDateTime(DateTime.Today) };

    private void Delete(DataGridDelete<EditableOrder> delete) => _orders.RemoveAll(o => o.Number == delete.Item.Number);
}

EditableOrder.cs

csharp
using System.ComponentModel.DataAnnotations;

namespace IzzyBlazor.Site.Examples.DataGrid;

// On a static page a row needs a parameterless constructor and settable properties, for the post to
// fill. The attributes make the form: [Required] marks and checks a field, and MultilineText makes
// it a text area.
public sealed class EditableOrder
{
    public int Number { get; set; }

    [Required, StringLength(60)]
    public string? Customer { get; set; }

    public DateOnly Date { get; set; }

    public OrderStatus Status { get; set; }

    [Range(0, 1_000_000)]
    public decimal Total { get; set; }

    [StringLength(200), DataType(DataType.MultilineText)]
    public string? Notes { get; set; }
}

public enum OrderStatus { Paid, Pending, Refunded, Failed }

Editing in the row

DataGridEditMode.Row edits where the row stands, with Save and Cancel in place of its actions, and it still needs no script. Only the columns shown have a field, so a hidden column keeps its value. A new row starts at the top of the page. Sorting or changing page ends the edit, and what's typed goes with it. It can't be used with Virtualize or loading on scroll, where the row could scroll away.

Preview

Orders edited in the row
Order Customer StatusDateTotalActions
1001Margaret HamiltonFailed9/8/2026$66.25
1002Katherine JohnsonRefunded9/15/2026$108.00
1003Grace HopperPending9/22/2026$149.75
1004Barbara LiskovPaid9/29/2026$191.50
1005Linus TorvaldsFailed10/6/2026$233.25

Code

EditInRow.razor

razor
@using Rows = IzzyBlazor.Site.Examples.Table.Orders
@* Edit where the row stands. Only the columns shown have a field; Notes keeps its value. A total over
   10,000 comes back as an error under its field. Escape cancels, and F2 starts from a cell of text. *@
<DataGrid TItem="EditableOrder" Items="_orders" PageSize="5" Caption="Orders edited in the row" ItemKey="o => o.Number.ToString()" QueryPrefix="row-"
          EditMode="DataGridEditMode.Row" OnSave="Save" NewItem="NewOrder" OnDelete="Delete" Grow>
    <PropertyColumn Property="o => o.Number" Title="Order" Align="CellAlign.Start" Width="6.Rem" Editable="false" Sortable />
    <PropertyColumn Property="o => o.Customer" Sortable />
    <PropertyColumn Property="o => o.Status" />
    <PropertyColumn Property="o => o.Date" />
    <PropertyColumn Property="o => o.Total" Format="C" Width="9.Rem" />
    <PropertyColumn Property="o => o.Notes" Visible="false" />
</DataGrid>

@code {
    [CascadingParameter] private HttpContext? Http { get; set; }

    private List<EditableOrder> _orders = [];

    // Stands in for your database, with a copy of the rows for each visitor to this site.
    protected override void OnInitialized() => _orders = ExampleRows.For(Http, "edit-in-row", () => Rows.All
        .Select(o => new EditableOrder { Number = o.Number, Customer = o.Customer, Date = o.Date, Status = (OrderStatus)o.Status, Total = o.Total, Notes = "Keeps its value" })
        .ToList());

    private void Save(DataGridSave<EditableOrder> save)
    {
        if (save.Item.Total > 10_000)
        {
            save.AddError(o => o.Total, "An order can't be over 10,000.");
            return;
        }

        if (save.IsNew)
        {
            _orders.Insert(0, save.Item);
            return;
        }

        save.CopyTo(_orders.First(o => o.Number == save.Item.Number));
    }

    private EditableOrder NewOrder() => new() { Number = _orders.Max(o => o.Number) + 1, Date = DateOnly.FromDateTime(DateTime.Today) };

    private void Delete(DataGridDelete<EditableOrder> delete) => _orders.RemoveAll(o => o.Number == delete.Item.Number);
}

EditableOrder.cs

csharp
using System.ComponentModel.DataAnnotations;

namespace IzzyBlazor.Site.Examples.DataGrid;

// On a static page a row needs a parameterless constructor and settable properties, for the post to
// fill. The attributes make the form: [Required] marks and checks a field, and MultilineText makes
// it a text area.
public sealed class EditableOrder
{
    public int Number { get; set; }

    [Required, StringLength(60)]
    public string? Customer { get; set; }

    public DateOnly Date { get; set; }

    public OrderStatus Status { get; set; }

    [Range(0, 1_000_000)]
    public decimal Total { get; set; }

    [StringLength(200), DataType(DataType.MultilineText)]
    public string? Notes { get; set; }
}

public enum OrderStatus { Paid, Pending, Refunded, Failed }

Editing one cell at a time

DataGridEditMode.Cell edits a cell on a click, Enter, F2 or typing, and Tab, Enter or a click elsewhere saves it. OnSave runs for each cell that changed, and an invalid cell stays open. A new row is edited as with Row. It's for interactive pages: on a static page the same grid edits a row at a time, as with Row, since a round trip for each cell would make moving through them unusable.

Always Interactive

Preview

Orders edited by the cell
OrderCustomerStatusDateTotalActions
1001Margaret HamiltonFailed9/8/2026$66.25
1002Katherine JohnsonRefunded9/15/2026$108.00
1003Grace HopperPending9/22/2026$149.75
1004Barbara LiskovPaid9/29/2026$191.50
1005Linus TorvaldsFailed10/6/2026$233.25

Code

EditCells.razor

razor
@using Rows = IzzyBlazor.Site.Examples.Table.Orders
@* A click, Enter, F2 or typing starts a cell, and Tab saves it and starts the next. A total over 10,000
   comes back as an error under its field, and the cell stays open. *@
<DataGrid TItem="EditableOrder" Items="_orders" PageSize="5" Caption="Orders edited by the cell" ItemKey="o => o.Number.ToString()" QueryPrefix="cell-"
          EditMode="DataGridEditMode.Cell" OnSave="Save" NewItem="NewOrder" Grow>
    <PropertyColumn Property="o => o.Number" Title="Order" Align="CellAlign.Start" Width="6.Rem" Editable="false" />
    <PropertyColumn Property="o => o.Customer" />
    <PropertyColumn Property="o => o.Status" />
    <PropertyColumn Property="o => o.Date" />
    <PropertyColumn Property="o => o.Total" Format="C" Width="9.Rem" />
</DataGrid>

@code {
    [CascadingParameter] private HttpContext? Http { get; set; }

    private List<EditableOrder> _orders = [];

    // Stands in for your database, with a copy of the rows for each visitor to this site.
    protected override void OnInitialized() => _orders = ExampleRows.For(Http, "edit-cells", () => Rows.All
        .Select(o => new EditableOrder { Number = o.Number, Customer = o.Customer, Date = o.Date, Status = (OrderStatus)o.Status, Total = o.Total })
        .ToList());

    private void Save(DataGridSave<EditableOrder> save)
    {
        if (save.Item.Total > 10_000)
        {
            save.AddError(o => o.Total, "An order can't be over 10,000.");
            return;
        }

        if (save.IsNew)
        {
            _orders.Insert(0, save.Item);
            return;
        }

        save.CopyTo(_orders.First(o => o.Number == save.Item.Number));
    }

    private EditableOrder NewOrder() => new() { Number = _orders.Max(o => o.Number) + 1, Date = DateOnly.FromDateTime(DateTime.Today) };
}

EditableOrder.cs

csharp
using System.ComponentModel.DataAnnotations;

namespace IzzyBlazor.Site.Examples.DataGrid;

// On a static page a row needs a parameterless constructor and settable properties, for the post to
// fill. The attributes make the form: [Required] marks and checks a field, and MultilineText makes
// it a text area.
public sealed class EditableOrder
{
    public int Number { get; set; }

    [Required, StringLength(60)]
    public string? Customer { get; set; }

    public DateOnly Date { get; set; }

    public OrderStatus Status { get; set; }

    [Range(0, 1_000_000)]
    public decimal Total { get; set; }

    [StringLength(200), DataType(DataType.MultilineText)]
    public string? Notes { get; set; }
}

public enum OrderStatus { Paid, Pending, Refunded, Failed }

Laying out the edit dialog

Use EditLayout for the dialog's and the form's options, such as Columns and Size, and an EditField for each field's place, ColSpan, Label and Hint. A column's EditTemplate replaces its field with your own. On the row, DisabledReason keeps an action that ItemDeletable turns off, with a tooltip saying why.

Preview

Orders in two columns
OrderCustomerStatusTotalActions
1001Margaret HamiltonFailed$66.25
1002Katherine JohnsonRefunded$108.00
1003Grace HopperPending$149.75
1004Barbara LiskovPaid$191.50
1005Linus TorvaldsFailed$233.25

Code

TwoColumns.razor

razor
@using Rows = IzzyBlazor.Site.Examples.Table.Orders
@* The dialog in two columns, with Notes across both, and Status edited by a field of your own. A paid
   order's Delete stays, disabled, and says why. *@
<DataGrid TItem="EditableOrder" Items="_orders" PageSize="5" Caption="Orders in two columns" ItemKey="o => o.Number.ToString()" QueryPrefix="two-"
          EditMode="DataGridEditMode.Dialog" OnSave="Save" OnDelete="Delete" ItemDeletable="o => o.Status != OrderStatus.Paid" Grow>
    <PropertyColumn Property="o => o.Number" Title="Order" Align="CellAlign.Start" Width="6.Rem" Editable="false" />
    <PropertyColumn Property="o => o.Customer" />
    <PropertyColumn Property="o => o.Status">
        <EditTemplate>
            <SegmentedControl @bind-Value="context.Item.Status" Items="Enum.GetValues<OrderStatus>()" @attributes="context.Attributes" />
        </EditTemplate>
    </PropertyColumn>
    <PropertyColumn Property="o => o.Total" Format="C" Width="8.Rem" />
    <PropertyColumn Property="o => o.Date" Visible="false" />
    <PropertyColumn Property="o => o.Notes" Visible="false" />
    <EditLayout Columns="2" Size="DialogSize.Lg">
        <EditField Key="customer" />
        <EditField Key="status" />
        <EditField Key="total" />
        <EditField Key="date" />
        <EditField Key="notes" ColSpan="2" />
    </EditLayout>
    <ActionColumn Context="order">
        <EditAction />
        <DeleteAction DisabledReason="A paid order can't be deleted." />
    </ActionColumn>
</DataGrid>

@code {
    [CascadingParameter] private HttpContext? Http { get; set; }

    private List<EditableOrder> _orders = [];

    // Stands in for your database, with a copy of the rows for each visitor to this site.
    protected override void OnInitialized() => _orders = ExampleRows.For(Http, "two-columns", () => Rows.All
        .Select(o => new EditableOrder { Number = o.Number, Customer = o.Customer, Date = o.Date, Status = (OrderStatus)o.Status, Total = o.Total })
        .ToList());

    private void Save(DataGridSave<EditableOrder> save) => save.CopyTo(_orders.First(o => o.Number == save.Item.Number));

    private void Delete(DataGridDelete<EditableOrder> delete) => _orders.RemoveAll(o => o.Number == delete.Item.Number);
}

EditableOrder.cs

csharp
using System.ComponentModel.DataAnnotations;

namespace IzzyBlazor.Site.Examples.DataGrid;

// On a static page a row needs a parameterless constructor and settable properties, for the post to
// fill. The attributes make the form: [Required] marks and checks a field, and MultilineText makes
// it a text area.
public sealed class EditableOrder
{
    public int Number { get; set; }

    [Required, StringLength(60)]
    public string? Customer { get; set; }

    public DateOnly Date { get; set; }

    public OrderStatus Status { get; set; }

    [Range(0, 1_000_000)]
    public decimal Total { get; set; }

    [StringLength(200), DataType(DataType.MultilineText)]
    public string? Notes { get; set; }
}

public enum OrderStatus { Paid, Pending, Refunded, Failed }

Your own form in the dialog

Use EditFormTemplate for a form that isn't fields of the row. The grid opens and closes the dialog; the form, its model and its post are yours, so on a static page it's yours to guard against forged fields, with a view model. Here the model is one.

Preview

Orders with your form
OrderCustomerQuantityActions
1001Margaret Hamilton1
1002Katherine Johnson1
1003Grace Hopper1
1004Barbara Liskov1
1005Linus Torvalds1

Code

OwnForm.razor
@using System.ComponentModel.DataAnnotations
@using Rows = IzzyBlazor.Site.Examples.Table.Orders
@* A form of your own in the dialog: its model holds only what it edits, so no other field can be
   forged, and it says when it has saved. *@
<DataGrid TItem="Order" Items="_orders" PageSize="5" Caption="Orders with your form" ItemKey="o => o.Number.ToString()" QueryPrefix="own-"
          EditMode="DataGridEditMode.Dialog" Grow>
    <PropertyColumn Property="o => o.Number" Title="Order" Align="CellAlign.Start" Width="6.Rem" />
    <PropertyColumn Property="o => o.Customer" />
    <PropertyColumn Property="o => o.Quantity" />
    <EditFormTemplate Context="edit">
        <Form Model="ModelFor(edit.Item!)" FormName="own-form" OnValidSubmit="() => SaveAsync(edit)">
            <FormErrors />
            <NumberField @bind-Value="Model!.Quantity" />
            <DialogFooter>
                @edit.Cancel
                <Button Submit>Save</Button>
            </DialogFooter>
        </Form>
    </EditFormTemplate>
</DataGrid>

@code {
    [CascadingParameter] private HttpContext? Http { get; set; }

    [SupplyParameterFromForm(FormName = "own-form")]
    private QuantityEdit? Model { get; set; }

    private List<Order> _orders = [];

    // Stands in for your database, with a copy of the rows for each visitor to this site.
    protected override void OnInitialized() => _orders = ExampleRows.For(Http, "own-form", () => Rows.All
        .Select(o => new Order { Number = o.Number, Customer = o.Customer })
        .ToList());

    private QuantityEdit ModelFor(Order order) => Model ??= new() { Quantity = order.Quantity };

    private async Task SaveAsync(DataGridEditFormContext<Order> edit)
    {
        edit.Item!.Quantity = Model!.Quantity;
        await edit.CloseAsync();
        Model = null;
    }

    public sealed class Order
    {
        public int Number { get; set; }

        public string? Customer { get; set; }

        public int Quantity { get; set; } = 1;
    }

    public sealed class QuantityEdit
    {
        [Range(1, 1000)]
        public int Quantity { get; set; }
    }
}

Connection lost

Rejoining the server…