Skip to content

Commit 30aaf09

Browse files
authored
[DataGrid] Add a Selectable function parameter to SelectColumn (#2709)
* 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.
1 parent 3a3666b commit 30aaf09

File tree

4 files changed

+164
-5
lines changed

4 files changed

+164
-5
lines changed

examples/Demo/Shared/Microsoft.FluentUI.AspNetCore.Components.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1679,6 +1679,11 @@
16791679
This action is required to update you data model.
16801680
</summary>
16811681
</member>
1682+
<member name="P:Microsoft.FluentUI.AspNetCore.Components.SelectColumn`1.Selectable">
1683+
<summary>
1684+
Gets or sets the function executed to determine if the item can be selected.
1685+
</summary>
1686+
</member>
16821687
<member name="P:Microsoft.FluentUI.AspNetCore.Components.SelectColumn`1.Property">
16831688
<summary>
16841689
Gets or sets the function to executed to determine checked/unchecked status.

examples/Demo/Shared/Pages/DataGrid/Examples/DataGridMultiSelect.razor

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
<FluentCheckbox @bind-Value="@SelectFromEntireRow"
88
@bind-Value:after="@(() => ResetSelectItems())"
99
Label="Use `SelectFromEntireRow` property" />
10+
<FluentCheckbox @bind-Value="@SelectableBefore2000"
11+
@bind-Value:after="@(() => ResetSelectItems())"
12+
Label="Use `Selectable` property" />
1013
</FluentStack>
1114

1215
@if (UseSelectedItems)
@@ -18,6 +21,7 @@
1821
<SelectColumn TGridItem="Person"
1922
SelectMode="@Mode"
2023
SelectFromEntireRow="@SelectFromEntireRow"
24+
Selectable="@(e => !SelectableBefore2000 || e.BirthDate.Year >= 2000)"
2125
@bind-SelectedItems="@SelectedItems" />
2226
<PropertyColumn Width="100px" Property="@(p => p.PersonId)" Title="ID" />
2327
<PropertyColumn Width="300px" Property="@(p => p.Name)" />
@@ -38,6 +42,7 @@ else
3842
<SelectColumn TGridItem="Person"
3943
SelectMode="@Mode"
4044
SelectFromEntireRow="@SelectFromEntireRow"
45+
Selectable="@(e => !SelectableBefore2000 || e.BirthDate.Year >= 2000)"
4146
Property="@(e => e.Selected)"
4247
OnSelect="@(e => e.Item.Selected = e.Selected)"
4348
SelectAll="@(People.All(p => p.Selected))"
@@ -56,6 +61,7 @@ else
5661
@code {
5762
bool UseSelectedItems = true;
5863
bool SelectFromEntireRow = true;
64+
bool SelectableBefore2000 = false;
5965
DataGridSelectMode Mode = DataGridSelectMode.Single;
6066

6167
IEnumerable<Person> SelectedItems = People.Where(p => p.Selected);

src/Core/Components/DataGrid/Columns/SelectColumn.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class SelectColumn<TGridItem> : ColumnBase<TGridItem>
2121
private readonly Icon IconSelectedSingle = new CoreIcons.Filled.Size20.RadioButton();
2222

2323
private DataGridSelectMode _selectMode = DataGridSelectMode.Single;
24-
private readonly List<TGridItem> _selectedItems = new List<TGridItem>();
24+
private readonly List<TGridItem> _selectedItems = [];
2525

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

179+
/// <summary>
180+
/// Gets or sets the function executed to determine if the item can be selected.
181+
/// </summary>
182+
[Parameter]
183+
public Func<TGridItem, bool>? Selectable { get; set; }
184+
179185
/// <summary>
180186
/// Gets or sets the function to executed to determine checked/unchecked status.
181187
/// </summary>
@@ -271,7 +277,7 @@ protected internal override Task OnCellKeyDownAsync(FluentDataGridCell<TGridItem
271277
/// <summary />
272278
private async Task AddOrRemoveSelectedItemAsync(TGridItem? item)
273279
{
274-
if (item != null)
280+
if (item != null && (Selectable == null || Selectable.Invoke(item)))
275281
{
276282
if (SelectedItems.Contains(item))
277283
{
@@ -332,7 +338,7 @@ private Icon GetIcon(bool? selected)
332338

333339
private async Task KeepOnlyFirstSelectedItemAsync()
334340
{
335-
if (_selectedItems.Count() <= 1)
341+
if (_selectedItems.Count <= 1)
336342
{
337343
return;
338344
}
@@ -364,6 +370,11 @@ private RenderFragment<TGridItem> GetDefaultChildContent()
364370
{
365371
return (item) => new RenderFragment((builder) =>
366372
{
373+
if (Selectable != null && Selectable.Invoke(item) == false)
374+
{
375+
return;
376+
}
377+
367378
var selected = _selectedItems.Contains(item) || Property.Invoke(item);
368379

369380
// Sync with SelectedItems list
@@ -381,6 +392,7 @@ private RenderFragment<TGridItem> GetDefaultChildContent()
381392
builder.AddAttribute(1, "Value", GetIcon(selected));
382393
builder.AddAttribute(2, "Title", selected ? TitleChecked : TitleUnchecked);
383394
builder.AddAttribute(3, "row-selected", selected);
395+
384396
if (!SelectFromEntireRow)
385397
{
386398
builder.AddAttribute(4, "style", "cursor: pointer;");

tests/Core/DataGrid/FluentDataGridColumSelectTests.razor

Lines changed: 138 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,38 @@
4949
Assert.Single(cut.FindAll("svg[row-selected]"));
5050
Assert.Single(SelectedItems);
5151
}
52+
53+
[Fact]
54+
public async Task FluentDataGrid_ColumSelect_Selectable_SingleSelect_SelectedItems()
55+
{
56+
IEnumerable<Person> SelectedItems = Array.Empty<Person>();
57+
58+
// Arrange
59+
var cut = Render(
60+
@<FluentDataGrid Items="@People" TGridItem="Person">
61+
<SelectColumn TGridItem="Person"
62+
SelectMode="@DataGridSelectMode.Single"
63+
Selectable="@(e => e.BirthDate.Year >= 2000)"
64+
@bind-SelectedItems="@SelectedItems" />
65+
<PropertyColumn Property="@(p => p.Name)" />
66+
</FluentDataGrid>
67+
);
68+
69+
// Pre-Assert
70+
Assert.Equal(1, cut.FindAll("svg").Count);
71+
Assert.Empty(cut.FindAll("svg[row-selected]"));
72+
Assert.Empty(SelectedItems);
73+
74+
// Act - Click and select Row 0
75+
await ClickOnRowAsync(cut, row: 0);
76+
Assert.Empty(cut.FindAll("svg[row-selected]"));
77+
Assert.Empty(SelectedItems);
78+
79+
// Act - Click and select Row 1
80+
await ClickOnRowAsync(cut, row: 1);
81+
Assert.Single(cut.FindAll("svg[row-selected]"));
82+
Assert.Single(SelectedItems);
83+
}
5284

5385
[Fact]
5486
public async Task FluentDataGrid_ColumSelect_SingleSelect_Property()
@@ -81,6 +113,39 @@
81113
Assert.Single(items.Where(i => i.Selected));
82114
}
83115

116+
[Fact]
117+
public async Task FluentDataGrid_ColumSelect_Selectable_SingleSelect_Property()
118+
{
119+
var items = new List<Person>(People).AsQueryable();
120+
121+
// Arrange
122+
var cut = Render(
123+
@<FluentDataGrid Items="@items" TGridItem="Person">
124+
<SelectColumn TGridItem="Person"
125+
SelectMode="@DataGridSelectMode.Single"
126+
Selectable="@(e => e.BirthDate.Year >= 2000)"
127+
Property="@(e => e.Selected)"
128+
OnSelect="@(e => e.Item.Selected = e.Selected)" />
129+
<PropertyColumn Property="@(p => p.Name)" />
130+
</FluentDataGrid>
131+
);
132+
133+
// Pre-Assert
134+
Assert.Equal(1, cut.FindAll("svg").Count);
135+
Assert.Empty(cut.FindAll("svg[row-selected]"));
136+
Assert.Empty(items.Where(i => i.Selected));
137+
138+
// Act - Click and select Row 0
139+
await ClickOnRowAsync(cut, row: 0);
140+
Assert.Empty(cut.FindAll("svg[row-selected]"));
141+
Assert.Empty(items.Where(i => i.Selected));
142+
143+
// Act - Click and select Row 1
144+
await ClickOnRowAsync(cut, row: 1);
145+
Assert.Single(cut.FindAll("svg[row-selected]"));
146+
Assert.Single(items.Where(i => i.Selected));
147+
}
148+
84149
[Fact]
85150
public void FluentDataGrid_ColumSelect_MultiSelect_Rendering()
86151
{
@@ -132,7 +197,43 @@
132197
await ClickOnRowAsync(cut, row: 0);
133198
Assert.Single(cut.FindAll("svg[row-selected]"));
134199
Assert.Single(SelectedItems);
200+
}
135201

202+
[Fact]
203+
public async Task FluentDataGrid_ColumSelect_Selectable_MultiSelect_SelectedItems()
204+
{
205+
IEnumerable<Person> SelectedItems = Array.Empty<Person>();
206+
207+
// Arrange
208+
var cut = Render(
209+
@<FluentDataGrid Items="@People" TGridItem="Person">
210+
<SelectColumn TGridItem="Person"
211+
SelectMode="@DataGridSelectMode.Multiple"
212+
Selectable="@(e => e.BirthDate.Year >= 2000)"
213+
@bind-SelectedItems="@SelectedItems" />
214+
<PropertyColumn Property="@(p => p.Name)" />
215+
</FluentDataGrid>
216+
);
217+
218+
// Pre-Assert
219+
Assert.Equal(2, cut.FindAll("svg").Count);
220+
Assert.Empty(cut.FindAll("svg[row-selected]"));
221+
Assert.Empty(SelectedItems);
222+
223+
// Act - Click and select Row 0
224+
await ClickOnRowAsync(cut, row: 0);
225+
Assert.Empty(cut.FindAll("svg[row-selected]"));
226+
Assert.Empty(SelectedItems);
227+
228+
// Act - Click and select Row 1
229+
await ClickOnRowAsync(cut, row: 1);
230+
Assert.Equal(1, cut.FindAll("svg[row-selected]").Count);
231+
Assert.Equal(1, SelectedItems.Count());
232+
233+
// Act - Click and unselect Row 1
234+
await ClickOnRowAsync(cut, row: 1);
235+
Assert.Empty(cut.FindAll("svg[row-selected]"));
236+
Assert.Empty(SelectedItems);
136237
}
137238

138239
[Fact]
@@ -169,7 +270,44 @@
169270
await ClickOnRowAsync(cut, row: 0);
170271
Assert.Single(cut.FindAll("svg[row-selected]"));
171272
Assert.Single(items.Where(i => i.Selected));
273+
}
274+
275+
[Fact]
276+
public async Task FluentDataGrid_ColumSelect_Selectable_MultiSelect_Property()
277+
{
278+
var items = new List<Person>(People).AsQueryable();
279+
280+
// Arrange
281+
var cut = Render(
282+
@<FluentDataGrid Items="@items" TGridItem="Person">
283+
<SelectColumn TGridItem="Person"
284+
SelectMode="@DataGridSelectMode.Multiple"
285+
Selectable="@(e => e.BirthDate.Year >= 2000)"
286+
Property="@(e => e.Selected)"
287+
OnSelect="@(e => e.Item.Selected = e.Selected)" />
288+
<PropertyColumn Property="@(p => p.Name)" />
289+
</FluentDataGrid>
290+
);
291+
292+
// Pre-Assert
293+
Assert.Equal(2, cut.FindAll("svg").Count);
294+
Assert.Empty(cut.FindAll("svg[row-selected]"));
295+
Assert.Empty(items.Where(i => i.Selected));
296+
297+
// Act - Click and select Row 0
298+
await ClickOnRowAsync(cut, row: 0);
299+
Assert.Empty(cut.FindAll("svg[row-selected]"));
300+
Assert.Empty(items.Where(i => i.Selected));
172301

302+
// Act - Click and select Row 1
303+
await ClickOnRowAsync(cut, row: 1);
304+
Assert.Equal(1, cut.FindAll("svg[row-selected]").Count);
305+
Assert.Equal(1, items.Where(i => i.Selected).Count());
306+
307+
// Act - Click and unselect Row 1
308+
await ClickOnRowAsync(cut, row: 1);
309+
Assert.Empty(cut.FindAll("svg[row-selected]"));
310+
Assert.Empty(items.Where(i => i.Selected));
173311
}
174312

175313
[Fact]
@@ -200,7 +338,6 @@
200338
await ClickOnAllAsync(cut);
201339
Assert.Empty(cut.FindAll("svg[row-selected]"));
202340
Assert.Empty(SelectedItems);
203-
204341
}
205342

206343
[Fact]
@@ -234,7 +371,6 @@
234371
await ClickOnAllAsync(cut);
235372
Assert.Empty(cut.FindAll("svg[row-selected]"));
236373
Assert.Empty(items.Where(i => i.Selected));
237-
238374
}
239375

240376
[Fact]

0 commit comments

Comments
 (0)