Hi Telerik team,
When I want to bind the Blazor Grid to an ExpandoObject I need to assign the FieldType. I know I have to use non-nullable types.
But I want to use nullable types (for example int?) to have a blank cell within the Grid when there is no value for it. With "typeof(int)" instead of "typeof(int?)" every empty cell shows "0" which I don't want.
Is there any chance to let FieldType accept nullable types?
Best regards,
Rayko
Hi Telerik team,
In a Blazor Grid with filters enabled the event "OnStateChanged" is fired twice when I use a filter (set, change, remove).
---
ADMIN EDIT
This behavior is expected - when the grid is filtered, there are two actions that happen:
This is not something we intend to change at this point.
---
To reproduce this I took one of the provided examples and added the event handler:
@page "/"
<TelerikGrid Data=@GridData
SelectionMode="GridSelectionMode.Multiple"
SelectedItemsChanged="@((IEnumerable<Employee> employeeList) => OnSelect(employeeList))"
SelectedItems="@PersistedSelectedItems"
@bind-Page="@CurrentPage"
PageSize="@PageSize"
Pageable="true"
FilterMode="GridFilterMode.FilterRow"
OnStateChanged="@((GridStateEventArgs<Employee> args) => OnStateChangedHandler(args))">
<GridColumns>
<GridCheckboxColumn />
<GridColumn Field=@nameof(Employee.EmployeeId) />
<GridColumn Field=@nameof(Employee.Name) />
<GridColumn Field=@nameof(Employee.Team) />
</GridColumns>
</TelerikGrid>
@if (PersistedSelectedItems != null)
{
<ul>
@foreach (Employee employee in PersistedSelectedItems.OrderBy(e => e.EmployeeId))
{
<li>
@employee.EmployeeId
</li>
}
</ul>
}
@code {
public List<Employee> PersistedSelectedItems { get; set; } = new List<Employee>();
int CurrentPage { get; set; }
int PageSize { get; set; } = 5;
private async void OnStateChangedHandler(GridStateEventArgs<Employee> args)
{
await Task.Delay(5000);
}
protected void OnSelect(IEnumerable<Employee> employees)
{
IEnumerable<Employee> CurrentPageEmployees = GridData.Skip(PageSize * (CurrentPage - 1)).Take(PageSize);
if (employees == null || employees.Count() == 0)
{
//the user de-selected all items with the header checkbox
PersistedSelectedItems = PersistedSelectedItems.Except(CurrentPageEmployees).ToList();
}
else
{
//handle any deselected items
var UnselectedEmployees = CurrentPageEmployees.Except(employees);
PersistedSelectedItems = PersistedSelectedItems.Except(UnselectedEmployees).ToList();
//add any new items if they were not selected already
foreach (var item in employees)
{
if (!PersistedSelectedItems.Contains(item))
{
PersistedSelectedItems.Add(item);
}
}
}
}
//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
});
}
}
public class Employee
{
public int EmployeeId { get; set; }
public string Name { get; set; }
public string Team { get; set; }
}
}
Best regards,
Rayko
Hi,
I am using a Blazor TelerikGrid component with Virtual ScrollMode. I have set a fixed Height, PageSize that exceeds the rows that fit in the grid, and a RowHeight which I have confirmed fits the row contents and resolves to a computed Height of the same value (using Chrome Dev Tools -> Computed tab). Everything works fine except that sometimes scrolling does not trigger the OnRead event and the rows displayed after scrolling are placeholders.
I don't experience this problem when I click the scroll arrow buttons (up or down). I don't experience the problem at all if I drag the scroll bar up or down. But if I page up/down, or click on the scroll page area up/down it will sometimes work and sometimes not. If placeholders are shown, I only need to click a scroll button up/down a few times for it to then read and display the correct rows.
To reproduce use the attached VS2019 project, click "Telerik Scroll Issue" nav menu, and in the TelerikGrid click the page down areas of the vertical scrollbar 3 times.
<AdminEdit>
I'm using the OnRead / OnCreate / OnUpdate / OnDelete events of the TelerikGrid. Most examples do not use OnRead to load data, instead using OnInitializedAsync().
When performing an update two events will fire, such as OnCreate and then OnRead to reload the grid data - which makes sense. The problem is OnRead is not awaited, so in effect the Read method can reload the data prior to data being updated on the server. This is especially the case if I have a JavaScript delete confirmation prompt.
What's my best way forward? Not use OnRead and use OnInitializedAsync instead? Somehow trigger OnRead again (which would effectively trigger it twice)?
Thanks,
Dave
It would be good to have a Grid parameter like "ExpandDetailsOnSelection" for the Grid:
-> When the user selects a row, the DetailTemplate automatically expands - the DetailTemplate of the previous selected item is automatically collapsed
Advantages:
- no "+" button needed
- easy to integrate for 2-way-binding on SelectedItems (no need to use GridState and RowClick event)
Hello everyone,
We created this Feature Request to gather feedback on the way the item expansion is working in the Grid state. Currently, you can expand any items with the ExpandedRows collection of int where you pass the indexes of the rows.
Another option, which we are thinking of, is to provide a Collection<Model>. This would let you pass models, instead of indexes and the Grid would automatically expand those items. This approach would make the need to preserve the indexes redundant, which you should currently do when Filtering/Sorting and Grouping the items in the Grid.
We would appreciate your feedback on both approaches and which you think will be easier to use.
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.
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; }
}
}
---
I am using this sample to implement server-side grouping:
https://github.com/telerik/blazor-ui/tree/master/grid/datasourcerequest-on-server/WasmApp
Now I need Aggregate sum for the price in the GroupFooterTemplate. When I add aggregates, exceptions are thrown if a group footer uses the aggregate value, if not - the grid simply does not look grouped.
---
ADMIN EDIT
The issue stems from the inability of the System.Text.Json to deserialize interfaces - there are a couple of interfaces in the datasource request and data source result related to aggregates. Preliminary review indicates that perhaps some or all of them might be changed to, for example, Dictionary<string, object> from the current IDictionary<string, object>, or perhaps the framework might "learn" to deserialize interfaces.
----
By the following steps, the problem occurs:
So, it not possible to navigate through the whole grid with virtualized rows when Navigable=true
@* Scroll the grid instead of paging *@
<TelerikGrid Data=@GridData
ScrollMode="@GridScrollMode.Virtual" Navigable="true"
Height="480px" RowHeight="60" PageSize="20"
Sortable="true" FilterMode="@GridFilterMode.FilterMenu">
<GridColumns>
<GridColumn Field="Id" />
<GridColumn Field="Name" Title="First Name" />
<GridColumn Field="LastName" Title="Last Name" />
<GridColumn Field="HireData" Width="200px">
<Template>
@((context as SampleData).HireDate.ToString("MMMM dd, yyyy"))
</Template>
</GridColumn>
</GridColumns>
</TelerikGrid>
@code {
public List<SampleData> GridData { get; set; }
protected override async Task OnInitializedAsync()
{
GridData = await GetData();
}
private async Task<List<SampleData>> GetData()
{
return Enumerable.Range(1, 1000).Select(x => new SampleData
{
Id = x,
Name = $"name {x}",
LastName = $"Surname {x}",
HireDate = DateTime.Now.Date.AddDays(-x)
}).ToList();
}
public class SampleData
{
public int Id { get; set; }
public string Name { get; set; }
public string LastName { get; set; }
public DateTime HireDate { get; set; }
}
}
I have a reset all button that clears a grid of all column, filter, sorts etc.
I need it to also reset any searchbox criteria. I can easily clear the searchbox of it's value using javascript, but this does not then trigger the searchbox to reset the grid.
---
TELERIK EDIT
The officially supported way to clear the GridSeachBox is through the Grid state. You can see an example in the Grid SeachBox documentation.
A possible workaround for old versions is to clear the search textbox with JavaScript and to trigger its input event:
@inject IJSRuntime js
<TelerikGrid Data="@GridData"
Pageable="true"
Sortable="true"
Class="custom-grid-class">
<GridToolBarTemplate>
<GridSearchBox />
<TelerikButton Icon="@SvgIcon.X" OnClick="@OnClearButtonClick"></TelerikButton>
</GridToolBarTemplate>
<GridColumns>
<GridColumn Field="@nameof(SampleModel.Name)" />
<GridColumn Field="@nameof(SampleModel.Description)" />
</GridColumns>
</TelerikGrid>
<script suppress-error="BL9992">//
function clearSearch() {
// locate the searchbox of the Grid
const input = document.querySelector(".custom-grid-class .k-grid-search input");
// clear the value of the input
input.value = "";
// dispatch the input event in order to force the Grid event binding to trigger the filtering
input.dispatchEvent(new Event('input', {bubbles: true} ));
}
//</script>
@code {
private List<SampleModel> GridData { get; set; } = new();
private async Task OnClearButtonClick()
{
await js.InvokeVoidAsync("clearSearch");
}
protected override void OnInitialized()
{
for (int i = 1; i <= 50; i++)
{
GridData.Add(new SampleModel()
{
Id = i,
Name = $"{(char)(64 + i % 26 + 1)}{(char)(64 + i % 26 + 1)} {i}",
Description = $"{(char)(123 - i % 26 - 1)}{(char)(123 - i % 26 - 1)} {i}"
});
}
}
public class SampleModel
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
}
}
In a Grid loaded with data made of ExpandoObject, set an aggregate GridAggregateType.Sum breaks the grouping feature while GridAggregateType Max, Min and Count work properly
Please find the attached project: in the grid on Index.razor grouping does not work, just comment Index.razor:43 to restore grouping feature
Exception message:
Error: System.InvalidOperationException: No generic method 'Sum' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.
at System.Linq.Expressions.Expression.FindMethod(Type type, String methodName, Type[] typeArgs, Expression[] args, BindingFlags flags)
at System.Linq.Expressions.Expression.Call(Type type, String methodName, Type[] typeArguments, Expression[] arguments)
at Telerik.DataSource.Expressions.EnumerableSelectorAggregateFunctionExpressionBuilder.CreateMethodCallExpression(LambdaExpression memberSelectorExpression)
at Telerik.DataSource.Expressions.EnumerableSelectorAggregateFunctionExpressionBuilder.CreateAggregateExpression()
at Telerik.DataSource.EnumerableSelectorAggregateFunction.CreateAggregateExpression(Expression enumerableExpression, Boolean liftMemberAccessToNull)
at Telerik.DataSource.Expressions.GroupDescriptorExpressionBuilder.<ProjectionPropertyValueExpressions>b__39_0(AggregateFunction f)
at System.Linq.Enumerable.SelectIListIterator`2.ToList()
at Telerik.DataSource.Expressions.GroupDescriptorExpressionBuilder.CreateProjectionInitExpression()
at Telerik.DataSource.Expressions.GroupDescriptorExpressionBuilder.CreateAggregateFunctionsProjectionMemberBinding()
at Telerik.DataSource.Expressions.QueryableAggregatesExpressionBuilder.CreateMemberBindings()+MoveNext()
at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
at System.Dynamic.Utils.CollectionExtensions.ToReadOnly[T](IEnumerable`1 enumerable)
at System.Linq.Expressions.Expression.MemberInit(NewExpression newExpression, IEnumerable`1 bindings)
at Telerik.DataSource.Expressions.GroupDescriptorExpressionBuilder.CreateSelectBodyExpression()
at Telerik.DataSource.Expressions.GroupDescriptorExpressionBuilder.CreateSelectExpression()
at Telerik.DataSource.Expressions.GroupDescriptorExpressionBuilderBase.CreateQuery()
at Telerik.DataSource.Extensions.QueryableExtensions.Aggregate(IQueryable source, IEnumerable`1 aggregateFunctions)
at Telerik.DataSource.Extensions.QueryableExtensions.CreateDataSourceResult[TModel,TResult](IQueryable queryable, DataSourceRequest request, Func`2 selector)
at Telerik.DataSource.Extensions.QueryableExtensions.ToDataSourceResult(IQueryable queryable, DataSourceRequest request)
at Telerik.DataSource.Extensions.QueryableExtensions.ToDataSourceResult(IEnumerable enumerable, DataSourceRequest request)
at Telerik.Blazor.Data.TelerikDataSourceBase.ProcessData(IEnumerable data)
at Telerik.Blazor.Components.Common.DataBoundComponent`1.ProcessDataInternal()
at Telerik.Blazor.Components.Common.DataBoundComponent`1.ProcessDataAsync()
at Telerik.Blazor.Components.TelerikGrid`1.DataBoundProcessData()
at Telerik.Blazor.Components.TelerikGrid`1.ProcessDataAsync()
at Telerik.Blazor.Components.TelerikGrid`1.ApplyFiltersAsync()
at Telerik.Blazor.Components.TelerikGrid`1.OnFilterChange(FilterDescriptorBase filter)
at Telerik.Blazor.Components.Common.Filters.TelerikFilterHeader`1.Filter(FilterDescriptor filterDescriptor)
at Telerik.Blazor.Components.Common.Filters.TelerikFilterHeader`1.OnValueChanged(Object newValue)
at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__139_0(Object state)
at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteSynchronously(TaskCompletionSource`1 completion, SendOrPostCallback d, Object state)
at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.<>c.<.cctor>b__23_0(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.ExecuteBackground(WorkItem item)
I'm using the Grid with a GridToolbar containing a GridSearchBox. I have the following requirement that I wish to be able to implement:
When focus is on the GridSearchBox and I hit Esc I would like to clear the search text.
When I have a grid with Reordable=true and a ComboBox in the HeaderTemplate, I cannot click in the combo box because the reordable functionality prevents it.
---
ADMIN EDIT
This could be exposed through a data- attribute that you could add to your DOM elements so that the grid can know to skip them, for example:
<TelerikGrid Data="@MyData" Height="300px" Pageable="true" Sortable="true" FilterMode="@GridFilterMode.FilterMenu" Reorderable="true">
<GridColumns>
<GridColumn Field="@(nameof(SampleData.ID))" Title="This title will not be rendered">
<HeaderTemplate>
Continent <br />
<div style="text-align:center">Id</div>
<div @onclick:stopPropagation="true" data-draggable="false">
<TelerikComboBox Data="@Continents" Filterable="true" class="headerCombo"
@bind-Value="@SelectedContinentId"
TextField="@nameof(Continent.Name)" ValueField="@nameof(Continent.Id)" Id="MyId">
</TelerikComboBox>
</div>
</HeaderTemplate>
</GridColumn>
<GridColumn Field=@nameof(SampleData.Name) Title="First Name" />
</GridColumns>
</TelerikGrid>
@code {
/* Grid */
string result { get; set; }
void DoSomething()
{
result = $"button in header template clicked on {DateTime.Now}, something happened";
}
public class SampleData
{
public int ID { get; set; }
public string Name { get; set; }
public DateTime HireDate { get; set; }
}
public IEnumerable<SampleData> MyData = Enumerable.Range(1, 50).Select(x => new SampleData
{
ID = x,
Name = "name " + x,
HireDate = DateTime.Now.AddDays(-x)
});
/* ComboBox */
public class Continent
{
public int Id { get; set; }
public string Name { get; set; }
}
IEnumerable<Continent> Continents = new List<Continent>() { new Continent { Id = 1, Name = "Africa" }, new Continent { Id = 2, Name = "Asia" }, new Continent { Id = 3, Name = "South America" } };
public int SelectedContinentId { get; set; } = 1;
}
---