November 2018

Volume 33 Number 12

[Cutting Edge]

Blazor Custom Components

By Dino Esposito

Dino EspositoThe Blazor programming experience leads naturally toward an extensive use of components. In Blazor, a component is a .NET class that implements some UI rendering logic based on some state. Blazor components are close to the idea of Web components from the upcoming W3C specification and analogous implementations in single-page application (SPA) frameworks. A Blazor component is a combination of HTML, C# and interoperable JavaScript code and CSS that together act as a single element with a common programming interface. A Blazor component can fire events and expose properties in much the same way an HTML DOM element does.

In my recent article on Blazor, I presented a couple of dedicated components. The first provided a comprehensive UI to type some text, run a query and fire an external event with retrieved data as an argument. The second component in the example acted as a made-to-measure, non-customizable grid that would handle the event, grab the data and populate the internal table. The input field collecting the query text offered auto-completion via the type-ahead Bootstrap extension. In this article, I build from that point and discuss the design and implementation of a completely Blazor-based component for type-ahead functionality. At the end of the task, you’ll have a reusable CSHTML file with no dependencies on anything but the core Bootstrap 4 bundle.

The Public Interface of TypeAhead

The goal is to produce a Blazor-native component that behaves like an extended version of the classic typeahead.js component (see twitter.github.io/typeahead.js). The final component lies in the middle of a textbox and a dropdown list. As users type in the text input field, the component queries a remote URL and gets hints of possible values to enter. The JavaScript typeahead provides suggestions but doesn’t force users to accept any of the suggestions. In other words, custom text different from suggestions is still acceptable.

In some cases, though, you want users to enter text and then select from a list of valid entries. Think, for example, of an input field where you need to provide a country name or a customer name. There are more than 200 hundreds countries in the world and often hundreds (or more) customers in a software system. Should you really use a dropdown list? A smarter type-ahead component could let users type in, say, the word “United,” and then present a list of matching countries like United Arab Emirates, United Kingdom and United States. If any free text is typed, the code automatically clears the buffer.

Another related issue: When free text isn’t an option, you likely need to also have a code. You ideally want to type the country or customer name and have posted from the hosting form the ID of the country or the unique identifier for the customer. In pure HTML and JavaScript, you need some extra script that adds a hidden field and manages selections from the typeahead plug-in. A Blazor native component will have all these capabilities hidden under the hood. Let’s see how to write such a component.

Design of the Component

The Typeahead component is meant to be an additional input field to use within an HTML form. It’s made of a couple standard input fields, one of type text and one hidden. The hidden input field will feature a NAME attribute that will make it fully interoperable if used within a form. Here’s some sample markup for using the new component:

<typeahead style="margin-top: 40px;"
           class="form-control"
           url="/hint/countries1"
           selectionOnly="true"
           name="country"
           placeholder="Type something"
           onSelectionMade="@ShowSelection" />

As you can see, the component mirrors some HTML-specific attributes such as Style, Class, Name and Placeholder side-by-side with custom attributes such as Url, SelectionOnly and events like onSelectionMode. The Url attribute sets the remote endpoint to call for hints, while the Boolean SelectionOnly attribute controls the behavior of the component and whether input should only come from a selection or if freely typed text is allowed as input. Let’s take a look at the Razor markup and related C# of the component in Figure 1. You’ll find full details in the typeahead.cshtml file of the sample project at bit.ly/2ATgEKm.

Figure 1 Markup of the Typeahead Component

<div class="blazor-typeahead-container">
  <div class="input-group">
    <input type="text" class="@Class" style="@Style"
           placeholder="@Placeholder"
           oninput="this.blur(); this.focus();"
           bind="@SelectedText"
           onblur="@(ev => TryAutoComplete(ev))" />
    <input type="hidden" name="@Name" bind="@SelectedValue" />
    <div class="input-group-append">
      <button class="btn btn-outline-secondary dropdown-toggle"
              type="button" data-toggle="dropdown"
              style="display: none;">
      </button>
      <div class="dropdown-menu dropdown-menu-right
                  scrollable-menu @(_isOpen ? "show" : "")"
         style="width: 100%;">
        <h6 class="dropdown-header">@Items.Count item(s)</h6>
        @foreach (var item in Items)
        {
          <a class="dropdown-item"
           onclick="@(() => TrySelect(item))">
            @((MarkupString) item.MenuText)
          </a>
        }
      </div>
    </div>
  </div>
</div>

Figure 2 lists the properties defined and supported by the component.

Figure 2 Properties of TypeAhead Component

Name Description
Class Gets and sets the collection of CSS classes to be applied to the internal HTML elements of the component.
Name Gets and sets the value of the NAME attribute when the component is incorporated in an HTML form.
Placeholder Gets and sets the text to serve as a placeholder for the HTML elements being rendered.
SelectedText Gets and sets the display text. This serves as both the initial value and the selected text, whether typed by the user or picked from a dropdown menu.
SelectedValue Gets and sets the selected value. This may or may not match the value of SelectedText. The selected value is bound to the hidden field, whereas SelectedText is bound to the text field.
SelectionOnly Boolean value that determines whether free text is allowed or users are forced to only select one of the provided hints.
Style Gets and sets the collection of CSS styles to be applied to the internal HTML elements of the component.
Url Address of the remote endpoint to call for hints.

It’s important to note that the HTML layout of the Typeahead component is more complex than just a couple of input fields. It’s designed to receive hints in the form of a TypeAheadItem class defined as shown here:

public class TypeAheadItem
{
  public string MenuText { get; set; }
  public string Value { get; set; }
  public string DisplayText { get; set; }}

Any suggested hint is made of a display text (such as the country name) that sets the input field, a menu text (such as a richer HTML-based text) that appears in the dropdown list, and a value (such as the country code) that uniquely identifies the selected item. The value attribute is optional, but serves a crucial purpose in case the Typeahead component is used as a smart dropdown list. In this case, the role of the Value attribute is the same as the value attribute of the HTML Option element. The code located at the remote endpoint referenced by the Url attribute is expected to return an array of TypeAheadItem entities. Figure 3 provides an example of an endpoint that returns the list of country names that match a query string.

Figure 3 Returning a List of Country Names

public JsonResult Countries(
  [Bind(Prefix = "id")] string filter = "")
{
  var list = (from country in CountryRepository().All();
    let match =
      $"{country.CountryName} {country.ContinentName}".ToLower()
    where match.Contains(filter.ToLower())
    select new TypeAheadItem()
    {
      Value = country.CountryCode,
      DisplayText = country.CountryName,
      MenuText = $"{country.CountryName} <b>{country.ContinentName}</b>
        <span class='pull-right'>{country.Capital}</span>"
    }).ToList();
  return Json(list);
}

There are a couple of things to notice here and both have to do with expressivity. There should be some sort of intimacy between the hint server and the type-ahead component. As a developer, you have the final word about that. In the sample code discussed in this article, the Web service endpoint returns a collection of Type­AheadItem objects. In a customized implementation, though, you can have the endpoint return a collection of application-specific properties and have the component decide dynamically which property should be used for the text and which for the value. The TypeAheadItem object, however, supplies a third property—MenuText—that contains an HTML string to be assigned to the dropdown list item, as shown in Figure 4.

Typeahead Blazor Component in Action
Figure 4 Typeahead Blazor Component in Action

The MenuText is set to the concatenation of country name and continent name, along with the name of the capital right justified in the control. You can use any HTML formatting that suits your visual needs.

Having the markup statically determined on the server isn’t ideal, however. A much better approach would be sending any relevant data to the client and let the markup be specified as a template parameter to the component. Not coincidentally, templated components are a cool upcoming feature of Blazor that I’ll cover in a future column.

In addition, note that in Figure 4 the user is typing the name of a continent and receives hints for all the countries in that continent. The implementation of the Countries endpoint, in fact, matches the query string (“oce” in the screenshot) against the concatenation of country and continent name, as in the LINQ variable match you see in the code snippet above.

This is the first step of an obviously more powerful way to search for related data. You can, for example, split the query string by commas or spaces to obtain an array of filter strings and then combine them by OR or AND operators. Yet another improvement is the template of the drop-down list item that in the current example is hard-coded in the endpoint implementation, but can be provided as an HTML template along with the query string.

The bottom line is that the Typeahead component presented here uses the Twitter JavaScript typeahead plugin only as a starting point. The Blazor components allow developers  to easily hide implementation details, ultimately raising the abstraction level of the code that Web developers write.

The Mechanics of the Component

In Figure 1, you have examined the markup behind the Blazor Typeahead component. It relies on Bootstrap 4 and counts on a few custom CSS styles defined in the same CSHTML source file. If you like developers to customize those styles, you simply provide documentation for them. At any rate, developers willing to use the Typeahead component don’t have to know about custom CSS styles such as scrollable-menu.

The component is articulated as a Bootstrap 4 input group made of a text input field, a hidden field, and a drop-down button. The text input field is where the user types any query string. The hidden field is where the value of the accepted hint is stored to be forwarded through any host HTML form. Only the hidden field has the HTML name attribute set. The drop-down button provides the menu with hints. The list of menu items is populated any time new text is typed into the input field. The button is not visible by default but its drop-down window is programmatically shown whenever needed. This is done by leveraging Blazor’s data binding capabilities. An internal Boolean variable is defined (_isOpen) that determines whether the “show” CSS class of Bootstrap 4 should be added to the drop-down section of the button. Here’s the code:

<div class="dropdown-menu
            dropdown-menu-right
            scrollable-menu
            @(_isOpen ? "show" : "")"> ...
</div>

The Blazor bind operator is used to bind the SelectedText property to the value property of the text input field and the SelectedValue property to the value property of the hidden field.

How do you trigger the remote query for hints? In plain HTML 5, you would define a handler for the input event. The change event is not appropriate for text boxes as it triggers only once the focus has been lost, which doesn’t apply in this particular case. In the version of Blazor used for the article (version 0.5.0), you can attach some C# code to the input event, but it doesn’t really work as expected yet.

As a temporary solution, I attached the code that populates the drop-down to the blur event and added some JavaScript code to handle the input event that just calls blur and focus at the DOM level. As you can see in Figure 1, the TryAutoComplete method that runs in response to the blur event places the remote call, grabs a JSON array of TypeAheadItem objects, and populates the internal Items collection:

async Task TryAutoComplete(UIFocusEventArgs ev)
{
  if (string.IsNullOrWhiteSpace(SelectedText))
  {
    Items.Clear();
      _isOpen = false;
    return;
  }
  var actualUrl = string.Concat(Url.TrimEnd('/'), "/", SelectedText);
  Items = await HttpExecutor.GetJsonAsync<IList<TypeAheadItem>>(actualUrl);
    _isOpen = Items.Count > 0;
}

When this happens the dropdown is populated with menu items and displayed, like so:

@foreach (var item in Items)
{
  <a class="dropdown-item"
    onclick="@(() => TrySelect(item))">
    @((MarkupString) item.MenuText)
  </a>
}

Note the cast to MarkupString that is the Blazor counterpart to Html.Raw in ASP.NET MVC Razor. By default any text processed by Razor is encoded, except when the expression is cast to the MarkupString type. Hence, if you want HTML to be displayed, you must pass through the MarkupString cast. Every time a menu item is clicked, the method TrySelect runs, like so:

void TrySelect(TypeAheadItem item)
{
  _isOpen = false;
  SelectedText = item.DisplayText;
  SelectedValue = item.Value;
  OnSelectionMade?.Invoke(item);
}

The method receives the TypeAheadItem object associated with the clicked element. Next, it closes the drop-down by setting _isOpen to false and updates SelectedText and SelectedValue as appropriate. Finally, it calls StateHasChanged to refresh the user interface and raises the custom SelectionMade event.

Connecting to the Typeahead Component

A Blazor view that uses the Typeahead component will bind portions of its user interface to the SelectionMade event. Again, for the changes to take effect the StateHasChanged method should be invoked, with this code:

void ShowSelection(TypeAheadItem item)
{
  _countryName = item.DisplayText;
  _countryDescription = item.MenuText;
  this.StateHasChanged();
}

In the code snippet, data coming with the event is bound to the local properties of the view and once the DOM is refreshed the view is automatically updated (see Figure 5).

The Updated View
Figure 5 The Updated View

Wrapping Up

Modern Web front ends are more and more made of components. Components raise the abstraction level of the markup language and provide a much cleaner way to create Web content. Like other client-side frameworks, Blazor has its own definition of custom components to speed up and simplify development. The source code for this article can be found at bit.ly/2ATgEKm, side by side with last month’s column about using the JavaScript typeahead plug-in.


Dino Esposito  has authored more than 20 books and 1,000-plus articles in his 25-year career. Author of “The Sabbatical Break,” a theatrical-style show, Esposito is busy writing software for a greener world as the digital strategist at BaxEnergy. Follow him on Twitter: @despos.

Thanks to the following Microsoft technical expert for reviewing this article: Daniel Roth


Discuss this article in the MSDN Magazine forum