I have a filterable Grid with Enum values. I am saving the state in the OnStateChanged so I can then restore it on future page loads (similar to the approach here).
When trying to restore the deserialized state in the OnStateInit, the Grid throws:
Unhandled exception rendering component: Specified cast is not valid.
Reproduction: https://blazorrepl.telerik.com/QSaiaDkq55RfazF402.
===
ADMIN EDIT
===
At the time of writing (UI for Blazor 6.1.0), the Grid only supports int as an underlying type of the Enum values. When the state is deserialized, the Enum values are returned as short and this causes the issue.
To handle the scenario for the time being you can modify the state object before setting it to the Grid. Loop through the filter descriptors in the state and convert the filter value of the columns bound to Enum, so the value type in the state is the supported int.
Here is a basic example: https://blazorrepl.telerik.com/woaCOZlT29E05lpL45.
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.
I have a Grid using checkbox selection and when I un-check a row, the SelectedItemsChanged event is not firing. I'm using an async handler.
The problem appears to happen when a row is selected and then un-selected. The event is firing when it is selected but is not firing when it is un-selected.
<TelerikGrid Data="strategicLevelItems" SelectedItems="selectedStrategic" Width="100%" Height="500px"
ScrollMode="GridScrollMode.Scrollable" SelectionMode="GridSelectionMode.Multiple"
SelectedItemsChanged="@((IEnumerable<GetNavigationNodesModel> strategicItems) => OnStrategicSelectAsync(strategicItems))"
FilterMode="GridFilterMode.FilterRow">
<GridColumns>
<GridCheckboxColumn SelectAll="true" Width="40px" OnCellRender="@GridHelpers.CenterAlign" />
<GridColumn Field="@(nameof(GetNavigationNodesModel.Name))" />
</GridColumns>
</TelerikGrid>
private async Task OnStrategicSelectAsync(IEnumerable<GetNavigationNodesModel> selectedItems)
{
selectedStrategic = selectedItems;
var state = tacticalGrid.GetState();
var compositeFilter = new CompositeFilterDescriptor() { LogicalOperator = FilterCompositionLogicalOperator.Or };
foreach (var item in selectedItems)
{
compositeFilter.FilterDescriptors.Add(new FilterDescriptor()
{
Member = "ParentId",
Operator = FilterOperator.IsEqualTo,
Value = item.Id
});
}
state.FilterDescriptors.Clear();
state.FilterDescriptors.Add(compositeFilter);
await tacticalGrid.SetState(state);
}
---
ADMIN EDIT
Here is a workaround - using a bit of JS to clear the checked state of the checkbox element when you clear the SelectedItems collection:
At the end of this post you can find attached a sample project that showcases a more complex scenario where you may want to keep some rows selected but cancel the selection of others (in that sample, only one row with a given ItemType can be selected). It shows how you can get the index in the rendering of a particular row to use for a slightly modified version of the JS function.
@inject IJSRuntime _js
@* Move JavaScript code to a separate JS file in production *@
<script suppress-error="BL9992">
function uncheckGrid() {
var checkboxes = document.querySelectorAll(".no-select .k-grid-checkbox");
for (var i = 0; i < checkboxes.length; i++) {
checkboxes[i].checked = false;
}
}
</script>
<style>
.no-select {
cursor: not-allowed;
}
.no-select .k-checkbox {
pointer-events: none;
}
</style>
<TelerikGrid Data=@GridData
SelectionMode="@GridSelectionMode.Multiple"
SelectedItemsChanged="@((IEnumerable<Employee> employeeList) => OnSelect(employeeList))"
SelectedItems="@SelectedEmployees"
Pageable="true"
Height="400px">
<GridColumns>
<GridCheckboxColumn SelectAll="true" OnCellRender="@OnGridCellRender" />
<GridColumn Field="@nameof(Employee.Name)" />
<GridColumn Field="@nameof(Employee.Team)" />
<GridColumn Field="@nameof(Employee.Active)" />
</GridColumns>
</TelerikGrid>
@if (SelectedEmployees != null)
{
<ul>
@foreach (Employee employee in SelectedEmployees)
{
<li>
@employee.Name
</li>
}
</ul>
}
@code {
private List<Employee> GridData { get; set; } = new List<Employee>();
private IEnumerable<Employee> SelectedEmployees { get; set; } = new List<Employee>();
private void OnGridCellRender(GridCellRenderEventArgs args)
{
var product = (Employee)args.Item;
if (!product.Active)
{
args.Class = "no-select";
}
}
protected void OnSelect(IEnumerable<Employee> newSelected)
{
var validItemsToSelect = newSelected.Where(x => x.Active);
SelectedEmployees = validItemsToSelect;
if (validItemsToSelect.Count() < newSelected.Count())
{
_js.InvokeVoidAsync("uncheckGrid");
}
}
protected override void OnInitialized()
{
for (int i = 1; i <= 7; i++)
{
GridData.Add(new Employee()
{
EmployeeId = i,
Name = $"Employee {i}",
Team = $"Team {i % 3 + 1}",
Active = i % 2 == 0
});
}
}
public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; } = string.Empty;
public string Team { get; set; } = string.Empty;
public bool Active { get; set; }
}
}
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;
}
Based on some program logic and conditions I want to programmatic scroll to a chosen row so the user can see a specific row. For example:
- Scroll to a selected row
- Scroll to a record, based on its Id
-Scroll to the top of the Grid when the user adds a new item
- Scroll to an expanded item in hierarchical Grid
=====ADMIN EDIT======
We have a knowledge-based article that showcases how to scroll the Grid to the selected row. This article links to a sample GitHub application where you see code samples. If you need to scroll to another row, you can use a similar approach.
As of UI for Blazor 6.0.2 OnRead event handler is triggered twice if Grid has defined the GridAggregates Parameter (if you try defining Grid without the GridAggregates Parameter, the OnRead event handler is caller once). In the first call the args.Request.Aggregates value is empty, while in the second one that field is populated correctly.
The behavior may be related to the fix of this issue but I'd prefer the Grid to fire OnRead only once if possible.
===
ADMIN EDIT
===
Currently, OnRead fires twice only on initial load. A possible optimization that you can implement on your side is to toggle a flag based on which you can process the data only once. You can do that in the second OnRead as this is when the AggregateResults field of the request gets populated.
Here is a basic example: https://blazorrepl.telerik.com/GokrRkYX536x1mSF35.
If LoadGroupsOnDemand="false", I am able to programmatically expand the groups through the state. However, this is not possible when loading group data on demand.
Please allow programmatically expanding the groups when LoadGroupsOnDemand="true". This should go together with an event (OnStateChanged?) that will fire when LOD groups are expanded or collapsed.
The Grid automatically scrolls to top when adding a new item in inline or incell edit mode. However, when the Grid has locked (frozen) columns, this scrolling doesn't occur.
Here is a test page with a JavaScript workaround in the OnAdd event:
@using System.ComponentModel.DataAnnotations
@inject IJSRuntime js
<label><TelerikCheckBox @bind-Value="@EnableScrollWorkaround" /> Enable Workaround</label>
<TelerikGrid Data="@GridData"
EditMode="@GridEditMode.Inline"
OnAdd="@OnGridAdd"
OnUpdate="@OnGridUpdate"
OnCreate="@OnGridCreate"
Width="700px"
Height="400px"
Class="grid-class">
<GridToolBarTemplate>
<GridCommandButton Command="Add">Add Item</GridCommandButton>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@nameof(Product.Name)" Width="200px" Locked="true" />
<GridColumn Field="@nameof(Product.Price)" Width="200px" />
<GridColumn Field="@nameof(Product.Quantity)" Width="200px" />
<GridCommandColumn Width="200px" Locked="true">
<GridCommandButton Command="Edit">Edit</GridCommandButton>
<GridCommandButton Command="Save" ShowInEdit="true">Save</GridCommandButton>
<GridCommandButton Command="Cancel" ShowInEdit="true">Cancel</GridCommandButton>
</GridCommandColumn>
</GridColumns>
</TelerikGrid>
<script suppress-error="BL9992">
function scrollGridToTop() {
var gridContentArea = document.querySelector(".grid-class .k-grid-content");
if (gridContentArea) {
gridContentArea.scrollTop = 0;
}
}
</script>
@code {
private bool EnableScrollWorkaround { get; set; } = true;
private List<Product> GridData { get; set; } = new();
private int LastId { get; set; }
private async Task OnGridAdd(GridCommandEventArgs args)
{
if (EnableScrollWorkaround)
{
await js.InvokeVoidAsync("scrollGridToTop");
}
}
private void OnGridUpdate(GridCommandEventArgs args)
{
var updatedItem = (Product)args.Item;
var originalItemIndex = GridData.FindIndex(i => i.Id == updatedItem.Id);
if (originalItemIndex != -1)
{
GridData[originalItemIndex] = updatedItem;
}
}
private void OnGridCreate(GridCommandEventArgs args)
{
var createdItem = (Product)args.Item;
createdItem.Id = ++LastId;
GridData.Insert(0, createdItem);
}
protected override void OnInitialized()
{
for (int i = 1; i <= 30; i++)
{
GridData.Add(new Product()
{
Id = ++LastId,
Name = $"Product {LastId}",
Quantity = (short)Random.Shared.Next(0, 1000),
StartDate = DateTime.Now.AddDays(-Random.Shared.Next(60, 1000)),
IsActive = LastId % 4 > 0
});
}
}
public class Product
{
public int Id { get; set; }
[Required]
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Quantity { get; set; }
public DateTime StartDate { get; set; }
public bool IsActive { get; set; }
}
}
In a navigable Grid with incell edit, if a column has Editable=false the tabbing behavior is as follows: while the user is editing the previous editable cell pressing tab results in skipping the non-editable cell and moving the focus to the next editable cell to open it for edit.
However, the behavior is different when cancelling the editing through the OnEdit event. In this case, while the user is editing the previous editable cell pressing tab results in exiting the edit and keeping the focus to the cell. Subsequent tab press moves to focus to the pager.
Reproduction: https://blazorrepl.telerik.com/mykLFEFJ14mlqei429.
===
ADMIN EDIT
===
A possible option for the time being is to cancel the edit as shown in the example above but also force the Grid to enter edit of the next editable cell. You can do that through the State: Enter and Exit Grid Edit Mode Programmatically.
Here is a basic example, you may adjust it as needed to fit your exact requirements: https://blazorrepl.telerik.com/mSuLPAFP05HRs7B555.
The Grid shows row skeletons in virtual scrolling scenarios when:
Test page: https://blazorrepl.telerik.com/mSkBaXkC50cEXmMB42
A possible workaround is to:
Example: https://blazorrepl.telerik.com/cSYrvEYW19htMCD934
Another possible solution is to disable the virtual scrolling at runtime if the total item count is too small: https://blazorrepl.telerik.com/mSOhvYuW47DaMk6c00
I have a grid with the excell Export. Now I have two custom Export buttons on the Page (Over and under the Grid).
Can I somehow trigger the Grid Excell Export command Manually?
---
ADMIN EDIT
Here is a workaround - a bit of JS to click the desired button and the second snippet shows how to invoke it from a blazor component and code.
<script>
function clickButton(selector) {
var btn = document.querySelector(selector);
if (btn && btn.click) {
btn.click();
}
}
</script>
@inject IJSRuntime _js
<style>
/* hide the built-in button if you like */
.myExcelExportButton {
display:none;
}
</style>
<button @onclick="@MyExternalExportTrigger">my external export button</button>
<TelerikGrid Data="@GridData" Pageable="true" Sortable="true" Resizable="true" Reorderable="true"
FilterMode="@GridFilterMode.FilterRow" Groupable="true"
Class="@GridClass">
<GridToolBar>
<GridCommandButton Class="@ExportBtnClass" Command="ExcelExport" Icon="@IconName.FileExcel">Export to Excel</GridCommandButton>
<label><TelerikCheckBox @bind-Value="@ExportAllPages" />Export All Pages</label>
</GridToolBar>
<GridExport>
<GridExcelExport FileName="telerik-grid-export" AllPages="@ExportAllPages" />
</GridExport>
<GridColumns>
<GridColumn Field="@nameof(SampleData.ProductId)" Title="ID" Width="100px" />
<GridColumn Field="@nameof(SampleData.ProductName)" Title="Product Name" Width="300px" />
<GridColumn Field="@nameof(SampleData.UnitsInStock)" Title="In stock" Width="100px" />
<GridColumn Field="@nameof(SampleData.Price)" Title="Unit Price" Width="200px" />
<GridColumn Field="@nameof(SampleData.Discontinued)" Title="Discontinued" Width="100px" />
<GridColumn Field="@nameof(SampleData.FirstReleaseDate)" Title="Release Date" Width="300px" />
</GridColumns>
</TelerikGrid>
@code {
// cascade through classes on the grid and/or on the built-in button to click it with JS
// so that it can invoke the built-in export operations
string GridClass { get; set; } = "MyGrid";
string ExportBtnClass { get; set; } = "myExcelExportButton";
async Task MyExternalExportTrigger()
{
var selector = $".{GridClass} .k-header .{ExportBtnClass}";
await _js.InvokeVoidAsync("clickButton", selector);
}
List<SampleData> GridData { get; set; }
bool ExportAllPages { get; set; }
protected override void OnInitialized()
{
GridData = Enumerable.Range(1, 100).Select(x => new SampleData
{
ProductId = x,
ProductName = $"Product {x}",
UnitsInStock = x * 2,
Price = 3.14159m * x,
Discontinued = x % 4 == 0,
FirstReleaseDate = DateTime.Now.AddDays(-x)
}).ToList();
}
public class SampleData
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public int UnitsInStock { get; set; }
public decimal Price { get; set; }
public bool Discontinued { get; set; }
public DateTime FirstReleaseDate { get; set; }
}
}
---
I am using a ui framework that has some elements on the body with z-indexes > 1000. Eg a side menu. I am using the Telerik Grid. The Column Menus use the animation container, and because the animation containers get placed directly on the body with a z-index of 2, they always appear behind this side menu.
I want to be able to set a CSS to the main wrapping element of the Column Menu (<div class="k-animation-container>), so I can specifically set the z-index for only Column Menu instances of the animation container.