Blazor, cancelling a two way bind

Viewed 47

Just getting into understanding Blazor and trying to figure out two way binding.

I'm trying to create an inline editable grid. I've got a collection of items, and using a foreach to display rows. I have edit/delete buttons appearing for most rows, and I have update/cancel buttons for the row that is being edited.

I use the @bind attribute on inputs in the edit row, but they update the item instance when the input loses focus and I want to be able to cancel and revert that change. Is there a built in way to do that, or do I need to explicitly store the old values on edit, then manually revert them on cancel?

@page "/inlineeditingsample"

<PageTitle>Inline Editing Sample</PageTitle>

<h1>Inline Editing Sample</h1>

@if (_items == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Title</th>
                <th>Description</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            @foreach (var item in _items)
            {
                var link = $"TableSampleDetails/{@item.ID}";
                <tr>
                    @if (item == _editItem)
                    {
                        <td>
                        @item.ID
                        </td>
                        <td><input type="text" class="form-control" @bind="@item.Title"/></td>
                        <td><input type="text" class="form-control" @bind="@item.Description" /></td>
                        <td>
                            <button type="button" class="btn btn-link" @onclick="() => Cancel(item)">Cancel</button>
                            <button type="button" class="btn btn-link" @onclick="() => Save(item)">Save</button>
                        </td>
                    }
                    else
                    {
                        <td>
                            <NavLink href="@link">@item.ID</NavLink>
                        </td>
                        <td>@item.Title</td>
                        <td>@item.Description</td>
                        <td>
                            <button type="button" class="btn btn-link" @onclick="() => Edit(item)">Edit</button>
                            <button type="button" class="btn btn-link" @onclick="() => Delete(item)">Delete</button>
                        </td>
                    }
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private DAL.SampleObject? _editItem;
    private List<DAL.SampleObject> _items;

    private DAL.SampleDal _dal = new DAL.SampleDal();


    private void Edit(DAL.SampleObject item)
    {
        _editItem = item;
    }

    private void Save(DAL.SampleObject item)
    {
        _editItem = null;

        //call the repository to update the instance here.
        //show toast after done.
    }

    private void Delete(DAL.SampleObject item)
    {
        _editItem = null;

        //call the repository to delete the instance here.
        //show toast after done.
        _items.Remove(item);
    }

    private void Cancel(DAL.SampleObject item)
    {
        _editItem = null;   
    }

    protected override async Task OnInitializedAsync()
    {
        var items = await _dal.ListObjectsAsync(0, int.MaxValue);
        _items = items.ToList();
    }
}
1 Answers

There isn't a native Blazor based solution for this, Blazor is no different than other frameworks in this respect. The problem and the solutions are the same whether you are using Blazor, UWP, WPF, React, Angular, Vue...

The bindings just write to the properties on the bound view model, it is up to you to design your model to deal with this. There are two fundamental paradigms behind supporting undo logic in data applications.

  1. You store the original or previous state of the object, usually a clone from when the data was provided. Then when you need to roll back, the values from or the original object itself is used to replace that of the current object.
  2. Edit operations are performed in a separate object instance for the duration of the edit, then when the data is persisted, those values are copied into the original object.

For such a common requirement, I recommend you use a component library rather than trying to re-invent the wheel yourself. I have found the MudBlazor package to be very simple to implement, but there are many to choose from.

What you are describing sounds like a MudTable that has inline row level editing. You do not need to make any changes to your model to get this simple behaviour to work, it uses the 2nd option from above:

Mud Table - Inline editing

Have a play with it here: https://try.mudblazor.com/snippet/waQQYtuDYpotsVyL


You can also manage this directly in your data model. There are other 3rd party libraries that can provide this to different degrees and your data access layer may already provide generalised support for this. Entity Framework as an example has the ability to track, inspect and manipulate changes before you commit the results to the underlying database.

You can even roll your own, I still use an interceptor pattern in some models where the setter on each property on the model writes to a complex object instead of a simple value-typed backing field but managing this specific behaviour in the user interface as a component is very practical.

Related