Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DataGrid] Make SelectColumn work when using ItemsProvider #2060

Merged
merged 5 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1462,7 +1462,7 @@
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.SelectColumn`1.Property">
<summary>
Gets or sets the function to be executed to display the checked/unchecked icon, depending of you data model.
Gets or sets the function to executed to determine checked/unchecked status.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.SelectColumn`1.SortBy">
Expand Down
51 changes: 51 additions & 0 deletions examples/Demo/Shared/Pages/Lab/IssueTester.razor
Original file line number Diff line number Diff line change
@@ -1 +1,52 @@
@page "/IssueTester"

@page "/TestSelectColumns"

<FluentDataGrid ItemsProvider="PersonProvider" ShowHover="true" TGridItem="Person" Pagination="@pagination" GridTemplateColumns="50px 1fr 2fr 1fr">
<SelectColumn TGridItem="Person"
SelectMode="DataGridSelectMode.Multiple"
Property="@(e => e.Selected)"
OnSelect="@(e => e.Item.Selected = e.Selected)"
SelectAll="@(PeopleList.All(p => p.Selected))"
SelectAllChanged="@(all => PeopleList.ToList().ForEach(p => p.Selected = (all == true)))" />
<PropertyColumn Property="@(p => p.PersonId)" Title="ID" Sortable="true" />
<PropertyColumn Property="@(p => p.Name)" Sortable="true" />
<PropertyColumn Property="@(p => p.BirthDate)" Format="yyyy-MM-dd" Sortable="true" />
</FluentDataGrid>
<FluentPaginator State="@pagination" />

<div>
<b>Peoples:</b>
@String.Join("; ", PeopleList.Where(p => p.Selected).Select(p => p.Name))
</div>

@code {
PaginationState pagination { get; set; } = new PaginationState() { ItemsPerPage = 10 };

public record Person(int PersonId, string Name, DateOnly BirthDate)
{
public bool Selected { get; set; }
};

public static List<Person> PeopleList = new()
{
new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)),
new Person(10944, "António Langa", new DateOnly(1991, 12, 1)),
new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)),
new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)),
new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)),
new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)),
new Person(10895, "Jean 2 Martin", new DateOnly(1985, 3, 16)),
new Person(10944, "António 2 Langa", new DateOnly(1991, 12, 1)),
new Person(11203, "Julie 2 Smith", new DateOnly(1958, 10, 10)),
new Person(11205, "Nur 2 Sari", new DateOnly(1922, 4, 27)),
new Person(11898, "Jose 2 Hernandez", new DateOnly(2011, 5, 3)),
new Person(12130, "Kenji 2 Sato", new DateOnly(2004, 1, 9)),
};

private async ValueTask<GridItemsProviderResult<Person>> PersonProvider(GridItemsProviderRequest<Person> req)
{
var subList = PeopleList.Skip(req.StartIndex).Take(req.Count ?? PeopleList.Count).ToList();
return GridItemsProviderResult.From(subList, PeopleList.Count);
}
}
12 changes: 7 additions & 5 deletions src/Core/Components/DataGrid/Columns/SelectColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public DataGridSelectMode SelectMode
public EventCallback<bool?> SelectAllChanged { get; set; }

/// <summary>
/// Gets or sets the function to be executed to display the checked/unchecked icon, depending of you data model.
/// Gets or sets the function to executed to determine checked/unchecked status.
/// </summary>
[Parameter]
public Func<TGridItem, bool> Property { get; set; } = (item) => false;
Expand All @@ -175,6 +175,7 @@ internal async Task AddOrRemoveSelectedItemAsync(TGridItem? item)
if (SelectedItems.Contains(item))
{
_selectedItems.Remove(item);
SelectAll = false;
await CallOnSelect(item, false);
}
else
Expand Down Expand Up @@ -268,6 +269,7 @@ private RenderFragment<TGridItem> GetDefaultChildContent()
if (selected && !_selectedItems.Contains(item))
{
_selectedItems.Add(item);
RefreshHeaderContent();
}
else if (!selected && _selectedItems.Contains(item))
{
Expand Down Expand Up @@ -324,13 +326,13 @@ private void RefreshHeaderContent()
private bool? GetSelectAll()
{
// Using SelectedItems only
if (InternalGridContext != null && Grid.Items != null)
if (InternalGridContext != null && (Grid.Items != null || Grid.ItemsProvider != null))
{
if (!SelectedItems.Any())
{
return false;
}
else if (SelectedItems.Count() == Grid.Items.Count())
else if (SelectedItems.Count() == InternalGridContext.TotalItemCount || SelectAll == true)
{
return true;
}
Expand Down Expand Up @@ -361,7 +363,7 @@ protected internal override void CellContent(RenderTreeBuilder builder, TGridIte
/// <summary />
internal async Task OnClickAllAsync(MouseEventArgs e)
{
if (Grid == null || Grid.Items == null || SelectMode != DataGridSelectMode.Multiple)
if (Grid == null || SelectMode != DataGridSelectMode.Multiple)
{
return;
}
Expand All @@ -377,7 +379,7 @@ internal async Task OnClickAllAsync(MouseEventArgs e)
_selectedItems.Clear();
if (SelectAll == true)
{
_selectedItems.AddRange(Grid.Items);
_selectedItems.AddRange(InternalGridContext.Items);
}

if (SelectedItemsChanged.HasDelegate)
Expand Down
8 changes: 4 additions & 4 deletions src/Core/Components/DataGrid/FluentDataGrid.razor
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
grid-template-columns=@_internalGridTemplateColumns
class="@GridClass()"
style="@Style"
aria-rowcount="@(_ariaBodyRowCount + 1)"
aria-rowcount="@(_internalGridContext.TotalItemCount + 1)"
vnbaaij marked this conversation as resolved.
Show resolved Hide resolved
@onrowfocus=HandleOnRowFocusAsync
@onclosecolumnoptions="CloseColumnOptions"
@attributes="AdditionalAttributes">
Expand All @@ -40,7 +40,7 @@
{
@if (Virtualize)
{
if (_ariaBodyRowCount == 0)
if (_internalGridContext.TotalItemCount == 0)
{
@_renderEmptyContent
}
Expand Down Expand Up @@ -72,10 +72,10 @@
{
var initialRowIndex = (GenerateHeader != GenerateHeaderOption.None) ? 2 : 1; // aria-rowindex is 1-based, plus 1 if there is a header
var rowIndex = initialRowIndex;
if (_currentNonVirtualizedViewItems.Any())
if (_internalGridContext.Items.Any())
{
Loading = false;
foreach (var item in _currentNonVirtualizedViewItems)
foreach (var item in _internalGridContext.Items)
{
RenderRow(__builder, rowIndex++, item);
}
Expand Down
20 changes: 9 additions & 11 deletions src/Core/Components/DataGrid/FluentDataGrid.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -179,8 +179,6 @@ public partial class FluentDataGrid<TGridItem> : FluentComponentBase, IHandleEve

private ElementReference? _gridReference;
private Virtualize<(int, TGridItem)>? _virtualizeComponent;
private int _ariaBodyRowCount;
private ICollection<TGridItem> _currentNonVirtualizedViewItems = Array.Empty<TGridItem>();

// IQueryable only exposes synchronous query APIs. IAsyncQueryExecutor is an adapter that lets us invoke any
// async query APIs that might be available. We have built-in support for using EF Core's async query APIs.
Expand Down Expand Up @@ -333,7 +331,7 @@ private void FinishCollectingColumns()
_collectingColumns = false;
_manualGrid = _columns.Count == 0;

if (!string.IsNullOrWhiteSpace(GridTemplateColumns) && _columns.Any(x => !string.IsNullOrWhiteSpace(x.Width)))
if (!string.IsNullOrWhiteSpace(GridTemplateColumns) && _columns.Where(x => x is not SelectColumn<TGridItem>).Any(x => !string.IsNullOrWhiteSpace(x.Width)))
{
throw new Exception("You can use either the 'GridTemplateColumns' parameter on the grid or the 'Width' property at the column level, not both.");
}
Expand Down Expand Up @@ -441,9 +439,9 @@ private async Task RefreshDataCoreAsync()
var result = await ResolveItemsRequestAsync(request);
if (!thisLoadCts.IsCancellationRequested)
{
_currentNonVirtualizedViewItems = result.Items;
_ariaBodyRowCount = _currentNonVirtualizedViewItems.Count;
Pagination?.SetTotalItemCountAsync(result.TotalItemCount);
_internalGridContext.Items = result.Items;
_internalGridContext.TotalItemCount = result.TotalItemCount;
Pagination?.SetTotalItemCountAsync(_internalGridContext.TotalItemCount);
_pendingDataLoadCancellationTokenSource = null;
}
_internalGridContext.ResetRowIndexes(startIndex);
Expand Down Expand Up @@ -486,10 +484,10 @@ private async ValueTask<ItemsProviderResult<(int, TGridItem)>> ProvideVirtualize
// the current viewport. In the case where you're also paginating then it means what's conceptually on the current page.
// TODO: This currently assumes we always want to expand the last page to have ItemsPerPage rows, but the experience might
// be better if we let the last page only be as big as its number of actual rows.
_ariaBodyRowCount = Pagination is null ? providerResult.TotalItemCount : Pagination.ItemsPerPage;
_internalGridContext.TotalItemCount = Pagination is null ? providerResult.TotalItemCount : Pagination.ItemsPerPage;

Pagination?.SetTotalItemCountAsync(providerResult.TotalItemCount);
if (_ariaBodyRowCount > 0)
Pagination?.SetTotalItemCountAsync(_internalGridContext.TotalItemCount);
if (_internalGridContext.TotalItemCount > 0)
{
Loading = false;
}
Expand All @@ -499,7 +497,7 @@ private async ValueTask<ItemsProviderResult<(int, TGridItem)>> ProvideVirtualize
// to make sure it doesn't get out of sync with the rows being rendered.
return new ItemsProviderResult<(int, TGridItem)>(
items: providerResult.Items.Select((x, i) => ValueTuple.Create(i + request.StartIndex + 2, x)),
totalItemCount: _ariaBodyRowCount);
totalItemCount: _internalGridContext.TotalItemCount);
}

return default;
Expand All @@ -520,7 +518,7 @@ private async ValueTask<GridItemsProviderResult<TGridItem>> ResolveItemsRequestA
else if (Items is not null)
{
var totalItemCount = _asyncQueryExecutor is null ? Items.Count() : await _asyncQueryExecutor.CountAsync(Items);
_ariaBodyRowCount = totalItemCount;
_internalGridContext.TotalItemCount = totalItemCount;
var result = request.ApplySorting(Items).Skip(request.StartIndex);
if (request.Count.HasValue)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ internal sealed class InternalGridContext<TGridItem>

public Dictionary<string, FluentDataGridRow<TGridItem>> Rows { get; set; } = [];

public ICollection<TGridItem> Items { get; set; } = [];
public int TotalItemCount { get; set; }

public FluentDataGrid<TGridItem> Grid { get; }
public EventCallbackSubscribable<object?> ColumnsFirstCollected { get; } = new();

Expand Down
Loading