I am using Telerik Grid in our project and I have enabled the feature Reorderable="true". At GridColumnMenuSettings I am getting the following options:
Set column position :
Those options are commonly showing for all columns but I want to disable "Move Next" option for the last column and "Move Previous" option for the first column as these options are not applicable to the corresponding columns.
I have created a custom model where I added an ExpandoObject and a couple properties to it. The Grid seems to render the data successfully but the data operations are not possible - the nested properties are treated as invalid. Please add support for nested ExpandoObject properties in custom models.
---
ADMIN EDIT
---
For the time being, there is a workaround that you may try. Use the ExpandoObject directly, do not nest it in the custom model - bind the Grid Data to a collection of ExpandoObject and populate its properties in the OnInitialized.
@using System.Dynamic
<TelerikGrid Data="@Data"
Pageable="true" Sortable="true" Groupable="true"
FilterMode="Telerik.Blazor.GridFilterMode.FilterRow"
EditMode="@GridEditMode.Incell"
Resizable="true" Reorderable="true">
<GridColumns>
<GridColumn Field="Id" FieldType="typeof(string)" Width="120px" />
<GridColumn Field="DetailData.PropertyInt" Title="Column A" FieldType="@typeof(int)" />
<GridColumn Field="DetailData.ProptertyString" Title="Column B" FieldType="@typeof(string)"/>
<GridColumn Field="DetailData.PropertyDate" Title="Column C" FieldType="@typeof(DateTime)"/>
</GridColumns>
</TelerikGrid>
@code {
public ExpandoObject[] Data { get; set; } = Array.Empty<ExpandoObject>();
protected override async Task OnInitializedAsync()
{
Data = new ExpandoObject[]
{
GetPopulatedExpando(1),
GetPopulatedExpando(2),
GetPopulatedExpando(3),
GetPopulatedExpando(4),
GetPopulatedExpando(5),
};
}
private ExpandoObject GetPopulatedExpando(int id)
{
dynamic expando = new ExpandoObject();
expando.Id = id;
dynamic nested = new ExpandoObject();
nested.PropertyInt = id;
nested.ProptertyString = "ID " + id;
nested.PropertyDate = new DateTime(2022, 4, id);
expando.DetailData = nested;
return expando;
}
}
Hello,
when using grid with multiple-selection mode and row template, ONLY single row selection works. Not able to select multiple rows. Grid somehow internally "reverts"(lose) selection to single row.
Here is full detail, with video and sample "what acts weird". Start with the post " Michal - Posted on: 19 Oct 2023 08:38":
Simplified reproducible sample from Nadezhda Tacheva(thanks), has that issue also - try to select multiple rows:
Video with the problem AND expected result(video is based on sample posted at same feedback above "Posted on: 19 Oct 2023 08:38":
https://feedback.telerik.com/attachment/download/1120622
Fully working example(recorded video) in VS - GridCheckBoxColumn in sample "IS HACK!", not required at all(just hint, which can be removed):
@using System.Collections.Generic;
@using System.Dynamic;
<span>Selection bind not working as expected:</span>
<TelerikGrid TItem="ExpandoObject"
@bind-SelectedItems="@gSelectedItems"
OnRowClick="@OnGridRowClicked"
SelectionMode="GridSelectionMode.Multiple"
OnRead=@gHLReadItems
RowHeight="60">
<RowTemplate Context="ctx">
<td>
@{
var it = (ctx as IDictionary<string, object>);
@(it["Name"].ToString())
}
</td>
</RowTemplate>
<GridColumns>
<GridCheckboxColumn CheckBoxOnlySelection="true" Visible="false" @key="@("sIDX1")" SelectAll="false" />
<GridColumn Field="Name" FieldType=@typeof(string) Title="Name" />
</GridColumns>
</TelerikGrid>
<span>WORKs OK:</span>
<TelerikGrid TItem="ExpandoObject"
@bind-SelectedItems="@gSelectedItems"
OnRowClick="@OnGridRowClicked"
SelectionMode="GridSelectionMode.Multiple"
OnRead=@gHLReadItems
RowHeight="60">
<GridColumns>
<GridCheckboxColumn CheckBoxOnlySelection="true" Visible="false" @key="@("sIDX2")" SelectAll="false" />
<GridColumn Field="Name" FieldType=@typeof(string) Title="Name" />
</GridColumns>
</TelerikGrid>
@code
{
private List<ExpandoObject> RowData;
IEnumerable<ExpandoObject> gSelectedItems { get; set; } = Enumerable.Empty<ExpandoObject>();
protected override void OnInitialized()
{
RowData = new List<ExpandoObject>();
dynamic obj0 = new ExpandoObject();
obj0.Name = "Tester";
RowData.Add(obj0);
dynamic obj1 = new ExpandoObject();
obj1.Name = "Testovicz";
RowData.Add(obj1);
dynamic obj2 = new ExpandoObject();
obj2.Name = "Selectant";
RowData.Add(obj2);
}
protected async Task gHLReadItems(GridReadEventArgs args)
{
//RowData are readen from DYNAMIC source, cannot add any NEW property to it
args.Data = RowData;
args.Total = 3;
}
protected void OnGridRowClicked(GridRowClickEventArgs args)
{
var it = args.Item as ExpandoObject;
if (gSelectedItems.Any(x => x == it))
{
gSelectedItems = gSelectedItems.Where(x => x != it);
}
else
{
gSelectedItems = gSelectedItems.Union(RowData.Where(x => x == it));
}
}
}
thanks
Please support Display(Order = ...) with autogenerated Grid columns.
Such functionality will also be useful for controlling the order of manually declared columns when using a custom component for reusable columns. For example: https://blazorrepl.telerik.com/cGOtbwOX21f6zQcy35 - the "Name" column is rendered last and currently one cannot control its order.
public class DateModel
{
[Display(Order = 2)]
public string Id { get; set; }
[Display(Order = 1)]
public string Text { get; set; }
}
Here's the reproducible:
@using System.ComponentModel.DataAnnotations
@* Used for the model annotations only *@
<strong>REPRO: activate the validation for the Name field to see the issue - click Save with an empty input</strong>
<TelerikGrid Data=@MyData EditMode="@GridEditMode.Popup" Pageable="true" Height="500px"
OnUpdate="@UpdateHandler" OnDelete="@DeleteHandler" OnCreate="@CreateHandler">
<GridToolBar>
<GridCommandButton Command="Add" Icon="add">Add Employee</GridCommandButton>
</GridToolBar>
<GridColumns>
<GridColumn Field=@nameof(SampleData.ID) Title="ID" Editable="false" />
<GridColumn Field=@nameof(SampleData.Name) Title="Name" />
<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 {
public class SampleData
{
public int ID { get; set; }
[Required(ErrorMessage = "aa something else")] // the first word is 1 or 2 characters to show the issue
public string Name { get; set; }
}
async Task UpdateHandler(GridCommandEventArgs args)
{
SampleData item = (SampleData)args.Item;
// perform actual data source operations here through your service
// if the grid Data is not tied to the service, you may need to update the local view data too
var index = MyData.FindIndex(i => i.ID == item.ID);
if (index != -1)
{
MyData[index] = item;
}
}
async Task DeleteHandler(GridCommandEventArgs args)
{
SampleData item = (SampleData)args.Item;
// perform actual data source operation here through your service
// if the grid Data is not tied to the service, you may need to update the local view data too
MyData.Remove(item);
}
async Task CreateHandler(GridCommandEventArgs args)
{
SampleData item = (SampleData)args.Item;
// perform actual data source operation here through your service
// if the grid Data is not tied to the service, you may need to update the local view data too
item.ID = MyData.Count + 1;
MyData.Insert(0, item);
}
public List<SampleData> MyData { get; set; }
protected override void OnInitialized()
{
MyData = new List<SampleData>();
for (int i = 0; i < 50; i++)
{
MyData.Add(new SampleData()
{
ID = i,
Name = "Name " + i.ToString()
});
}
}
}
When you zoom out, the span that displays the Grid pager's content has its style set to display: none, and it does not always reappear when you zoom in after that.
Reproduction
1. Run this REPLWhen PageSize is set to "All" and a new item is added to the grid's data source, the PageSize parameter is not updated, so the new item is sent to a second page. Instead of being added at the top or bottom of the page.
Reproduction
1. Run this REPL
2. Set PageSize to All from the dropdown at the bottom
3. Add a new item from Update Data or Add
4. New Item is added to the second page
5. Video Example
I would like to lock a column at a certain index in the Grid. For example, I would like to have a column on index 0 (the first column in the Grid) and it should not be reordered by any user interaction (dragging the column itself or dragging other columns in its place).
Also if the user tries to drag a column over my locked column the drag handlers must not be present/or show that it is an invalid drop location.
===
Telerik edit:
In the meantime, a possible workaround is to:
<p>The <strong>Name</strong> column index will always be 0.
The Grid will either move the reordered column to index 1, or put it back to its previous index (revert).</p>
<p><label><TelerikCheckBox @bind-Value="@ShouldRevertGridColumnOrder" /> Revert Invalid Column Reordering</label></p>
<TelerikGrid @ref="@GridRef"
Data="@GridData"
TItem="@SampleModel"
Pageable="true"
Sortable="true"
Reorderable="true"
OnStateChanged="@OnGridStateChanged">
<GridColumns>
<GridColumn Field="@nameof(SampleModel.Name)" Reorderable="false" />
<GridColumn Field="@nameof(SampleModel.GroupName)" />
<GridColumn Field="@nameof(SampleModel.Price)" />
<GridColumn Field="@nameof(SampleModel.Quantity)" />
<GridColumn Field="@nameof(SampleModel.StartDate)" />
<GridColumn Field="@nameof(SampleModel.IsActive)" />
</GridColumns>
</TelerikGrid>
@code {
private TelerikGrid<SampleModel>? GridRef { get; set; }
private List<SampleModel> GridData { get; set; } = new();
private bool ShouldRevertGridColumnOrder { get; set; }
private IEnumerable<int>? CachedGridColumnIndexes { get; set; }
private async Task OnGridStateChanged(GridStateEventArgs<SampleModel> args)
{
if (args.PropertyName == "ColumnStates")
{
if (args.GridState.ColumnStates.First().Index > 0 && GridRef != null)
{
if (ShouldRevertGridColumnOrder)
{
await RevertGridColumnOrder();
}
else
{
args.GridState.ColumnStates.First(x => x.Index == 0).Index = 1;
args.GridState.ColumnStates.First().Index = 0;
await GridRef.SetStateAsync(args.GridState);
}
}
CacheGridColumnOrder();
}
}
private void CacheGridColumnOrder()
{
var gridColumnState = GridRef?.GetState().ColumnStates;
if (gridColumnState != null)
{
CachedGridColumnIndexes = gridColumnState.Select(x => x.Index);
}
}
private async Task RevertGridColumnOrder()
{
var gridState = GridRef?.GetState();
if (gridState != null && CachedGridColumnIndexes != null)
{
for (int i = 0; i < gridState.ColumnStates.Count; i++)
{
gridState.ColumnStates.ElementAt(i).Index = CachedGridColumnIndexes.ElementAt(i);
}
await GridRef!.SetStateAsync(gridState);
}
}
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
CacheGridColumnOrder();
}
base.OnAfterRender(firstRender);
}
protected override void OnInitialized()
{
var rnd = new Random();
for (int i = 1; i <= 7; i++)
{
GridData.Add(new SampleModel()
{
Id = i,
Name = $"Name {i}",
GroupName = $"Group {i % 3 + 1}",
Price = rnd.Next(1, 100) * 1.23m,
Quantity = rnd.Next(0, 1000),
StartDate = DateTime.Now.AddDays(-rnd.Next(60, 1000)),
IsActive = i % 4 > 0
});
}
}
public class SampleModel
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public string GroupName { get; set; } = string.Empty;
public decimal Price { get; set; }
public int Quantity { get; set; }
public DateTime StartDate { get; set; }
public bool IsActive { get; set; }
}
}
The TelerikCheckBoxListFilter starts working very slowly with hundreds of items or more. That includes rendering and searching in it.
A possible workaround is to put a virtual Grid inside the FilterMenuTemplate. The example below is a modified version of this one - Blazor Grid checkbox list filtering with custom data. The FilterMenuButtonsTemplate is necessary to clear the selected Grid rows when the user clears the column filter.
@using Telerik.DataSource
@using Telerik.DataSource.Extensions
<TelerikGrid TItem="@Employee"
OnRead="@OnReadHandler"
Pageable="true"
FilterMode="@( NameOptions != null ? GridFilterMode.FilterMenu : GridFilterMode.None )"
FilterMenuType="@FilterMenuType.CheckBoxList"
Height="400px">
<GridColumns>
<GridColumn Field="@(nameof(Employee.EmployeeId))" Filterable="false" />
<GridColumn Field="@nameof(Employee.Name)">
<FilterMenuTemplate Context="context">
<TelerikGrid Data="@NameOptions"
ScrollMode="@GridScrollMode.Virtual"
Height="240px"
RowHeight="36"
FilterMode="@GridFilterMode.FilterRow"
SelectionMode="@GridSelectionMode.Multiple"
SelectedItems="@SelectedGridItems"
SelectedItemsChanged="@( (IEnumerable<NameFilterOption> newSelected) =>
GridSelectedItemsChanged(newSelected, context.FilterDescriptor) )">
<GridColumns>
<GridCheckboxColumn SelectAllMode="@GridSelectAllMode.All" />
<GridColumn Field="@nameof(NameFilterOption.Name)"></GridColumn>
</GridColumns>
</TelerikGrid>
</FilterMenuTemplate>
<FilterMenuButtonsTemplate Context="filterContext">
<TelerikButton OnClick="@(async _ => await filterContext.FilterAsync())"
ThemeColor="@ThemeConstants.Button.ThemeColor.Primary">Filter </TelerikButton>
<TelerikButton OnClick="@(() => ClearFilterAsync(filterContext))">Clear</TelerikButton>
</FilterMenuButtonsTemplate>
</GridColumn>
<GridColumn Field="@nameof(Employee.Team)" Title="Team" />
<GridColumn Field="@nameof(Employee.IsOnLeave)" Title="On Vacation" />
</GridColumns>
</TelerikGrid>
@code {
string DDLValue { get; set; }
List<Employee> AllGridData { get; set; }
private IEnumerable<NameFilterOption> SelectedGridItems { get; set; } = new List<NameFilterOption>();
private void GridSelectedItemsChanged(IEnumerable<NameFilterOption> newSelectedItems, CompositeFilterDescriptor cfd)
{
SelectedGridItems = newSelectedItems;
cfd.LogicalOperator = FilterCompositionLogicalOperator.Or;
cfd.FilterDescriptors.Clear();
foreach (var sgi in SelectedGridItems)
{
cfd.FilterDescriptors.Add(new FilterDescriptor()
{
Member = nameof(Employee.Name),
MemberType = typeof(string),
Operator = FilterOperator.IsEqualTo,
Value = sgi.Name
});
}
}
private async Task ClearFilterAsync(FilterMenuTemplateContext filterContext)
{
SelectedGridItems = new List<NameFilterOption>();
await filterContext.ClearFilterAsync();
}
#region custom-filter-data
List<TeamNameFilterOption> TeamsList { get; set; }
List<NameFilterOption> NameOptions { get; set; }
//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()
{
// generate data that simulates the database for this example
// the actual grid data is retrieve in its OnRead handler
AllGridData = new List<Employee>();
var rand = new Random();
for (int i = 1; i <= 1000; i++)
{
AllGridData.Add(new Employee()
{
EmployeeId = i,
Name = "Employee " + i.ToString(),
Team = "Team " + i % 3,
IsOnLeave = i % 2 == 0
});
}
// get custom filters data. In a future version you will be able to call these methods
// from the template initialization instead of here (or in OnRead) so that they fetch data
// only when the user actually needs filter values, instead of always - that could improve server performance
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; }
}
}
The Grid PageSizes DropDownList will not select a new value if it the current value is not visible in the open dropdown. This can happen if there are too many items in the DropDownList and the user has scrolled the PageSizes dropdown.
The problem occurs only when Navigable="true".
Here is a REPL test page
The easiest workaround is to fewer items in the PageSizes DropDownList, so that there is no need for scrolling.
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.
Hello,
after updating to the versin 4.6.0 from 4.5.0, almost all svg icons dissapeared(inside the grid). No erros, no console warnings.
Is there some breaking changes or aditional steps how to bring them back?
Original markup without changes(first blue button in example should have "play" icon at the beginning):
<TelerikButton OnClick="@(_ => OZRowCmd(null,20))" Icon="@FontIcon.Play" Title="Zahájit novou" Class="bg-primary" Size="@Telerik.Blazor.ThemeConstants.Button.Size.Large">Zahájit novou</TelerikButton>
Hello,
The Grid header and data cells become misaligned if the user changes the page zoom level. The right padding of the Grid header area resizes, according to the zoom level, but the scrollbar width remains the same. This triggers the misalignment.
The problem disappears after browser refresh at the current zoom level.