-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Virtualization support #24179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Virtualization support #24179
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
0d90cbb
Basic virtualization prototype.
MackinnonBuck 8d16b79
Started on asynchronous data fetching
MackinnonBuck 202bf61
Restructured + infinite scroll
MackinnonBuck 9d668c5
Fixed rebase issues.
MackinnonBuck bed2a6b
Implemented alternate approach suggested in CR.
MackinnonBuck 1b76cff
Improved design which now supports fixed lists of fetched items
MackinnonBuck dbd87e7
Support for bubbling exceptions thrown in ItemsProvider.
MackinnonBuck 25059f9
Small improvements
MackinnonBuck 93fdd29
Merge branch 'release/5.0-preview8' of https://github.com/dotnet/aspn…
MackinnonBuck 03d97a4
Added CancellationToken parameter to ItemsProviderDelegate.
MackinnonBuck 6c2b79d
Combined VirtualizeDeferred and VirtualizeFixed, addressed CR feedback
MackinnonBuck 8d0a884
Updated Virtualize to not cache items.
MackinnonBuck 1aedbe0
Updated exception message.
MackinnonBuck efec547
Fixed inaccurate comments.
MackinnonBuck 978562f
CR feedback.
MackinnonBuck 02a03f4
Added E2E tests.
MackinnonBuck 532c22c
Started on unit tests.
MackinnonBuck f2791a0
Added unit tests.
MackinnonBuck 2bd0acd
Merge branch 'master' of https://github.com/dotnet/aspnetcore into t-…
MackinnonBuck 3548a98
Fixed async tests.
MackinnonBuck 99964b9
Merge branch 'master' of https://github.com/dotnet/aspnetcore into t-…
MackinnonBuck 98b6aa6
Addressed API review feedback.
MackinnonBuck 57d8416
Update VirtualizationComponent.razor
MackinnonBuck c091b1e
Changed the item template to require a key
MackinnonBuck File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
export const Virtualize = { | ||
init, | ||
dispose, | ||
}; | ||
|
||
const observersByDotNetId = {}; | ||
|
||
function findClosestScrollContainer(element: Element | null): Element | null { | ||
if (!element) { | ||
return null; | ||
} | ||
|
||
const style = getComputedStyle(element); | ||
|
||
if (style.overflowY !== 'visible') { | ||
return element; | ||
} | ||
|
||
return findClosestScrollContainer(element.parentElement); | ||
} | ||
|
||
function init(dotNetHelper: any, spacerBefore: HTMLElement, spacerAfter: HTMLElement, rootMargin = 50): void { | ||
const intersectionObserver = new IntersectionObserver(intersectionCallback, { | ||
root: findClosestScrollContainer(spacerBefore), | ||
rootMargin: `${rootMargin}px`, | ||
}); | ||
|
||
intersectionObserver.observe(spacerBefore); | ||
intersectionObserver.observe(spacerAfter); | ||
|
||
const mutationObserverBefore = createSpacerMutationObserver(spacerBefore); | ||
const mutationObserverAfter = createSpacerMutationObserver(spacerAfter); | ||
|
||
observersByDotNetId[dotNetHelper._id] = { | ||
SteveSandersonMS marked this conversation as resolved.
Show resolved
Hide resolved
|
||
intersectionObserver, | ||
mutationObserverBefore, | ||
mutationObserverAfter, | ||
}; | ||
|
||
function createSpacerMutationObserver(spacer: HTMLElement): MutationObserver { | ||
// Without the use of thresholds, IntersectionObserver only detects binary changes in visibility, | ||
// so if a spacer gets resized but remains visible, no additional callbacks will occur. By unobserving | ||
// and reobserving spacers when they get resized, the intersection callback will re-run if they remain visible. | ||
MackinnonBuck marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const mutationObserver = new MutationObserver((): void => { | ||
intersectionObserver.unobserve(spacer); | ||
intersectionObserver.observe(spacer); | ||
}); | ||
|
||
mutationObserver.observe(spacer, { attributes: true }); | ||
|
||
return mutationObserver; | ||
} | ||
|
||
function intersectionCallback(entries: IntersectionObserverEntry[]): void { | ||
entries.forEach((entry): void => { | ||
if (!entry.isIntersecting) { | ||
return; | ||
} | ||
|
||
const containerSize = entry.rootBounds?.height; | ||
|
||
if (entry.target === spacerBefore) { | ||
dotNetHelper.invokeMethodAsync('OnSpacerBeforeVisible', entry.intersectionRect.top - entry.boundingClientRect.top, containerSize); | ||
} else if (entry.target === spacerAfter && spacerAfter.offsetHeight > 0) { | ||
// When we first start up, both the "before" and "after" spacers will be visible, but it's only relevant to raise a | ||
// single event to load the initial data. To avoid raising two events, skip the one for the "after" spacer if we know | ||
// it's meaningless to talk about any overlap into it. | ||
dotNetHelper.invokeMethodAsync('OnSpacerAfterVisible', entry.boundingClientRect.bottom - entry.intersectionRect.bottom, containerSize); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
function dispose(dotNetHelper: any): void { | ||
const observers = observersByDotNetId[dotNetHelper._id]; | ||
|
||
if (observers) { | ||
observers.intersectionObserver.disconnect(); | ||
observers.mutationObserverBefore.disconnect(); | ||
observers.mutationObserverAfter.disconnect(); | ||
|
||
dotNetHelper.dispose(); | ||
|
||
delete observersByDotNetId[dotNetHelper._id]; | ||
} | ||
} |
11 changes: 11 additions & 0 deletions
11
src/Components/Web/src/Virtualization/IVirtualizeJsCallbacks.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
namespace Microsoft.AspNetCore.Components.Web.Virtualization | ||
{ | ||
internal interface IVirtualizeJsCallbacks | ||
{ | ||
void OnBeforeSpacerVisible(float spacerSize, float containerSize); | ||
void OnAfterSpacerVisible(float spacerSize, float containerSize); | ||
} | ||
} |
15 changes: 15 additions & 0 deletions
15
src/Components/Web/src/Virtualization/ItemsProviderDelegate.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.Threading.Tasks; | ||
|
||
namespace Microsoft.AspNetCore.Components.Web.Virtualization | ||
{ | ||
/// <summary> | ||
/// A function that provides items to a virtualized source. | ||
/// </summary> | ||
/// <typeparam name="TItem">The type of the context for each item in the list.</typeparam> | ||
/// <param name="request">The <see cref="ItemsProviderRequest"/> defining the request details.</param> | ||
/// <returns>A <see cref="ValueTask"/> whose result is a <see cref="ItemsProviderResult{TItem}"/> upon successful completion.</returns> | ||
public delegate ValueTask<ItemsProviderResult<TItem>> ItemsProviderDelegate<TItem>(ItemsProviderRequest request); | ||
} |
44 changes: 44 additions & 0 deletions
44
src/Components/Web/src/Virtualization/ItemsProviderRequest.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.Threading; | ||
|
||
namespace Microsoft.AspNetCore.Components.Web.Virtualization | ||
{ | ||
/// <summary> | ||
/// Represents a request to an <see cref="ItemsProviderDelegate{TItem}"/>. | ||
/// </summary> | ||
public readonly struct ItemsProviderRequest | ||
{ | ||
/// <summary> | ||
/// The start index of the data segment requested. | ||
/// </summary> | ||
public int StartIndex { get; } | ||
|
||
/// <summary> | ||
/// The requested number of items to be provided. The actual number of provided items does not need to match | ||
/// this value. | ||
/// </summary> | ||
public int Count { get; } | ||
|
||
/// <summary> | ||
/// The <see cref="System.Threading.CancellationToken"/> used to relay cancellation of the request. | ||
/// </summary> | ||
public CancellationToken CancellationToken { get; } | ||
|
||
/// <summary> | ||
/// Constructs a new <see cref="ItemsProviderRequest"/> instance. | ||
/// </summary> | ||
/// <param name="startIndex">The start index of the data segment requested.</param> | ||
/// <param name="count">The requested number of items to be provided.</param> | ||
/// <param name="cancellationToken"> | ||
/// The <see cref="System.Threading.CancellationToken"/> used to relay cancellation of the request. | ||
/// </param> | ||
public ItemsProviderRequest(int startIndex, int count, CancellationToken cancellationToken) | ||
{ | ||
StartIndex = startIndex; | ||
Count = count; | ||
CancellationToken = cancellationToken; | ||
} | ||
} | ||
} |
35 changes: 35 additions & 0 deletions
35
src/Components/Web/src/Virtualization/ItemsProviderResult.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System.Collections.Generic; | ||
|
||
namespace Microsoft.AspNetCore.Components.Web.Virtualization | ||
{ | ||
/// <summary> | ||
/// Represents the result of a <see cref="ItemsProviderDelegate{TItem}"/>. | ||
/// </summary> | ||
/// <typeparam name="TItem">The type of the context for each item in the list.</typeparam> | ||
public readonly struct ItemsProviderResult<TItem> | ||
{ | ||
/// <summary> | ||
/// The items to provide. | ||
/// </summary> | ||
public IEnumerable<TItem> Items { get; } | ||
|
||
/// <summary> | ||
/// The total item count in the source generating the items provided. | ||
/// </summary> | ||
public int TotalItemCount { get; } | ||
|
||
/// <summary> | ||
/// Instantiates a new <see cref="ItemsProviderResult{TItem}"/> instance. | ||
/// </summary> | ||
/// <param name="items">The items to provide.</param> | ||
/// <param name="totalItemCount">The total item count in the source generating the items provided.</param> | ||
public ItemsProviderResult(IEnumerable<TItem> items, int totalItemCount) | ||
{ | ||
Items = items; | ||
TotalItemCount = totalItemCount; | ||
} | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
src/Components/Web/src/Virtualization/PlaceholderContext.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
namespace Microsoft.AspNetCore.Components.Web.Virtualization | ||
{ | ||
/// <summary> | ||
/// Contains context for a placeholder in a virtualized list. | ||
/// </summary> | ||
public readonly struct PlaceholderContext | ||
{ | ||
/// <summary> | ||
/// The item index of the placeholder. | ||
/// </summary> | ||
public int Index { get; } | ||
|
||
/// <summary> | ||
/// Constructs a new <see cref="PlaceholderContext"/> instance. | ||
/// </summary> | ||
/// <param name="index">The item index of the placeholder.</param> | ||
public PlaceholderContext(int index) | ||
{ | ||
Index = index; | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.