Reproducible below, expected results is that after editing the decimal field you'll get the data under the grid. Actual: either it does not get updated, or the value is always 0.
@
using
Telerik.Blazor.Components.Grid
<TelerikGrid Data=@MyData EditMode=
"incell"
Pageable=
"true"
Height=
"500px"
>
<TelerikGridEvents>
<EventsManager OnUpdate=
"@UpdateHandler"
OnEdit=
"@EditHandler"
OnDelete=
"@DeleteHandler"
OnCreate=
"@CreateHandler"
></EventsManager>
</TelerikGridEvents>
<TelerikGridToolBar>
<TelerikGridCommandButton Command=
"Add"
Icon=
"add"
>Add Employee</TelerikGridCommandButton>
</TelerikGridToolBar>
<TelerikGridColumns>
<TelerikGridColumn Field=@nameof(SampleData.ID) Title=
"ID"
Editable=
"false"
/>
<TelerikGridColumn Field=@nameof(SampleData.Name) Title=
"Name"
/>
<TelerikGridColumn Field=@nameof(SampleData.SomeDecimal) Title=
"Some Decimal"
/>
<TelerikGridCommandColumn>
<TelerikGridCommandButton Command=
"Delete"
Icon=
"delete"
>Delete</TelerikGridCommandButton>
<TelerikGridCommandButton Command=
"Save"
Icon=
"save"
ShowInEdit=
"true"
>Update</TelerikGridCommandButton>
</TelerikGridCommandColumn>
</TelerikGridColumns>
</TelerikGrid>
@lastUpdateOnDecimal
@code {
public
void
EditHandler(GridCommandEventArgs args)
{
SampleData item = (SampleData)args.Item;
Console.WriteLine(
"Edit event is fired for column "
+ args.Field);
}
string
lastUpdateOnDecimal;
public
void
UpdateHandler(GridCommandEventArgs args)
{
string
fieldName = args.Field;
object
newVal = args.Value;
//you can cast this, if necessary, according to your model
SampleData item = (SampleData)args.Item;
//you can also use the entire model
//perform actual data source operation here
//if you have a context added through an @inject statement, you could call its SaveChanges() method
//myContext.SaveChanges();
if
(fieldName ==
"SomeDecimal"
)
{
lastUpdateOnDecimal = $
"decimal for {item.ID} updated to {newVal} on {DateTime.Now}"
;
}
var matchingItem = MyData.FirstOrDefault(c => c.ID == item.ID);
if
(matchingItem !=
null
)
{
matchingItem.Name = item.Name;
}
Console.WriteLine(
"Update event is fired for "
+ args.Field +
" with value "
+ args.Value);
}
public
void
CreateHandler(GridCommandEventArgs args)
{
SampleData item = (SampleData)args.Item;
//perform actual data source operation here
item.ID = MyData.Count;
MyData.Add(item);
Console.WriteLine(
"Create event is fired."
);
}
public
void
DeleteHandler(GridCommandEventArgs args)
{
SampleData item = (SampleData)args.Item;
//perform actual data source operation here
//if you have a context added through an @inject statement, you could call its SaveChanges() method
//myContext.SaveChanges();
MyData.Remove(item);
Console.WriteLine(
"Delete event is fired."
);
}
//in a real case, keep the models in dedicated locations, this is just an easy to copy and see example
public
class
SampleData
{
public
int
ID {
get
;
set
; }
public
string
Name {
get
;
set
; }
public
decimal
SomeDecimal {
get
;
set
; }
}
public
List<SampleData> MyData {
get
;
set
; }
protected
override
void
OnInit()
{
MyData =
new
List<SampleData>();
for
(
int
i = 0; i < 50; i++)
{
MyData.Add(
new
SampleData()
{
ID = i,
Name =
"Name "
+ i.ToString(),
SomeDecimal = i
});
}
}
}
Using the keyboard to save items works inconsistently. When editing an item, hitting enter seems to save the change as expected. When inserting an item, however, the item is lost.
---
ADMIN EDIT
Tthe grid calls the OnUpdate handler again, not OnCreate which results in data loss - the newly inserted item is unlikely to match existing data and so it appears to be lost.
Workaround - call the OnCreate handler yourself when the ID of the record is the default for its type - for example, zero for an integer. This indicates a newly inserted record in the grid.
<TelerikGrid Data=@MyData EditMode="@GridEditMode.Inline" Pageable="true" Height="500px" Navigable="true"
OnUpdate="@UpdateHandler" OnDelete="@DeleteHandler" OnCreate="@CreateHandler" OnCancel="@CancelHandler">
<GridToolBar>
<GridCommandButton Command="Add" Icon="add">Add Employee</GridCommandButton>
</GridToolBar>
<GridColumns>
<GridColumn Field=@nameof(SampleData.ID) Title="ID" Editable="false" />
<GridColumn Field=@nameof(SampleData.Name) Title="Name" />
<GridCommandColumn>
<GridCommandButton Command="Save" Icon="save" ShowInEdit="true">Update</GridCommandButton>
<GridCommandButton Command="Edit" Icon="edit">Edit</GridCommandButton>
<GridCommandButton Command="Delete" Icon="delete">Delete</GridCommandButton>
<GridCommandButton Command="Cancel" Icon="cancel" ShowInEdit="true">Cancel</GridCommandButton>
</GridCommandColumn>
</GridColumns>
</TelerikGrid>
@code {
async Task UpdateHandler(GridCommandEventArgs args)
{
SampleData item = (SampleData)args.Item;
if (item.ID == 0)
{
await CreateHandler(args);
}
else
{
// perform actual data source operations here through your service
await MyService.Update(item);
// update the local view-model data with the service data
await GetGridData();
Console.WriteLine("Update event is fired.");
}
}
async Task DeleteHandler(GridCommandEventArgs args)
{
SampleData item = (SampleData)args.Item;
// perform actual data source operation here through your service
await MyService.Delete(item);
// update the local view-model data with the service data
await GetGridData();
Console.WriteLine("Delete event is fired.");
}
async Task CreateHandler(GridCommandEventArgs args)
{
SampleData item = (SampleData)args.Item;
// perform actual data source operation here through your service
await MyService.Create(item);
// update the local view-model data with the service data
await GetGridData();
Console.WriteLine("Create event is fired.");
}
async Task CancelHandler(GridCommandEventArgs args)
{
SampleData item = (SampleData)args.Item;
// if necessary, perform actual data source operation here through your service
Console.WriteLine("Cancel event is fired.");
}
// in a real case, keep the models in dedicated locations, this is just an easy to copy and see example
public class SampleData
{
public int ID { get; set; }
public string Name { get; set; }
}
public List<SampleData> MyData { get; set; }
async Task GetGridData()
{
MyData = await MyService.Read();
}
protected override async Task OnInitializedAsync()
{
await GetGridData();
}
// the following static class mimics an actual data service that handles the actual data source
// replace it with your actual service through the DI, this only mimics how the API can look like and works for this standalone page
public static class MyService
{
private static List<SampleData> _data { get; set; } = new List<SampleData>();
public static async Task Create(SampleData itemToInsert)
{
itemToInsert.ID = _data.Count + 1;
_data.Insert(0, itemToInsert);
}
public static async Task<List<SampleData>> Read()
{
if (_data.Count < 1)
{
for (int i = 1; i < 50; i++)
{
_data.Add(new SampleData()
{
ID = i,
Name = "Name " + i.ToString()
});
}
}
return await Task.FromResult(_data);
}
public static async Task Update(SampleData itemToUpdate)
{
var index = _data.FindIndex(i => i.ID == itemToUpdate.ID);
if (index != -1)
{
_data[index] = itemToUpdate;
}
}
public static async Task Delete(SampleData itemToDelete)
{
_data.Remove(itemToDelete);
}
}
}
---
If the "Navigable" parameter of the Grid is set to false (its default state) and the EditMode is PopUp, there is no focus on the first input in Add/Edit mode.
Reproducible
@using Telerik.Blazor
@using Telerik.Blazor.Components.Grid
<TelerikGrid Data=@GridData
SelectionMode="GridSelectionMode.Multiple"
@bind-SelectedItems="SelectedItems"
Pageable="true"
Height="400px">
<TelerikGridToolBar>
<TelerikGridCommandButton Command="MyDelete" OnClick="DeleteSelectedAsync"
Enabled=@(SelectedItems?.Count() > 0) Icon="delete">Delete</TelerikGridCommandButton>
<TelerikGridCommandButton Enabled="false" OnClick="DeleteSelectedAsync">I must always be disabled</TelerikGridCommandButton>
</TelerikGridToolBar>
<TelerikGridColumns>
<TelerikGridCheckboxColumn />
<TelerikGridColumn Field=@nameof(Employee.Name) />
<TelerikGridColumn Field=@nameof(Employee.Team) Title="Team" />
</TelerikGridColumns>
</TelerikGrid>
@result
@if (SelectedItems != null)
{
<ul>
@foreach (Employee employee in SelectedItems)
{
<li>
@employee.Name
</li>
}
</ul>
}
@code {
void DeleteSelectedAsync()
{
result = $"On {DateTime.Now} there are {SelectedItems?.Count()} items selected";
}
string result;
public List<Employee> GridData { get; set; }
public IEnumerable<Employee> SelectedItems { get; set; }
protected override void OnInitialized()
{
GridData = new List<Employee>();
for (int i = 0; i < 15; i++)
{
GridData.Add(new Employee()
{
EmployeeId = i,
Name = "Employee " + i.ToString(),
Team = "Team " + i % 3
});
}
// select Employee with 3 through 5
SelectedItems = GridData.Skip(2).Take(3).ToList();
}
public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public string Team { get; set; }
}
}
Select one or more rows
Right click another of the rows (there is code in the OnContextMenu handler that changes the selected items to the currently clicked row)
<TelerikContextMenu @ref="@ContextMenuRef" Data="@MenuItems" OnClick="@((MenuItem item) => OnItemClick(item))"></TelerikContextMenu>
<TelerikGrid Data=@GridData
@ref="Grid"
SelectionMode="GridSelectionMode.Multiple"
@bind-SelectedItems="@SelectedEmployees"
@bind-Page="@CurrentPage"
PageSize="@PageSize"
OnRowContextMenu="OnContextMenu"
Pageable="true">
<GridColumns>
<GridCheckboxColumn />
<GridColumn Field=@nameof(Employee.EmployeeId) />
<GridColumn Field=@nameof(Employee.Name) />
<GridColumn Field=@nameof(Employee.Team) />
</GridColumns>
</TelerikGrid>
@if (SelectedEmployees != null)
{
<ul>
@foreach (Employee employee in SelectedEmployees.OrderBy(e => e.EmployeeId))
{
<li>
@employee.EmployeeId
</li>
}
</ul>}
@code {
public IEnumerable<Employee> SelectedEmployees { get; set; } = Enumerable.Empty<Employee>();
TelerikContextMenu<MenuItem> ContextMenuRef { get; set; }
TelerikGrid<Employee> Grid { get; set; }
List<MenuItem> MenuItems { get; set; }
int CurrentPage { get; set; } = 1;
int PageSize { get; set; } = 5;
//data binding and sample data
public List<Employee> GridData { get; set; }
protected override void OnInitialized()
{
GridData = new List<Employee>();
for (int i = 0; i < 15; i++)
{
GridData.Add(new Employee()
{
EmployeeId = i,
Name = "Employee " + i.ToString(),
Team = "Team " + i % 3
});
}
MenuItems = new List<MenuItem>()
{
new MenuItem(){ Text = "Delete", Icon = IconName.Delete, CommandName = "Delete"}
};
}
protected async Task OnItemClick(MenuItem item)
{
if (item.Action != null)
{
item.Action.Invoke();
}
else
{
switch (item.CommandName)
{
case "Delete":
await Task.Delay(1); // do something
break;
}
}
}
protected async Task OnContextMenu(GridRowClickEventArgs args)
{
if (!(args.Item is Employee employee))
return;
SelectedEmployees = new List<Employee> { employee }; // this does not work
if (args.EventArgs is MouseEventArgs mouseEventArgs)
{
await ContextMenuRef.ShowAsync(mouseEventArgs.ClientX, mouseEventArgs.ClientY);
}
}
public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public string Team { get; set; }
}
public class MenuItem
{
public string Text { get; set; }
public string Icon { get; set; }
public Action Action { get; set; }
public string CommandName { get; set; }
}
}
*** Thread created by admin on customer behalf ***
When I have a Navigable grid and I press Esc on the keyboard while editing/inserting a row, I want to do something (e.g., clean up the newly inserted row altogether from the data). Usually, I can use the OnCancel event for this, but it does not fire when pressing the Esc key on the keyboard.
*** Thread created on customer behalf by admin ***
In the following reproducible, try filtering the third column (SomeNavigationProperty.Field1). It does not work.
<TelerikGrid Data="@myData" Pageable="true" Sortable="true" FilterMode="@GridFilterMode.FilterRow" Groupable="true">Reproducible
<TelerikButton OnClick="@(_ => _Splited = !_Splited)">Toggle columns - works fine</TelerikButton>Reproducible
@page "/"
Simplest repro code is below - expand a few rows and select items in them, or in the parent grid. I would expect that selection in each grid is independent, but the child grid seems to control the parent selection too, even though it is disabled.
<TelerikGrid Data="salesTeamMembers" PageSize="4" Pageable="true" @bind-SelectedItems="@SelectedItemsMainGrid" SelectionMode="@GridSelectionMode.Single">
<DetailTemplate>
@{
var employee = context as MainModel;
<TelerikGrid Data="employee.Orders" Pageable="true" PageSize="7" SelectionMode="@GridSelectionMode.None">
<GridColumns>
<GridColumn Field="OrderId"></GridColumn>
<GridColumn Field="DealSize"></GridColumn>
</GridColumns>
</TelerikGrid>
}
</DetailTemplate>
<GridColumns>
<GridColumn Field="Id"></GridColumn>
<GridColumn Field="Name"></GridColumn>
</GridColumns>
</TelerikGrid>
@foreach (var item in SelectedItemsMainGrid)
{
<div>@item.Name</div>
}
@code {
List<MainModel> salesTeamMembers { get; set; }
public IEnumerable<MainModel> SelectedItemsMainGrid { get; set; } = Enumerable.Empty<MainModel>();
protected override void OnInitialized()
{
salesTeamMembers = GenerateData();
}
private List<MainModel> GenerateData()
{
List<MainModel> data = new List<MainModel>();
for (int i = 1; i < 16; i++)
{
MainModel mdl = new MainModel { Id = i, Name = $"Name {i}" };
mdl.Orders = Enumerable.Range(1, 15).Select(x => new DetailsModel { OrderId = x, DealSize = x ^ i }).ToList();
data.Add(mdl);
}
return data;
}
public class MainModel
{
public int Id { get; set; }
public string Name { get; set; }
public List<DetailsModel> Orders { get; set; }
}
public class DetailsModel
{
public int OrderId { get; set; }
public double DealSize { get; set; }
}
}