55/// </summary>
66public partial class BitBasicList < TItem > : BitComponentBase
77{
8+ private int _loadMoreSkip = 0 ;
9+ private bool _loadMoreFinished ;
10+ private ICollection < TItem > _viewItems = [ ] ;
11+ private CancellationTokenSource ? _globalCts ;
12+ private ICollection < TItem > ? _internalItems = null ;
813 private _BitBasicListVirtualize < TItem > ? _bitBasicListVirtualizeRef ;
14+ private BitBasicListItemsProvider < TItem > ? _internalItemsProvider = null ;
915
1016
1117
12- /// <summary>
13- /// The custom content that gets rendered when there is no item to show.
14- /// </summary>
15- [ Parameter ] public RenderFragment ? EmptyContent { get ; set ; }
18+ private bool _isLoadingMore => _globalCts is not null ;
19+
20+
1621
1722 /// <summary>
18- /// Enables virtualization in rendering the list .
23+ /// The custom content that will be rendered when there is no item to show .
1924 /// </summary>
20- [ Parameter ] public bool EnableVirtualization { get ; set ; }
25+ [ Parameter ] public RenderFragment ? EmptyContent { get ; set ; }
2126
2227 /// <summary>
2328 /// Sets the height of the list to fit its content.
@@ -56,44 +61,72 @@ public partial class BitBasicList<TItem> : BitComponentBase
5661 public bool FullWidth { get ; set ; }
5762
5863 /// <summary>
59- /// Gets or sets the list of items to render.
64+ /// The list of items to render.
6065 /// </summary>
61- [ Parameter ] public ICollection < TItem > Items { get ; set ; } = Array . Empty < TItem > ( ) ;
66+ [ Parameter ] public ICollection < TItem > ? Items { get ; set ; }
6267
6368 /// <summary>
64- /// Gets the size of each item in pixels. Defaults to 50px.
69+ /// Size of each item in pixels. Defaults to 50px.
6570 /// </summary>
6671 [ Parameter ] public float ItemSize { get ; set ; } = 50f ;
6772
6873 /// <summary>
69- /// Gets or sets a value that determines how many additional items will be rendered before and after the visible region.
74+ /// The function providing items to the list.
75+ /// </summary>
76+ [ Parameter ] public BitBasicListItemsProvider < TItem > ? ItemsProvider { get ; set ; }
77+
78+ /// <summary>
79+ /// Enables the LoadMore mode for the list.
80+ /// </summary>
81+ [ Parameter ] public bool LoadMore { get ; set ; }
82+
83+ /// <summary>
84+ /// The number of items to be loaded and rendered after the LoadMore button is clicked. Defaults to 20.
85+ /// </summary>
86+ [ Parameter ] public int LoadMoreSize { get ; set ; } = 20 ;
87+
88+ /// <summary>
89+ /// The template of the LoadMore button.
90+ /// </summary>
91+ [ Parameter ] public RenderFragment < bool > ? LoadMoreTemplate { get ; set ; }
92+
93+ /// <summary>
94+ /// The custom text of the default LoadMore button. Defaults to "LoadMore".
95+ /// </summary>
96+ [ Parameter ] public string ? LoadMoreText { get ; set ; } = "LoadMore" ;
97+
98+ /// <summary>
99+ /// A value that determines how many additional items will be rendered before and after the visible region in Virtualize mode.
70100 /// </summary>
71101 [ Parameter ] public int OverscanCount { get ; set ; } = 3 ;
72102
73103 /// <summary>
74- /// Gets or set the role attribute of the BasicList html element.
104+ /// The role attribute of the html element of the list .
75105 /// </summary>
76106 [ Parameter ] public string Role { get ; set ; } = "list" ;
77107
78108 /// <summary>
79- /// Gets or sets the Template to render each row.
109+ /// The template to render each row.
80110 /// </summary>
81- [ Parameter ] public RenderFragment < TItem > RowTemplate { get ; set ; } = default ! ;
111+ [ Parameter ] public RenderFragment < TItem > ? RowTemplate { get ; set ; }
82112
83113 /// <summary>
84- /// The function providing items to the list
114+ /// Enables virtualization in rendering the list.
85115 /// </summary>
86- [ Parameter ] public BitBasicListItemsProvider < TItem > ? ItemsProvider { get ; set ; }
116+ [ Parameter ] public bool Virtualize { get ; set ; }
87117
88118 /// <summary>
89- /// The template for items that have not yet been loaded in memory .
119+ /// The template for items that have not yet rendered .
90120 /// </summary>
91121 [ Parameter ] public RenderFragment < PlaceholderContext > ? VirtualizePlaceholder { get ; set ; }
92122
93123
94124
95125 public async Task RefreshDataAsync ( )
96126 {
127+ _globalCts ? . Cancel ( ) ;
128+ _globalCts = null ;
129+
97130 if ( ItemsProvider is null ) return ;
98131 if ( _bitBasicListVirtualizeRef is null ) return ;
99132
@@ -114,8 +147,91 @@ protected override void RegisterCssStyles()
114147 StyleBuilder . Register ( ( ) => ( FitSize || FitHeight ) ? "height:fit-content" : string . Empty ) ;
115148 }
116149
150+ protected override async Task OnParametersSetAsync ( )
151+ {
152+ if ( _internalItems != Items )
153+ {
154+ _internalItems = Items ;
155+
156+ if ( ItemsProvider is null ) // ItemsProvider always has priority over Items
157+ {
158+ _viewItems = Items ?? [ ] ;
159+
160+ if ( LoadMore )
161+ {
162+ await PerformLoadMore ( true ) ;
163+ }
164+ }
165+ }
166+
167+ if ( _internalItemsProvider != ItemsProvider )
168+ {
169+ _internalItemsProvider = ItemsProvider ;
170+
171+ if ( LoadMore && ItemsProvider is not null )
172+ {
173+ await PerformLoadMore ( true ) ;
174+ }
175+ }
176+
177+ await base . OnParametersSetAsync ( ) ;
178+ }
179+
180+
181+ private async Task PerformLoadMore ( bool reset )
182+ {
183+ if ( reset )
184+ {
185+ _viewItems = [ ] ;
186+ _loadMoreSkip = 0 ;
187+ _loadMoreFinished = false ;
188+ }
189+
190+ if ( LoadMore is false || _globalCts is not null ) return ;
191+
192+ var localCts = new CancellationTokenSource ( ) ;
193+ _globalCts = localCts ;
194+
195+ try
196+ {
197+ StateHasChanged ( ) ;
198+
199+ try
200+ {
201+ if ( ItemsProvider is null )
202+ {
203+ var items = Items ?? [ ] ;
204+
205+ _viewItems = [ .. _viewItems , .. items . Skip ( _loadMoreSkip ) . Take ( LoadMoreSize ) ] ;
206+
207+ _loadMoreFinished = _viewItems . Count >= items . Count ;
208+ }
209+ else
210+ {
211+ var result = await ProvideVirtualizedItems ( new ( _loadMoreSkip , LoadMoreSize , localCts . Token ) ) ;
212+
213+ if ( localCts . IsCancellationRequested is false )
214+ {
215+ _viewItems = [ .. _viewItems , .. result . Items ] ;
216+
217+ //_loadMoreFinished = _viewItems.Count >= result.TotalItemCount; // for performance purposes we won't use TotalItemCount here!
218+ _loadMoreFinished = result . Items . Any ( ) is false ;
219+ }
220+ }
221+
222+ _loadMoreSkip += LoadMoreSize ;
223+ }
224+ catch ( OperationCanceledException oce ) when ( oce . CancellationToken == localCts . Token ) { }
225+ }
226+ finally
227+ {
228+ _globalCts = null ;
229+ localCts . Dispose ( ) ;
230+ }
231+
232+ StateHasChanged ( ) ;
233+ }
117234
118- // Gets called both by RefreshDataCoreAsync and directly by the Virtualize child component during scrolling
119235 private async ValueTask < ItemsProviderResult < TItem > > ProvideVirtualizedItems ( ItemsProviderRequest request )
120236 {
121237 if ( ItemsProvider is null ) return default ;
@@ -133,4 +249,19 @@ private async ValueTask<ItemsProviderResult<TItem>> ProvideVirtualizedItems(Item
133249
134250 return new ItemsProviderResult < TItem > ( providerResult . Items , providerResult . TotalItemCount ) ;
135251 }
252+
253+
254+
255+ protected override async ValueTask DisposeAsync ( bool disposing )
256+ {
257+ if ( IsDisposed || disposing is false ) return ;
258+
259+ if ( _globalCts is not null )
260+ {
261+ _globalCts . Dispose ( ) ;
262+ _globalCts = null ;
263+ }
264+
265+ await base . DisposeAsync ( disposing ) ;
266+ }
136267}
0 commit comments