Search code examples
c#asp.netblazortypeahead

How to display selected item in the input tag of a custom Blazor dropdown-typeahead component


I'm trying to build a custom dropdown-typeahead component in Blazor. I want it to be as following: 

Instead of the classic <select> tag which on click displays a list, i want it to have an <input> tag where the user can write something and search the list. On each character they write, the dropdown <div> opens below with results depending on the search text. The user can select any of the displaying results of the list. So far so good. 

The problem I'm dealing with is that I cannot display the selected item inside the <input> tag. My code is the following:

Parent Component

...
<div class="col" @onfocusout="ClearHstrCommonSearchTextHandler">
   <label class="form-label">Location</label>
   <TypeaheadDropdownComponent OnEmptySearchText="ClearHstrCommonSearchTextHandler"
                               SearchMethod="SearchHstrCommon" />
   
   @if (showDropdownResults && HstrCommonDisplayed.Count > 0)
   {
      <div class="custom-dropdown-results">
         @foreach (HstrCommonDTO hstr in HstrCommonDisplayed)
         {
            <div class="custom-dropdown-results-item" @onclick="() => { locationHstrId = hstr.HstrId; showDropdownResults = false; }">
               @hstr.Title <br />
            </div>
         }
      </div>
   }
</div>
...

@code {
   [Parameter] public List<HstrCommonDTO> HstrCommon { get; set; }
   
   private int locationHstrId = -1;
   private bool showDropdownResults = false;
   private List<HstrCommonDTO> HstrCommonDisplayed = new();

   ...

   private void SearchHstrCommon(string searchText)
   {
      searchText = searchText.RemoveDiacritics()
                             .Trim()
                             .ToUpper();
      showDropdownResults = true;
      HstrCommonDisplayed = HstrCommon.FindAll(x => x.Title.Contains(searchText, StringComparison.OrdinalIgnoreCase)).ToList();
   }

   private void ClearHstrCommonSearchTextHandler()
   {
      showDropdownResults = false;
      HstrCommonDisplayed = new();
      locationHstrId = -1;
   }
}

TypeaheadDropdownComponent

<div style="position: relative">
    <input class="form-control"
           type="text"
           autocomplete="off"
           placeholder="@Placeholder"
           @bind-value="@SearchText"
           @bind-value:event="oninput" />
</div>

@code {
    [Parameter] public string Placeholder { get; set; } = "Search";
    [Parameter] public int MinimumLength { get; set; } = 1;
    [Parameter] public int Debounce { get; set; } = 200;
    [Parameter] public EventCallback<string> SearchMethod { get; set; }
    [Parameter] public EventCallback OnEmptySearchText { get; set; }

    private Timer _debounceTimer;
    private bool _isSearching = false;
    private string _searchText = string.Empty;

    private string SearchText
    {
        get => _searchText;
        set
        {
            _searchText = value;
            if (value.Length == 0)
            {
                _debounceTimer.Stop();
                OnEmptySearchText.InvokeAsync();
            }
            else if (value.Length >= MinimumLength)
            {
                _debounceTimer.Stop();
                _debounceTimer.Start();
            }
        }
    }

    protected override void OnInitialized()
    {
        _debounceTimer = new Timer();
        _debounceTimer.Interval = Debounce;
        _debounceTimer.AutoReset = false;
        _debounceTimer.Elapsed += Search;

        base.OnInitialized();
    }

    protected async void Search(Object source, ElapsedEventArgs e)
    {
        _isSearching = true;
        await InvokeAsync(StateHasChanged);
        await SearchMethod.InvokeAsync(_searchText);
        _isSearching = false;
        await InvokeAsync(StateHasChanged);
    }
}

With my code, I take the required value (locationHstrId) as expected, but in the <input> tag of the TypeaheadDropdownComponent, the text which the user wrote for searching is displayed.

I cannot figure out what I have to add to my code in order the selected word of the dropdown list to be displayed in the input tag.

Thanks in advance for your time!


Solution

  • Even though the design of the component is 'strange' (I don't understand why the results are not in the same component?), I will provide you with a solution to your problem.

    Add a public function to your TypeaheadDropdownComponent that sets the search value.

    public void SetSearchValue(string value){
     _searchText = value;
     StateHasChanged();
    }
    

    Now, in the parent component, get the dropdown component's reference and call the SetSearchValue when selecting an item.

    
    
     ...
    <div class="col" @onfocusout="ClearHstrCommonSearchTextHandler">
       <label class="form-label">Location</label>
       <TypeaheadDropdownComponent @ref="TypeaheadRef" OnEmptySearchText="ClearHstrCommonSearchTextHandler"
                                   SearchMethod="SearchHstrCommon" />
       
       @if (showDropdownResults && HstrCommonDisplayed.Count > 0)
       {
          <div class="custom-dropdown-results">
             @foreach (HstrCommonDTO hstr in HstrCommonDisplayed)
             {
                <div class="custom-dropdown-results-item" @onclick="() => OnItemSelected(hstr)">
                   @hstr.Title <br />
                </div>
             }
          </div>
       }
    </div>
    ...
    @code{
     TypeheadDropdownComponent TypeaheadRef;
    
    
     public void OnItemSelected(HstrCommonDTO hstr){
      locationHstrId = hstr.HstrId; 
      showDropdownResults = false; 
      TypeaheadRef.SetSearchValue("<your value>");
     }
    }