Skip to content

Commit 93c156f

Browse files
committed
Add ValueLoaded callback to LazyValue, Fix issues with Typeahead
1 parent 5e2be02 commit 93c156f

5 files changed

Lines changed: 94 additions & 11 deletions

File tree

src/LoreSoft.Blazor.Controls/Lazy/LazyValue.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ public class LazyValue<TKey, TValue> : ComponentBase
4747
[Parameter]
4848
public string? LoadingText { get; set; }
4949

50+
/// <summary>
51+
/// Gets or sets the callback invoked when the value has been successfully loaded.
52+
/// </summary>
53+
[Parameter]
54+
public EventCallback<TValue?> ValueLoaded { get; set; }
55+
5056
/// <summary>
5157
/// Gets or sets the loaded value.
5258
/// </summary>
@@ -74,6 +80,8 @@ protected override async Task OnParametersSetAsync()
7480
// load the value async
7581
Value = await LoadMethod(Key);
7682
_previousKey = Key;
83+
84+
await ValueLoaded.InvokeAsync(Value);
7785
}
7886
finally
7987
{

src/LoreSoft.Blazor.Controls/Typeahead/Typeahead.razor

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,8 @@
168168
TValue="TItem"
169169
Key="@value"
170170
LoadingText="Loading..."
171-
LoadMethod="@LookupMethod" />
171+
LoadMethod="@LookupMethod"
172+
ValueLoaded="@OnValueLoaded" />
172173
}
173174
else if (value == null)
174175
{

src/LoreSoft.Blazor.Controls/Typeahead/Typeahead.razor.cs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public partial class Typeahead<TItem, TValue> : StandardComponent
2424
{
2525
private readonly DebounceAction<string?> _debouncer = new();
2626
private readonly LoadingState _loadingState = new();
27+
private bool _suppressValueLoadedEvent;
28+
private bool _previousDisabled = true;
2729

2830
/// <summary>
2931
/// Gets or sets the cascading <see cref="EditContext"/> for form validation.
@@ -268,20 +270,17 @@ protected override void OnParametersSet()
268270
.AddStyle(MenuStyle)
269271
.ToString();
270272
});
271-
}
272273

273-
/// <inheritdoc />
274-
protected override async Task OnInitializedAsync()
275-
{
276-
await base.OnInitializedAsync();
277-
278-
// load items if an item loader is provided
279-
// will overwrite Items parameter if both are set
280-
if (!Disabled && ItemLoader != null)
274+
// load items when component transitions from disabled to enabled
275+
if (_previousDisabled && !Disabled && ItemLoader != null && CurrentItems.Count == 0)
281276
ExecuteAfterRender(LoadItems);
277+
278+
_previousDisabled = Disabled;
282279
}
283280

284281

282+
283+
285284
/// <summary>
286285
/// Performs a search using the current search text and updates the search results.
287286
/// </summary>
@@ -353,6 +352,7 @@ public async Task SelectResult(TItem item)
353352
{
354353
valueList.Add(value);
355354
await ValuesChanged.InvokeAsync(valueList);
355+
_suppressValueLoadedEvent = true;
356356
await SelectionAdded.InvokeAsync(item);
357357
}
358358
else
@@ -364,6 +364,7 @@ public async Task SelectResult(TItem item)
364364
else
365365
{
366366
await ValueChanged.InvokeAsync(value);
367+
_suppressValueLoadedEvent = true;
367368
await SelectionAdded.InvokeAsync(item);
368369
}
369370

@@ -606,6 +607,24 @@ private void MoveSelection(int count)
606607
SelectedIndex = index;
607608
}
608609

610+
/// <summary>
611+
/// Handles the <see cref="LazyValue{TKey, TValue}.ValueLoaded"/> callback,
612+
/// forwarding to <see cref="SelectionAdded"/> unless the event was already
613+
/// raised by <see cref="SelectResult"/>.
614+
/// </summary>
615+
/// <param name="item">The item resolved by the lazy loader.</param>
616+
private async Task OnValueLoaded(TItem? item)
617+
{
618+
if (_suppressValueLoadedEvent)
619+
{
620+
_suppressValueLoadedEvent = false;
621+
return;
622+
}
623+
624+
if (item is not null)
625+
await SelectionAdded.InvokeAsync(item);
626+
}
627+
609628
/// <summary>
610629
/// Converts an item to its corresponding value using <see cref="ConvertMethod"/> or direct casting.
611630
/// </summary>

test/LoreSoft.Blazor.Controls.Tests/Lazy/LazyValueTests.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,4 +460,58 @@ public void Loading_Property_Is_False_After_Completion()
460460
Assert.NotNull(cut.Instance.Value);
461461
});
462462
}
463+
464+
[Fact]
465+
public void Invokes_ValueLoaded_After_Successful_Load()
466+
{
467+
// arrange
468+
var fruit = Fruit.Data().First();
469+
var key = fruit.Id;
470+
var loadMethod = Fruit.GetByIdAsync;
471+
Fruit? loadedValue = null;
472+
473+
// act
474+
var cut = Render<LazyValue<Guid, Fruit>>(parameters => parameters
475+
.Add(p => p.LoadMethod, loadMethod)
476+
.Add(p => p.Key, key)
477+
.Add(p => p.ValueLoaded, value => loadedValue = value)
478+
);
479+
480+
// assert
481+
cut.WaitForAssertion(() =>
482+
{
483+
Assert.NotNull(loadedValue);
484+
Assert.Equal(fruit.Id, loadedValue.Id);
485+
});
486+
}
487+
488+
[Fact]
489+
public void Invokes_ValueLoaded_On_Key_Change()
490+
{
491+
// arrange
492+
var fruits = Fruit.Data().Take(2).ToArray();
493+
var loadMethod = Fruit.GetByIdAsync;
494+
var loadedValues = new List<Fruit?>();
495+
496+
// act
497+
var cut = Render<LazyValue<Guid, Fruit>>(parameters => parameters
498+
.Add(p => p.LoadMethod, loadMethod)
499+
.Add(p => p.Key, fruits[0].Id)
500+
.Add(p => p.ValueLoaded, value => loadedValues.Add(value))
501+
);
502+
503+
cut.WaitForAssertion(() => Assert.Single(loadedValues));
504+
505+
// change key
506+
cut.Render(parameters => parameters
507+
.Add(p => p.Key, fruits[1].Id)
508+
);
509+
510+
// assert both values were loaded
511+
cut.WaitForAssertion(() =>
512+
{
513+
Assert.Equal(2, loadedValues.Count);
514+
Assert.Equal(fruits[1].Id, loadedValues[1]!.Id);
515+
});
516+
}
463517
}

test/LoreSoft.Blazor.Controls.Tests/Snapshots/PublicApiTest.PublicApiHasNotChanged.verified.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,6 +985,8 @@ namespace LoreSoft.Blazor.Controls
985985
[Microsoft.AspNetCore.Components.Parameter]
986986
public string? LoadingText { get; set; }
987987
public TValue Value { get; set; }
988+
[Microsoft.AspNetCore.Components.Parameter]
989+
public Microsoft.AspNetCore.Components.EventCallback<TValue?> ValueLoaded { get; set; }
988990
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder) { }
989991
protected override System.Threading.Tasks.Task OnParametersSetAsync() { }
990992
}
@@ -1674,7 +1676,6 @@ namespace LoreSoft.Blazor.Controls
16741676
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder @__builder) { }
16751677
public System.Threading.Tasks.Task Clear() { }
16761678
public void CloseMenu() { }
1677-
protected override System.Threading.Tasks.Task OnInitializedAsync() { }
16781679
protected override void OnParametersSet() { }
16791680
public System.Threading.Tasks.Task RemoveValue(TValue item) { }
16801681
public System.Threading.Tasks.Task Search(string? searchText) { }

0 commit comments

Comments
 (0)