Virtualize component: method to trigger data refresh (#26177)
* E2E test cases for Virtualize data refresh * Expose public RefreshDataAsync API * Optimize: don't instantiate CancellationTokenSource when it won't be used * For in-memory data, refresh automatically on each render cycle * Fix typo
This commit is contained in:
parent
c0bd50075a
commit
7140f7cae7
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -97,6 +98,20 @@ namespace Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int OverscanCount { get; set; } = 3;
|
public int OverscanCount { get; set; } = 3;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Instructs the component to re-request data from its <see cref="ItemsProvider"/>.
|
||||||
|
/// This is useful if external data may have changed. There is no need to call this
|
||||||
|
/// when using <see cref="Items"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A <see cref="Task"/> representing the completion of the operation.</returns>
|
||||||
|
public async Task RefreshDataAsync()
|
||||||
|
{
|
||||||
|
// We don't auto-render after this operation because in the typical use case, the
|
||||||
|
// host component calls this from one of its lifecycle methods, and will naturally
|
||||||
|
// re-render afterwards anyway. It's not desirable to re-render twice.
|
||||||
|
await RefreshDataCoreAsync(renderOnSuccess: false);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void OnParametersSet()
|
protected override void OnParametersSet()
|
||||||
{
|
{
|
||||||
|
|
@ -125,6 +140,14 @@ namespace Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
else if (Items != null)
|
else if (Items != null)
|
||||||
{
|
{
|
||||||
_itemsProvider = DefaultItemsProvider;
|
_itemsProvider = DefaultItemsProvider;
|
||||||
|
|
||||||
|
// When we have a fixed set of in-memory data, it doesn't cost anything to
|
||||||
|
// re-query it on each cycle, so do that. This means the developer can add/remove
|
||||||
|
// items in the collection and see the UI update without having to call RefreshDataAsync.
|
||||||
|
var refreshTask = RefreshDataCoreAsync(renderOnSuccess: false);
|
||||||
|
|
||||||
|
// We know it's synchronous and has its own error handling
|
||||||
|
Debug.Assert(refreshTask.IsCompletedSuccessfully);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -270,7 +293,7 @@ namespace Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
{
|
{
|
||||||
_itemsBefore = itemsBefore;
|
_itemsBefore = itemsBefore;
|
||||||
_visibleItemCapacity = visibleItemCapacity;
|
_visibleItemCapacity = visibleItemCapacity;
|
||||||
var refreshTask = RefreshDataAsync();
|
var refreshTask = RefreshDataCoreAsync(renderOnSuccess: true);
|
||||||
|
|
||||||
if (!refreshTask.IsCompleted)
|
if (!refreshTask.IsCompleted)
|
||||||
{
|
{
|
||||||
|
|
@ -279,12 +302,25 @@ namespace Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RefreshDataAsync()
|
private async ValueTask RefreshDataCoreAsync(bool renderOnSuccess)
|
||||||
{
|
{
|
||||||
_refreshCts?.Cancel();
|
_refreshCts?.Cancel();
|
||||||
_refreshCts = new CancellationTokenSource();
|
CancellationToken cancellationToken;
|
||||||
|
|
||||||
|
if (_itemsProvider == DefaultItemsProvider)
|
||||||
|
{
|
||||||
|
// If we're using the DefaultItemsProvider (because the developer supplied a fixed
|
||||||
|
// Items collection) we know it will complete synchronously, and there's no point
|
||||||
|
// instantiating a new CancellationTokenSource
|
||||||
|
_refreshCts = null;
|
||||||
|
cancellationToken = CancellationToken.None;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_refreshCts = new CancellationTokenSource();
|
||||||
|
cancellationToken = _refreshCts.Token;
|
||||||
|
}
|
||||||
|
|
||||||
var cancellationToken = _refreshCts.Token;
|
|
||||||
var request = new ItemsProviderRequest(_itemsBefore, _visibleItemCapacity, cancellationToken);
|
var request = new ItemsProviderRequest(_itemsBefore, _visibleItemCapacity, cancellationToken);
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -298,7 +334,10 @@ namespace Microsoft.AspNetCore.Components.Web.Virtualization
|
||||||
_loadedItems = result.Items;
|
_loadedItems = result.Items;
|
||||||
_loadedItemsStartIndex = request.StartIndex;
|
_loadedItemsStartIndex = request.StartIndex;
|
||||||
|
|
||||||
StateHasChanged();
|
if (renderOnSuccess)
|
||||||
|
{
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using BasicTestApp;
|
using BasicTestApp;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure;
|
||||||
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
using Microsoft.AspNetCore.Components.E2ETest.Infrastructure.ServerFixtures;
|
||||||
|
|
@ -13,7 +14,7 @@ using Xunit.Abstractions;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
{
|
{
|
||||||
public class VirtualizationTest : E2ETest.Infrastructure.ServerTestBase<ToggleExecutionModeServerFixture<Program>>
|
public class VirtualizationTest : ServerTestBase<ToggleExecutionModeServerFixture<Program>>
|
||||||
{
|
{
|
||||||
public VirtualizationTest(
|
public VirtualizationTest(
|
||||||
BrowserFixture browserFixture,
|
BrowserFixture browserFixture,
|
||||||
|
|
@ -26,12 +27,12 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
protected override void InitializeAsyncCore()
|
protected override void InitializeAsyncCore()
|
||||||
{
|
{
|
||||||
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
|
Navigate(ServerPathBase, noReload: _serverFixture.ExecutionMode == ExecutionMode.Client);
|
||||||
Browser.MountTestComponent<VirtualizationComponent>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AlwaysFillsVisibleCapacity_Sync()
|
public void AlwaysFillsVisibleCapacity_Sync()
|
||||||
{
|
{
|
||||||
|
Browser.MountTestComponent<VirtualizationComponent>();
|
||||||
var topSpacer = Browser.FindElement(By.Id("sync-container")).FindElement(By.TagName("div"));
|
var topSpacer = Browser.FindElement(By.Id("sync-container")).FindElement(By.TagName("div"));
|
||||||
var expectedInitialSpacerStyle = "height: 0px;";
|
var expectedInitialSpacerStyle = "height: 0px;";
|
||||||
|
|
||||||
|
|
@ -61,6 +62,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void AlwaysFillsVisibleCapacity_Async()
|
public void AlwaysFillsVisibleCapacity_Async()
|
||||||
{
|
{
|
||||||
|
Browser.MountTestComponent<VirtualizationComponent>();
|
||||||
var finishLoadingButton = Browser.FindElement(By.Id("finish-loading-button"));
|
var finishLoadingButton = Browser.FindElement(By.Id("finish-loading-button"));
|
||||||
|
|
||||||
// Check that no items or placeholders are visible.
|
// Check that no items or placeholders are visible.
|
||||||
|
|
@ -112,6 +114,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RerendersWhenItemSizeShrinks_Sync()
|
public void RerendersWhenItemSizeShrinks_Sync()
|
||||||
{
|
{
|
||||||
|
Browser.MountTestComponent<VirtualizationComponent>();
|
||||||
int initialItemCount = 0;
|
int initialItemCount = 0;
|
||||||
|
|
||||||
// Wait until items have been rendered.
|
// Wait until items have been rendered.
|
||||||
|
|
@ -131,6 +134,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RerendersWhenItemSizeShrinks_Async()
|
public void RerendersWhenItemSizeShrinks_Async()
|
||||||
{
|
{
|
||||||
|
Browser.MountTestComponent<VirtualizationComponent>();
|
||||||
var finishLoadingButton = Browser.FindElement(By.Id("finish-loading-button"));
|
var finishLoadingButton = Browser.FindElement(By.Id("finish-loading-button"));
|
||||||
|
|
||||||
// Load the initial set of items.
|
// Load the initial set of items.
|
||||||
|
|
@ -165,6 +169,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CancelsOutdatedRefreshes_Async()
|
public void CancelsOutdatedRefreshes_Async()
|
||||||
{
|
{
|
||||||
|
Browser.MountTestComponent<VirtualizationComponent>();
|
||||||
var cancellationCount = Browser.FindElement(By.Id("cancellation-count"));
|
var cancellationCount = Browser.FindElement(By.Id("cancellation-count"));
|
||||||
var finishLoadingButton = Browser.FindElement(By.Id("finish-loading-button"));
|
var finishLoadingButton = Browser.FindElement(By.Id("finish-loading-button"));
|
||||||
|
|
||||||
|
|
@ -191,6 +196,7 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/24922")]
|
[QuarantinedTest("https://github.com/dotnet/aspnetcore/issues/24922")]
|
||||||
public void CanUseViewportAsContainer()
|
public void CanUseViewportAsContainer()
|
||||||
{
|
{
|
||||||
|
Browser.MountTestComponent<VirtualizationComponent>();
|
||||||
var expectedInitialSpacerStyle = "height: 0px;";
|
var expectedInitialSpacerStyle = "height: 0px;";
|
||||||
var topSpacer = Browser.FindElement(By.Id("viewport-as-root")).FindElement(By.TagName("div"));
|
var topSpacer = Browser.FindElement(By.Id("viewport-as-root")).FindElement(By.TagName("div"));
|
||||||
|
|
||||||
|
|
@ -204,5 +210,136 @@ namespace Microsoft.AspNetCore.Components.E2ETest.Tests
|
||||||
// Validate that the top spacer has expanded.
|
// Validate that the top spacer has expanded.
|
||||||
Browser.NotEqual(expectedInitialSpacerStyle, () => topSpacer.GetAttribute("style"));
|
Browser.NotEqual(expectedInitialSpacerStyle, () => topSpacer.GetAttribute("style"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanMutateDataInPlace_Sync()
|
||||||
|
{
|
||||||
|
Browser.MountTestComponent<VirtualizationDataChanges>();
|
||||||
|
|
||||||
|
// Initial data
|
||||||
|
var container = Browser.FindElement(By.Id("using-items"));
|
||||||
|
Browser.Collection(() => GetPeopleNames(container),
|
||||||
|
name => Assert.Equal("Person 1", name),
|
||||||
|
name => Assert.Equal("Person 2", name),
|
||||||
|
name => Assert.Equal("Person 3", name));
|
||||||
|
|
||||||
|
// Mutate one of them
|
||||||
|
var itemToMutate = container.FindElements(By.ClassName("person"))[1];
|
||||||
|
itemToMutate.FindElement(By.TagName("button")).Click();
|
||||||
|
|
||||||
|
// See changes
|
||||||
|
Browser.Collection(() => GetPeopleNames(container),
|
||||||
|
name => Assert.Equal("Person 1", name),
|
||||||
|
name => Assert.Equal("Person 2 MUTATED", name),
|
||||||
|
name => Assert.Equal("Person 3", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanMutateDataInPlace_Async()
|
||||||
|
{
|
||||||
|
Browser.MountTestComponent<VirtualizationDataChanges>();
|
||||||
|
|
||||||
|
// Initial data
|
||||||
|
var container = Browser.FindElement(By.Id("using-itemsprovider"));
|
||||||
|
Browser.Collection(() => GetPeopleNames(container),
|
||||||
|
name => Assert.Equal("Person 1", name),
|
||||||
|
name => Assert.Equal("Person 2", name),
|
||||||
|
name => Assert.Equal("Person 3", name));
|
||||||
|
|
||||||
|
// Mutate one of them
|
||||||
|
var itemToMutate = container.FindElements(By.ClassName("person"))[1];
|
||||||
|
itemToMutate.FindElement(By.TagName("button")).Click();
|
||||||
|
|
||||||
|
// See changes
|
||||||
|
Browser.Collection(() => GetPeopleNames(container),
|
||||||
|
name => Assert.Equal("Person 1", name),
|
||||||
|
name => Assert.Equal("Person 2 MUTATED", name),
|
||||||
|
name => Assert.Equal("Person 3", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanChangeDataCount_Sync()
|
||||||
|
{
|
||||||
|
Browser.MountTestComponent<VirtualizationDataChanges>();
|
||||||
|
|
||||||
|
// Initial data
|
||||||
|
var container = Browser.FindElement(By.Id("using-items"));
|
||||||
|
Browser.Collection(() => GetPeopleNames(container),
|
||||||
|
name => Assert.Equal("Person 1", name),
|
||||||
|
name => Assert.Equal("Person 2", name),
|
||||||
|
name => Assert.Equal("Person 3", name));
|
||||||
|
|
||||||
|
// Add another item
|
||||||
|
Browser.FindElement(By.Id("add-person-to-fixed-list")).Click();
|
||||||
|
|
||||||
|
// See changes
|
||||||
|
Browser.Collection(() => GetPeopleNames(container),
|
||||||
|
name => Assert.Equal("Person 1", name),
|
||||||
|
name => Assert.Equal("Person 2", name),
|
||||||
|
name => Assert.Equal("Person 3", name),
|
||||||
|
name => Assert.Equal("Person 4", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanChangeDataCount_Async()
|
||||||
|
{
|
||||||
|
Browser.MountTestComponent<VirtualizationDataChanges>();
|
||||||
|
|
||||||
|
// Initial data
|
||||||
|
var container = Browser.FindElement(By.Id("using-itemsprovider"));
|
||||||
|
Browser.Collection(() => GetPeopleNames(container),
|
||||||
|
name => Assert.Equal("Person 1", name),
|
||||||
|
name => Assert.Equal("Person 2", name),
|
||||||
|
name => Assert.Equal("Person 3", name));
|
||||||
|
|
||||||
|
// Add another item
|
||||||
|
Browser.FindElement(By.Id("add-person-to-itemsprovider")).Click();
|
||||||
|
|
||||||
|
// Initially this has no effect because we don't re-query the provider until told to do so
|
||||||
|
Browser.Collection(() => GetPeopleNames(container),
|
||||||
|
name => Assert.Equal("Person 1", name),
|
||||||
|
name => Assert.Equal("Person 2", name),
|
||||||
|
name => Assert.Equal("Person 3", name));
|
||||||
|
|
||||||
|
// Request refresh
|
||||||
|
Browser.FindElement(By.Id("refresh-itemsprovider")).Click();
|
||||||
|
|
||||||
|
// See changes
|
||||||
|
Browser.Collection(() => GetPeopleNames(container),
|
||||||
|
name => Assert.Equal("Person 1", name),
|
||||||
|
name => Assert.Equal("Person 2", name),
|
||||||
|
name => Assert.Equal("Person 3", name),
|
||||||
|
name => Assert.Equal("Person 4", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanRefreshItemsProviderResultsInPlace()
|
||||||
|
{
|
||||||
|
Browser.MountTestComponent<VirtualizationDataChanges>();
|
||||||
|
|
||||||
|
// Mutate the data
|
||||||
|
var container = Browser.FindElement(By.Id("using-itemsprovider"));
|
||||||
|
var itemToMutate = container.FindElements(By.ClassName("person"))[1];
|
||||||
|
itemToMutate.FindElement(By.TagName("button")).Click();
|
||||||
|
|
||||||
|
// Verify the mutation was applied
|
||||||
|
Browser.Collection(() => GetPeopleNames(container),
|
||||||
|
name => Assert.Equal("Person 1", name),
|
||||||
|
name => Assert.Equal("Person 2 MUTATED", name),
|
||||||
|
name => Assert.Equal("Person 3", name));
|
||||||
|
|
||||||
|
// Refresh and verify the mutation was reverted
|
||||||
|
Browser.FindElement(By.Id("refresh-itemsprovider")).Click();
|
||||||
|
Browser.Collection(() => GetPeopleNames(container),
|
||||||
|
name => Assert.Equal("Person 1", name),
|
||||||
|
name => Assert.Equal("Person 2", name),
|
||||||
|
name => Assert.Equal("Person 3", name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] GetPeopleNames(IWebElement container)
|
||||||
|
{
|
||||||
|
var peopleElements = container.FindElements(By.CssSelector(".person span"));
|
||||||
|
return peopleElements.Select(element => element.Text).ToArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
<option value="BasicTestApp.ComponentRefComponent">Component ref component</option>
|
<option value="BasicTestApp.ComponentRefComponent">Component ref component</option>
|
||||||
<option value="BasicTestApp.ConcurrentRenderParent">Concurrent rendering</option>
|
<option value="BasicTestApp.ConcurrentRenderParent">Concurrent rendering</option>
|
||||||
<option value="BasicTestApp.ConfigurationComponent">Configuration</option>
|
<option value="BasicTestApp.ConfigurationComponent">Configuration</option>
|
||||||
|
<option value="BasicTestApp.ContentEditable">Content-editable</option>
|
||||||
<option value="BasicTestApp.CounterComponent">Counter</option>
|
<option value="BasicTestApp.CounterComponent">Counter</option>
|
||||||
<option value="BasicTestApp.CounterComponentUsingChild">Counter using child component</option>
|
<option value="BasicTestApp.CounterComponentUsingChild">Counter using child component</option>
|
||||||
<option value="BasicTestApp.CounterComponentWrapper">Counter wrapped in parent</option>
|
<option value="BasicTestApp.CounterComponentWrapper">Counter wrapped in parent</option>
|
||||||
|
|
@ -80,16 +81,16 @@
|
||||||
<option value="BasicTestApp.RouterTest.TestRouterWithOnNavigate">Router with OnNavigate</option>
|
<option value="BasicTestApp.RouterTest.TestRouterWithOnNavigate">Router with OnNavigate</option>
|
||||||
<option value="BasicTestApp.RouterTest.TestRouterWithLazyAssembly">Router with dynamic assembly</option>
|
<option value="BasicTestApp.RouterTest.TestRouterWithLazyAssembly">Router with dynamic assembly</option>
|
||||||
<option value="BasicTestApp.RouterTest.TestRouterWithAdditionalAssembly">Router with additional assembly</option>
|
<option value="BasicTestApp.RouterTest.TestRouterWithAdditionalAssembly">Router with additional assembly</option>
|
||||||
|
<option value="BasicTestApp.SelectVariantsComponent">Select with component options</option>
|
||||||
<option value="BasicTestApp.SignalRClientComponent">SignalR client</option>
|
<option value="BasicTestApp.SignalRClientComponent">SignalR client</option>
|
||||||
<option value="BasicTestApp.StringComparisonComponent">StringComparison</option>
|
<option value="BasicTestApp.StringComparisonComponent">StringComparison</option>
|
||||||
<option value="BasicTestApp.SvgComponent">SVG</option>
|
<option value="BasicTestApp.SvgComponent">SVG</option>
|
||||||
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
|
<option value="BasicTestApp.SvgWithChildComponent">SVG with child component</option>
|
||||||
<option value="BasicTestApp.TextOnlyComponent">Plain text</option>
|
<option value="BasicTestApp.TextOnlyComponent">Plain text</option>
|
||||||
|
<option value="BasicTestApp.ToggleEventComponent">Toggle Event</option>
|
||||||
<option value="BasicTestApp.TouchEventComponent">Touch events</option>
|
<option value="BasicTestApp.TouchEventComponent">Touch events</option>
|
||||||
<option value="BasicTestApp.VirtualizationComponent">Virtualization</option>
|
<option value="BasicTestApp.VirtualizationComponent">Virtualization</option>
|
||||||
<option value="BasicTestApp.SelectVariantsComponent">Select with component options</option>
|
<option value="BasicTestApp.VirtualizationDataChanges">Virtualization data changes</option>
|
||||||
<option value="BasicTestApp.ToggleEventComponent">Toggle Event</option>
|
|
||||||
<option value="BasicTestApp.ContentEditable">Content-editable</option>
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<span id="runtime-info"><code><tt>@System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription</tt></code></span>
|
<span id="runtime-info"><code><tt>@System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription</tt></code></span>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
<h3 id="virtualization-data-changes">Virtualization data changes</h3>
|
||||||
|
|
||||||
|
<p>This scenario shows how the data behind a Virtualize component can change.</p>
|
||||||
|
|
||||||
|
<button id="add-person-to-fixed-list" @onclick="AddPersonToFixedList">Add person</button>
|
||||||
|
|
||||||
|
<h4>Using Items parameter</h4>
|
||||||
|
|
||||||
|
<div id="using-items" style="overflow-y: auto; height: 200px; border: 1px dashed gray;">
|
||||||
|
<Virtualize Items="@fixedPeople" Context="person">
|
||||||
|
@*
|
||||||
|
Note that for best performance, you really should use @key on the top-level elements within the <Virtualize>.
|
||||||
|
This test case doesn't do so only as a way of verifying it's not strictly required (though it is recommended).
|
||||||
|
*@
|
||||||
|
<div class="person" style="border-bottom: 1px dashed red; display: flex; justify-content: space-between; padding: 4px;">
|
||||||
|
<span>@person.Name</span>
|
||||||
|
<button @onclick="@person.Mutate">Mutate</button>
|
||||||
|
</div>
|
||||||
|
</Virtualize>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4>Using ItemsProvider parameter</h4>
|
||||||
|
<p>
|
||||||
|
<button id="add-person-to-itemsprovider" @onclick="AddPersonToItemsProvider">Add person</button>
|
||||||
|
<button id="refresh-itemsprovider" @onclick="() => asyncVirtualize.RefreshDataAsync()">Refresh</button>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div id="using-itemsprovider" style="overflow-y: auto; height: 200px; border: 1px dashed gray;">
|
||||||
|
<Virtualize @ref="asyncVirtualize" ItemsProvider="GetPeopleAsync" Context="person">
|
||||||
|
@*
|
||||||
|
Note that for best performance, you really should use @key on the top-level elements within the <Virtualize>.
|
||||||
|
This test case doesn't do so only as a way of verifying it's not strictly required (though it is recommended).
|
||||||
|
*@
|
||||||
|
<div class="person" style="border-bottom: 1px dashed red; display: flex; justify-content: space-between; padding: 4px;">
|
||||||
|
<span>@person.Name</span>
|
||||||
|
<button @onclick="@person.Mutate">Mutate</button>
|
||||||
|
</div>
|
||||||
|
</Virtualize>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code {
|
||||||
|
Virtualize<Person> asyncVirtualize;
|
||||||
|
List<Person> fixedPeople = Enumerable.Range(1, 3).Select(GeneratePerson).ToList();
|
||||||
|
int numPeopleInItemsProvider = 3;
|
||||||
|
|
||||||
|
void AddPersonToFixedList()
|
||||||
|
{
|
||||||
|
// When using Items (not ItemsProvider), the Virtualize component re-queries
|
||||||
|
// the data on every refresh cycle without requiring you to call RefreshDataAsync.
|
||||||
|
// This is because there's no cost involved in doing so. Thus, the following
|
||||||
|
// line is enough to make the UI change on its own.
|
||||||
|
fixedPeople.Add(GeneratePerson(fixedPeople.Count + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddPersonToItemsProvider()
|
||||||
|
{
|
||||||
|
// On its own, this isn't going to make the UI change, because it doesn't know
|
||||||
|
// to re-query the underlying items provider until you call RefreshDataAsync.
|
||||||
|
numPeopleInItemsProvider++;
|
||||||
|
}
|
||||||
|
|
||||||
|
async ValueTask<ItemsProviderResult<Person>> GetPeopleAsync(ItemsProviderRequest request)
|
||||||
|
{
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
var lastIndexExcl = Math.Min(request.StartIndex + request.Count, numPeopleInItemsProvider);
|
||||||
|
return new ItemsProviderResult<Person>(
|
||||||
|
Enumerable.Range(1 + request.StartIndex, lastIndexExcl - request.StartIndex).Select(GeneratePerson).ToList(),
|
||||||
|
numPeopleInItemsProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Person
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public void Mutate()
|
||||||
|
{
|
||||||
|
Name += " MUTATED";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Person GeneratePerson(int index)
|
||||||
|
=> new Person { Name = $"Person {index}" };
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue