+
@@ -30,9 +37,13 @@ else
@* Sample using Property and OnSelect *@
Using Property and OnSelect
-
-
+ ? SelectColumn;
+
bool UseSelectedItems = true;
DataGridSelectMode Mode = DataGridSelectMode.Single;
IEnumerable SelectedItems = People.Where(p => p.Selected);
+ bool RestrictToCheckbox = false;
record Person(int PersonId, string Name, DateOnly BirthDate)
{
@@ -72,7 +86,6 @@ else
private void ResetSelectItems()
{
People.ToList().ForEach(i => i.Selected = false);
- People.First().Selected = true;
- SelectedItems = People.Where(p => p.Selected);
+ SelectColumn?.ClearSelection();
}
}
diff --git a/src/Core/Components/DataGrid/Columns/SelectColumn.cs b/src/Core/Components/DataGrid/Columns/SelectColumn.cs
index 9e0fed801c..c879bc8dc2 100644
--- a/src/Core/Components/DataGrid/Columns/SelectColumn.cs
+++ b/src/Core/Components/DataGrid/Columns/SelectColumn.cs
@@ -38,6 +38,13 @@ public SelectColumn()
[Parameter]
public RenderFragment ChildContent { get; set; }
+ ///
+ /// Gets or sets whether the selection of rows is restricted to the checkbox cells (true) or if the whole row can be clicked to toggled the selection of rows (false).
+ /// ⚠ Setting this to true will stop the from being invoked, when a cell in this column is clicked.
+ ///
+ [Parameter]
+ public bool RestrictToCheckbox { get; set; } = false;
+
///
/// Gets or sets whether the [All] checkbox is disabled (not clickable).
///
diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor b/src/Core/Components/DataGrid/FluentDataGrid.razor
index 2021c63008..74d3d6a92b 100644
--- a/src/Core/Components/DataGrid/FluentDataGrid.razor
+++ b/src/Core/Components/DataGrid/FluentDataGrid.razor
@@ -92,9 +92,9 @@
var rowStyle = RowStyle?.Invoke(item) ?? null;
Loading = false;
- @for (var colIndex = 0; colIndex < _columns.Count; colIndex++)
+ @for (var colIndex = 0; colIndex < Columns.Count; colIndex++)
{
- var col = _columns[colIndex];
+ var col = Columns[colIndex];
string? tooltip = col.Tooltip ? @col.RawCellContent(item) : null;
@@ -110,9 +110,9 @@
string? _rowsDataSize = $"height: {ItemSize}px";
- @for (var colIndex = 0; colIndex < _columns.Count; colIndex++)
+ @for (var colIndex = 0; colIndex < Columns.Count; colIndex++)
{
- var col = _columns[colIndex];
+ var col = Columns[colIndex];
@((RenderFragment)(__builder => col.RenderPlaceholderContent(__builder, placeholderContext)))
@@ -124,9 +124,9 @@
private void RenderColumnHeaders(RenderTreeBuilder __builder)
{
- @for (var colIndex = 0; colIndex < _columns.Count; colIndex++)
+ @for (var colIndex = 0; colIndex < Columns.Count; colIndex++)
{
- var col = _columns[colIndex];
+ var col = Columns[colIndex];
string CellId = Identifier.NewId();
if (_sortByColumn == col)
col.ShowSortIcon = true;
diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs
index 0768a0efdc..9ebbb75812 100644
--- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs
+++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs
@@ -130,6 +130,12 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve
[Parameter]
public EventCallback> OnRowClick { get; set; }
+ ///
+ /// Gets or sets a callback when a cell is clicked.
+ ///
+ [Parameter]
+ public EventCallback> OnCellClick { get; set; }
+
///
/// Gets or sets a callback when a row is double-clicked.
///
@@ -175,7 +181,7 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve
///
/// Gets the first (optional) SelectColumn
///
- internal IEnumerable> SelectColumns => _columns.Where(col => col is SelectColumn).Cast< SelectColumn>();
+ internal IEnumerable> SelectColumns => Columns.Where(col => col is SelectColumn).Cast< SelectColumn>();
private ElementReference? _gridReference;
private Virtualize<(int, TGridItem)>? _virtualizeComponent;
@@ -187,7 +193,7 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve
// We cascade the InternalGridContext to descendants, which in turn call it to add themselves to _columns
// This happens on every render so that the column list can be updated dynamically
private readonly InternalGridContext _internalGridContext;
- private readonly List> _columns;
+ internal readonly List> Columns;
private bool _collectingColumns; // Columns might re-render themselves arbitrarily. We only want to capture them at a defined time.
// Tracking state for options and sorting
@@ -229,7 +235,7 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(DataGridRowFocusEventArgs))]
public FluentDataGrid()
{
- _columns = [];
+ Columns = [];
_internalGridContext = new(this);
_currentPageItemsChanged = new(EventCallback.Factory.Create(this, RefreshDataCoreAsync));
_renderColumnHeaders = RenderColumnHeaders;
@@ -279,7 +285,7 @@ protected override Task OnParametersSetAsync()
// We don't want to trigger the first data load until we've collected the initial set of columns,
// because they might perform some action like setting the default sort order, so it would be wasteful
// to have to re-query immediately
- return (_columns.Count > 0 && mustRefreshData) ? RefreshDataCoreAsync() : Task.CompletedTask;
+ return (Columns.Count > 0 && mustRefreshData) ? RefreshDataCoreAsync() : Task.CompletedTask;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -309,7 +315,7 @@ internal void AddColumn(ColumnBase column, SortDirection? initialSort
{
if (_collectingColumns)
{
- _columns.Add(column);
+ Columns.Add(column);
if (isDefaultSortColumn && _sortByColumn is null && initialSortDirection.HasValue)
{
@@ -322,23 +328,23 @@ internal void AddColumn(ColumnBase column, SortDirection? initialSort
private void StartCollectingColumns()
{
- _columns.Clear();
+ Columns.Clear();
_collectingColumns = true;
}
private void FinishCollectingColumns()
{
_collectingColumns = false;
- _manualGrid = _columns.Count == 0;
+ _manualGrid = Columns.Count == 0;
- if (!string.IsNullOrWhiteSpace(GridTemplateColumns) && _columns.Where(x => x is not SelectColumn).Any(x => !string.IsNullOrWhiteSpace(x.Width)))
+ if (!string.IsNullOrWhiteSpace(GridTemplateColumns) && Columns.Where(x => x is not SelectColumn).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.");
}
- if (string.IsNullOrWhiteSpace(_internalGridTemplateColumns) && _columns.Any(x => !string.IsNullOrWhiteSpace(x.Width)))
+ if (string.IsNullOrWhiteSpace(_internalGridTemplateColumns) && Columns.Any(x => !string.IsNullOrWhiteSpace(x.Width)))
{
- _internalGridTemplateColumns = string.Join(" ", _columns.Select(x => x.Width ?? "1fr"));
+ _internalGridTemplateColumns = string.Join(" ", Columns.Select(x => x.Width ?? "1fr"));
}
if (ResizableColumns)
@@ -376,7 +382,7 @@ public Task SortByColumnAsync(ColumnBase column, SortDirection direct
/// The direction of sorting. The default is . If the value is , then it will toggle the direction on each call.
public Task SortByColumnAsync(string title, SortDirection direction = SortDirection.Auto)
{
- var column = _columns.FirstOrDefault(c => c.Title?.Equals(title, StringComparison.InvariantCultureIgnoreCase) ?? false);
+ var column = Columns.FirstOrDefault(c => c.Title?.Equals(title, StringComparison.InvariantCultureIgnoreCase) ?? false);
if (column is not null)
{
@@ -393,9 +399,9 @@ public Task SortByColumnAsync(string title, SortDirection direction = SortDirect
/// The direction of sorting. The default is . If the value is , then it will toggle the direction on each call.
public Task SortByColumnAsync(int index, SortDirection direction = SortDirection.Auto)
{
- if (index >= 0 && index < _columns.Count)
+ if (index >= 0 && index < Columns.Count)
{
- return SortByColumnAsync(_columns[index], direction);
+ return SortByColumnAsync(Columns[index], direction);
}
return Task.CompletedTask;
diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor b/src/Core/Components/DataGrid/FluentDataGridCell.razor
index 5e05d90a24..e94605961d 100644
--- a/src/Core/Components/DataGrid/FluentDataGridCell.razor
+++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor
@@ -6,6 +6,7 @@
grid-column=@GridColumn
class="@Class"
style="@StyleValue"
+ @onclick="@HandleOnCellClickAsync"
@attributes="AdditionalAttributes">
@ChildContent
diff --git a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs
index ebd89b844e..b1847779d0 100644
--- a/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs
+++ b/src/Core/Components/DataGrid/FluentDataGridCell.razor.cs
@@ -61,4 +61,23 @@ protected override void OnInitialized()
public void Dispose() => Owner.Unregister(this);
+ ///
+ internal async Task HandleOnCellClickAsync()
+ {
+ if (GridContext.Grid.OnCellClick.HasDelegate)
+ {
+ await GridContext.Grid.OnCellClick.InvokeAsync(this);
+ }
+
+ if (CellType == DataGridCellType.Default && Owner.Owner.Grid.SelectColumns.Any(selColumn => selColumn.RestrictToCheckbox))
+ {
+ foreach (var selColumn in Owner.Owner.Grid.SelectColumns)
+ {
+ if (selColumn != null && selColumn.RestrictToCheckbox is true && Owner.Owner.Grid.Columns.IndexOf(selColumn) == GridColumn - 1)
+ {
+ await selColumn.AddOrRemoveSelectedItemAsync(Item);
+ }
+ }
+ }
+ }
}
diff --git a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs
index 7954c4e983..b2856d5007 100644
--- a/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs
+++ b/src/Core/Components/DataGrid/FluentDataGridRow.razor.cs
@@ -13,7 +13,7 @@ namespace Microsoft.FluentUI.AspNetCore.Components;
public partial class FluentDataGridRow : FluentComponentBase, IHandleEvent, IDisposable
{
internal string RowId { get; set; } = string.Empty;
- private readonly Dictionary> cells = [];
+ internal Dictionary> Cells { get; } = [];
///
/// Gets or sets the reference to the item that holds this row's values.
@@ -56,7 +56,7 @@ public partial class FluentDataGridRow : FluentComponentBase, IHandle
/// Gets or sets the owning component
///
[CascadingParameter]
- private InternalGridContext Owner { get; set; } = default!;
+ internal InternalGridContext Owner { get; set; } = default!;
protected string? ClassValue => new CssBuilder(Class)
.AddClass("hover", when: Owner.Grid.ShowHover)
@@ -80,18 +80,18 @@ internal void Register(FluentDataGridCell cell)
{
cell.CellId = $"c{Owner.GetNextCellId()}";
- cells.Add(cell.CellId, cell);
+ Cells.Add(cell.CellId, cell);
}
internal void Unregister(FluentDataGridCell cell)
{
- cells.Remove(cell.CellId!);
+ Cells.Remove(cell.CellId!);
}
private async Task HandleOnCellFocusAsync(DataGridCellFocusEventArgs args)
{
var cellId = args.CellId;
- if (cells.TryGetValue(cellId!, out var cell))
+ if (Cells.TryGetValue(cellId!, out var cell))
{
if (cell != null && cell.CellType == DataGridCellType.Default)
{
@@ -114,7 +114,10 @@ internal async Task HandleOnRowClickAsync(string rowId)
{
foreach (var selColumn in Owner.Grid.SelectColumns)
{
- await selColumn.AddOrRemoveSelectedItemAsync(Item);
+ if (!selColumn.RestrictToCheckbox)
+ {
+ await selColumn.AddOrRemoveSelectedItemAsync(Item);
+ }
}
}
}
@@ -144,7 +147,10 @@ internal async Task HandleOnRowKeyDownAsync(string rowId, KeyboardEventArgs e)
{
foreach (var selColumn in Owner.Grid.SelectColumns)
{
- await selColumn.AddOrRemoveSelectedItemAsync(Item);
+ if (!selColumn.RestrictToCheckbox)
+ {
+ await selColumn.AddOrRemoveSelectedItemAsync(Item);
+ }
}
}
}
diff --git a/tests/Core/DataGrid/FluentDataGridColumSelectTests.razor b/tests/Core/DataGrid/FluentDataGridColumSelectTests.razor
index d0acb17bc3..d6295373e5 100644
--- a/tests/Core/DataGrid/FluentDataGridColumSelectTests.razor
+++ b/tests/Core/DataGrid/FluentDataGridColumSelectTests.razor
@@ -318,6 +318,88 @@
cut.Verify();
}
+ [Fact]
+ public async Task FluentDataGrid_ColumSelect_SingleSelect_SelectedItems_RestrictToCheckbox()
+ {
+ IEnumerable SelectedItems = Array.Empty();
+
+ // Arrange
+ var cut = Render(
+ @
+
+
+
+ );
+
+ // Pre-Assert
+ Assert.Empty(cut.FindAll("svg[row-selected]"));
+ Assert.Empty(SelectedItems);
+
+ // Act - Click Row 0 Cell 1
+ await ClickOnCellAsync(cut, row: 0, cell: 1);
+ Assert.Empty(cut.FindAll("svg[row-selected]"));
+ Assert.Empty(SelectedItems);
+
+ // Act - Click and select Row 0 Cell 0
+ await ClickOnCellAsync(cut, row: 0, cell: 0);
+ Assert.Single(cut.FindAll("svg[row-selected]"));
+ Assert.Single(SelectedItems);
+
+ // Act - Click Row 1 Cell 1
+ await ClickOnCellAsync(cut, row: 1, cell: 1);
+ Assert.Single(cut.FindAll("svg[row-selected]"));
+ Assert.Single(SelectedItems);
+
+ // Act - Click and select Row 1 Cell 0
+ await ClickOnCellAsync(cut, row: 1, cell: 0);
+ Assert.Single(cut.FindAll("svg[row-selected]"));
+ Assert.Single(SelectedItems);
+ }
+
+ [Fact]
+ public async Task FluentDataGrid_ColumSelect_MultiSelect_SelectedItems_RestrictToCheckbox()
+ {
+ IEnumerable SelectedItems = Array.Empty();
+
+ // Arrange
+ var cut = Render(
+ @
+
+
+
+ );
+
+ // Pre-Assert
+ Assert.Empty(cut.FindAll("svg[row-selected]"));
+ Assert.Empty(SelectedItems);
+
+ // Act - Click Row 0 Cell 1
+ await ClickOnCellAsync(cut, row: 0, cell: 1);
+ Assert.Empty(cut.FindAll("svg[row-selected]"));
+ Assert.Empty(SelectedItems);
+
+ // Act - Click and select Row 0 Cell 0
+ await ClickOnCellAsync(cut, row: 0, cell: 0);
+ Assert.Single(cut.FindAll("svg[row-selected]"));
+ Assert.Single(SelectedItems);
+
+ // Act - Click Row 1 Cell 1
+ await ClickOnCellAsync(cut, row: 1, cell: 1);
+ Assert.Single(cut.FindAll("svg[row-selected]"));
+ Assert.Single(SelectedItems);
+
+ // Act - Click and select Row 1 Cell 0
+ await ClickOnCellAsync(cut, row: 1, cell: 0);
+ Assert.Equal(2, cut.FindAll("svg[row-selected]").Count);
+ Assert.Equal(2, SelectedItems.Count());
+ }
+
///
/// Simulate a click on the DataGrid row number .
///
@@ -331,6 +413,20 @@
cut.FindComponent>().Render();
}
+ ///
+ /// Simulate a click on the DataGrid cell number in row number .
+ ///
+ ///
+ ///
+ ///
+ ///
+ private async Task ClickOnCellAsync(IRenderedFragment cut, int row, int cell)
+ {
+ var item = cut.FindComponents>().ElementAt(row + 1).FindComponents>().ElementAt(cell);
+ await item.Instance.HandleOnCellClickAsync();
+ cut.FindComponent>().Render();
+ }
+
///
/// Simulate a click on the All Checkbox.
///