Skip to content

Commit

Permalink
[DataGrid] Add a Selectable function parameter to SelectColumn (#2709)
Browse files Browse the repository at this point in the history
* Added Selectable property to DataGrid's SelectColumn allowing to determine if the item can be selected

* Added unit tests for DataGrid's SelectColumn 'Selectable' propert.
  • Loading branch information
miguelhasse authored Sep 24, 2024
1 parent 3a3666b commit 30aaf09
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1679,6 +1679,11 @@
This action is required to update you data model.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.SelectColumn`1.Selectable">
<summary>
Gets or sets the function executed to determine if the item can be selected.
</summary>
</member>
<member name="P:Microsoft.FluentUI.AspNetCore.Components.SelectColumn`1.Property">
<summary>
Gets or sets the function to executed to determine checked/unchecked status.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
<FluentCheckbox @bind-Value="@SelectFromEntireRow"
@bind-Value:after="@(() => ResetSelectItems())"
Label="Use `SelectFromEntireRow` property" />
<FluentCheckbox @bind-Value="@SelectableBefore2000"
@bind-Value:after="@(() => ResetSelectItems())"
Label="Use `Selectable` property" />
</FluentStack>

@if (UseSelectedItems)
Expand All @@ -18,6 +21,7 @@
<SelectColumn TGridItem="Person"
SelectMode="@Mode"
SelectFromEntireRow="@SelectFromEntireRow"
Selectable="@(e => !SelectableBefore2000 || e.BirthDate.Year >= 2000)"
@bind-SelectedItems="@SelectedItems" />
<PropertyColumn Width="100px" Property="@(p => p.PersonId)" Title="ID" />
<PropertyColumn Width="300px" Property="@(p => p.Name)" />
Expand All @@ -38,6 +42,7 @@ else
<SelectColumn TGridItem="Person"
SelectMode="@Mode"
SelectFromEntireRow="@SelectFromEntireRow"
Selectable="@(e => !SelectableBefore2000 || e.BirthDate.Year >= 2000)"
Property="@(e => e.Selected)"
OnSelect="@(e => e.Item.Selected = e.Selected)"
SelectAll="@(People.All(p => p.Selected))"
Expand All @@ -56,6 +61,7 @@ else
@code {
bool UseSelectedItems = true;
bool SelectFromEntireRow = true;
bool SelectableBefore2000 = false;
DataGridSelectMode Mode = DataGridSelectMode.Single;

IEnumerable<Person> SelectedItems = People.Where(p => p.Selected);
Expand Down
18 changes: 15 additions & 3 deletions src/Core/Components/DataGrid/Columns/SelectColumn.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class SelectColumn<TGridItem> : ColumnBase<TGridItem>
private readonly Icon IconSelectedSingle = new CoreIcons.Filled.Size20.RadioButton();

private DataGridSelectMode _selectMode = DataGridSelectMode.Single;
private readonly List<TGridItem> _selectedItems = new List<TGridItem>();
private readonly List<TGridItem> _selectedItems = [];

/// <summary>
/// Initializes a new instance of <see cref="SelectColumn{TGridItem}"/>.
Expand Down Expand Up @@ -176,6 +176,12 @@ public DataGridSelectMode SelectMode
[Parameter]
public EventCallback<bool?> SelectAllChanged { get; set; }

/// <summary>
/// Gets or sets the function executed to determine if the item can be selected.
/// </summary>
[Parameter]
public Func<TGridItem, bool>? Selectable { get; set; }

/// <summary>
/// Gets or sets the function to executed to determine checked/unchecked status.
/// </summary>
Expand Down Expand Up @@ -271,7 +277,7 @@ protected internal override Task OnCellKeyDownAsync(FluentDataGridCell<TGridItem
/// <summary />
private async Task AddOrRemoveSelectedItemAsync(TGridItem? item)
{
if (item != null)
if (item != null && (Selectable == null || Selectable.Invoke(item)))
{
if (SelectedItems.Contains(item))
{
Expand Down Expand Up @@ -332,7 +338,7 @@ private Icon GetIcon(bool? selected)

private async Task KeepOnlyFirstSelectedItemAsync()
{
if (_selectedItems.Count() <= 1)
if (_selectedItems.Count <= 1)
{
return;
}
Expand Down Expand Up @@ -364,6 +370,11 @@ private RenderFragment<TGridItem> GetDefaultChildContent()
{
return (item) => new RenderFragment((builder) =>
{
if (Selectable != null && Selectable.Invoke(item) == false)
{
return;
}
var selected = _selectedItems.Contains(item) || Property.Invoke(item);
// Sync with SelectedItems list
Expand All @@ -381,6 +392,7 @@ private RenderFragment<TGridItem> GetDefaultChildContent()
builder.AddAttribute(1, "Value", GetIcon(selected));
builder.AddAttribute(2, "Title", selected ? TitleChecked : TitleUnchecked);
builder.AddAttribute(3, "row-selected", selected);
if (!SelectFromEntireRow)
{
builder.AddAttribute(4, "style", "cursor: pointer;");
Expand Down
140 changes: 138 additions & 2 deletions tests/Core/DataGrid/FluentDataGridColumSelectTests.razor
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,38 @@
Assert.Single(cut.FindAll("svg[row-selected]"));
Assert.Single(SelectedItems);
}

[Fact]
public async Task FluentDataGrid_ColumSelect_Selectable_SingleSelect_SelectedItems()
{
IEnumerable<Person> SelectedItems = Array.Empty<Person>();

// Arrange
var cut = Render(
@<FluentDataGrid Items="@People" TGridItem="Person">
<SelectColumn TGridItem="Person"
SelectMode="@DataGridSelectMode.Single"
Selectable="@(e => e.BirthDate.Year >= 2000)"
@bind-SelectedItems="@SelectedItems" />
<PropertyColumn Property="@(p => p.Name)" />
</FluentDataGrid>
);

// Pre-Assert
Assert.Equal(1, cut.FindAll("svg").Count);
Assert.Empty(cut.FindAll("svg[row-selected]"));
Assert.Empty(SelectedItems);

// Act - Click and select Row 0
await ClickOnRowAsync(cut, row: 0);
Assert.Empty(cut.FindAll("svg[row-selected]"));
Assert.Empty(SelectedItems);

// Act - Click and select Row 1
await ClickOnRowAsync(cut, row: 1);
Assert.Single(cut.FindAll("svg[row-selected]"));
Assert.Single(SelectedItems);
}

[Fact]
public async Task FluentDataGrid_ColumSelect_SingleSelect_Property()
Expand Down Expand Up @@ -81,6 +113,39 @@
Assert.Single(items.Where(i => i.Selected));
}

[Fact]
public async Task FluentDataGrid_ColumSelect_Selectable_SingleSelect_Property()
{
var items = new List<Person>(People).AsQueryable();

// Arrange
var cut = Render(
@<FluentDataGrid Items="@items" TGridItem="Person">
<SelectColumn TGridItem="Person"
SelectMode="@DataGridSelectMode.Single"
Selectable="@(e => e.BirthDate.Year >= 2000)"
Property="@(e => e.Selected)"
OnSelect="@(e => e.Item.Selected = e.Selected)" />
<PropertyColumn Property="@(p => p.Name)" />
</FluentDataGrid>
);

// Pre-Assert
Assert.Equal(1, cut.FindAll("svg").Count);
Assert.Empty(cut.FindAll("svg[row-selected]"));
Assert.Empty(items.Where(i => i.Selected));

// Act - Click and select Row 0
await ClickOnRowAsync(cut, row: 0);
Assert.Empty(cut.FindAll("svg[row-selected]"));
Assert.Empty(items.Where(i => i.Selected));

// Act - Click and select Row 1
await ClickOnRowAsync(cut, row: 1);
Assert.Single(cut.FindAll("svg[row-selected]"));
Assert.Single(items.Where(i => i.Selected));
}

[Fact]
public void FluentDataGrid_ColumSelect_MultiSelect_Rendering()
{
Expand Down Expand Up @@ -132,7 +197,43 @@
await ClickOnRowAsync(cut, row: 0);
Assert.Single(cut.FindAll("svg[row-selected]"));
Assert.Single(SelectedItems);
}

[Fact]
public async Task FluentDataGrid_ColumSelect_Selectable_MultiSelect_SelectedItems()
{
IEnumerable<Person> SelectedItems = Array.Empty<Person>();

// Arrange
var cut = Render(
@<FluentDataGrid Items="@People" TGridItem="Person">
<SelectColumn TGridItem="Person"
SelectMode="@DataGridSelectMode.Multiple"
Selectable="@(e => e.BirthDate.Year >= 2000)"
@bind-SelectedItems="@SelectedItems" />
<PropertyColumn Property="@(p => p.Name)" />
</FluentDataGrid>
);

// Pre-Assert
Assert.Equal(2, cut.FindAll("svg").Count);
Assert.Empty(cut.FindAll("svg[row-selected]"));
Assert.Empty(SelectedItems);

// Act - Click and select Row 0
await ClickOnRowAsync(cut, row: 0);
Assert.Empty(cut.FindAll("svg[row-selected]"));
Assert.Empty(SelectedItems);

// Act - Click and select Row 1
await ClickOnRowAsync(cut, row: 1);
Assert.Equal(1, cut.FindAll("svg[row-selected]").Count);
Assert.Equal(1, SelectedItems.Count());

// Act - Click and unselect Row 1
await ClickOnRowAsync(cut, row: 1);
Assert.Empty(cut.FindAll("svg[row-selected]"));
Assert.Empty(SelectedItems);
}

[Fact]
Expand Down Expand Up @@ -169,7 +270,44 @@
await ClickOnRowAsync(cut, row: 0);
Assert.Single(cut.FindAll("svg[row-selected]"));
Assert.Single(items.Where(i => i.Selected));
}

[Fact]
public async Task FluentDataGrid_ColumSelect_Selectable_MultiSelect_Property()
{
var items = new List<Person>(People).AsQueryable();

// Arrange
var cut = Render(
@<FluentDataGrid Items="@items" TGridItem="Person">
<SelectColumn TGridItem="Person"
SelectMode="@DataGridSelectMode.Multiple"
Selectable="@(e => e.BirthDate.Year >= 2000)"
Property="@(e => e.Selected)"
OnSelect="@(e => e.Item.Selected = e.Selected)" />
<PropertyColumn Property="@(p => p.Name)" />
</FluentDataGrid>
);

// Pre-Assert
Assert.Equal(2, cut.FindAll("svg").Count);
Assert.Empty(cut.FindAll("svg[row-selected]"));
Assert.Empty(items.Where(i => i.Selected));

// Act - Click and select Row 0
await ClickOnRowAsync(cut, row: 0);
Assert.Empty(cut.FindAll("svg[row-selected]"));
Assert.Empty(items.Where(i => i.Selected));

// Act - Click and select Row 1
await ClickOnRowAsync(cut, row: 1);
Assert.Equal(1, cut.FindAll("svg[row-selected]").Count);
Assert.Equal(1, items.Where(i => i.Selected).Count());

// Act - Click and unselect Row 1
await ClickOnRowAsync(cut, row: 1);
Assert.Empty(cut.FindAll("svg[row-selected]"));
Assert.Empty(items.Where(i => i.Selected));
}

[Fact]
Expand Down Expand Up @@ -200,7 +338,6 @@
await ClickOnAllAsync(cut);
Assert.Empty(cut.FindAll("svg[row-selected]"));
Assert.Empty(SelectedItems);

}

[Fact]
Expand Down Expand Up @@ -234,7 +371,6 @@
await ClickOnAllAsync(cut);
Assert.Empty(cut.FindAll("svg[row-selected]"));
Assert.Empty(items.Where(i => i.Selected));

}

[Fact]
Expand Down

0 comments on commit 30aaf09

Please sign in to comment.