Search code examples
c#datagridblazormudblazormaui-blazor

MudBlazor DataGrid Question - How to control the action of the auto generated save button


I am trying to teach myself MudBlazor since I got sick of dealing with all the CSS myself. It is so nice and powerful, I am loving it!

I did run into a small problem though. Say you want to use a DataGrid and have the columns automatically generate a form based on their model properties. The code example is here: https://www.mudblazor.com/components/datagrid#editing Search for the Editing Example.

In the code, there is a small edit button and when you click it the auto generated form comes up. There you have an accept and cancel button. The question is:

How do you control the accept button in the auto-generated form?

I understand that I could change the OnClick property so it uses my own function but then the auto generated form will not come up. I actually want to use it but I just need to control the action of the Accept button. In the example they provide, the button correctly updates the List<T> but I need to push into the database. the idea is to avoid iterating through 300-1000 items if it can be helped.

Thank you!

BMET1.

This picture Shows what I am talking about


@using System.Net.Http.Json @using MudBlazor.Examples.Data.Models @inject HttpClient httpClient

<MudDataGrid T="Element" Items="@Elements.Take(4)" ReadOnly="@_readOnly" EditMode="@(_isCellEditMode ? DataGridEditMode.Cell : DataGridEditMode.Form)"
    StartedEditingItem="@StartedEditingItem" CanceledEditingItem="@CanceledEditingItem" CommittedItemChanges="@CommittedItemChanges"
    Bordered="true" Dense="true" EditTrigger="@(_editTriggerRowClick ? DataGridEditTrigger.OnRowClick : DataGridEditTrigger.Manual)">
    <Columns>
        <PropertyColumn Property="x => x.Number" Title="Nr" IsEditable="false" />
        <PropertyColumn Property="x => x.Sign" />
        <PropertyColumn Property="x => x.Name" />
        <PropertyColumn Property="x => x.Position">
            <EditTemplate>
                <MudSelect @bind-Value="context.Item.Position" Required RequiredError="You must select a Position!!!" Margin="@Margin.Dense">
                    <MudSelectItem Value="0">zero</MudSelectItem>
                    <MudSelectItem Value="1">one</MudSelectItem>
                    <MudSelectItem Value="17">seventeen</MudSelectItem>
                </MudSelect>
            </EditTemplate>
        </PropertyColumn>
        <PropertyColumn Property="x => x.Molar" Title="Molar mass" />
        <TemplateColumn Hidden="@(_isCellEditMode || _readOnly || _editTriggerRowClick)" CellClass="d-flex justify-end">
            <CellTemplate>
                <MudIconButton Size="@Size.Small" Icon="@Icons.Material.Outlined.Edit" OnClick="@context.Actions.StartEditingItemAsync" />
            </CellTemplate>
        </TemplateColumn>
    </Columns> </MudDataGrid>

<div class="d-flex flex-wrap mt-4">
    <MudSwitch @bind-Checked="@_readOnly" Color="Color.Primary">Read Only</MudSwitch>
    <div class="d-flex justify-start align-center">
        <p class="mud-typography mud-typography-body1 mud-inherit-text mr-2">Form</p>
        <MudSwitch @bind-Checked="@_isCellEditMode">Cell</MudSwitch>
    </div>
    <div class="d-flex justify-start align-center">
        <p class="mud-typography mud-typography-body1 mud-inherit-text mr-2">Manual</p>
        <MudSwitch @bind-Checked="@_editTriggerRowClick">On Row Click</MudSwitch>
    </div> </div>

<MudExpansionPanels Style="flex:1">
    <MudExpansionPanel Text="Show Events">
        @foreach (var message in _events)
        {
            <MudText Typo="@Typo.body2">@message</MudText>
        }
        @if(_events.Count > 0) 
        {
            <div class="d-flex">
                <MudSpacer/>
                <MudButton Class="mt-3" ButtonType="ButtonType.Button" Variant="Variant.Filled" OnClick="@(() =>
_events.Clear())">Clear</MudButton>
            </div>
        }
    </MudExpansionPanel> </MudExpansionPanels>

@code {
    private IEnumerable<Element> Elements = new List<Element>();
    private bool _readOnly;
    private bool _isCellEditMode;
    private List<string> _events = new();
    private bool _editTriggerRowClick;

    protected override async Task OnInitializedAsync()
    {
        Elements = await httpClient.GetFromJsonAsync<List<Element>>("webapi/periodictable");
    }

    // events
    void StartedEditingItem(Element item)
    {
        _events.Insert(0, $"Event = StartedEditingItem, Data = {System.Text.Json.JsonSerializer.Serialize(item)}");
    }

    void CanceledEditingItem(Element item)
    {
        _events.Insert(0, $"Event = CanceledEditingItem, Data = {System.Text.Json.JsonSerializer.Serialize(item)}");
    }

    void CommittedItemChanges(Element item)
    {
        _events.Insert(0, $"Event = CommittedItemChanges, Data = {System.Text.Json.JsonSerializer.Serialize(item)}");
    } }

Solution

  • The save button in this form:

    enter image description here

    Does some stuff internally to MB, and then MB invokes whatever EventCallback compatible thing you specified in the CommittedItemChanges="...", probably in the markup. When MB calls the method you supplied, it passes in the item that was changed

    You would then perhaps use this to save your item to DB. Remember that best practice isn't to use your database entities directly here, so you'd perhaps have a code where you have a ViewModel type object that has properties and logic suited to your UI, and you filled an enumerable of those things when you loaded your page, then used that to drive the grid contents.

    An example code to save could look like:

        async Task CommittedItemChangesAsync(UserViewModel uvm) //async, because it's doing DB IO
        {
            //let's assume you use EF Core and have correctly set up a context factory for your blazor server side app
            var context = await _contextFactory.CreateDbContextAsync();
    
            //pull down the user
            var u = await context.Users.FindAsync(uvm.Id);
    
            //copy the data across. You could use a mapper lib for this too
            if(u != default){
                u.Name = uvm.Name;
                u.Address = uvm.Address;
            }
            
            await context.SaveChangesAsync();
        }
    

    If you're not writing server side, don't worry; it's not going to look significantly different, but that save operation would be in a controller endpoint on the server, and your WASM app would likely be calling that endpoint and supplying the uvm viewmodel