Skip to content

Commit

Permalink
[DataGrid] Make SelectColumn work when using ItemsProvider (#2060)
Browse files Browse the repository at this point in the history
* Fix #2055 by including ItemsProvider in the logic

* Also fix #2053 by excluding SelectColumns from GridTemplateColumns/Width check
  • Loading branch information
vnbaaij committed May 23, 2024
1 parent dd4ad0b commit 312d11e
Show file tree
Hide file tree
Showing 6 changed files with 75 additions and 21 deletions.
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)

Check warning on line 47 in examples/Demo/Shared/Pages/Lab/IssueTester.razor

View workflow job for this annotation

GitHub Actions / Build and deploy Demo site

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
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)"
@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

0 comments on commit 312d11e

Please sign in to comment.