Skip to content

Commit

Permalink
Autocomplete: Add class templates and FoundItemsCount (and improve do…
Browse files Browse the repository at this point in the history
…cs mobile search dialog) (#8362)

* focus search bar
  • Loading branch information
danielchalmers committed Mar 21, 2024
1 parent 09f94ff commit a047ed3
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 15 deletions.
26 changes: 19 additions & 7 deletions src/MudBlazor.Docs/Shared/Appbar.razor
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@
<MudSpacer/>
@if (DisplaySearchBar)
{
<MudAutocomplete AutoFocus="true" @ref="_searchAutocomplete" T="ApiLinkServiceEntry" Placeholder="Search" SearchFunc="async text => await Search(text)" Variant="Variant.Outlined" ValueChanged="OnSearchResult" Class="docs-search-bar mx-4" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search">
<MudAutocomplete @ref="_searchAutocomplete" T="ApiLinkServiceEntry" Class="docs-search-bar mx-4"
AutoFocus="true" Placeholder="Search" Variant="Variant.Outlined"
SearchFunc="async text => await Search(text)" ValueChanged="OnSearchResult" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search">
<ItemTemplate Context="result">
<MudText>@result.Title</MudText> <MudText Typo="Typo.body2">@result.SubTitle</MudText>
</ItemTemplate>
Expand All @@ -86,16 +88,26 @@
</MudTooltip>
</div>


<MudDialog @bind-IsVisible="_searchDialogOpen" Options="_dialogOptions" Class="docs-gray-bg" ClassContent="docs-mobile-dialog-search">
<MudDialog @bind-IsVisible="IsSearchDialogOpen" Options="_dialogOptions" Class="docs-gray-bg" ClassContent="docs-mobile-dialog-search d-flex flex-column" DefaultFocus="DefaultFocus.FirstChild">
<DialogContent>
<MudAutocomplete AutoFocus="true" @ref="_searchAutocomplete" T="ApiLinkServiceEntry" PopoverClass="docs-mobile-dialog-search-popover" Placeholder="Search docs" SearchFunc="async text => await Search(text)" ValueChanged="OnSearchResult" Clearable="true" Variant="Variant.Outlined" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search">
<MudAutocomplete @ref="_searchAutocomplete" T="ApiLinkServiceEntry" PopoverClass="docs-mobile-dialog-search-popover"
AutoFocus="true" Placeholder="Search docs" Clearable="true" Variant="Variant.Outlined" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Search"
SearchFunc="async text => await Search(text)" ValueChanged="OnSearchResult" IsOpenChanged="o => _searchDialogAutocompleteOpen = o" ReturnedItemsCountChanged="c => _searchDialogReturnedItemsCount = c">
<ItemTemplate Context="result">
<MudText>@result.Title</MudText> <MudText Typo="Typo.body2">@result.SubTitle</MudText>
</ItemTemplate>
</MudAutocomplete>
<div class="d-flex justify-center align-center mud-height-full pb-16">
<MudText Typo="Typo.body2" Class="mud-text-secondary">Nothing found, do a search</MudText>
</div>

@* This text element will always be rendered but it's easier to write it this way *@
<MudText Typo="Typo.body2" Class="flex-grow-1 mud-text-secondary" Align="Align.Center">
@if (!_searchDialogAutocompleteOpen)
{
@("Use the box above to search the docs")
}
else if (_searchDialogReturnedItemsCount == 0)
{
@("No results found")
}
</MudText>
</DialogContent>
</MudDialog>
15 changes: 14 additions & 1 deletion src/MudBlazor.Docs/Shared/Appbar.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace MudBlazor.Docs.Shared;
public partial class Appbar
{
private bool _searchDialogOpen;
private bool _searchDialogAutocompleteOpen;
private int _searchDialogReturnedItemsCount;
private string _badgeTextSoon = "coming soon";
private MudAutocomplete<ApiLinkServiceEntry> _searchAutocomplete = null!;
private DialogOptions _dialogOptions = new() { Position = DialogPosition.TopCenter, NoHeader = true };
Expand Down Expand Up @@ -91,6 +93,17 @@ public partial class Appbar
}
];

public bool IsSearchDialogOpen
{
get => _searchDialogOpen;
set
{
_searchDialogAutocompleteOpen = default;
_searchDialogReturnedItemsCount = default;
_searchDialogOpen = value;
}
}

[Inject]
private NavigationManager NavigationManager { get; set; } = null!;

Expand Down Expand Up @@ -130,5 +143,5 @@ private Task<IReadOnlyCollection<ApiLinkServiceEntry>> Search(string text)
return ApiLinkService.Search(text);
}

private void OpenSearchDialog() => _searchDialogOpen = true;
private void OpenSearchDialog() => IsSearchDialogOpen = true;
}
36 changes: 36 additions & 0 deletions src/MudBlazor.UnitTests/Components/AutocompleteTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,8 @@ public async Task AutocompleteTest6()

var mudText = comp.FindAll("p.mud-typography");
mudText[mudText.Count - 1].InnerHtml.Should().Contain("Not all items are shown"); //ensure the text is shown

comp.FindAll("div.mud-popover .mud-autocomplete-more-items").Count.Should().Be(1);
}

/// <summary>
Expand All @@ -280,6 +282,8 @@ public async Task AutocompleteTest7()

var mudText = comp.FindAll("p.mud-typography");
mudText[mudText.Count - 1].InnerHtml.Should().Contain("No items found, try another search"); //ensure the text is shown

comp.FindAll("div.mud-popover .mud-autocomplete-no-items").Count.Should().Be(1);
}

/// <summary>
Expand Down Expand Up @@ -1237,6 +1241,8 @@ public async Task Autocomplete_Should_LoadListStartWhenSetAndThereAreItems()

var mudText = comp.FindAll("p.mud-typography");
mudText[0].InnerHtml.Should().Contain("StartList_Content"); //ensure the text is shown

comp.FindAll("div.mud-popover .mud-autocomplete-before-items").Count.Should().Be(1);
}

/// <summary>
Expand All @@ -1253,6 +1259,8 @@ public async Task Autocomplete_Should_LoadListEndWhenSetAndThereAreItems()

var mudText = comp.FindAll("p.mud-typography");
mudText[mudText.Count - 1].InnerHtml.Should().Contain("EndList_Content"); //ensure the text is shown

comp.FindAll("div.mud-popover .mud-autocomplete-after-items").Count.Should().Be(1);
}

/// <summary>
Expand Down Expand Up @@ -1299,5 +1307,33 @@ public async Task Autocomplete_Should_ApplyListItemClass()

comp.WaitForAssertion(() => comp.Find("div.mud-list-item").ClassList.Should().Contain(listItemClassTest));
}

[Test]
public async Task Autocomplete_ReturnedItemsCount_Should_Be_Accurate()
{
Task<IEnumerable<string>> search(string value)
{
var values = new string[] { "Lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipiscing", "elit" };
return Task.FromResult(values.Where(x => x.Contains(value, StringComparison.InvariantCultureIgnoreCase)));
}

var comp = Context.RenderComponent<MudAutocomplete<string>>();
comp.SetParametersAndRender(p => p
.Add(x => x.Value, "nothing will ever match this")
.Add(x => x.SearchFunc, search)
.Add(x => x.DebounceInterval, 0));

int? count = null;
comp.Instance.ReturnedItemsCountChanged = new EventCallbackFactory().Create<int>(this, v => count = v);

comp.Find("input").Input("Lorem");
comp.WaitForAssertion(() => count.Should().Be(1));
;
comp.Find("input").Input("ip");
comp.WaitForAssertion(() => count.Should().Be(2));
;
comp.Find("input").Input("wtf");
comp.WaitForAssertion(() => count.Should().Be(0));
}
}
}
10 changes: 5 additions & 5 deletions src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<MudList Class="@ListClass" Clickable="true" Dense="@Dense">
@if (BeforeItemsTemplate != null)
{
<div class="pa-1">
<div class="mud-autocomplete-before-items pa-1">
@BeforeItemsTemplate
</div>
}
Expand Down Expand Up @@ -80,23 +80,23 @@
}
</MudListItem>
}
@if (MoreItemsTemplate != null && _itemsReturned > MaxItems)
@if (MoreItemsTemplate != null && _returnedItemsCount > MaxItems)
{
<div class="pa-1">
<div class="mud-autocomplete-more-items pa-1">
@MoreItemsTemplate
</div>
}
@if (AfterItemsTemplate != null)
{
<div class="pa-1">
<div class="mud-autocomplete-after-items pa-1">
@AfterItemsTemplate
</div>
}
</MudList>
}
else if (NoItemsTemplate != null)
{
<div class="pa-1">
<div class="mud-autocomplete-no-items pa-1">
@NoItemsTemplate
</div>
}
Expand Down
20 changes: 18 additions & 2 deletions src/MudBlazor/Components/Autocomplete/MudAutocomplete.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public partial class MudAutocomplete<T> : MudBaseInput<T>, IDisposable
private bool _isProcessingValue;
private int _selectedListItemIndex = 0;
private int _elementKey = 0;
private int _itemsReturned; //the number of items returned by the search function
private int _returnedItemsCount;
private bool _isOpen;
private MudInput<string> _elementReference;
private CancellationTokenSource _cancellationTokenSrc;
Expand Down Expand Up @@ -354,6 +354,16 @@ public partial class MudAutocomplete<T> : MudBaseInput<T>, IDisposable
[Parameter]
public EventCallback<MouseEventArgs> OnClearButtonClick { get; set; }

/// <summary>
/// <para>An event triggered when the number of items returned by the search query has changed.</para>
/// <para>
/// If the number is <c>0</c>, <see cref="NoItemsTemplate"/> will be shown.<br />
/// If the number is beyond <see cref="MaxItems"/>, <see cref="MoreItemsTemplate"/> will be shown.
/// </para>
/// </summary>
[Parameter]
public EventCallback<int> ReturnedItemsCountChanged { get; set; }

/// <summary>
/// Returns the open state of the drop-down.
/// </summary>
Expand Down Expand Up @@ -494,6 +504,12 @@ private void CancelToken()
}
}

private Task SetReturnedItemsCountAsync(int value)
{
_returnedItemsCount = value;
return ReturnedItemsCountChanged.InvokeAsync(value);
}

/// <remarks>
/// This async method needs to return a task and be awaited in order for
/// unit tests that trigger this method to work correctly.
Expand Down Expand Up @@ -542,7 +558,7 @@ private async Task OnSearchAsync()
Logger.LogWarning("The search function failed to return results: " + e.Message);
}

_itemsReturned = searchedItems.Length;
await SetReturnedItemsCountAsync(searchedItems.Length);
if (MaxItems.HasValue)
{
searchedItems = searchedItems.Take(MaxItems.Value).ToArray();
Expand Down

0 comments on commit a047ed3

Please sign in to comment.