I have recently encountered an accessibility issue with the grid popup editor where the labels for the generated fields are not linked to their respective editor. It seems that the label does have a "for" attribute that is the same as the column title which I expect, but the Id of the input does not get set to the same thing. I can't see any option to make the association happen automatically.
===
ADMIN EDIT
===
A possible option for the time being is to use a custom popup edit form. You may either declare your desired custom content for the form and link the labels to their respective inputs or use the Form component with the field autogeneration feature which will automatically link the labels to the inputs.
I am using InCell Grid editing. I want to prevent an edit cell from closing on certain condition. However, when I cancel the update with args.IsCancelled = true in OnUpdate, the user can still close the edited cell with Enter or Tab. This is inconsistent with inline or popup editing.
===
A possible workaround is to cancel both OnUpdate and the subsequent OnEdit.
https://blazorrepl.telerik.com/QykMHkks10APuDLQ47
@using System.ComponentModel.DataAnnotations
<TelerikGrid Data="@GridData"
EditMode="@GridEditMode.Incell"
OnEdit="@OnGridEdit"
OnUpdate="@OnGridUpdate">
<GridColumns>
<GridColumn Field="@nameof(SampleModel.Name)" />
<GridColumn Field="@nameof(SampleModel.Min)" />
<GridColumn Field="@nameof(SampleModel.Max)" />
</GridColumns>
</TelerikGrid>
<TelerikNotification @ref="@NotificationRef"
HorizontalPosition="@NotificationHorizontalPosition.Center"
VerticalPosition="@NotificationVerticalPosition.Top" />
@code {
private TelerikNotification? NotificationRef { get; set; }
private List<SampleModel> GridData { get; set; } = new();
private bool ShouldCancelOnEdit { get; set; }
private int LastId { get; set; }
private void OnGridEdit(GridCommandEventArgs args)
{
if (ShouldCancelOnEdit)
{
ShouldCancelOnEdit = false;
args.IsCancelled = true;
}
}
private void OnGridUpdate(GridCommandEventArgs args)
{
var updatedItem = (SampleModel)args.Item;
if (updatedItem.Min > updatedItem.Max)
{
NotificationRef?.Show(new NotificationModel()
{
ThemeColor = ThemeConstants.Notification.ThemeColor.Error,
Text = "Min must be smaller than Max"
});
args.IsCancelled = true;
ShouldCancelOnEdit = true;
}
else
{
var originalItemIndex = GridData.FindIndex(i => i.Id == updatedItem.Id);
if (originalItemIndex != -1)
{
GridData[originalItemIndex] = updatedItem;
}
ShouldCancelOnEdit = false;
}
}
protected override void OnInitialized()
{
for (int i = 1; i <= 5; i++)
{
GridData.Add(new SampleModel()
{
Id = ++LastId,
Name = $"SampleModel {LastId}",
Min = Random.Shared.Next(1, 10),
Max = Random.Shared.Next(11, 20)
});
}
}
public class SampleModel
{
public int Id { get; set; }
[Required]
public string Name { get; set; } = string.Empty;
public int Min { get; set; }
public int Max { get; set; }
}
}
I am using Grid with Cell Selection enabled and DragToSelect="true". If I define a Template inside the GridColumn tag and I add a div inside that, SelectedCellsChanged event handler does not trigger if I start dragging by clicking on the div or if I drop inside that one.
Reproduction: https://blazorrepl.telerik.com/myasmnvQ46Jt7k5n50.
===
ADMIN EDIT
===
The only workaround for the time being is to disable the drag selection and let the user only select cells by clicking them. The cells will be selected even if the user clicks on the custom <div> element inside the template.
Initial groups are duplicated in version 6.1.0. The OnStateInit event fires twice and the group descriptors are added twice. The problem occurs only when using <GridAggregates>.
The workaround is to clear the GroupDescriptors collection on OnStateInit.
@using Telerik.DataSource
<h2>No Aggregates</h2>
<p><code>OnStateInitCounter1:</code> @OnStateInitCounter1</p>
<TelerikGrid Data="@GridData"
TItem="@SampleModel"
Groupable="true"
OnStateInit="@OnGridStateInit1">
<GridAggregates>
</GridAggregates>
<GridColumns>
<GridColumn Field="@nameof(SampleModel.Name)" />
<GridColumn Field="@nameof(SampleModel.GroupName)" />
</GridColumns>
</TelerikGrid>
<h2>With Aggregates</h2>
<p><code>OnStateInitCounter2:</code> @OnStateInitCounter2</p>
<TelerikGrid Data="@GridData"
TItem="@SampleModel"
Groupable="true"
OnStateInit="@OnGridStateInit2">
<GridAggregates>
<GridAggregate Field="@nameof(SampleModel.Name)" Aggregate="@GridAggregateType.Count" />
</GridAggregates>
<GridColumns>
<GridColumn Field="@nameof(SampleModel.Name)" />
<GridColumn Field="@nameof(SampleModel.GroupName)" />
</GridColumns>
</TelerikGrid>
<h2>With Aggregates and Workaround</h2>
<p><code>OnStateInitCounter3:</code> @OnStateInitCounter3</p>
<TelerikGrid Data="@GridData"
TItem="@SampleModel"
Groupable="true"
OnStateInit="@OnGridStateInit3">
<GridAggregates>
<GridAggregate Field="@nameof(SampleModel.Name)" Aggregate="@GridAggregateType.Count" />
</GridAggregates>
<GridColumns>
<GridColumn Field="@nameof(SampleModel.Name)" />
<GridColumn Field="@nameof(SampleModel.GroupName)" />
</GridColumns>
</TelerikGrid>
@code {
private int OnStateInitCounter1 { get; set; }
private int OnStateInitCounter2 { get; set; }
private int OnStateInitCounter3 { get; set; }
private void OnGridStateInit1(GridStateEventArgs<SampleModel> args)
{
++OnStateInitCounter1;
args.GridState.GroupDescriptors.Add(new GroupDescriptor
{
Member = nameof(SampleModel.GroupName)
});
}
private void OnGridStateInit2(GridStateEventArgs<SampleModel> args)
{
++OnStateInitCounter2;
args.GridState.GroupDescriptors.Add(new GroupDescriptor
{
Member = nameof(SampleModel.GroupName)
});
}
private void OnGridStateInit3(GridStateEventArgs<SampleModel> args)
{
++OnStateInitCounter3;
args.GridState.GroupDescriptors = new List<GroupDescriptor>();
// OR
args.GridState.GroupDescriptors.Clear();
args.GridState.GroupDescriptors.Add(new GroupDescriptor
{
Member = nameof(SampleModel.GroupName)
});
}
private List<SampleModel> GridData { get; set; } = new();
protected override void OnInitialized()
{
for (int i = 1; i <= 4; i++)
{
GridData.Add(new SampleModel()
{
Id = i,
Name = $"Name {i}",
GroupName = $"Group {i % 2 + 1}"
});
}
}
public class SampleModel
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string GroupName { get; set; } = string.Empty;
}
}
When trying to make a cell selection using Shift + Click, the clicked cells are not correctly added to the selection range. When clicking on adjacent cells, the selection range gets misplaced resulting in deselection of the first selected cells.
The Grid will throw a JavaScript error if the user wants to group and drops a column header near the edges of the grouping header or between existing group chips.
This is a regression in version 6.0.0.
REPL test page: https://blazorrepl.telerik.com/wouBdbvw36O4UaAR14
A possible workaround is to remove the empty space, which triggers the error, so that users cannot drop on it:
CSS
.k-grid .k-grouping-header {
padding: 0;
border-bottom-width: 0;
gap: 0;
}
.k-grid .k-grouping-header::before {
margin: 0;
}
.k-grid .k-grouping-header .k-grouping-drop-container {
margin: 0;
}
In a filterable Grid, if a column is not bound to a field from the model the Grid uses, it throws with:
Error: System.ArgumentNullException: Value cannot be null. (Parameter 'nullableType')
Reproduction: https://blazorrepl.telerik.com/GyEUlFEs04AUJoJ601.
The issue is reproducible :
===
ADMIN EDIT
===
A possible workaround for the time being is to set the FieldType of the column: https://blazorrepl.telerik.com/wSugvFui10jhcpZy00.
Reproduceable example: https://blazorrepl.telerik.com/QSEganvg12BVw2ZU37
Steps to reproduce:
If you remove the GridAggregates emtpy render fragment, or put at least one GridAggregate component in it, OnStateInit fires like expected.
In my project, I've created a component that wraps around the TelerikGrid that I use on all my pages, so that I can have many properties and functions set to reasonable defaults for my use case. My component has an option to conditionally include GridAggregate components within the inner TelerikGrid's GridAggregates render fragment based on separate configuration. If no separate configuration is provided, no GridAggregate components are included, and the GridAggregates render fragment is left blank. Since my component is built in razor markup, there isn't a great option for nulling out that render fragment if it's empty. I had no problems with this exact same code in UI for Blazor version 5.1, and I hope this can be fixed.
The Grid exits edit mode when expanding or collapsing rows in a hierarchy scenario. This only happens when OnStateChanged is set.
Test page that reproduces the behavior: https://blazorrepl.telerik.com/wIkJvdlo09hXCV8u03
I have the following Grid setup:
Exception: System.ArgumentException: Operator 'IsEqualTo' is incompatible with operand types 'DateOnly?' and 'DateTime'
The header cell includes the following inner elements when the Grid is sortable:
However, if I disable sorting all those elements are omitted which is not consistent.
I have a TelerikGrid with Reordarable enabled inside of a TelerikWindow. Reordering of the column works fine only the drop clue is not showing. I think this is because the z-index is incorrect.
Missing drop clue:
z-index of drop clue is 10000:
z-index of window is 10002:
Consider this test page: https://blazorrepl.telerik.com/cSEQOTQY53LRL4je36
This happens only when using a column menu - ShowColumnMenu="true".
===
A possible workaround is to use a FilterButtonsTemplate and clear the filters programmatically if the filter descriptor is empty:
@using Telerik.DataSource
@using Telerik.DataSource.Extensions
<TelerikGrid TItem="@Employee"
OnRead="@OnReadHandler"
Pageable="true"
FilterMode="@GridFilterMode.FilterMenu"
FilterMenuType="@FilterMenuType.CheckBoxList"
ShowColumnMenu="true"
Height="400px">
<GridColumns>
<GridColumn Field="@(nameof(Employee.EmployeeId))" Filterable="false" />
<GridColumn Field="@nameof(Employee.Name)">
<FilterMenuTemplate Context="context">
<TelerikCheckBoxListFilter Data="@NameOptions"
Field="@(nameof(NameFilterOption.Name))"
@bind-FilterDescriptor="@context.FilterDescriptor">
</TelerikCheckBoxListFilter>
</FilterMenuTemplate>
<FilterMenuButtonsTemplate Context="filterContext">
<TelerikButton OnClick="@( async () => await ApplyFilterAsync(filterContext) )"
ThemeColor="primary">Filter</TelerikButton>
<TelerikButton OnClick="@( async () => await ClearFilterAsync(filterContext) )">Clear</TelerikButton>
</FilterMenuButtonsTemplate>
</GridColumn>
<GridColumn Field="@nameof(Employee.Team)" Title="Team">
<FilterMenuTemplate Context="context">
<TelerikCheckBoxListFilter Data="@TeamsList"
Field="@(nameof(TeamNameFilterOption.Team))"
@bind-FilterDescriptor="@context.FilterDescriptor">
</TelerikCheckBoxListFilter>
</FilterMenuTemplate>
</GridColumn>
<GridColumn Field="@nameof(Employee.IsOnLeave)" Title="On Vacation" />
</GridColumns>
</TelerikGrid>
@code {
List<Employee> AllGridData { get; set; }
#region custom-filter-data
List<TeamNameFilterOption> TeamsList { get; set; }
List<NameFilterOption> NameOptions { get; set; }
private async Task ApplyFilterAsync(FilterMenuTemplateContext filterContext)
{
var hasFilters = filterContext.FilterDescriptor.FilterDescriptors.OfType<FilterDescriptor>().Any(x => !string.IsNullOrEmpty(x.Value.ToString()));
if (hasFilters)
{
await filterContext.FilterAsync();
}
else
{
await filterContext.ClearFilterAsync();
}
}
private async Task ClearFilterAsync(FilterMenuTemplateContext filterContext)
{
await filterContext.ClearFilterAsync();
}
//obtain filter lists data from the data source to show all options
async Task GetTeamOptions()
{
if (TeamsList == null) // sample of caching since we always want all distinct options,
//but we don't want to make unnecessary requests
{
TeamsList = await GetNamesFromService();
}
}
async Task<List<TeamNameFilterOption>> GetNamesFromService()
{
await Task.Delay(500);// simulate a real service delay
// this is just one example of getting distinct values from the full data source
// in a real case you'd probably call your data service here instead
// or apply further logic (such as tie the returned data to the data the grid will have according to your business logic)
List<TeamNameFilterOption> data = AllGridData.OrderBy(z => z.Team).Select(z => z.Team).
Distinct().Select(t => new TeamNameFilterOption { Team = t }).ToList();
return await Task.FromResult(data);
}
async Task GetNameOptions()
{
if (NameOptions == null)
{
NameOptions = await GetNameOptionsFromService();
}
}
async Task<List<NameFilterOption>> GetNameOptionsFromService()
{
await Task.Delay(500);// simulate a real service delay
List<NameFilterOption> data = AllGridData.OrderBy(z => z.Name).Select(z => z.Name).
Distinct().Select(n => new NameFilterOption { Name = n }).ToList();
return await Task.FromResult(data);
}
#endregion custom-filter-data
async Task OnReadHandler(GridReadEventArgs args)
{
//typical data retrieval for the grid
var filteredData = await AllGridData.ToDataSourceResultAsync(args.Request);
args.Data = filteredData.Data as IEnumerable<Employee>;
args.Total = filteredData.Total;
}
protected override async Task OnInitializedAsync()
{
AllGridData = new List<Employee>();
var rand = new Random();
for (int i = 1; i <= 15; i++)
{
AllGridData.Add(new Employee()
{
EmployeeId = i,
Name = "Employee " + i.ToString(),
Team = "Team " + i % 3,
IsOnLeave = i % 2 == 0
});
}
await GetTeamOptions();
await GetNameOptions();
}
public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public string Team { get; set; }
public bool IsOnLeave { get; set; }
}
// in this sample we use simplified models to fetch less data from the service
// instead of using the full Employee model that has many fields we do not need for the filters
public class TeamNameFilterOption
{
public string Team { get; set; }
}
public class NameFilterOption
{
public string Name { get; set; }
}
}
There is a change in the Grid behavior from version 4.6.0 to 5.0.0.
Consider the following REPL test page: https://blazorrepl.telerik.com/cSabmRPy306iPiXK18
Clicking on a Grid cell for editing will not open it for editing if there is already an open cell in edit mode on the same table row. The issue is reproduced more easily if the OnUpdate handler is empty or not defined at all.
This used to work in version 4.6.0.