From 5d57a3b43279684b9d9efa52c9d7d031c2228db3 Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 2 Dec 2025 19:36:20 +0100 Subject: [PATCH 1/3] Add OnRowClick EventCallback to QuickGrid This change adds an OnRowClick parameter to QuickGrid that allows users to handle row click events. When the OnRowClick delegate is set: - The row receives a 'row-clickable' CSS class - A cursor: pointer style is applied via scoped CSS - Clicking the row invokes the callback with the row item Fixes #44899 --- .../src/PublicAPI.Unshipped.txt | 2 + .../src/QuickGrid.razor | 24 +++++++++--- .../src/QuickGrid.razor.cs | 5 +++ .../src/QuickGrid.razor.css | 5 +++ .../test/E2ETest/Tests/QuickGridTest.cs | 39 +++++++++++++++++++ .../SampleQuickGridComponent.razor | 23 ++++++++++- 6 files changed, 91 insertions(+), 7 deletions(-) diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/PublicAPI.Unshipped.txt b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/PublicAPI.Unshipped.txt index 7dc5c58110bf..8aa8edca4b77 100644 --- a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/PublicAPI.Unshipped.txt +++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ #nullable enable +Microsoft.AspNetCore.Components.QuickGrid.QuickGrid.OnRowClick.get -> Microsoft.AspNetCore.Components.EventCallback +Microsoft.AspNetCore.Components.QuickGrid.QuickGrid.OnRowClick.set -> void diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor index 5228ed1d2b00..f11fca3fa5fc 100644 --- a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor +++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor @@ -66,12 +66,24 @@ private void RenderRow(RenderTreeBuilder __builder, int rowIndex, TGridItem item) { var rowClass = RowClass?.Invoke(item); - - @foreach (var col in _columns) - { - @{ col.CellContent(__builder, item); } - } - + if (OnRowClick.HasDelegate) + { + + @foreach (var col in _columns) + { + @{ col.CellContent(__builder, item); } + } + + } + else + { + + @foreach (var col in _columns) + { + @{ col.CellContent(__builder, item); } + } + + } } private void RenderPlaceholderRow(RenderTreeBuilder __builder, PlaceholderContext placeholderContext) diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor.cs b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor.cs index 28729c2eb58d..88350bcfd207 100644 --- a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor.cs +++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor.cs @@ -109,6 +109,11 @@ public partial class QuickGrid : IAsyncDisposable /// [Parameter] public Func? RowClass { get; set; } + /// + /// Optional. A callback that is invoked when a row is clicked. + /// + [Parameter] public EventCallback OnRowClick { get; set; } + [Inject] private IServiceProvider Services { get; set; } = default!; [Inject] private IJSRuntime JS { get; set; } = default!; diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor.css b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor.css index 4747ec263339..e6766fa5040c 100644 --- a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor.css +++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor.css @@ -116,3 +116,8 @@ html[dir=rtl] .col-justify-end .col-options { right: unset; left: 0; } + +/* Clickable rows when OnRowClick is set */ +tr.row-clickable { + cursor: pointer; +} diff --git a/src/Components/test/E2ETest/Tests/QuickGridTest.cs b/src/Components/test/E2ETest/Tests/QuickGridTest.cs index 6b1c2f0713bf..8eeef99de766 100644 --- a/src/Components/test/E2ETest/Tests/QuickGridTest.cs +++ b/src/Components/test/E2ETest/Tests/QuickGridTest.cs @@ -215,4 +215,43 @@ public void ItemsProviderCalledOnceWithVirtualize() app = Browser.MountTestComponent(); Browser.Equal("1", () => app.FindElement(By.Id("items-provider-call-count")).Text); } + + [Fact] + public void OnRowClickTriggersCallback() + { + var grid = app.FindElement(By.CssSelector("#grid > table")); + + // Verify no row has been clicked yet + Browser.Exists(By.Id("no-click")); + + // Click on the first row (Julie Smith) + var firstRow = grid.FindElement(By.CssSelector("tbody > tr:nth-child(1)")); + firstRow.Click(); + + // Verify the callback was triggered with correct data + Browser.Equal("PersonId: 11203", () => app.FindElement(By.Id("clicked-person-id")).Text); + Browser.Equal("Name: Julie Smith", () => app.FindElement(By.Id("clicked-person-name")).Text); + Browser.Equal("Click count: 1", () => app.FindElement(By.Id("click-count")).Text); + + // Click on another row (Jose Hernandez - 3rd row) + var thirdRow = grid.FindElement(By.CssSelector("tbody > tr:nth-child(3)")); + thirdRow.Click(); + + // Verify the callback was triggered with the new row's data + Browser.Equal("PersonId: 11898", () => app.FindElement(By.Id("clicked-person-id")).Text); + Browser.Equal("Name: Jose Hernandez", () => app.FindElement(By.Id("clicked-person-name")).Text); + Browser.Equal("Click count: 2", () => app.FindElement(By.Id("click-count")).Text); + } + + [Fact] + public void OnRowClickAppliesCursorPointerStyle() + { + var grid = app.FindElement(By.CssSelector("#grid > table")); + + // Verify the row has cursor: pointer style via the row-clickable class + var cursorStyle = Browser.ExecuteJavaScript(@" + const row = document.querySelector('#grid > table > tbody > tr:nth-child(1)'); + return row ? getComputedStyle(row).cursor : null;"); + Assert.Equal("pointer", cursorStyle); + } } diff --git a/src/Components/test/testassets/BasicTestApp/QuickGridTest/SampleQuickGridComponent.razor b/src/Components/test/testassets/BasicTestApp/QuickGridTest/SampleQuickGridComponent.razor index 8de3631566f6..fba7825e6cd1 100644 --- a/src/Components/test/testassets/BasicTestApp/QuickGridTest/SampleQuickGridComponent.razor +++ b/src/Components/test/testassets/BasicTestApp/QuickGridTest/SampleQuickGridComponent.razor @@ -3,7 +3,7 @@

Sample QuickGrid Component

- + @@ -20,11 +20,32 @@
+
+ @if (clickedPerson is not null) + { +

PersonId: @clickedPerson.PersonId

+

Name: @clickedPerson.FirstName @clickedPerson.LastName

+

Click count: @clickCount

+ } + else + { +

No row clicked yet

+ } +
+ @code { record Person(int PersonId, string FirstName, string LastName, DateOnly BirthDate); PaginationState pagination = new PaginationState { ItemsPerPage = 10 }; string firstNameFilter; QuickGrid quickGridRef; + Person clickedPerson; + int clickCount; + + void HandleRowClick(Person person) + { + clickedPerson = person; + clickCount++; + } int ComputeAge(DateOnly birthDate) => DateTime.Now.Year - birthDate.Year - (birthDate.DayOfYear < DateTime.Now.DayOfYear ? 0 : 1); From 72fc21a780669833d9569124da779b7de0332b07 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Dec 2025 22:46:42 +0100 Subject: [PATCH 2/3] Add OnRowClick EventCallback to QuickGrid (#64606) * Initial plan * Fix RowClassApplied test and consolidate RenderRow logic Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> * Fix trailing space in combinedClass when rowClass is null Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../src/QuickGrid.razor | 13 +++++++++++-- src/Components/test/E2ETest/Tests/QuickGridTest.cs | 4 ++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor index f11fca3fa5fc..a6c87173ca11 100644 --- a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor +++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor @@ -66,9 +66,16 @@ private void RenderRow(RenderTreeBuilder __builder, int rowIndex, TGridItem item) { var rowClass = RowClass?.Invoke(item); + var combinedClass = OnRowClick.HasDelegate + ? string.IsNullOrEmpty(rowClass) ? "row-clickable" : $"row-clickable {rowClass}" + : rowClass; + if (OnRowClick.HasDelegate) { - + @foreach (var col in _columns) { @{ col.CellContent(__builder, item); } @@ -77,7 +84,9 @@ } else { - + @foreach (var col in _columns) { @{ col.CellContent(__builder, item); } diff --git a/src/Components/test/E2ETest/Tests/QuickGridTest.cs b/src/Components/test/E2ETest/Tests/QuickGridTest.cs index 8eeef99de766..e2994a1eb41f 100644 --- a/src/Components/test/E2ETest/Tests/QuickGridTest.cs +++ b/src/Components/test/E2ETest/Tests/QuickGridTest.cs @@ -136,11 +136,11 @@ public void RowClassApplied() if (firstName == "Julie") { isJulieRowFound = true; - Assert.Equal("highlight", row.GetDomAttribute("class")); + Assert.Equal("row-clickable highlight", row.GetDomAttribute("class")); } else { - Assert.Null(row.GetDomAttribute("class")); + Assert.Equal("row-clickable", row.GetDomAttribute("class")); } } From d16f068d2975ecd7c048568707441ada22bcba17 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 3 Dec 2025 12:21:09 +0100 Subject: [PATCH 3/3] Simplify QuickGrid OnRowClick rendering by removing conditional duplication (#64621) * Initial plan * Simplify RenderRow by removing if/else and coalescing OnRowClick callback Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: javiercn <6995051+javiercn@users.noreply.github.com> --- .../src/QuickGrid.razor | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor index a6c87173ca11..e9368104ec43 100644 --- a/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor +++ b/src/Components/QuickGrid/Microsoft.AspNetCore.Components.QuickGrid/src/QuickGrid.razor @@ -69,30 +69,17 @@ var combinedClass = OnRowClick.HasDelegate ? string.IsNullOrEmpty(rowClass) ? "row-clickable" : $"row-clickable {rowClass}" : rowClass; + var rowClick = OnRowClick.HasDelegate ? EventCallback.Factory.Create(this, () => OnRowClick.InvokeAsync(item)) : default; - if (OnRowClick.HasDelegate) - { - - @foreach (var col in _columns) - { - @{ col.CellContent(__builder, item); } - } - - } - else - { - - @foreach (var col in _columns) - { - @{ col.CellContent(__builder, item); } - } - - } + + @foreach (var col in _columns) + { + @{ col.CellContent(__builder, item); } + } + } private void RenderPlaceholderRow(RenderTreeBuilder __builder, PlaceholderContext placeholderContext)