Blazor new and improved Search Box

 Back in March, I posted a new Blazor data grid with a search box (see post).  Since then I have found a new and improved search box that I wanted to implement.  The search part is implemented the same.  What I added was the ability for the user to narrow down the search by selecting which field they wanted to search on.  This will help the user find what they are looking for and in a large collection, the search will end up being faster.

The goal of making the change was to make the search box a new component and have it handle the UI functionality part.

Prep Work

Here is the prep work I did before adding the search box:

  1. Started with the source code from the post listed above.
  2. Upgraded the project to .Net 5
  3. Upgraded the Nuget packages that were out of date.
  4. Ensured every build and run.


Change to a Component

I wanted to create a component for the search box so I could use it in other applications.  

1. Create a Folder under UIControls called "SearchBox"
2. Created a new razor component
3. Moved the HTML for the Old search box into the new component

Update the component

The first change was to add a form tag, this will allow us to treat the box as a whole.  I then added a select tag to the form.  We will be populating this with a list of fields that the calling parent will provide.

We need to add a check to this section to only display the field selection options if there are actually some options defined.  This will prevent any null exceptions.

<div class="row searchactionrow">
    <div class="col-md-6">
        <div class="input-group">
            <form @onsubmit="@SearchClick">
                <input type="text" name="" placeholder="Search For ..."  @bind="searchTerm" @onkeyup="RunLookup">
                @if (SearchOptions != null && SearchOptions.Count > 0)
                {
                    <select @bind="@filter">
                        @foreach (var opt in SearchOptions)
                        {
                            <option>@opt</option>
                        }
                    </select>
                }
                <input class="btn btn-default" type="submit" name="" value="Search" disabled="@disableSearchButton">
                @if (filterIsOn)
                {
                    <input class="btn btn-default btnClear" value="Clear"       @onclick="ClearFilter">
                }
            </form>
        </div>
    </div>
</div>

Add the component to the Parent

In the parent we needed to add a collection of the field options for the search box will be using.  This is just a string collection with the property names of the data objects in the collection.  We could use reflection to pull these out.  when we make the data grid itself a component, we will do that.  For now, to keep the focus on the search box change we will just manually create the list.  For our demo it is simple:

        //set filter options
        filterOptions.Add("Date");
        filterOptions.Add("Summary");
        filterOptions.Add("TemperatureC");
        filterOptions.Add("TemperatureF");

The next step was to add the new component to the page:

<UILab.UIControls.SearchBox.SearchBox SearchOptions="filterOptions" OnSearchClick="NewSearch" />

You can see where we set the filter options to the component parameter "SearchOptions" from above:

    SearchOptions="filterOptions" 

We will be looking at the filter, field selected if any, and the search term when we execute the search.  We created a new method to handle the different ways to search now.

     protected Task NewSearch(string term, string filter)

New Search Method

The new search method will accept the search term and the filter.  If there is no filter value, it will do the search the old way.  If the search term is empty, we will just clear the search results and filter results.

If there the filter has a value, we need to change how we search.  We need to limit the search to just the field asked for.  We could / should use the dynamic linq library to create the linq we need, but we are focusing on just the search box here so I kept it simple:

    private void ApplyFilter(string filter, string searchTerm)
    {
        if (!string.IsNullOrEmpty(filter))
        {
            List<WeatherForecast> matches = new List<WeatherForecast>();
            if (filter == "Summary")
            {
                matches = (from a in dataCollection
                           where (a.Summary.ToLower().Contains(searchTerm))
                           select a).ToList();            }
            else if (filter == "Date")
            {
                matches = (from a in dataCollection
                           where (a.Date.ToShortDateString().ToLower().Contains(searchTerm))
                           select a).ToList();
            }
            else if (filter == "TemperatureC")
            {
                matches = (from a in dataCollection
                           where (a.TemperatureC.ToString().Contains(searchTerm))
                           select a).ToList();
            }
            else if (filter == "TemperatureF")
            {
                matches = (from a in dataCollection
                           where (a.TemperatureF.ToString().Contains(searchTerm))
                           select a).ToList();
            }
            RefreshGridValues(matches);
        }
    }

As you can see, it is a fairly straightforward linq on a collection.


Search Box Component Callback Method

This is where things became interesting.  I needed a callback method on the search button that would execute the search and populate the table with the results.  

In the search box component, I created a Func, "OnSearchClick" that we can set the "NewSearch" method to in the parent form:

    OnSearchClick="NewSearch"

This allows us to call the NewSearch method from the child component, passing in the search term and the filter.

[Parameter] public Func<string, string, Task> OnSearchClick { get; set; } = null;

The Func definition is as follows:
1. sting, string is for the 2 string parameters needed by the NewSearch method in the parent
2. Task is the return method.

The form has an onSubmit event that we set to the SearchClick method in the component:

            private void SearchClick()
            {
                filterIsOn = true;
                OnSearchClick(searchTerm, filter);
            }

Once called, we set the flag to display the "Clear" button so a user can reset back to the default data set.  Then we call the OnSeachClick event that is mapped to the NewSearch Method in the parent.

Clearing the Results

We needed a way to allow the user to clear out the search results and go back to the original data set.  We have the "Clear" button for this.

Once clicked it will:
1. set the search term to an empty string
2. set the filter value to an empty string
3. set the filter on the flag to off
4. Call the NewSearch method with the blank search term that will trigger the NewSearch method to load the initial data and clean out the search results.

I am not a fan of hiding controls and showing them with the stat changes, but the Clear button, it seems to work better.


Search Button State Management

An additional UX change I did was to make the search button disabled until the user put in at least 1 char.

I did this with a "disableSearchButton" flag.  As soon as a user enters in char, we set the flag to off.

I do check to make sure the search term has at least 1 char in it.  So if a user enters a value, and then backspaces to remove it, the button will disable since there are no characters for the search term.

Summary

This was a cool project.  I ended up with a reusable search box that I can add to all my data grids now.  The CSS for the control was left simple, but you can add its own CSS and use the new nested CSS to limit the use to just the search box.



Comments

Popular posts from this blog

Yes, Blazor Server can scale!

Blazor Wizard Step Component