The Blazor TelerikGrid component should support a dropdown column. It should be exactly the same as the dropdown column in the Ajax grid.
https://docs.telerik.com/devtools/aspnet-ajax/controls/grid/columns/column-types#dropdown
It would be nice if I could specify an item in the inline editor (or any other for that matter) to get focus when the editor is activated.
Thanks,
Kenny
===
Telerik edit:
A possible workaround is:
The example below also shows how to enter Grid edit mode programmatically and how to focus a specific field when editing with OnRowClick.
<TelerikGrid @ref="@GridInlineRef"
Data="@GridInlineData"
EditMode="@GridEditMode.Inline"
OnAdd="@OnGridAddEdit"
OnEdit="@OnGridAddEdit"
OnCreate="@OnGridInlineCreate"
OnUpdate="@OnGridInlineUpdate"
OnRowClick="@OnGridRowClick">
<GridToolBarTemplate>
<GridCommandButton Command="Add">Add</GridCommandButton>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@nameof(Product.Name)">
<EditorTemplate>
@{ var editItem = (Product)context; }
<TelerikTextBox @ref="@NameTextBoxRef" @bind-Value="@editItem.Name" DebounceDelay="0" />
</EditorTemplate>
</GridColumn>
<GridColumn Field="@nameof(Product.Price)">
<EditorTemplate>
@{ var editItem = (Product)context; }
<TelerikNumericTextBox @ref="@PriceNumericTextBoxRef" @bind-Value="@editItem.Price" DebounceDelay="0" />
</EditorTemplate>
</GridColumn>
<GridColumn Field="@nameof(Product.Stock)">
<EditorTemplate>
@{ var editItem = (Product)context; }
<TelerikNumericTextBox @ref="@StockNumericTextBoxRef" @bind-Value="@editItem.Stock" DebounceDelay="0" />
</EditorTemplate>
</GridColumn>
<GridCommandColumn>
<GridCommandButton Command="Edit">Edit</GridCommandButton>
<GridCommandButton Command="Save" ShowInEdit="true">Update</GridCommandButton>
<GridCommandButton Command="Cancel" ShowInEdit="true">Cancel</GridCommandButton>
</GridCommandColumn>
</GridColumns>
</TelerikGrid>
@code {
private TelerikGrid<Product>? GridInlineRef { get; set; }
private TelerikTextBox? NameTextBoxRef { get; set; }
private TelerikNumericTextBox<decimal?>? PriceNumericTextBoxRef { get; set; }
private TelerikNumericTextBox<int>? StockNumericTextBoxRef { get; set; }
private string EditFieldToFocus { get; set; } = string.Empty;
private List<Product> GridInlineData { get; set; } = new List<Product>();
private void OnGridAddEdit(GridCommandEventArgs args)
{
EditFieldToFocus = nameof(Product.Stock);
}
private async Task OnGridRowClick(GridRowClickEventArgs args)
{
EditFieldToFocus = args.Field;
var itemToEdit = (Product)args.Item;
args.ShouldRender = true;
if (GridInlineRef != null)
{
var gridState = GridInlineRef.GetState();
gridState.InsertedItem = null!;
gridState.OriginalEditItem = itemToEdit;
gridState.EditItem = itemToEdit.Clone();
await GridInlineRef.SetStateAsync(gridState);
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!string.IsNullOrEmpty(EditFieldToFocus))
{
await Task.Delay(200);
switch (EditFieldToFocus)
{
case nameof(Product.Name):
if (NameTextBoxRef != null)
{
await NameTextBoxRef.FocusAsync();
}
break;
case nameof(Product.Price):
if (PriceNumericTextBoxRef != null)
{
await PriceNumericTextBoxRef.FocusAsync();
}
break;
case nameof(Product.Stock):
if (StockNumericTextBoxRef != null)
{
await StockNumericTextBoxRef.FocusAsync();
}
break;
default:
break;
}
EditFieldToFocus = string.Empty;
}
}
#region Programmatic Inline Editing
private async Task InlineAdd()
{
var gridState = GridInlineRef!.GetState();
gridState.InsertedItem = new Product();
gridState.InsertedItem.Name = "New value";
gridState.OriginalEditItem = null!;
gridState.EditItem = null!;
await GridInlineRef.SetStateAsync(gridState);
}
private async Task InlineEdit()
{
if (GridInlineData.Any())
{
var gridState = GridInlineRef!.GetState();
gridState.InsertedItem = null!;
gridState.OriginalEditItem = GridInlineData.First();
gridState.EditItem = GridInlineData.First().Clone();
gridState.EditItem.Name = "Updated inline value";
EditFieldToFocus = nameof(Product.Price);
await GridInlineRef.SetStateAsync(gridState);
}
}
private async Task InlineCancel()
{
var gridState = GridInlineRef!.GetState();
gridState.InsertedItem = null!;
gridState.OriginalEditItem = null!;
gridState.EditItem = null!;
await GridInlineRef.SetStateAsync(gridState);
}
private async Task InlineUpdate()
{
var gridState = GridInlineRef!.GetState();
if (gridState.EditItem != null)
{
OnGridInlineUpdate(new GridCommandEventArgs()
{
Item = gridState.EditItem
});
}
else if (gridState.InsertedItem != null)
{
OnGridInlineCreate(new GridCommandEventArgs()
{
Item = gridState.InsertedItem
});
}
gridState.InsertedItem = null!;
gridState.OriginalEditItem = null!;
gridState.EditItem = null!;
await GridInlineRef.SetStateAsync(gridState);
}
#endregion Programmatic Inline Editing
#region Grid Inline Editing Handlers
private void OnGridInlineUpdate(GridCommandEventArgs args)
{
var updatedItem = (Product)args.Item;
var index = GridInlineData.FindIndex(i => i.Id == updatedItem.Id);
if (index != -1)
{
GridInlineData[index] = updatedItem;
}
}
private void OnGridInlineCreate(GridCommandEventArgs args)
{
var createdItem = (Product)args.Item;
createdItem.Id = Guid.NewGuid();
GridInlineData.Insert(0, createdItem);
}
#endregion Grid Inline Editing Handlers
#region Data Generation and Model
protected override void OnInitialized()
{
for (int i = 1; i <= 3; i++)
{
GridInlineData.Add(new Product()
{
Id = Guid.NewGuid(),
Name = $"Product {i}",
Price = Random.Shared.Next(1, 100) * 1.23m,
Stock = (short)Random.Shared.Next(0, 1000)
});
}
}
public class Product
{
public Guid Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal? Price { get; set; }
public int Stock { get; set; }
public DateTime ReleaseDate { get; set; }
public bool InProduction { get; set; }
public Product Clone()
{
return new Product()
{
Id = Id,
Name = Name,
Price = Price,
Stock = Stock,
ReleaseDate = ReleaseDate,
InProduction = InProduction
};
}
}
#endregion Data Generation and Model
}
It seems the Grid and GridSearchBox assume the search string by taking the value of GridState.SearchFilter.FilterDescriptors[0].Value and this is causing a few issues:
I am expanding grid search to include non-string columns, and I want this to apply by default across our entire application without having to update every grid individually or developers having to remember to opt in.
To search other type columns, I’ve largely taken the logic from Search Grid in numeric and date fields - Telerik UI for Blazor and it works well. However, that solution utilizes a custom search box component that I would have to add to each grid.
Instead, I have placed this filter creation logic in the OnStateChanged handler as exemplified in How to Search Grid Items with a StartsWith Filter Operator - Telerik UI for Blazor, as we already have a handler for this event that all grids utilize.
This has worked wonderfully except for the issues I’ve mentioned.
If the first filter is a CompositeFilterDescriptor, it causes an exception. Specifically:
Unable to cast object of type 'Telerik.DataSource.CompositeFilterDescriptor' to type 'Telerik.DataSource.FilterDescriptor'.
at Telerik.Blazor.Components.Common.TableGridBase`2.LoadSearchFilter(IFilterDescriptor descriptor)
at Telerik.Blazor.Components.TelerikGrid`1.SetStateInternal(GridState`1 state)
at Telerik.Blazor.Components.TelerikGrid`1.<SetStateAsync>d__311.MoveNext()
at Web.Pages.Grid.<OnStateChangedHandler>d__18.MoveNext() in C:\src\Web\Pages\Grid.razor:line 196
If there is another filter in the collection that is not a CompositeFilterDescriptor, I can work around this problem by moving it to the front of the list.
// Make sure first filter is not composite.
var nonCompositeFilter = newSearchFilter.FilterDescriptors.OfType<FilterDescriptor>().FirstOrDefault();
if (nonCompositeFilter is not null && newSearchFilter.FilterDescriptors[0] is CompositeFilterDescriptor)
{
newSearchFilter.FilterDescriptors.Remove(nonCompositeFilter);
newSearchFilter.FilterDescriptors.Insert(0, nonCompositeFilter);
}
However, if all filters are composite, the exception is unavoidable.
If the GridState.SearchFilter.FilterDescriptors collection is empty, the search box gets cleared. This is a problem when a user needs to type more characters for a filter to be created.
For example, let’s say you are only searching DateTime columns and using the logic from Search Grid in numeric and date fields - Telerik UI for Blazor. As you type, it checks to see if the input is an integer between 1000 and 2100. Until you type the fourth digit, it will not meet that condition thus will not add a filter. So if you begin by typing “2”, it searches and a filter will not be added yet. However, because there are no filters, it will clear the search box. You won’t be able to type out a full year value of “2023” unless you type fast enough to outpace the debounce delay.
Using the same example as above but with an Enum column instead of a DateTime, begin typing text. If the text matches an enum name, a filter is added for the enum item’s underlying value. For example:
Sample project attached.
I am trying to get the currently filtered data out of the grid as per this KB article and I want to include the searchbox filters. I do not, however, want to use OnRead but I want to get the grid state on a click of a button and get the filters plus the searchbox filters from it instead.
---
ADMIN EDIT
Here is a sample of getting those filters through the OnRead event without using remote operations - all the data is in the view model (the SourceData field) so this does not change the way operations happen compared to not using OnRead.
@using Telerik.DataSource
@using Telerik.DataSource.Extensions
@( new MarkupString(output) )
<br />
<TelerikButton OnClick="@GetFilters">Get Filters</TelerikButton>
<TelerikGrid Data=@GridData TotalCount=@Total OnRead=@ReadItems
FilterMode=@GridFilterMode.FilterRow Sortable=true Pageable=true EditMode="@GridEditMode.Inline">
<GridToolBar>
<GridSearchBox />
</GridToolBar>
<GridColumns>
<GridColumn Field=@nameof(Employee.ID) />
<GridColumn Field=@nameof(Employee.Name) Title="Name" />
<GridColumn Field=@nameof(Employee.HireDate) Title="Hire Date" />
<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 {
TelerikGrid<Employee> GridRef { get; set; }
string output { get; set; }
public DataSourceRequest CurrentRequest { get; set; }
void GetFilters()
{
output = string.Empty;
foreach (var item in CurrentRequest.Filters)
{
if (item is FilterDescriptor) // filter row
{
FilterDescriptor currFilter = item as FilterDescriptor;
output += $"field: {currFilter.Member}, operator {currFilter.Operator}, value: {currFilter.Value}<br />";
}
if (item is CompositeFilterDescriptor) // filter menu
{
CompositeFilterDescriptor currFilter = item as CompositeFilterDescriptor;
output += $"START nested filter: logical operator: {currFilter.LogicalOperator}, details:<br />";
// there will actually be 1 or 2 only, this showcases the concept and the types
foreach (FilterDescriptor nestedFilter in currFilter.FilterDescriptors)
{
output += $"field: {nestedFilter.Member}, operator {nestedFilter.Operator}, value: {nestedFilter.Value}<br />";
}
output += "END nested filter<br />";
}
}
}
public List<Employee> SourceData { get; set; }
public List<Employee> GridData { get; set; }
public int Total { get; set; } = 0;
protected override void OnInitialized()
{
SourceData = GenerateData();
}
protected async Task ReadItems(GridReadEventArgs args)
{
CurrentRequest = args.Request;
var datasourceResult = SourceData.ToDataSourceResult(args.Request);
GridData = (datasourceResult.Data as IEnumerable<Employee>).ToList();
Total = datasourceResult.Total;
StateHasChanged();
}
//This sample implements only reading of the data. To add the rest of the CRUD operations see
//https://docs.telerik.com/blazor-ui/components/grid/editing/overview
private List<Employee> GenerateData()
{
var result = new List<Employee>();
var rand = new Random();
for (int i = 0; i < 100; i++)
{
result.Add(new Employee()
{
ID = i,
Name = "Name " + i,
HireDate = DateTime.Now.Date.AddDays(rand.Next(-20, 20))
});
}
return result;
}
public class Employee
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime HireDate { get; set; }
}
}
---
Imagine a Grid with two editable columns, which are separated by a few non-editable ones. In the standard use case, tabbing from the first editable column should jump over the non-editable columns, and the user should end up in the second editable column.
However, if the Grid uses virtual columns and the second editable column is not rendered, tab-to-edit will stop working.
Hello,
I seem to have stubled upon a strange bug in TelerikGrid. We have wrapped a TelerikGrid and the Columns are also wrapped to allow special actions.
The bug is present in "raw-telerik-code" as well.
We have an edge case where we have a TelerikGrid and some of its columns should be Locked (Stickied/Frozen) as a default behaviour.
But depending on user interaction we want to change the state. We cannot use a property for each column that we want to be Locked/Unlocked as it should be handled by the GridState.
When the Column is using default behaviour (not Templated), it works as intended. But as soon as you use a <Template> for the Column, the Locked state cannot be changed from the default/supplied value.
TLDR: Programmatically changing the Locked state of a column where the cell is templated will not change the locked state.
I have prepared two REPL examples. One for 3.7.0 as it is what we currently are using, and one for 4.0.1 as to prove that it still exists in the current itteration.
3.7.0
https://blazorrepl.telerik.com/cHEHurlI06olUsJ410
4.0.1
https://blazorrepl.telerik.com/cdYRuBvo07aB6BGY39
With best regards
Currently, the Grid doesn't display its built-in loading animation on initial data (page) load.
The OnRead handler is now completely decoupled with the Data parameter, so the limitation can be removed at least for OnRead scenarios.
We found out during SQL profiling that when bound to an IQueryable (linked to DbContext) with pagination enabled, the Telerik data grid called SQL to count elements twice. This is not a pre-render problem as those deplicated queries are actually executed twice (one pair for each render).
Our simplified code:
<TelerikGrid TItem="Item"
Data="_data"
PageSize="10"
Pageable="true"
Sortable="true">
<GridColumns>
<GridColumn Field="@nameof(Item.Id)" />
<GridColumn Field="@nameof(Item.Code)" />
</GridColumns>
</TelerikGrid>
@code {
[Inject] protected DbContext DbContext { get; set; }
private IEnumerable<Item> _data;
protected override void OnInitialized()
{
base.OnInitialized();
_data = DbContext.Items
.Select(x => new Item()
{
Id = x.Id,
Code = x.Code
});
}
}
===
ADMIN EDIT:
There are two possible ways to avoid the double COUNT:
I would like to be able to customize the default format for dates and numbers that the grid has to, for example, use the current UI culture of my app.
*** Thread created by admin on customer behalf ***
When the user tries to edit a cell in the Grid an exception is thrown. This happens if the Model to which the Grid is bound has an indexer property.
Reproduction: https://blazorrepl.telerik.com/mdEcQxkr15PH4bip51
===
A possible workaround is to map the data to a collection of a different type that has no indexer property: https://blazorrepl.telerik.com/cxaGwcOC383PD8Jr24
When I autofit all columns multiple times in a row, the Grid misbehaves and wraps longer column content in multiple lines.
<AdminEdit>
As a workaround, set the MinResizableWidth parameter of the Grid columns that wrap their content.
</AdminEdit>
Hello,
Imagine a Grid with in-cell editing and a cell X, which restricts editing via IsCancellable = true in the OnEdit event.
If the user clicks on cell X, then editing will be cancelled and the focus will be on this cell.
If the user tabs from the previous cell, then editing will be cancelled and the focus will be on the previous cell.
This looks like an inconsistency. Can the focus go to cell X when editing is cancelled from a tab?
Page Down and Page Up buttons are not working until the user clicks on the scroll when virtual scrolling is enabled.
To reproduce the issue:
1. Open the following REPL example:
https://blazorrepl.telerik.com/QHYPQVFv55nHGRWV59
2. Focus on the Grid.
3. Try to scroll with the PageDown button.
Hello,
The filter row noticeably slows down the tabbing speed during Grid in-cell editing in WASM apps.
As of UI for Blazor 4.0. ExcelExportableColumn is inaccessible due to its protection level and GridExcelExportColumn should be used instead.
I am trying to add the hidden columns to the exported file through the OnBeforeExport event. However, I am unable to create an exportable column instance using the GridExcelExportColumn. I get the following error:
CS1729: GridExcelExportColumn does not contain a constructor that takes 0 arguments.
The GroupFooterTemplate works great for showing aggregate values per group.
Need the same functionality for the entire Grid, ie, sum of all values displayed for a column even if no grouping is applied.
Hello,
Please consider Grid data binding support for ImmutableArray. Currently, it crashes when the Grid tries to retrieve the total items count at:
Data.AsQueryable().Count();
Immutable*<T> classes are popular in state libraries.
Currently, the possible workarounds are: