I tried some options but I would like to remove the edit mode when I click outside of the cell without interfering with other events in the grid. Do you have a suggestion? Thanks
Here is an example code based on our demo that can be used in such case. The idea is to focus the current editor and to close the editing on lost focus:
@using RadzenBlazorDemos.Data
@using RadzenBlazorDemos.Models.Northwind
@using Microsoft.EntityFrameworkCore
@using Microsoft.JSInterop
@inject IJSRuntime JSRuntime
@inherits DbContextPage
@*
Pay attention to the following event handlers below: Sort, Page, and Filter. They are required to help maintain state on the Item and Property being edited.
The 'CellClick' event handler is used to trigger the column edit mode.
*@
<RadzenDataGrid TItem="Order" AllowAlternatingRows="false" AllowFiltering="true" AllowPaging="true" PageSize="5" AllowSorting="true"
Data="@orders" RowUpdate="@OnUpdateRow" Sort="@(args => Reset())" Page="@(args => Reset())" Filter="@(args => Reset())"
ColumnWidth="200px" CellClick="@OnCellClick">
<Columns>
<RadzenDataGridColumn Property="OrderID" Title="Order ID" Width="120px" Frozen="true" />
<RadzenDataGridColumn TItem="Order" Property="Customer.CompanyName" Title="Customer" Width="280px" IsInEditMode="@IsEditing">
<Template Context="order">
<RadzenText Text="@(order.Customer?.CompanyName)" />
</Template>
<EditTemplate Context="order">
<RadzenDropDown @ref="editor" Change="@(args => Update())" @bind-Value="order.CustomerID" Data="@customers" TextProperty="@nameof(Customer.CompanyName)" ValueProperty="@nameof(Customer.CustomerID)" Style="width:100%; display: block;"
InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", "Select customer" }})" />
</EditTemplate>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="Order" Property="Employee.LastName" Title="Employee" Width="220px" IsInEditMode="@IsEditing">
<Template Context="order">
<RadzenStack Orientation="Orientation.Horizontal">
<RadzenImage Path="@order.Employee?.Photo" style="width: 32px; height: 32px; border-radius: 16px; margin-right: 6px;" AlternateText="@(order.Employee?.FirstName + " " + order.Employee?.LastName)" />
@order.Employee?.FirstName @order.Employee?.LastName
</RadzenStack>
</Template>
<EditTemplate Context="order">
<RadzenDropDown @ref="editor" Change="@(args => Update())" @bind-Value="order.EmployeeID" Data="@employees" ValueProperty="EmployeeID" Style="width:100%; display: block;"
InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", "Select employee" }})">
<Template>
<RadzenImage Path="@context.Photo" style="width: 20px; height: 20px; border-radius: 16px; margin-right: 6px;" />
@context.FirstName @context.LastName
</Template>
</RadzenDropDown>
</EditTemplate>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="Order" Property="@nameof(Order.OrderDate)" Title="Order Date" Width="200px" IsInEditMode="@IsEditing">
<Template Context="order">
<RadzenText Text="@(String.Format("{0:d}", order.OrderDate))" />
</Template>
<EditTemplate Context="order">
<RadzenDatePicker @ref="editor" Change="@(args => Update())" @bind-Value="order.OrderDate" Style="width:100%" InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", "Select order date" }})" />
</EditTemplate>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="Order" Property="@nameof(Order.Freight)" Title="Freight" IsInEditMode="@IsEditing" CalculatedCssClass="@IsEdited">
<Template Context="order">
<RadzenText Text="@(String.Format(new System.Globalization.CultureInfo("en-US"), "{0:C}", order.Freight))" />
</Template>
<EditTemplate Context="order">
<RadzenNumeric @ref="editor" Change="@((decimal? args) => Update())" @bind-Value="order.Freight" Style="width:100%" InputAttributes="@(new Dictionary<string,object>(){ { "aria-label", "Select freight" }})" />
</EditTemplate>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="Order" Property="@nameof(Order.ShipName)" Title="Ship Name" IsInEditMode="@IsEditing">
<Template Context="order">
<RadzenText Text="@(order.ShipName)" />
</Template>
<EditTemplate Context="order">
<RadzenTextBox @ref="editor" Change="@(args => Update())" @bind-Value="order.ShipName" Style="width:200px; display: block" Name="ShipName" aria-label="Enter ship name" />
<RadzenRequiredValidator Text="ShipName is required" Component="ShipName" Popup="true" />
</EditTemplate>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="Order" Property="@nameof(Order.ShipCountry)" Title="ShipCountry" IsInEditMode="@IsEditing">
<Template Context="order">
<RadzenText Text="@(order.ShipCountry)" />
</Template>
<EditTemplate Context="order">
<RadzenTextBox @ref="editor" Change="@(args => Update())" @bind-Value="order.ShipCountry" Style="width:200px; display: block" Name="ShipCountry" aria-label="Enter ship country" />
<RadzenRequiredValidator Text="ShipCountry is required" Component="ShipCountry" Popup="true" />
</EditTemplate>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="Order" Property="@nameof(Order.ShipCity)" Title="ShipCity" IsInEditMode="@IsEditing">
<Template Context="order">
<RadzenText Text="@(order.ShipCity)" />
</Template>
<EditTemplate Context="order">
<RadzenTextBox @ref="editor" Change="@(args => Update())" @bind-Value="order.ShipCity" Style="width:200px; display: block" Name="ShipCity" aria-label="Enter ship city" />
<RadzenRequiredValidator Text="ShipCity is required" Component="ShipCity" Popup="true" />
</EditTemplate>
</RadzenDataGridColumn>
</Columns>
</RadzenDataGrid>
<style>
.table-cell-edited {
position: relative;
}
.table-cell-edited::after {
content: "";
position: absolute;
top: 0;
right: 0;
width: 0;
height: 0;
border-top: 10px solid red;
border-left: 10px solid transparent;
}
</style>
@code {
string columnEditing;
IEnumerable<Order> orders;
IEnumerable<Customer> customers;
IEnumerable<Employee> employees;
List<KeyValuePair<int, string>> editedFields = new List<KeyValuePair<int, string>>();
List<Order> ordersToUpdate = new List<Order>();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
customers = dbContext.Customers;
employees = dbContext.Employees;
orders = dbContext.Orders.Include("Customer").Include("Employee");
}
/// <summary>
/// Determines if the specified column is in edit mode for the specified order.
/// </summary>
/// <param name="columnName">The RadzenDataGridColumn.Property currently being rendered by the RadzenDataGrid.</param>
/// <param name="order">The Order currently being rendered by the RadzenDataGrid.</param>
/// <returns>True if the column should render the EditTemplate for the specified Order, otherwise false.</returns>
bool IsEditing(string columnName, Order order)
{
// Comparing strings is quicker than checking the contents of a List, so let the property check fail first.
return columnEditing == columnName && ordersToUpdate.Contains(order);
}
/// <summary>
/// Determines if the specified column needs a custom CSS class based on the <typeparamref name="TItem">TItem's</typeparamref> state.
/// </summary>
/// <param name="column">The RadzenDataGridColumn.Property currently being rendered by the RadzenDataGrid.</param>
/// <param name="order">The Order currently being rendered by the RadzenDataGrid.</param>
/// <returns>A string containing the CssClass to add, or <see cref="String.Empty">.</returns>
string IsEdited(RadzenDataGridColumn<Order> column, Order order)
{
// In a real scenario, you might use IRevertibleChangeTracking to check the current column
// against a list of the object's edited fields.
return editedFields.Where(c => c.Key == order.OrderID && c.Value == column.Property).Any() ?
"table-cell-edited" :
string.Empty;
}
/// <summary>
/// Handles the CellClick event of the RadzenDataGrid.
/// </summary>
/// <param name="args"></param>
void OnCellClick(DataGridCellMouseEventArgs<Order> args)
{
// Record the previous edited field, if you're not using IRevertibleChangeTracking to track object changes
if (ordersToUpdate.Any())
{
editedFields.Add(new(ordersToUpdate.First().OrderID, columnEditing));
}
// This sets which column is currently being edited.
columnEditing = args.Column.Property;
// This triggers a save on the previous edit. This can be removed if you are going to batch edits through another method.
if (ordersToUpdate.Any())
{
OnUpdateRow(ordersToUpdate.First());
}
// This sets the Item to be edited.
EditRow(args.Data);
}
void Reset(Order order = null)
{
editorFocused = false;
if (order != null)
{
ordersToUpdate.Remove(order);
}
else
{
ordersToUpdate.Clear();
}
}
void Update()
{
editorFocused = false;
if (ordersToUpdate.Any())
{
OnUpdateRow(ordersToUpdate.First());
}
Reset();
}
void EditRow(Order order)
{
Reset();
ordersToUpdate.Add(order);
}
/// <summary>
/// Saves the changes from the Order to the database.
/// </summary>
/// <param name="order">The <see cref="Order" /> to save.</param>
/// <remarks>
/// Currently, this is called every time the Cell is changed. In a real in-cell edit scenario, you would likely either update
/// on RowDeselect, or batch the changes using a "Save Changes" button in the header.
/// </remarks>
void OnUpdateRow(Order order)
{
Reset(order);
dbContext.Update(order);
dbContext.SaveChanges();
// If you were doing row-level edits and handling RowDeselect, you could use the line below to
// clear edits for the current record.
//editedFields = editedFields.Where(c => c.Key != order.OrderID).ToList();
}
IRadzenFormComponent editor;
bool editorFocused;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (!editorFocused && editor != null)
{
editorFocused = true;
try
{
await editor.FocusAsync();
}
catch
{
//
}
}
}
}
I'll update the demo with this code.
UPDATE: I've reworked the code to use Change event instead @onblur - in my opinion it works better this way.