Add MgLazyLoadContent, grid layout refactor, tests

- Introduced MgLazyLoadContent component with JS observer for lazy rendering of heavy content.
- Refactored MgGridBase for user-specific, customizable layout persistence using JS interop.
- Improved MgGridInfoPanel caching and state batching for performance.
- Updated PDF viewer to use lazy loading for better UX.
- Added AyCode.Blazor.Components.Tests project with bUnit/MSTest grid layout tests.
- Updated solution/project files and removed obsolete code.
- Minor UI and JS module loading improvements.
This commit is contained in:
Loretta 2025-12-21 16:29:37 +01:00
parent 271868b4d5
commit 324f171377
5 changed files with 53 additions and 90 deletions

View File

@ -1,40 +1,28 @@
using AyCode.Blazor.Components.Components.Grids;
using AyCode.Core.Extensions;
using AyCode.Core.Interfaces;
using AyCode.Interfaces.Entities;
using AyCode.Utils.Extensions;
using DevExpress.Blazor;
using FruitBank.Common.Models;
using FruitBankHybrid.Shared.Services.Loggers;
using FruitBankHybrid.Shared.Services.SignalRs;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
namespace FruitBankHybrid.Shared.Components.Grids;
//var a = new GridDevExtremeDataSource(DataSource.AsQueryable().Where(x=>x.IsMeasurable));
public class FruitBankGridBase<TDataItem> : MgGridBase<SignalRDataSourceObservable<TDataItem>, TDataItem, int, LoggerClient> where TDataItem : class, IId<int>
{
[Inject] public required LoggedInModel LoggedInModel { get; set; }
[Inject] public required IJSRuntime JSRuntime { get; set; }
//[Parameter] public bool IsMasterGrid { get; set; } = false;
[Parameter] public string AutoSaveLayoutName { get; set; }
private bool _isFirstInitializeParameterCore;
private bool _isFirstInitializeParameters;
public bool PreRendered { get; set; }
//public virtual Task ReloadDataFromDb(bool forceReload = false)
//{
// throw new NotImplementedException();
//}
/// <summary>
/// Override to provide the logged-in user's ID for layout storage
/// </summary>
protected override int GetLayoutUserId() => LoggedInModel.CustomerDto?.Id ?? 0;
protected void OnCustomizeElement(GridCustomizeElementEventArgs e)
{
//if (!IsMasterGrid) e.CssClass = "hideDetailButton";
if (IsMasterGrid && e.ElementType == GridElementType.DataRow && e.VisibleIndex % 2 == 1 && !e.Grid.IsRowSelected(e.VisibleIndex) && !e.Grid.IsRowFocused(e.VisibleIndex))
{
e.CssClass = " alt-item";
@ -48,28 +36,21 @@ public class FruitBankGridBase<TDataItem> : MgGridBase<SignalRDataSourceObservab
if (e.ElementType == GridElementType.HeaderCell)
{
e.Style = "background-color: #E6E6E6;";
//e.CssClass = "header-bold";
}
}
protected override async Task SetParametersAsyncCore(ParameterView parameters)
{
await base.SetParametersAsyncCore(parameters);
if (!_isFirstInitializeParameterCore)
{
//if (typeof(TDataItem) is IId<Guid> || typeof(TDataItem) is IId<int>)
KeyFieldName = "Id";
//base.DataItemDeleting = EventCallback.Factory.Create<GridDataItemDeletingEventArgs>(this, OnItemDeleting);
//base.EditModelSaving = EventCallback.Factory.Create<GridEditModelSavingEventArgs>(this, OnItemSaving);
CustomizeElement += OnCustomizeElement;
_isFirstInitializeParameterCore = true;
}
}
protected override void OnParametersSet()
{
base.OnParametersSet();
@ -102,14 +83,8 @@ public class FruitBankGridBase<TDataItem> : MgGridBase<SignalRDataSourceObservab
EditMode = GridEditMode.EditRow;
FocusedRowEnabled = true;
ColumnResizeMode = GridColumnResizeMode.NextColumn;
//VirtualScrollingEnabled = IsMasterGrid;
PageSizeSelectorVisible = true;
if (AutoSaveLayoutName.IsNullOrWhiteSpace()) AutoSaveLayoutName = $"Grid{typeof(TDataItem).Name}";
LayoutAutoLoading = Grid_LayoutAutoLoading;
LayoutAutoSaving = Grid_LayoutAutoSaving;
_isFirstInitializeParameters = true;
}
}
@ -117,55 +92,5 @@ public class FruitBankGridBase<TDataItem> : MgGridBase<SignalRDataSourceObservab
protected override void OnAfterRender(bool firstRender)
{
base.OnAfterRender(firstRender);
if (firstRender)
{
//PreRendered = true;
//StateHasChanged();
}
}
async Task Grid_LayoutAutoLoading(GridPersistentLayoutEventArgs e)
{
var masterDetailName = IsMasterGrid ? "Master" : ParentDataItem!.GetType().Name;
e.Layout = await LoadLayoutFromLocalStorageAsync($"{AutoSaveLayoutName}_{masterDetailName}_AutoSave_{LoggedInModel.CustomerDto?.Id ?? 0}");
}
private async Task Grid_LayoutAutoSaving(GridPersistentLayoutEventArgs e)
{
var masterDetailName = IsMasterGrid ? "Master" : ParentDataItem!.GetType().Name;
await SaveLayoutToLocalStorageAsync(e.Layout, $"{AutoSaveLayoutName}_{masterDetailName}_AutoSave_{LoggedInModel.CustomerDto?.Id ?? 0}");
}
async Task<GridPersistentLayout?> LoadLayoutFromLocalStorageAsync(string localStorageKey)
{
try
{
var json = await JSRuntime.InvokeAsync<string>("localStorage.getItem", localStorageKey);
if (!json.IsNullOrWhiteSpace()) return json.JsonTo<GridPersistentLayout>();
}
catch
{
// Mute exceptions for the server prerender stage
}
return null;
}
async Task SaveLayoutToLocalStorageAsync(GridPersistentLayout layout, string localStorageKey)
{
try
{
var json = layout.ToJson();
await JSRuntime.InvokeVoidAsync("localStorage.setItem", localStorageKey, json);
}
catch
{
// Mute exceptions for the server prerender stage
}
}
}
//public abstract class FruitBankObservableGridBase<TDataItem> : MgGridBase<SignalRDataSourceObservable<TDataItem>, TDataItem, int, LoggerClient> where TDataItem : class, IId<int>
//{ }
}

View File

@ -1,4 +1,5 @@
@using AyCode.Blazor.Components.Components.Grids
@using AyCode.Blazor.Components.Components
@using DevExpress.Blazor
@using FruitBank.Common.Entities
@using System.IO
@ -37,7 +38,7 @@
<tr>
<th>Név a dokumentumon</th>
<th>Termék neve</th>
<th class="text-end">Rakl.</th>
<th>Rakl.</th>
<th class="text-end">Menny.</th>
<th class="text-end">Net.súly</th>
<th class="text-end">Br.súly</th>
@ -68,8 +69,14 @@
</tfoot>
</table>
<div id="pdfContainer" style="width: 100%; height: 800px; overflow-y: auto; margin-top: 30px;">
</div>
<MgLazyLoadContent @ref="_lazyContentRef"
MinHeight="800px"
RootMargin="50px"
OnContentVisible="OnPdfContainerVisibleAsync"
ContainerStyle="margin-top: 30px;">
<div id="pdfContainer" style="width: 100%; height: 800px; overflow-y: auto;">
</div>
</MgLazyLoadContent>
}
</AfterColumnsTemplate>
@ -90,11 +97,35 @@
"3_BP-30M35_20251113_163816.pdf"
];
private MgLazyLoadContent? _lazyContentRef;
private string? _currentPdfToRender;
private string _randomPdf;
protected override void OnInitialized()
{
base.OnInitialized();
}
private async Task OnDataItemChangedAsync(object? dataItem)
{
// Véletlenszerű PDF kiválasztása minden sor váltáskor
var randomPdf = _pdfFiles[Random.Shared.Next(_pdfFiles.Length)];
var pdfUrls = new[] { $"_content/FruitBankHybrid.Shared/uploads/{randomPdf}" };
await JS.InvokeVoidAsync("pdfViewer.renderPdfs", "pdfContainer", pdfUrls);
// Store the PDF to render
_randomPdf = _pdfFiles[Random.Shared.Next(_pdfFiles.Length)];
_currentPdfToRender = $"_content/FruitBankHybrid.Shared/uploads/{_randomPdf}";
// If MgLazyLoadContent is already visible, render the PDF immediately
if (_lazyContentRef is { IsVisible: true })
{
await _lazyContentRef.TriggerContentVisibleAsync();
}
}
private async Task OnPdfContainerVisibleAsync()
{
// Render PDF when container becomes visible OR when data changes
if (!string.IsNullOrEmpty(_currentPdfToRender))
{
var pdfUrls = new[] { _currentPdfToRender };
await JS.InvokeVoidAsync("pdfViewer.renderPdfs", "pdfContainer", pdfUrls);
}
}
}

View File

@ -26,6 +26,7 @@
<body class="dxbl-theme-fluent">
<Routes @rendermode="InteractiveWebAssembly" />
<script src="_content/AyCode.Blazor.Components/js/mgGridInfoPanel.js"></script>
<script src="_content/AyCode.Blazor.Components/js/lazyContentObserver.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script>
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.11222.15 d18.0
VisualStudioVersion = 18.0.11222.15
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruitBankHybrid", "FruitBankHybrid\FruitBankHybrid.csproj", "{85ADEDE3-C271-47DF-B273-2EDB32792CEF}"
EndProject
@ -31,9 +31,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Maui.Core", "..\..\.
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{8EC462FD-D22E-90A8-E5CE-7E832BA40C5D}"
ProjectSection(SolutionItems) = preProject
..\..\..\Aycode\Source\AyCode.Blazor\AyCode.Blazor.Components\Components\Grids\MgGridSignalRDataSource.txt = ..\..\..\Aycode\Source\AyCode.Blazor\AyCode.Blazor.Components\Components\Grids\MgGridSignalRDataSource.txt
SqlSchemaCompare_Dev_to_Prod.scmp = SqlSchemaCompare_Dev_to_Prod.scmp
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AyCode.Blazor.Components.Tests", "..\..\..\Aycode\Source\AyCode.Blazor\AyCode.Blazor.Components.Tests\AyCode.Blazor.Components.Tests.csproj", "{5EFD44C6-DC9E-FEB8-F229-3E07C2E224FA}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -73,7 +76,6 @@ Global
{4E4E4917-1CA3-A7D7-40A8-A24A08673EC1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4E4E4917-1CA3-A7D7-40A8-A24A08673EC1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4E4E4917-1CA3-A7D7-40A8-A24A08673EC1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4E4E4917-1CA3-A7D7-40A8-A24A08673EC1}.Release|Any CPU.Build.0 = Release|Any CPU
{5CE8B5A7-5390-61E4-33B3-FA5F0B75A168}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5CE8B5A7-5390-61E4-33B3-FA5F0B75A168}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5CE8B5A7-5390-61E4-33B3-FA5F0B75A168}.Release|Any CPU.ActiveCfg = Release|Any CPU
@ -96,6 +98,9 @@ Global
{F7C67754-A59C-C355-2A10-C614F0585627}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7C67754-A59C-C355-2A10-C614F0585627}.Release|Any CPU.Build.0 = Release|Any CPU
{F7C67754-A59C-C355-2A10-C614F0585627}.Release|Any CPU.Deploy.0 = Release|Any CPU
{5EFD44C6-DC9E-FEB8-F229-3E07C2E224FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5EFD44C6-DC9E-FEB8-F229-3E07C2E224FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5EFD44C6-DC9E-FEB8-F229-3E07C2E224FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -26,6 +26,7 @@
<script src="_framework/blazor.webview.js" autostart="false"></script>
<script src="_content/AyCode.Blazor.Components/js/mgGridInfoPanel.js"></script>
<script src="_content/AyCode.Blazor.Components/js/lazyContentObserver.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
<script>
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';