Showing posts with label Dropdown. Show all posts
Showing posts with label Dropdown. Show all posts

An Enumeration-based Dropdown for WASM Blazor

This is the enumeration of the Sons of Israel    וּבְנֵ֣י יִשְׂרָאֵ֣ל לְֽמִסְפָּרָ֡ם

I found the above reference to enumeration in Chronicals 27:1 that seemed appropriate. The Hebrew word (l'misparam) here comes from the root ספר which is a bit unusual in that it has a couple of different meanings. In this instance, it relates to counting, but in other contexts it relates to writing.

I've often used dropdown controls that derive their dropdown items from a collection of values taken from a database. For some dropdowns where only a few items are needed whose values don't frequently change it may be simpler to use a dropdown that uses an enumeration as a data source.

I've created a sample enumeration called Desserts shown below. I'll elaborate on the use of the Display attribute momentarily.

  
   public enum Dessert
    {
        [Display(Name ="Chocolate Cake")]
        ChocolateCake,
        Baklavah,
        [Display(Name = "Fruit Compote")]
        FruitCompote,
        Tiramisu,
        [Display(Name = "Toffee Squares")]
        ToffeeSquares
    }
  
  

The Razor code seen here is very similar to the code I used for my generic dropdown . I make use of the Enum.GetValues() method to iterate over the various values found in my Desserts enumeration.


@typeparam TEnum
<div class="dropdown" @onfocusout="OnFocusOut">
    <button class="btn btn-primary dropdown-toggle @(Show ? "show" : string.Empty)" data-toggle="dropdown" type="button" @onmousedown="OnLabelMouseDown" aria-haspopup="true" aria-expanded="false">
        @Label
    </button>
    <div class="dropdown-menu @(Show ? "show" : string.Empty)">
        @{
            foreach (var enumValue in Enum.GetValues(typeof(TEnum)).Cast<TEnum>())
            {
                <button class="dropdown-item" type="button" @onmousedown="(() => OnItemMouseDown(enumValue))">@enumValue.DisplayName()</button>
            }
        }
    </div>
</div>

  

The C# code-behind is seen below:

  
public partial class EnumDropDown<TEnum> where TEnum : struct, Enum?
    {
        [Parameter]
        public EventCallback<TEnum?> Callback { get; set; }
        [Parameter]
        public TEnum? Selected { get; set; } = null;
        [Parameter]
        public string DefaultLabel { get; set; } = "Select";
        public string? Label => Selected == null ? DefaultLabel : Selected.DisplayName();
        private bool Show { get; set; } = false;

        private void OnFocusOut()
        {
            Show = false;
            StateHasChanged();
        }

        public void OnLabelMouseDown()
        {
            Show = !Show;
        }

        public async Task OnItemMouseDown(TEnum? item)
        {
            Show = false;
            if (item != null)
            {
                if (Callback.HasDelegate)
                {
                    await Callback.InvokeAsync(item);
                }
            }
        }
    }

The point of using the Display attribute from the System.ComponentModel.DataAnnotations namespace is to provide a mechanism for displaying spaces and reordering items in the dropdown. C# doesn't allow you to put spaces in the enum member names, so using the Display attribute is a simple workaround for this limitation. As you can see, at least 3 of the member names require spaces. You'll see that in both the Razor and C# code-behind for this component I employ an extension method called DisplayName() (see below). This method checks the selected member of the enumeration for the presence of a Display attribute. If the attribute is present and it has a Name parameter, that parameter value is returned instead of the ToString() value of the enumeration member. This trick allows us to display member names with spaces (and possibly other characters) that can't normally be used in the Enum definition.

  
public static class EnumExtensions
    {
        public static string? DisplayName(this Enum value)
        {
            if (value == null)
            {
                return null;
            }
            // Read the Display attribute name
            var name = value.ToString();
            if (name != null)
            {
                var member = value.GetType().GetMember(name)[0];
                var displayAttribute = member.GetCustomAttribute<DisplayAttribute>();
                if (displayAttribute != null)
                {
                    return displayAttribute.GetName();
                }
            }
            return Enum.GetName(value.GetType(), value);
        }
    }  
  
  

You can watch a short video demonstrating the use of the dropdown below.

The source code shown in this posting and a simple demo Razor page utilizing the dropdown can be found in my Github repository.

A Generic Dropdown control for WASM Blazor

Drop down, ye heavens, from above      הַרְעִיפוּ שָׁמַיִם מִמַּעַל

I thought that the Hebrew quote above and it's accompanying translation from the King James Bible (Isaiah 45:8) would make a good introduction to a short post on designing a dropdown control in Blazor. Truth be told, the King James translation of the first word isn't really that accurate. The Hebrew word "Harifu" translates roughly as "imparts" though it can also refer to "shower". However, since I'm talking about dropdown controls I figure we'll stick with King James' translation.

I recently needed to build a form for an application where I wanted to incorporate a dropdown control that could bind to any kind of class, and not just a string. I saw a few examples on Stack Overflow but none of them incorporated all the features that I wanted, and in a few cases, the code didn't work correctly. I thought I'd share the simple solution I came up with in case someone out there is looking for something similar.

In the past, I'd always created Blazor dropdowns using <select> and <option> elements. They work fine when you're dealing with string values but in this instance, I wanted to be able to iterate over a List of objects of some specified type and leverage a property or method to display the label for each dropdown item. Looking at the Dropdown component found in Bootstrap 5, they use a combination of <div>, <button> and/or <a> elements to produce a working widget. Since our goal is to create a pure Blazor component, we have to make some changes to the basic Bootstrap 5 design to handle mouse events and bind the selected dropdown item appropriately.

The DropDown<TItem> Razor class and its code behind below show how this accomplished. You may notice that I'm using a handler for the @onfocusout event on this control. While looking at other examples which did not include anything other than an @onmousedown handler, I noticed that the dropdown wouldn't close when you released the mouse button. Some examples tried using a @onblur event to resolve this, but I found this didn't work satisfactorily, hence the @onfocusout event show here.


@typeparam TItem
<div class="dropdown" @onfocusout="OnFocusOut">
    <button class="btn btn-primary dropdown-toggle @(Show ? "show" : string.Empty)" data-toggle="dropdown" type="button" @onmousedown="OnMouseDown" aria-haspopup="true" aria-expanded="false">
        @Label
    </button>
    <CascadingValue Value="@this">
        <div class="dropdown-menu @(Show ? "show" : string.Empty)">
            @ChildContent
        </div>
    </CascadingValue>
</div>


 public partial class DropDown<TItem>
     {
        [Parameter]
        public RenderFragment? Label { get; set; }
        [Parameter]
        public RenderFragment? ChildContent { get; set; }
        [Parameter]
        public EventCallback<TItem?> OnSelected { get; set; }
        private bool Show { get; set; } = false;
        private void OnMouseDown()
        {
            Show = !Show;
        }

        private void OnFocusOut()
        {
            Show = false;
            StateHasChanged();
        }

        public async Task HandleSelect(TItem? item)
        {
            Show = false;
            await OnSelected.InvokeAsync(item);
        }
    }

In order to display the dropdown items, I use the following code. Note that I chose to use <button> elements instead of <a> anchors. I found that anchors sometimes exhibit some odd behavior when you specify a hash character for the href attribute value.


@typeparam TItem
<button class="dropdown-item" type="button" @onmousedown="OnMouseDown">@RenderLabel</button>


public partial class DropDownItem<TItem> : ComponentBase
    {
        [CascadingParameter]
        public DropDown<TItem> DropDown { get; set; }
        [Parameter]
        public TItem Item { get; set; }
        [Parameter]
        public RenderFragment<TItem> Label { get; set; }

        private async Task OnMouseDown()
        {
            if (DropDown != null)
            {
                await DropDown.HandleSelect(Item, Label);
            }
        }

        private RenderFragment RenderLabel => Label != null && Item != null ? Label(Item) : null;
    }

A simple example of how to use this control is seen below. The type of class I use here, City, has a string property called Name that I use to label each dropdown item. You can see the relevant CallbackEvent (OnSelected) and other code referenced here in this Github repository.


<DropDown TItem="City" OnSelected="@OnSelectedCity">
    <Label>@((SelectedCity == null) ? "Select a City" : SelectedCity.Name)</Label>
		<ChildContent>
			@if (Cities != null)
				foreach (var city in Cities)
                {
					<DropDownItem TItem="City" Item="@city">
                    <Label>@city.Name</Label>
                    </DropDownItem>
                }
		</ChildContent>
</DropDown>

You can watch a short video demonstrating the use of the dropdown below.

The source code shown in this posting and a simple demo Razor page utilizing the dropdown can be found in my Github repository.