Trial - boosting prospect grid loading performance

This commit is contained in:
troogs
2026-09-20 11:09:44 +02:00
parent 8b298b37c7
commit b33f9f6c52
15 changed files with 688 additions and 73 deletions
@@ -97,7 +97,9 @@ namespace FoodsharingSiegen.Server.Controls
/// <summary>
/// Gets the value of the interactions (ab)
/// </summary>
private List<Interaction> Interactions => Prospect?.Interactions.Where(x => x.Type == Type).ToList() ?? [];
private List<Interaction> Interactions => Prospect?.Interactions is null
? []
: [.. Prospect.Interactions.Where(x => x.Type == Type)];
/// <summary>
/// Gets the value of the not needed (ab)
@@ -1,4 +1,7 @@
@using FoodsharingSiegen.Contracts.Enums
@using Microsoft.JSInterop
@implements IAsyncDisposable
@inject IJSRuntime JSRuntime
@code {
[Parameter] public List<Prospect>? Prospects { get; set; }
@@ -7,19 +10,95 @@
[Parameter] public Func<Task>? OnDataChanged { get; set; }
[Parameter] public EventCallback OnLoadMore { get; set; }
[Parameter] public bool IsLoadingMore { get; set; }
[Parameter] public bool HasMore { get; set; }
[Parameter] public string GridClass { get; set; } = string.Empty;
private IJSObjectReference? _module;
private DotNetObjectReference<ProspectGrid>? _dotNetHelper;
private string _gridId = $"prospect-grid-{Guid.NewGuid():N}";
private bool _isJsBound;
private bool _disposed;
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (_disposed || !firstRender || _isJsBound) return;
try
{
_dotNetHelper ??= DotNetObjectReference.Create(this);
_module ??= await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./js/prospect-grid.js");
await _module.InvokeVoidAsync("attachScrollLoader", _gridId, _dotNetHelper);
_isJsBound = true;
}
catch (JSDisconnectedException)
{
// Ignore browser disconnect during teardown.
}
catch (ObjectDisposedException)
{
// Ignore late render after the component is being torn down.
}
}
[JSInvokable]
public async Task OnScrollNearBottom()
{
if (OnLoadMore.HasDelegate && !IsLoadingMore && HasMore)
{
await OnLoadMore.InvokeAsync();
}
}
public async ValueTask DisposeAsync()
{
_disposed = true;
if (_module is not null)
{
try
{
await _module.InvokeVoidAsync("disposeScrollLoader", _gridId);
}
catch (JSDisconnectedException)
{
// ignored; the circuit is already disconnected
}
catch (ObjectDisposedException)
{
// ignored; the JS runtime is already disposed
}
}
_dotNetHelper?.Dispose();
_dotNetHelper = null;
_isJsBound = false;
}
}
<h6>@(Prospects?.Count ?? 0) Ergebnisse</h6>
@if (Prospects?.Any() == true)
{
<div class="prospect-grid @GridClass">
<Repeater Items="@Prospects">
<ProspectContainer
Prospect="context"
<div id="@_gridId" class="prospect-grid @GridClass" style="max-height: calc(100vh - 250px); overflow-y: auto;">
@foreach (var prospect in Prospects)
{
<ProspectContainer @key="prospect.Id"
Prospect="prospect"
OnDataChanged="@OnDataChanged"
StateFilter="StateFilter"></ProspectContainer>
</Repeater>
}
@if (IsLoadingMore)
{
<div class="d-flex justify-content-center py-3 text-muted">
<span class="me-2"><i class="fa-solid fa-spinner fa-spin"></i></span>
Weitere laden...
</div>
}
</div>
}
@@ -115,18 +115,24 @@ namespace FoodsharingSiegen.Server.Data.Service
.Include(x => x.Images)
.Include(x => x.Interactions.OrderBy(i => i.Date)).ThenInclude(x => x.User)
.OrderBy(x => x.Name).AsQueryable();
if(parameter.MustHaveInteractions != null && parameter.MustHaveInteractions.Any())
if (parameter.MustHaveInteractions != null && parameter.MustHaveInteractions.Any())
prospectsQuery = prospectsQuery.Where(x => x.Interactions.Any(i => parameter.MustHaveInteractions.Contains(i.Type)));
if(parameter.CannotHaveInteractions != null && parameter.CannotHaveInteractions.Any())
if (parameter.CannotHaveInteractions != null && parameter.CannotHaveInteractions.Any())
prospectsQuery = prospectsQuery.Where(x => x.Interactions.All(i => !parameter.CannotHaveInteractions.Contains(i.Type)));
if (!parameter.IncludeDeleted)
if (!parameter.IncludeDeleted)
prospectsQuery = prospectsQuery.Where(x => x.RecordState != RecordState.Archived);
if (parameter.Skip > 0)
prospectsQuery = prospectsQuery.Skip(parameter.Skip);
if (parameter.Take > 0)
prospectsQuery = prospectsQuery.Take(parameter.Take);
var prospects = await prospectsQuery.ToListAsync();
return new(prospects);
}
catch (Exception e)
+20 -8
View File
@@ -29,13 +29,25 @@
<ProspectSortControl @bind-CurrentSort="CurrentSort" StorageKey="@StorageKeys.SortProspects" OnSortChanged="StateHasChanged" Filter="Filter" FilterChanged="FilterChangedAsync" StateFilter="ProspectStateFilter.OnBoarding" />
@{
var filterList = ProspectList.ApplyFilter(Filter);
var sortList = filterList.ApplySort(CurrentSort);
var filtered = ProspectList.ApplyFilter(Filter);
var sortList = filtered.ApplySort(CurrentSort);
}
<ProspectGrid
Prospects="sortList"
OnDataChanged="@LoadProspects"
StateFilter="ProspectStateFilter.OnBoarding">
</ProspectGrid>
@if (IsLoadingProspects)
{
<div class="d-flex justify-content-center align-items-center py-5 text-muted">
<span class="me-2"><i class="fa-solid fa-spinner fa-spin"></i></span>
Lade Einarbeitungen...
</div>
}
else
{
<ProspectGrid
Prospects="sortList"
OnDataChanged="@LoadProspects"
OnLoadMore="@LoadMoreProspects"
IsLoadingMore="IsLoadingMoreProspects"
HasMore="HasMoreProspects"
StateFilter="ProspectStateFilter.OnBoarding">
</ProspectGrid>
}
@@ -3,6 +3,7 @@ using FoodsharingSiegen.Contracts.Entity;
using FoodsharingSiegen.Contracts.Enums;
using FoodsharingSiegen.Contracts.Model;
using FoodsharingSiegen.Server.Data.Service;
using FoodsharingSiegen.Shared.Helper;
using FoodsharingSiegen.Server.Dialogs;
using FoodsharingSiegen.Server.Service;
using Microsoft.AspNetCore.Components;
@@ -34,7 +35,17 @@ namespace FoodsharingSiegen.Server.Pages
/// <summary>
/// Gets or sets the value of the prospect list (ab)
/// </summary>
private List<Prospect>? ProspectList { get; set; }
private List<Prospect> ProspectList { get; set; } = [];
private bool IsLoadingProspects { get; set; }
private bool IsLoadingMoreProspects { get; set; }
private bool HasMoreProspects { get; set; }
private int CurrentPageSize { get; set; } = 50;
private int CurrentSkip { get; set; }
private ProspectSortOption CurrentSort { get; set; } = ProspectSortOption.NameAscending;
@@ -89,6 +100,9 @@ namespace FoodsharingSiegen.Server.Pages
{
Filter = arg;
await LocalStorageService.SetItem(StorageKeys.ProspectFilter, Filter);
CurrentSkip = 0;
ProspectList = [];
await LoadProspects();
}
#endregion
@@ -98,17 +112,68 @@ namespace FoodsharingSiegen.Server.Pages
/// <summary>
/// Loads the prospects (a. beging, 11.04.2022)
/// </summary>
private bool HasActiveFilter()
{
return !string.IsNullOrWhiteSpace(Filter.Text)
|| Filter.WithoutStepInBriefing
|| Filter.WithoutIdCheck
|| Filter.DeletedOnly
|| Filter.IdCheckPossible
|| Filter.NoActivity
|| Filter.RecentActivity;
}
private async Task LoadProspects()
{
if (IsLoadingProspects || IsLoadingMoreProspects) return;
try
{
var parameter = new GetProspectsParameter
if (CurrentSkip == 0)
{
CannotHaveInteractions = [InteractionType.Complete, InteractionType.Verify, InteractionType.ReleasedForVerification]
};
IsLoadingProspects = true;
}
else
{
IsLoadingMoreProspects = true;
}
var prospectsR = await ProspectService.GetProspectsAsync(parameter);
if (prospectsR.Success) ProspectList = prospectsR.Data;
await InvokeAsync(StateHasChanged);
while (true)
{
var parameter = new GetProspectsParameter
{
Skip = CurrentSkip,
Take = CurrentPageSize,
CannotHaveInteractions = [InteractionType.Complete, InteractionType.Verify, InteractionType.ReleasedForVerification]
};
var prospectsR = await ProspectService.GetProspectsAsync(parameter);
if (!prospectsR.Success)
{
break;
}
var loadedProspects = prospectsR.Data ?? [];
if (CurrentSkip == 0)
{
ProspectList = loadedProspects;
}
else
{
ProspectList = [.. ProspectList, .. loadedProspects];
}
CurrentSkip += loadedProspects.Count;
HasMoreProspects = loadedProspects.Count == CurrentPageSize;
var filteredCount = ProspectList.ApplyFilter(Filter).Count;
if (!HasActiveFilter() || filteredCount > 0 || !HasMoreProspects || loadedProspects.Count == 0)
{
break;
}
}
await InvokeAsync(StateHasChanged);
}
@@ -116,6 +181,18 @@ namespace FoodsharingSiegen.Server.Pages
{
await Notification.Error(ex.Message, "Fehler beim Laden");
}
finally
{
IsLoadingProspects = false;
IsLoadingMoreProspects = false;
await InvokeAsync(StateHasChanged);
}
}
private async Task LoadMoreProspects()
{
if (IsLoadingProspects || IsLoadingMoreProspects || !HasMoreProspects) return;
await LoadProspects();
}
#endregion
@@ -17,12 +17,25 @@
<ProspectSortControl @bind-CurrentSort="CurrentSort" StorageKey="@StorageKeys.SortProspectsAll" OnSortChanged="StateHasChanged" Filter="Filter" FilterChanged="FilterChangedAsync" StateFilter="ProspectStateFilter.All" />
@{
var filterList = ProspectList.ApplyFilter(Filter);
var sortList = filterList.ApplySort(CurrentSort);
var filtered = ProspectList.ApplyFilter(Filter);
var sortList = filtered.ApplySort(CurrentSort);
}
<ProspectGrid
Prospects="sortList"
OnDataChanged="@LoadProspects"
StateFilter="ProspectStateFilter.All">
</ProspectGrid>
@if (IsLoadingProspects)
{
<div class="d-flex justify-content-center align-items-center py-5 text-muted">
<span class="me-2"><i class="fa-solid fa-spinner fa-spin"></i></span>
Lade Einarbeitungen...
</div>
}
else
{
<ProspectGrid
Prospects="sortList"
OnDataChanged="@LoadProspects"
OnLoadMore="@LoadMoreProspects"
IsLoadingMore="IsLoadingMoreProspects"
HasMore="HasMoreProspects"
StateFilter="ProspectStateFilter.All">
</ProspectGrid>
}
@@ -5,6 +5,7 @@ using FoodsharingSiegen.Contracts.Helper;
using FoodsharingSiegen.Contracts.Model;
using FoodsharingSiegen.Server.Data.Service;
using FoodsharingSiegen.Server.Dialogs;
using FoodsharingSiegen.Shared.Helper;
using Microsoft.AspNetCore.Components;
namespace FoodsharingSiegen.Server.Pages
@@ -37,7 +38,17 @@ namespace FoodsharingSiegen.Server.Pages
/// <summary>
/// Gets or sets the value of the prospect list (ab)
/// </summary>
private List<Prospect>? ProspectList { get; set; }
private List<Prospect> ProspectList { get; set; } = [];
private bool IsLoadingProspects { get; set; }
private bool IsLoadingMoreProspects { get; set; }
private bool HasMoreProspects { get; set; }
private int CurrentPageSize { get; set; } = 50;
private int CurrentSkip { get; set; }
private ProspectSortOption CurrentSort { get; set; } = ProspectSortOption.NameAscending;
@@ -71,6 +82,9 @@ namespace FoodsharingSiegen.Server.Pages
{
Filter = arg;
await LocalStorageService.SetItem(StorageKeys.ProspectFilter, Filter);
CurrentSkip = 0;
ProspectList = [];
await LoadProspects();
}
#endregion
@@ -80,17 +94,83 @@ namespace FoodsharingSiegen.Server.Pages
/// <summary>
/// Loads the prospects (a. beging, 11.04.2022)
/// </summary>
private bool HasActiveFilter()
{
return !string.IsNullOrWhiteSpace(Filter.Text)
|| Filter.WithoutStepInBriefing
|| Filter.WithoutIdCheck
|| Filter.DeletedOnly
|| Filter.IdCheckPossible
|| Filter.NoActivity
|| Filter.RecentActivity;
}
private async Task LoadProspects()
{
var parameter = new GetProspectsParameter
{
IncludeDeleted = true
};
var prospectsR = await ProspectService.GetProspectsAsync(parameter);
if (prospectsR.Success) ProspectList = prospectsR.Data;
if (IsLoadingProspects || IsLoadingMoreProspects) return;
await InvokeAsync(StateHasChanged);
try
{
if (CurrentSkip == 0)
{
IsLoadingProspects = true;
}
else
{
IsLoadingMoreProspects = true;
}
await InvokeAsync(StateHasChanged);
while (true)
{
var parameter = new GetProspectsParameter
{
Skip = CurrentSkip,
Take = CurrentPageSize,
IncludeDeleted = true
};
var prospectsR = await ProspectService.GetProspectsAsync(parameter);
if (!prospectsR.Success)
{
break;
}
var loadedProspects = prospectsR.Data ?? [];
if (CurrentSkip == 0)
{
ProspectList = loadedProspects;
}
else
{
ProspectList = [.. ProspectList, .. loadedProspects];
}
CurrentSkip += loadedProspects.Count;
HasMoreProspects = loadedProspects.Count == CurrentPageSize;
var filteredCount = ProspectList.ApplyFilter(Filter).Count;
if (!HasActiveFilter() || filteredCount > 0 || !HasMoreProspects || loadedProspects.Count == 0)
{
break;
}
}
await InvokeAsync(StateHasChanged);
}
finally
{
IsLoadingProspects = false;
IsLoadingMoreProspects = false;
await InvokeAsync(StateHasChanged);
}
}
private async Task LoadMoreProspects()
{
if (IsLoadingProspects || IsLoadingMoreProspects || !HasMoreProspects) return;
await LoadProspects();
}
#endregion
@@ -16,12 +16,25 @@
<ProspectSortControl @bind-CurrentSort="CurrentSort" StorageKey="@StorageKeys.SortProspectsDone" OnSortChanged="StateHasChanged" Filter="Filter" FilterChanged="FilterChangedAsync" StateFilter="ProspectStateFilter.Completed" />
@{
var filterList = ProspectList.ApplyFilter(Filter);
var sortList = filterList.ApplySort(CurrentSort);
var filtered = ProspectList.ApplyFilter(Filter);
var sortList = filtered.ApplySort(CurrentSort);
}
<ProspectGrid
Prospects="sortList"
OnDataChanged="@LoadProspects"
StateFilter="ProspectStateFilter.Completed">
</ProspectGrid>
@if (IsLoadingProspects)
{
<div class="d-flex justify-content-center align-items-center py-5 text-muted">
<span class="me-2"><i class="fa-solid fa-spinner fa-spin"></i></span>
Lade Einarbeitungen...
</div>
}
else
{
<ProspectGrid
Prospects="sortList"
OnDataChanged="@LoadProspects"
OnLoadMore="@LoadMoreProspects"
IsLoadingMore="IsLoadingMoreProspects"
HasMore="HasMoreProspects"
StateFilter="ProspectStateFilter.Completed">
</ProspectGrid>
}
@@ -3,6 +3,7 @@ using FoodsharingSiegen.Contracts.Entity;
using FoodsharingSiegen.Contracts.Enums;
using FoodsharingSiegen.Contracts.Model;
using FoodsharingSiegen.Server.Data.Service;
using FoodsharingSiegen.Shared.Helper;
using Microsoft.AspNetCore.Components;
namespace FoodsharingSiegen.Server.Pages
@@ -29,7 +30,17 @@ namespace FoodsharingSiegen.Server.Pages
/// <summary>
/// Gets or sets the value of the prospect list (ab)
/// </summary>
private List<Prospect>? ProspectList { get; set; }
private List<Prospect> ProspectList { get; set; } = [];
private bool IsLoadingProspects { get; set; }
private bool IsLoadingMoreProspects { get; set; }
private bool HasMoreProspects { get; set; }
private int CurrentPageSize { get; set; } = 50;
private int CurrentSkip { get; set; }
private ProspectSortOption CurrentSort { get; set; } = ProspectSortOption.NameAscending;
@@ -60,6 +71,9 @@ namespace FoodsharingSiegen.Server.Pages
{
Filter = arg;
await LocalStorageService.SetItem(StorageKeys.ProspectFilter, Filter);
CurrentSkip = 0;
ProspectList = [];
await LoadProspects();
}
#endregion
@@ -69,13 +83,82 @@ namespace FoodsharingSiegen.Server.Pages
/// <summary>
/// Loads the prospects (a. beging, 11.04.2022)
/// </summary>
private bool HasActiveFilter()
{
return !string.IsNullOrWhiteSpace(Filter.Text)
|| Filter.WithoutStepInBriefing
|| Filter.WithoutIdCheck
|| Filter.DeletedOnly
|| Filter.IdCheckPossible
|| Filter.NoActivity
|| Filter.RecentActivity;
}
private async Task LoadProspects()
{
var parameter = new GetProspectsParameter { MustHaveInteractions = [InteractionType.Complete] };
var prospectsR = await ProspectService.GetProspectsAsync(parameter);
if (prospectsR.Success) ProspectList = prospectsR.Data;
if (IsLoadingProspects || IsLoadingMoreProspects) return;
await InvokeAsync(StateHasChanged);
try
{
if (CurrentSkip == 0)
{
IsLoadingProspects = true;
}
else
{
IsLoadingMoreProspects = true;
}
await InvokeAsync(StateHasChanged);
while (true)
{
var parameter = new GetProspectsParameter
{
Skip = CurrentSkip,
Take = CurrentPageSize,
MustHaveInteractions = [InteractionType.Complete]
};
var prospectsR = await ProspectService.GetProspectsAsync(parameter);
if (!prospectsR.Success)
{
break;
}
var loadedProspects = prospectsR.Data ?? [];
if (CurrentSkip == 0)
{
ProspectList = loadedProspects;
}
else
{
ProspectList = [.. ProspectList, .. loadedProspects];
}
CurrentSkip += loadedProspects.Count;
HasMoreProspects = loadedProspects.Count == CurrentPageSize;
var filteredCount = ProspectList.ApplyFilter(Filter).Count;
if (!HasActiveFilter() || filteredCount > 0 || !HasMoreProspects || loadedProspects.Count == 0)
{
break;
}
}
await InvokeAsync(StateHasChanged);
}
finally
{
IsLoadingProspects = false;
IsLoadingMoreProspects = false;
await InvokeAsync(StateHasChanged);
}
}
private async Task LoadMoreProspects()
{
if (IsLoadingProspects || IsLoadingMoreProspects || !HasMoreProspects) return;
await LoadProspects();
}
#endregion
@@ -16,13 +16,25 @@
<ProspectSortControl @bind-CurrentSort="CurrentSort" StorageKey="@StorageKeys.SortProspectsVerify" OnSortChanged="StateHasChanged" Filter="Filter" FilterChanged="FilterChangedAsync" StateFilter="ProspectStateFilter.Verification" />
@{
var filterList = ProspectList.ApplyFilter(Filter);
var sortList = filterList.ApplySort(CurrentSort);
var filtered = ProspectList.ApplyFilter(Filter);
var sortList = filtered.ApplySort(CurrentSort);
}
<ProspectGrid
Prospects="sortList"
OnDataChanged="@LoadProspects"
StateFilter="ProspectStateFilter.Verification">
</ProspectGrid>
@if (IsLoadingProspects)
{
<div class="d-flex justify-content-center align-items-center py-5 text-muted">
<span class="me-2"><i class="fa-solid fa-spinner fa-spin"></i></span>
Lade Einarbeitungen...
</div>
}
else
{
<ProspectGrid
Prospects="sortList"
OnDataChanged="@LoadProspects"
OnLoadMore="@LoadMoreProspects"
IsLoadingMore="IsLoadingMoreProspects"
HasMore="HasMoreProspects"
StateFilter="ProspectStateFilter.Verification">
</ProspectGrid>
}
@@ -3,6 +3,7 @@ using FoodsharingSiegen.Contracts.Entity;
using FoodsharingSiegen.Contracts.Enums;
using FoodsharingSiegen.Contracts.Model;
using FoodsharingSiegen.Server.Data.Service;
using FoodsharingSiegen.Shared.Helper;
using Microsoft.AspNetCore.Components;
namespace FoodsharingSiegen.Server.Pages
@@ -35,7 +36,17 @@ namespace FoodsharingSiegen.Server.Pages
/// <summary>
/// Gets or sets the value of the prospect list (ab)
/// </summary>
private List<Prospect>? ProspectList { get; set; }
private List<Prospect> ProspectList { get; set; } = [];
private bool IsLoadingProspects { get; set; }
private bool IsLoadingMoreProspects { get; set; }
private bool HasMoreProspects { get; set; }
private int CurrentPageSize { get; set; } = 50;
private int CurrentSkip { get; set; }
private ProspectSortOption CurrentSort { get; set; } = ProspectSortOption.NameAscending;
@@ -67,6 +78,9 @@ namespace FoodsharingSiegen.Server.Pages
{
Filter = arg;
await LocalStorageService.SetItem(StorageKeys.ProspectFilter, Filter);
CurrentSkip = 0;
ProspectList = [];
await LoadProspects();
}
#endregion
@@ -76,17 +90,83 @@ namespace FoodsharingSiegen.Server.Pages
/// <summary>
/// Loads the prospects (a. beging, 11.04.2022)
/// </summary>
private bool HasActiveFilter()
{
return !string.IsNullOrWhiteSpace(Filter.Text)
|| Filter.WithoutStepInBriefing
|| Filter.WithoutIdCheck
|| Filter.DeletedOnly
|| Filter.IdCheckPossible
|| Filter.NoActivity
|| Filter.RecentActivity;
}
private async Task LoadProspects()
{
var parameter = new GetProspectsParameter
{
CannotHaveInteractions = [InteractionType.Complete],
MustHaveInteractions = [InteractionType.ReleasedForVerification]
};
var prospectsR = await ProspectService.GetProspectsAsync(parameter);
if (prospectsR.Success) ProspectList = prospectsR.Data;
if (IsLoadingProspects || IsLoadingMoreProspects) return;
await InvokeAsync(StateHasChanged);
try
{
if (CurrentSkip == 0)
{
IsLoadingProspects = true;
}
else
{
IsLoadingMoreProspects = true;
}
await InvokeAsync(StateHasChanged);
while (true)
{
var parameter = new GetProspectsParameter
{
Skip = CurrentSkip,
Take = CurrentPageSize,
CannotHaveInteractions = [InteractionType.Complete],
MustHaveInteractions = [InteractionType.ReleasedForVerification]
};
var prospectsR = await ProspectService.GetProspectsAsync(parameter);
if (!prospectsR.Success)
{
break;
}
var loadedProspects = prospectsR.Data ?? [];
if (CurrentSkip == 0)
{
ProspectList = loadedProspects;
}
else
{
ProspectList = [.. ProspectList, .. loadedProspects];
}
CurrentSkip += loadedProspects.Count;
HasMoreProspects = loadedProspects.Count == CurrentPageSize;
var filteredCount = ProspectList.ApplyFilter(Filter).Count;
if (!HasActiveFilter() || filteredCount > 0 || !HasMoreProspects || loadedProspects.Count == 0)
{
break;
}
}
await InvokeAsync(StateHasChanged);
}
finally
{
IsLoadingProspects = false;
IsLoadingMoreProspects = false;
await InvokeAsync(StateHasChanged);
}
}
private async Task LoadMoreProspects()
{
if (IsLoadingProspects || IsLoadingMoreProspects || !HasMoreProspects) return;
await LoadProspects();
}
#endregion
@@ -0,0 +1,23 @@
export function attachScrollLoader(gridId, component) {
const grid = document.getElementById(gridId);
if (!grid) return;
const checkScroll = () => {
const distanceFromBottom = grid.scrollHeight - (grid.scrollTop + grid.clientHeight);
if (distanceFromBottom < 250) {
component.invokeMethodAsync('OnScrollNearBottom');
}
};
grid.addEventListener('scroll', checkScroll, { passive: true });
grid.__prospectScrollCheck = checkScroll;
checkScroll();
}
export function disposeScrollLoader(gridId) {
const grid = document.getElementById(gridId);
if (!grid || !grid.__prospectScrollCheck) return;
grid.removeEventListener('scroll', grid.__prospectScrollCheck);
delete grid.__prospectScrollCheck;
}