Sample reproducible with workaround (to initialize the data source so it is not null) is below
Sample error and stack trace
ArgumentNullException: Value cannot be null. (Parameter 'source')
<TelerikGrid Data=@GridData Pageable="true" Height="300px">
<GridColumns>
<GridColumn Field="@(nameof(Employee.EmployeeId))">
<FooterTemplate>
some footer
</FooterTemplate>
</GridColumn>
<GridColumn Field=@nameof(Employee.Salary) Title="Salary">
</GridColumn>
<GridColumn Field=@nameof(Employee.Name)>
</GridColumn>
</GridColumns>
</TelerikGrid>
@code {
public List<Employee> GridData { get; set; } // = new List<Employee>(); // workaround
public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public decimal Salary { get; set; }
}
}
When the sum of the widths of the Grid columns is higher than the total width of the Grid and a horizontal scrollbar appears the Virtual Scrolling twitches when the user scrolls to the bottom of the Grid. The same behavior can be observed if the Grid has Resizeable columns and by resizing them horizontal scrollbar appears.
<AdminEdit>
The issue is reproducible on mobile Safari without horizontal scrolling.
</AdminEdit>
When you shrink a Grid column, ellipsis is rendered to represent the clipped text. However, at some point of resizing the Column menu and Filter menu indicators are overlapping the Grid column header text.
==========
ADMIN EDIT
==========
In the meantime, a possible workaround would be to use some custom CSS to add right padding to the k-link span, so it does not get overlapped by the column menu icon. That padding should be approximately as wide as the span holding the column menu icon. You can also set a custom CSS class to the Grid through its Class parameter to make sure you are styling this exact instance of the Grid and not all instances on the page/app. The example below demonstrates how to achieve the described approach.
<style>
.my-grid .k-link {
padding-right: 40px;
}
</style>
<TelerikGrid Data="@MyData"
Class="my-grid"
Pageable="true"
PageSize="5"
FilterMode="@GridFilterMode.FilterMenu"
Sortable="true"
Resizable="true"
ShowColumnMenu="true">
<GridColumns>
<GridColumn Field="@(nameof(SampleData.Id))" Width="80px" />
<GridColumn Field="@(nameof(SampleData.Name))" Title="Employee Name"/>
<GridColumn Field="@(nameof(SampleData.Team))" Title="Team" />
<GridColumn Field="@(nameof(SampleData.HireDate))" Title="Hire Date" />
</GridColumns>
</TelerikGrid>
@code {
public IEnumerable<SampleData> MyData = Enumerable.Range(1, 30).Select(x => new SampleData
{
Id = x,
Name = "name " + x,
Team = "team " + x % 5,
HireDate = DateTime.Now.AddDays(-x).Date
});
public class SampleData
{
public int Id { get; set; }
public string Name { get; set; }
public string Team { get; set; }
public DateTime HireDate { get; set; }
}
}
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; }
}
}
I have the following Grid setup:
Exception: System.ArgumentException: Operator 'IsEqualTo' is incompatible with operand types 'DateOnly?' and 'DateTime'
<TelerikGrid Data="@GridData"
FilterMode="Telerik.Blazor.GridFilterMode.FilterRow"
SelectionMode="@GridSelectionMode.Multiple"
@bind-SelectedItems="SelectedItems">
<GridColumns>
<GridCheckboxColumn Width="35px"/>
<GridColumn Field="@(nameof(Data.Name))" />
</GridColumns>
</TelerikGrid>
@code{
public class Data
{
public string Name { get; set; }
}
public List<Data> GridData { get; set; }
public IEnumerable<Data> SelectedItems { get; set; }
protected override void OnInitialized()
{
GridData = new List<Data>();
SelectedItems = new List<Data>();
GridData.Add(new Data{Name="abc"});
GridData.Add(new Data{Name="abe"});
GridData.Add(new Data{Name="xyz"});
}
}
1. Filter the above grid by "abc" (the filtered grid will display "abc" correctly as the only entry)
2. Click into the "SelectAll" Checkbox (SelectedItems will wrongly contain "abc" AND "abe" !!!)
3. Click on "ClearFilter" Button --> the grid will display both "abc" and "abe" as selected !!!
It might be an indexing problem because the "SelectAll" Logic always seems to ignore the last character of the search string while the filtering of the display takes all characters into account.
This bug has cost be many hours and stomach pain! Please let me know, if this will be fixed soon. If not I will have to implement my own filtering (which makes me wonder why I'm using Telerik UI).
The null type of operator can cause errors on the backend
*** Thread created by admin on customer behalf ***
When using row virtualization, the visible rows are miscalculated when the browser zoom is used (hold control with mouse scroll).
The result of the miscalculation is that the first row at some point is not visible.
To reproduce the problem you should scroll to the top of the grid and see the first row.
Then start to zoom down and see how the first row is displaced.
Seems like only 50%, 100%, 150%, 200% (multiply of 50%) are calculated correctly in row virtualization
<AdminEdit>
You can reproduce the bug with the attached code.
It's browser-specific and is appearing only in chromium browsers.
It could be related to the way browsers calculate the size of the elements when zooming.
Currently, there is an open bug in the chromium: https://bugs.chromium.org/p/chromium/issues/detail?id=1048147&q=component%3AUI%3EBrowser%3EZoom%20height&can=2
</AdminEdit>
The orderby clause only has one column when multicolumn sorting is enabled, you can test it with this sample.
---
ADMIN EDIT
A workaround is to replace the orderby generated by the grid with your own clause that uses the DataSourceRequest to extract all sort descriptors, here's an example:
@using System.Net.Http.Json
@using System.Text.RegularExpressions
@using Telerik.Blazor.Extensions
@using WasmApp.Shared
@inject HttpClient Http
<TelerikGrid Data=@GridData
Height="460px"
RowHeight="60"
PageSize="10"
Pageable="true"
Sortable="true"
FilterMode="@GridFilterMode.FilterRow"
SortMode="@SortMode.Multiple"
OnRead=@ReadItems
TotalCount=@Total>
<GridColumns>
<GridColumn Field="ProductID" />
<GridColumn Field="ProductName" />
<GridColumn Field="Discontinued" />
</GridColumns>
</TelerikGrid>
@code{
public List<ODataProduct> GridData { get; set; } = new List<ODataProduct>();
public int Total { get; set; } = 0;
protected async Task ReadItems(GridReadEventArgs args)
{
var baseUrl = "https://demos.telerik.com/kendo-ui/service-v4/odata/Products?";
string OdataUrl = args.Request.ToODataString();
// replace the original orederby clause with one that contains all the order rules
Regex x = new Regex("(orderby=)(.*?)(&)", RegexOptions.IgnoreCase);
string actualOrderByClause = "orderby=";
for (int i = 0; i < args.Request.Sorts.Count; i++)
{
if (i > 0)
{
actualOrderByClause += ",";
}
string order = args.Request.Sorts[i].SortDirection == Telerik.DataSource.ListSortDirection.Ascending ? "" : "%20desc";
actualOrderByClause += $"{args.Request.Sorts[i].Member}{order}";
}
actualOrderByClause += "&";
string OdataQueryWithMultipleOrder = x.Replace(OdataUrl, actualOrderByClause);
//do the request as usual
var requestUrl = $"{baseUrl}{OdataQueryWithMultipleOrder}";
ODataResponseOrders response = await Http.GetFromJsonAsync<ODataResponseOrders>(requestUrl);
GridData = response.Products;
Total = response.Total;
}
}
---
If you set args.IsCancelled=true in any CUD event handler, the loading sign will never go away.
---
ADMIN EDIT
If you don't need to cancel the event (e.g., because remote validation or data operation failed), you should avoid cancelling it - its purpose is to keep the grid in the current mode (edit/insert) when the operation fails so the user does not lose data.
That said, the loading sing should disappear in thsoe cases too, and a workaround is to disable it by setting the EnableLoaderContainer="false" parameter of the grid.
---
I have a WASM application and I setup my Grid to use the editor as an editing field in the Grid. When the user types something the application is laggy and it might even hang.
---
ADMIN EDIT
Attached is a sample app that alleviates this a little by using a regular textarea and making editor updates less frequent.
---