fluentui-blazor icon indicating copy to clipboard operation
fluentui-blazor copied to clipboard

FluentDataGrid SelectColumn: No row events with ItemsProvider, DeselectAll reverts to previous state

Open cilerler opened this issue 1 year ago • 6 comments

🐛 Bug Report

SelectColumn in FluentDataGrid does not trigger individual change events for the rows when used with ItemsProvider.

💻 Repro or Code Sample

To reproduce the issue:

  1. Have a FluentDataGrid with ItemsProvider and a SelectColumn.
  2. Populate it with a few records.
  3. Select a single record.
  4. Click on the SelectAll checkbox. Observe that all records are selected, but no individual events are triggered for the rows.
  5. DeselectAll. The grid reverts to the previous state where only the initially selected records remains selected.

This behavior can be contrasted with using Items, where DeselectAll works as expected. You can see this in the Multi-Select sample at Fluent UI Blazor DataGrid Component.

.razor
<FluentDataGrid @ref="_dataGrid" ItemsProvider="_mergeListProvider" ItemSize="10" ShowHover="true" ResizableColumns="true" TGridItem="ImportFileMerge">
  <EmptyContent>
	  No records were found!
  </EmptyContent>
  <LoadingContent>
	  <FluentStack Orientation="Orientation.Vertical" HorizontalAlignment="HorizontalAlignment.Center">
		  Loading...<br/>
		  <FluentProgress Width="100%"/>
	  </FluentStack>
  </LoadingContent>
  <ChildContent>
	  <!-- TODO There's a bug where the select-all and deselect-all functionality retains previous selections when using ItemsProvider. -->
	  <SelectColumn TGridItem="ImportFileMerge"
					            SelectMode="DataGridSelectMode.Multiple"
					            SelectFromEntireRow="@true"
					            Property="@(c => c!.Merge)"
					            OnSelect="@OnSelectChange" SelectAllChanged="OnSelectAllChanged">
	  </SelectColumn>
	  <PropertyColumn Title="Order" Property="@(c => c!.Sequence)" Sortable="true" Align="Align.Start" Width="75px">
	  </PropertyColumn>
	  <PropertyColumn Title="ID" Property="@(c => c!.ImportId)" Sortable="true" Align="Align.Start" Width="80px">
	  </PropertyColumn>
	  <PropertyColumn Title="Content" Property="@(c => c!.ContentType)" Sortable="true" Align="Align.Start" Width="120px">
	  </PropertyColumn>
	  <PropertyColumn Title="Status" Property="@(c => c!.Status)" Sortable="true" Align="Align.Start" Width="150px">
	  </PropertyColumn>
	  <PropertyColumn Title="File Name" Property="@(c => c!.Filename)" Sortable="true" Align="Align.Start">
	  </PropertyColumn>
  </ChildContent>
</FluentDataGrid>
.cs
	private GridItemsProvider<ImportFileMerge> _mergeListProvider = default!;
	private FluentDataGrid<ImportFileMerge> _dataGrid = default!;
	
	protected override async Task OnInitializedAsync()
	{
		_mergeListProvider = async request =>
		{
			var response = await GetImportFilesForMergeOperationAsync();
			if (response == null)
			{
				return GridItemsProviderResult.From(items: new List<ImportFileMerge>(), totalItemCount: 0);
			}

			var entities = response.Value;
			if (entities == null)
			{
				return GridItemsProviderResult.From(items: new List<ImportFileMerge>(), totalItemCount: 0);
			}

			int count = response.GetODataCount();
			_dataGrid.SetLoadingState(false);
			return GridItemsProviderResult.From(items: MapToImportFileMerge(entities), totalItemCount: count);
		};

		await base.OnInitializedAsync();
	}

	private void OnSelectChange((ImportFileMerge Item, bool Selected) obj)
	{
            // ...  not getting hit by the `SelectAllChanged`
	}

	private void OnSelectAllChanged(bool? isSelected)
	{
            // ...  can't retrieve the all datagrid items (which would solve the issue)
	}

🤔 Expected Behavior

DeselectAll should remove all selections without reverting to the previous state when using ItemsProvider, similar to how it behaves with Items.

https://github.com/user-attachments/assets/00a932dc-4d84-49e3-935b-39c4f3bfac4d

😯 Current Behavior

When DeselectAll is used after SelectAll, the grid reverts to the previous state instead of deselecting all items. The issue seems tied to the lack of individual change events being triggered for each row when using ItemsProvider.

https://github.com/user-attachments/assets/7b44be41-2200-42b8-91f7-248a544188a5

💁 Possible Solution

Consider triggering individual change events for rows when SelectAll or DeselectAll is clicked, even when using ItemsProvider. Alternatively, provide a way to retrieve the latest SelectedItems or the entire DataGrid data.

🔦 Context

This issue is forcing me to remove the SelectAll option, as I need to capture changes in the selected items. Without individual row change events or a method to retrieve SelectedItems or the latest grid data, the current implementation is problematic.

🌍 Your Environment

  • Windows 11 Enterprise
  • Visual Studio 2022 Enterprise
  • Google Chrome

Blazor Hybrid / MAUI

<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.DataGrid.EntityFrameworkAdapter" Version="4.9.3" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Emoji" Version="4.6.0" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.9.3" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.80" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.80" />

cilerler avatar Aug 10 '24 16:08 cilerler

It is not possible to make it work with your situation. You have set up the SelectColum to use manual management via Property and OnSelect. This means when an item is selected, it's internal state is updated so that the selected property (specified by Property function) is set to true. When using the Select All functionality, only the internal SelectColumn's state is changed. On deselecting all, the internal column state is reset and it is restored to the item's previous state.

I have not figured out a way so the internal item's state could be changed. Maybe @dvoituron knows how to solve this.

vnbaaij avatar Aug 21 '24 13:08 vnbaaij

I'm on holiday at the moment ;-) I can work on it in a few days.

dvoituron avatar Aug 21 '24 19:08 dvoituron

Hi! Could this also be the reason why my grid with a itemprovider does not work with a OnRowClick or OnRowDoubleClick Event? I have tried both parameters but it seems that nothign is getting triggered.

My grid:

<FluentDataGrid OnRowDoubleClick="@OnView" Style="padding: 1rem" TGridItem="GetAppVersionControlResponse" ItemsProvider="@_appVersionControlProvider" Pagination="@_pagination" @ref="_appVersionControlGrid">
    <LoadingContent>
        <FluentProgressRing/>
    </LoadingContent>
    <ChildContent>
        <PropertyColumn Property="@(p => p.Environment!.App!.Name)" Title="App" Sortable="false"/>
        <PropertyColumn Property="@(p => p.Environment!.Name)" Title="Omgeving" Sortable="false"/>
        <PropertyColumn Property="@(p => p.Platform!.Name)" Title="Platform" Sortable="false"/>
        <PropertyColumn Property="@(p => p.StoreUrl)" Title="Store url" Sortable="false"/>
    </ChildContent>
</FluentDataGrid>

My method:

public void OnView(GetAppVersionControlResponse appVersionControl)
    {
        Navigation.NavigateTo($"appversioncontrols/{appVersionControl.Id}");
    }

It seems like no event is being triggered at all, so something might be going wrong with the binding?

Let me know if this is a complete separate issue, i thought it might be the same because we were both using a itemsprovider. if so i will move this question to a appropriate place.

PascalVorwerk avatar Aug 26 '24 06:08 PascalVorwerk

@PascalVorwerk Could you please create a separate issue for this? Otherwise, it may lead to the current issue being hijacked.

cilerler avatar Aug 26 '24 10:08 cilerler

@PascalVorwerk I just added OnRowDoubleClick="@(()=>DemoLogger.WriteLine("Row double clicked!"))" to the Remote Data example in the demos site (which is using the ItemsProvider and it is working as expected there:

image

So please create a new issue indeed with reproduction code we can run. Especially with the ItemsProvider it is hard to create working examples ourselves.

vnbaaij avatar Aug 26 '24 10:08 vnbaaij

Sure will do!

PascalVorwerk avatar Aug 26 '24 11:08 PascalVorwerk

I've used the “Remote Data” example and everything is correct using the current features.

In fact, when you use an ItemsProvider, the data is not entirely in memory. It is then necessary to manually store the selected items. You can capture selected lines individually via the OnSelect event. But you can also find out whether the user has clicked on the [All] box via the SelectAll event and its bool? isSelected attribute, which is True for selection of all elements or False when nothing is selected

By adding this SelectColumn and this code, you'll get the desired result.

  • The variable AllSelected is null when multiple items are selected, or ´True/False` when All / No items are selected.
  • The variable SelectedEventId contains the items selected (when AllSelected is null).
<SelectColumn TGridItem="FoodRecall"
                SelectMode="DataGridSelectMode.Multiple"
                SelectFromEntireRow="@true"
                Property="@IsSelected"
                OnSelect="@OnSelectChange"
                SelectAllChanged="OnSelectAllChanged">
</SelectColumn>
private bool? AllSelected = null;
private Dictionary<string, bool> SelectedEventId = new ();

private bool IsSelected(FoodRecall item)
{
    if (AllSelected is null)
    {
        return SelectedEventId.ContainsKey(item.Event_Id) ? SelectedEventId[item.Event_Id] : false;
    }

    return (bool)AllSelected;
}

private void OnSelectChange((FoodRecall Item, bool Selected) obj)
{
    Console.WriteLine($"OnSelectChange: {obj.Item.Event_Id} - {obj.Selected}");

    AllSelected = null;

    var key = obj.Item.Event_Id;
    var selected = obj.Selected;

    if (SelectedEventId.ContainsKey(key))
    {
        SelectedEventId[key] = selected;
    }
    else
    {
        SelectedEventId.Add(key, selected);
    }
}

private void OnSelectAllChanged(bool? isSelected)
{
    Console.WriteLine($"OnSelectAllChanged: {isSelected}");

    AllSelected = isSelected;
    if (AllSelected == false)
    {
            SelectedEventId.Clear();
    }
}

UnselectAll

Without knowing which objects/elements will be used with the supplier, we can't include this functionality in the library (without performance problems).

I propose to close this Issue. If you encounter any further problems, we can reopen it.

dvoituron avatar Sep 10 '24 10:09 dvoituron