diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor b/src/Core/Components/DataGrid/FluentDataGrid.razor
index 8ced7a3e77..66197188f0 100644
--- a/src/Core/Components/DataGrid/FluentDataGrid.razor
+++ b/src/Core/Components/DataGrid/FluentDataGrid.razor
@@ -5,260 +5,313 @@
@inherits FluentComponentBase
@typeparam TGridItem
- @{
- StartCollectingColumns();
- }
- @if (!_manualGrid)
- {
- @ChildContent
- }
-
- @{
- FinishCollectingColumns();
- }
-
+ @{
+ StartCollectingColumns();
+ }
+ @if (!_manualGrid)
+ {
+ @ChildContent
+ }
+
+ @{
+ FinishCollectingColumns();
+ }
+
-
- @if (GenerateHeader != DataGridGeneratedHeaderType.None)
- {
- DataGridRowType headerType = DataGridRowType.Header;
- if (GenerateHeader == DataGridGeneratedHeaderType.Sticky)
- {
- headerType = DataGridRowType.StickyHeader;
- }
-
-
- @_renderColumnHeaders
-
-
- }
-
- @if (EffectiveLoadingValue)
- {
- @_renderLoadingContent
- }
- else
- {
- @if (Virtualize)
- {
- if (_internalGridContext.TotalItemCount == 0)
- {
- @_renderEmptyContent
- }
- else
- {
-
- }
- }
- else
- {
- @_renderNonVirtualizedRows
- }
- }
- @if (_manualGrid)
- {
- @ChildContent
- }
-
-
-
+
+ @if (GenerateHeader != DataGridGeneratedHeaderType.None)
+ {
+ DataGridRowType headerType = DataGridRowType.Header;
+ if (GenerateHeader == DataGridGeneratedHeaderType.Sticky)
+ {
+ headerType = DataGridRowType.StickyHeader;
+ }
+
+
+ @_renderColumnHeaders
+
+
+ }
+
+ @if (_lastError != null)
+ {
+ @_renderErrorContent
+ }
+ else if (EffectiveLoadingValue)
+ {
+ @_renderLoadingContent
+ }
+ else
+ {
+ @if (Virtualize)
+ {
+ if (_internalGridContext.TotalItemCount == 0)
+ {
+ @_renderEmptyContent
+ }
+ else
+ {
+
+ }
+ }
+ else
+ {
+ @_renderNonVirtualizedRows
+ }
+ }
+ @if (_manualGrid)
+ {
+ @ChildContent
+ }
+
+
+
@code {
- private void RenderNonVirtualizedRows(RenderTreeBuilder __builder)
- {
- var initialRowIndex = (GenerateHeader != DataGridGeneratedHeaderType.None) ? 2 : 1; // aria-rowindex is 1-based, plus 1 if there is a header
- var rowIndex = initialRowIndex;
- if (_internalGridContext.Items.Any())
- {
- foreach (var item in _internalGridContext.Items)
- {
- RenderRow(__builder, rowIndex++, item);
- }
- }
- else
- {
- RenderEmptyContent(__builder);
- }
- }
+ private void RenderNonVirtualizedRows(RenderTreeBuilder __builder)
+ {
+ var initialRowIndex = (GenerateHeader != DataGridGeneratedHeaderType.None) ? 2 : 1; // aria-rowindex is 1-based, plus 1 if there is a header
+ var rowIndex = initialRowIndex;
+ if (_lastError != null)
+ {
+ RenderErrorContent(__builder);
+ }
+ else if (_internalGridContext.Items.Any())
+ {
+ foreach (var item in _internalGridContext.Items)
+ {
+ RenderRow(__builder, rowIndex++, item);
+ }
+ }
+ else
+ {
+ RenderEmptyContent(__builder);
+ }
+ }
- private void RenderRow(RenderTreeBuilder __builder, int rowIndex, TGridItem item)
- {
- var rowClass = RowClass?.Invoke(item) ?? null;
- var rowStyle = RowStyle?.Invoke(item) ?? null;
+ private void RenderRow(RenderTreeBuilder __builder, int rowIndex, TGridItem item)
+ {
+ var rowClass = RowClass?.Invoke(item) ?? null;
+ var rowStyle = RowStyle?.Invoke(item) ?? null;
-
- @for (var colIndex = 0; colIndex < _columns.Count; colIndex++)
- {
- var col = _columns[colIndex];
+
+ @for (var colIndex = 0; colIndex < _columns.Count; colIndex++)
+ {
+ var col = _columns[colIndex];
- string? tooltip = col.Tooltip ? @col.RawCellContent(item) : null;
+ string? tooltip = col.Tooltip ? @col.RawCellContent(item) : null;
-
- @((RenderFragment)(__builder => col.CellContent(__builder, item)))
-
- }
-
- }
+
+ @((RenderFragment)(__builder => col.CellContent(__builder, item)))
+
+ }
+
+ }
- [ExcludeFromCodeCoverage(Justification = "This method is used virtualized mode and cannot be tested with bUnit currently.")]
- private void RenderPlaceholderRow(RenderTreeBuilder __builder, PlaceholderContext placeholderContext)
- {
- string? _rowsDataSize = $"height: {ItemSize}px";
+ [ExcludeFromCodeCoverage(Justification = "This method is used virtualized mode and cannot be tested with bUnit currently.")]
+ private void RenderPlaceholderRow(RenderTreeBuilder __builder, PlaceholderContext placeholderContext)
+ {
+ string? _rowsDataSize = $"height: {ItemSize}px";
-
- @for (var i = 0; i < _columns.Count; i++)
- {
- var col = _columns[i];
+
+ RenderPlaceholderCells(placeholderContext)
+
+ }
-
- @((RenderFragment)(__builder => col.RenderPlaceholderContent(__builder, placeholderContext)))
-
- }
-
- }
+ [ExcludeFromCodeCoverage(Justification = "Circumvent EFCC shortcomings when using razor.")]
+ private void RenderPlaceholderCells(RenderTreeBuilder __builder, PlaceholderContext placeholderContext)
+ {
+ for (var i = 0; i < _columns.Count; i++)
+ {
+ var col = _columns[i];
- private void RenderColumnHeaders(RenderTreeBuilder __builder)
- {
- @for (var i = 0; i < _columns.Count; i++)
- {
- var col = _columns[i];
+
+ @((RenderFragment)(__builder => col.RenderPlaceholderContent(__builder, placeholderContext)))
+
+ }
+ }
- if (_sortByColumn == col)
- col.IsActiveSortColumn = true;
- else
- col.IsActiveSortColumn = false;
+ private void RenderColumnHeaders(RenderTreeBuilder __builder)
+ {
+ @for (var i = 0; i < _columns.Count; i++)
+ {
+ var col = _columns[i];
-
- @col.HeaderContent
- @if (HeaderCellAsButtonWithMenu)
- {
- @if (col == _displayOptionsForColumn)
- {
-
- @col.ColumnOptions
-
- }
- @if (ResizableColumns && col == _displayResizeForColumn)
- {
-
+ if (_sortByColumn == col)
+ col.IsActiveSortColumn = true;
+ else
+ col.IsActiveSortColumn = false;
- @if (ResizeType is not null)
- {
-
- }
+
+ @col.HeaderContent
+ @if (HeaderCellAsButtonWithMenu)
+ {
+ @if (col == _displayOptionsForColumn)
+ {
+
+ @col.ColumnOptions
+
+ }
+ @if (ResizableColumns && col == _displayResizeForColumn)
+ {
+
-
- }
- }
- else
- {
- @if (col == _displayOptionsForColumn)
- {
-
-
- @if (ResizeType is not null)
- {
-
- @if (@col.ColumnOptions is not null)
- {
-
- }
- }
- @col.ColumnOptions
-
-
- }
- }
+ @if (ResizeType is not null)
+ {
+
+ }
+
+ }
+ }
+ else
+ {
+ @if (col == _displayOptionsForColumn)
+ {
+
+
+ @if (ResizeType is not null)
+ {
+
+ @if (@col.ColumnOptions is not null)
+ {
+
+ }
+ }
+ @col.ColumnOptions
+
+
+ }
+ }
- @if (ResizableColumns)
- {
-
- }
-
- }
- }
- private void RenderEmptyContent(RenderTreeBuilder __builder)
- {
- if (_manualGrid)
- {
- return;
- }
+ @if (ResizableColumns)
+ {
+
+ }
+
+ }
+ }
- string? style = null;
- string? colspan = null;
- if (DisplayMode == DataGridDisplayMode.Grid)
- {
- style = $"grid-column: 1 / {_columns.Count + 1}";
- }
- else
- {
- colspan = _columns.Count.ToString();
- }
+ private void RenderEmptyContent(RenderTreeBuilder __builder)
+ {
+ if (_manualGrid)
+ {
+ return;
+ }
-
-
- @if (EmptyContent is null)
- {
+ string? style = null;
+ string? colspan = null;
+ if (DisplayMode == DataGridDisplayMode.Grid)
+ {
+ style = $"grid-column: 1 / {_columns.Count + 1}";
+ }
+ else
+ {
+ colspan = _columns.Count.ToString();
+ }
+
+
+
+ @if (EmptyContent is null)
+ {
@Localizer[Localization.LanguageResource.DataGrid_EmptyContent]
- }
- else
- {
- @EmptyContent
- }
-
-
+ }
+ else
+ {
+ @EmptyContent
+ }
+
+
+
+ }
+
+ private void RenderLoadingContent(RenderTreeBuilder __builder)
+ {
+ string? style = null;
+ string? colspan = null;
+ if (DisplayMode == DataGridDisplayMode.Grid)
+ {
+ style = $"grid-column: 1 / {_columns.Count + 1}";
+ }
+ else
+ {
+ colspan = _columns.Count.ToString(CultureInfo.InvariantCulture);
+ }
+
+
+
+ @if (LoadingContent is null)
+ {
+
+ Loading...
+
+ }
+ else
+ {
+ @LoadingContent
+ }
+
+
+ }
+
+ [ExcludeFromCodeCoverage(Justification = "This method requires a failing db connection and is to complex to be tested with bUnit.")]
+ private void RenderErrorContent(RenderTreeBuilder __builder)
+ {
+ if (_lastError == null)
+ {
+ return;
+ }
- }
+ string? style = null;
+ string? colspan = null;
+ if (DisplayMode == DataGridDisplayMode.Grid)
+ {
+ style = $"grid-column: 1 / {_columns.Count + 1}";
+ }
+ else
+ {
+ colspan = _columns.Count.ToString();
+ }
- private void RenderLoadingContent(RenderTreeBuilder __builder)
- {
- string? style = null;
- string? colspan = null;
- if (DisplayMode == DataGridDisplayMode.Grid)
- {
- style = $"grid-column: 1 / {_columns.Count + 1}";
- }
- else
- {
- colspan = _columns.Count.ToString(CultureInfo.InvariantCulture);
- }
+
+
+ RenderActualError(_lastError)
+
+
+ }
-
-
- @if (LoadingContent is null)
- {
-
- Loading...
-
- }
- else
- {
- @LoadingContent
- }
-
-
- }
+ [ExcludeFromCodeCoverage(Justification = "Circumvent EFCC shortcomings when using razor.")]
+ private void RenderActualError(RenderTreeBuilder __builder, Exception ex)
+ {
+ if (ErrorContent is null)
+ {
+ @("An error occurred while retrieving data.")
+ }
+ else
+ {
+ @ErrorContent(ex)
+ }
+ }
}
diff --git a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs
index bf527b56bd..760410fa86 100644
--- a/src/Core/Components/DataGrid/FluentDataGrid.razor.cs
+++ b/src/Core/Components/DataGrid/FluentDataGrid.razor.cs
@@ -26,6 +26,7 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve
internal const string EMPTY_CONTENT_ROW_CLASS = "empty-content-row";
internal const string LOADING_CONTENT_ROW_CLASS = "loading-content-row";
+ internal const string ERROR_CONTENT_ROW_CLASS = "error-content-row";
private ElementReference? _gridReference;
private Virtualize<(int, TGridItem)>? _virtualizeComponent;
@@ -44,11 +45,13 @@ public partial class FluentDataGrid : FluentComponentBase, IHandleEve
private readonly RenderFragment _renderNonVirtualizedRows;
private readonly RenderFragment _renderEmptyContent;
private readonly RenderFragment _renderLoadingContent;
+ private readonly RenderFragment _renderErrorContent;
private string? _internalGridTemplateColumns;
private PaginationState? _lastRefreshedPaginationState;
private IQueryable? _lastAssignedItems;
private GridItemsProvider? _lastAssignedItemsProvider;
private CancellationTokenSource? _pendingDataLoadCancellationTokenSource;
+ private Exception? _lastError;
private GridItemsProviderRequest? _lastRequest;
private bool _forceRefreshData;
private readonly EventCallbackSubscriber _currentPageItemsChanged;
@@ -64,11 +67,12 @@ public FluentDataGrid(LibraryConfiguration configuration) : base(configuration)
_renderNonVirtualizedRows = RenderNonVirtualizedRows;
_renderEmptyContent = RenderEmptyContent;
_renderLoadingContent = RenderLoadingContent;
+ _renderErrorContent = RenderErrorContent;
- // As a special case, we don't issue the first data load request until we've collected the initial set of columns
- // This is so we can apply default sort order (or any future per-column options) before loading data
- // We use EventCallbackSubscriber to safely hook this async operation into the synchronous rendering flow
- EventCallbackSubscriber