I am trying to display data using FluentDataGrid but I am getting very strange behavior. Issues:
Here is my code. I am using @rendermode = InteractiveAssembly.
@rendermode InteractiveWebAssembly
@inject HttpClientService httpClientService
<FluentDataGrid Items="RolesData?.Data?.AsQueryable()"
TGridItem="GetRoles.Response"
Pagination="paginationState"
ShowHover="true"
MultiLine="true"
Loading="@gridLoader">
<PropertyColumn Property="@(c => c.Name)" Sortable="false" />
<TemplateColumn Title="Permissions">
<p>@context.PermissionCount permissions assigned</p>
</TemplateColumn>
<TemplateColumn Title="Assigned To">
<p>@context.AssignedTo uers</p>
</TemplateColumn>
<TemplateColumn Title="Actions">
@if (context.IsPrimary == false)
{
<FluentButton aria-label="Edit item" IconEnd="@(new Icons.Regular.Size16.Edit())" OnClick="() => OnEditClick(context.Id)" />
<FluentButton aria-label="Delete item" IconEnd="@(new Icons.Regular.Size16.Delete())" OnClick="() => OnDeleteClick(context.Id)" />
}
</TemplateColumn>
</FluentDataGrid>
<FluentPaginator State="@paginationState" CurrentPageIndexChanged="OnCurrentPageIndexChanged" />
That is my component. I am fetching data on OnAfterRenderAsync initially
private GetRoles.Request request = new GetRoles.Request();
private Pagination.Response<IEnumerable<GetRoles.Response>>? RolesData;
PaginationState paginationState = new PaginationState { ItemsPerPage = 10 };
bool gridLoader;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await GetRoles();
}
}
private async Task GetRoles()
{
gridLoader = true;
StateHasChanged();
request.Page = paginationState.CurrentPageIndex;
request.PageSize = paginationState.ItemsPerPage;
var roleDataResponse = await httpClientService.GetAsync<Pagination.Response<IEnumerable<GetRoles.Response>>>("/api/roles", request);
if (roleDataResponse != null)
{
RolesData = roleDataResponse;
await paginationState.SetTotalItemCountAsync(roleDataResponse.TotalRecords);
}
gridLoader = false;
StateHasChanged();
Console.WriteLine($"CurrentPageIndex: {paginationState.CurrentPageIndex}");
Console.WriteLine($"LastPageIndex: {paginationState.LastPageIndex}");
Console.WriteLine($"TotalItemCount: {paginationState.TotalItemCount}");
}
The event which is handling the button click on the pagination is
async void OnCurrentPageIndexChanged(int index)
{
await paginationState.SetCurrentPageIndexAsync(index);
await GetRoles();
}
This is the data returned by the API
{
"totalRecords": 11,
"data": [
{
"id": "Dy9dkGzW",
"name": "Test 9",
"permissionCount": 2,
"assignedTo": 0,
"isPrimary": false
},
{
"id": "eV9ablPg",
"name": "Test 8",
"permissionCount": 2,
"assignedTo": 0,
"isPrimary": false
},
{
"id": "X892olBV",
"name": "Test 7",
"permissionCount": 10,
"assignedTo": 0,
"isPrimary": false
},
{
"id": "8e7V896p",
"name": "Test 6",
"permissionCount": 1,
"assignedTo": 0,
"isPrimary": false
},
{
"id": "1VGx2l5Q",
"name": "Test 5",
"permissionCount": 4,
"assignedTo": 0,
"isPrimary": false
},
{
"id": "5Y7Qq7Bk",
"name": "Test 4",
"permissionCount": 9,
"assignedTo": 0,
"isPrimary": false
},
{
"id": "O8l1wGqx",
"name": "Test 3",
"permissionCount": 2,
"assignedTo": 0,
"isPrimary": false
},
{
"id": "Y8Gb39Bq",
"name": "Test 2",
"permissionCount": 3,
"assignedTo": 0,
"isPrimary": false
},
{
"id": "QY7BV7OV",
"name": "Test 1",
"permissionCount": 2,
"assignedTo": 0,
"isPrimary": false
},
{
"id": "ZYG8o71v",
"name": "Finance",
"permissionCount": 8,
"assignedTo": 0,
"isPrimary": false
}
]
}
As you can see the 'totalRecords' are 11 here. The pagination should show total records as 11 and it should have 2 pages, but it is showing me this
If I move 'await paginationState.SetTotalItemCountAsync(roleDataResponse.TotalRecords);' to the end of the method, it sets the total items count correctly and the pagination buttons are not disabled, but when I click on the next button, the event handler get 0 in 'index'.
Am I missing something here or doing something really wrong? Any help will be appreciated.
Thank you in advance.
You're trying to plumb this up the wrong way. You need to use the ItemsProvider
, and then everything gets much simpler.
As I'm not sure of your data object [and not about to try and re-invent it], I've simplified everything by substituting in the good old WeatherForecast
.
Here's the page code:
@page "/"
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new Fluent Blazor app.
<FluentDataGrid TGridItem="WeatherForecast"
Pagination="_paginationState"
ShowHover="true"
MultiLine="true"
ItemsProvider="GetDataAsync">
<PropertyColumn Title="Date" Property="@(c => c!.Date)" Sortable="true" Align=Align.Start />
<PropertyColumn Title="Temp. (C)" Property="@(c => c!.TemperatureC)" Sortable="true" Align=Align.Center />
<PropertyColumn Title="Temp. (F)" Property="@(c => c!.TemperatureF)" Sortable="true" Align=Align.Center />
<PropertyColumn Title="Summary" Property="@(c => c!.Summary)" Sortable="true" Align=Align.End />
</FluentDataGrid>
<FluentPaginator State="@_paginationState" />
@code {
private PaginationState _paginationState = new PaginationState { ItemsPerPage = 10 };
private async ValueTask<GridItemsProviderResult<WeatherForecast>> GetDataAsync(GridItemsProviderRequest<WeatherForecast> request)
{
// Map the UI GridItemsProviderRequest to the data pipeline list request object
var listRequest = new ListRequest(request.StartIndex, request.Count ?? _paginationState.ItemsPerPage);
var result = await WeatherForecastProvider.GetAPIDataAsync(listRequest);
// Map the data pipeline result to the UI GridItemsProviderResult
return GridItemsProviderResult.From<WeatherForecast>(result.Items.ToList(), result.TotalRecords);
}
}
And the supporting objects:
// Stub Class to mimic getting data from an external source
public static class WeatherForecastProvider
{
private static List<WeatherForecast> _weatherForecasts = new();
public static async ValueTask<ListResult<WeatherForecast>> GetAPIDataAsync(ListRequest request)
{
// Fake as asynbc data get
await Task.Yield();
_weatherForecasts = _weatherForecasts ?? LoadData();
var list = _weatherForecasts
.Skip(request.StartIndex)
.Take(request.PageSize)
.ToList();
return new ListResult<WeatherForecast>(list, _weatherForecasts.Count());
}
private static List<WeatherForecast> LoadData()
{
var startDate = DateOnly.FromDateTime(DateTime.Now);
var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" };
return Enumerable.Range(1, 50).Select(index => new WeatherForecast
{
Date = startDate.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = summaries[Random.Shared.Next(summaries.Length)]
}).ToList();
}
}
// The data pipeline request and result objects
public readonly record struct ListRequest(int StartIndex, int PageSize);
public readonly record struct ListResult<TRecord>(IEnumerable<TRecord> Items, int TotalRecords);
public class WeatherForecast
{
public DateOnly Date { get; set; }
public int TemperatureC { get; set; }
public string? Summary { get; set; }
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}