Blazor Custom Radio Buttons

Blazor Custom Radio Buttons

For a project I was looking at radio buttons but none seem to suit what I was looking for so I decided to create my own custom component to achieve what I needed.

This may not be the bes to rmost efficent way so please feel free to comment a better method or a way to improve what I have done.

The code is not generic which is what I would like to do at some point if possible but for now it is not required for this project.

The styling is not what I want to to look like but can be worked.

The project has a requirnment for each maintenance request that risk levels can be selected from High "H", medium "M" and low "L". There is a number of risks depending on the type or request either general or plant.

My structure is that there is a maintenance request with a unique ID and a Risk table with the risks for the type of maintenance request. From here there is a table that has two FK which are the risk ID and maintenance request ID with a column string which is set to one of the risk level either "H", "M" or "L".

The reason for also doing it this way is that more risks could be added over time so I wanted to implement a way for more to be added without the need to alter the code etc.

Video of the component in action. The styling is not complete and has work to do but the functionality is there.

Blazor Component - Full

@using SACOMaintenance.Common.ModelDB
@using System.Collections.ObjectModel;
@using System;

<div class="container-full">
    <RadzenFieldset Text="Risks">
        <div class="inner-radio-container">
        @foreach (var item in ItemsChosen)
        {

            <div class="container-outer">
                <div class="row">
                    @foreach (var riskItem in Risks)
                    {
                        if (riskItem.Id == item.RiskId)
                        {
                            <label>@riskItem.RiskName</label>
                        }
                    }
                </div>
                <div class="row">
                    <div class="input-container">
                        <input name="@item.RiskId" type="radio" id="H" class="radio-button-H"
                               value="H" checked="@(item.Level.Equals("H"))"
                               @onchange="_ => HandleChangeHigh(item)" />
                        <div class="radio-tile-H">
                            <label>H</label>
                        </div>
                    </div>
                    <div class="input-container">
                        <input name="@item.RiskId" type="radio" id="M" class="radio-button-M"
                               value="H" checked="@(item.Level.Equals("M"))"
                               @onchange="_ => HandleChangeMedium(item)" />
                        <div class="radio-tile-M">
                            <label>M</label>
                        </div>
                    </div>
                    <div class="input-container">
                        <input name="@item.RiskId" type="radio" id="L" class="radio-button-L"
                               value="H" checked="@(item.Level.Equals("L"))"
                               @onchange="_ => HandleChangeLow(item)" />
                        <div class="radio-tile-L">
                            <label>L</label>
                        </div>
                    </div>
                </div>
            </div>
        }
        </div>
    </RadzenFieldset>


        </div>

        @code{
            [Parameter]
            public ObservableCollection<Risk> Risks { get; set; }

            [Parameter]
            public List<MaintRequestInitiationRisk> ItemsChosen { get; set; } = new();

            [Parameter]
            public EventCallback<List<MaintRequestInitiationRisk>> ItemsChosenChanged { get; set; }

            void HandleChangeHigh(MaintRequestInitiationRisk item)
            {

                item.Level = "H";
                ItemsChosenChanged.InvokeAsync(ItemsChosen);
            }

            void HandleChangeMedium(MaintRequestInitiationRisk item)
            {

                item.Level = "M";
                ItemsChosenChanged.InvokeAsync(ItemsChosen);
            }

            void HandleChangeLow(MaintRequestInitiationRisk item)
            {

                item.Level = "L";
                ItemsChosenChanged.InvokeAsync(ItemsChosen);
            }
        }

Breaking the above code down into sections to explain what each part is doing.

[Parameter]
            public ObservableCollection<Risk> Risks { get; set; }

            [Parameter]
            public List<MaintRequestInitiationRisk> ItemsChosen { get; set; } = new();

            [Parameter]
            public EventCallback<List<MaintRequestInitiationRisk>> ItemsChosenChanged { get; set; }

The ObservableCollection Risks is getting all off the risks that there are so that the risk name can be gotten.

List ItemsChosen are the risks that are for the type of maintenance request and it has the risk level set to either H, M or L.

public EventCallback> ItemsChosenChanged is a callback to a list of risks and what the risk level is so that any changes to the risk level of any of the risks are passed back in order to update the model and database table.

This code snippet starts to loop through the risks that are associated with the request.

@foreach (var item in ItemsChosen)
        {

This code snippet below loops through the risks and then checks to see if the risk Id is the same as the current chosen risk and of so then it outputs the Risk Name

@foreach (var riskItem in Risks)
                    {
                        if (riskItem.Id == item.RiskId)
                        {
                            <label>@riskItem.RiskName</label>
                        }
                    }

This next code snippet sets the RiskId and the imput as a radio button. Then on check it calls a method to change the risk level.

<div class="input-container">
                        <input name="@item.RiskId" type="radio" id="H" class="radio-button-H"
                               value="H" checked="@(item.Level.Equals("H"))"
                               @onchange="_ => HandleChangeHigh(item)" />
                        <div class="radio-tile-H">
                            <label>H</label>
                        </div>
                    </div>

There is one for each of the risk levels.

The implementation in blazor for the component is where the Risks and Risk Items lists are passed to the component as parameters.

<RequestRiskChoice @bind-ItemsChosen="maintReqInitation.RiskListsChosen" Risks="maintReqInitation.Risks">
                    </RequestRiskChoice>

CSS Code for styling.

.container-full {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 100%;
}

.inner-radio-container {
    display: flex;
    justify-content: center;
    align-items: center;
    flex-wrap:wrap;
}

.container-outer {
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 25%;
    background-color: lightgray;
    padding: 5px 5px 5px 5px;
    margin: 5px 5px 5px 5px;
}

.input-container {
    display: flex;
    position: relative;
    height: 4rem;
    width: 4rem;
    margin: 0.5rem;
}

    .radio-button-H, .radio-button-M, .radio-button-L {
        opacity: 0;
        position: absolute;
        top: 0;
        left: 0;
        height: 100%;
        width: 100%;
        margin: 0;
        cursor: pointer;
    }

    .radio-tile-H, .radio-tile-M, .radio-tile-L {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        width: 100%;
        height: 100%;
        border: 2px solid red;
        border-radius: 5px;
        padding: 1rem;
        transition: transform 300ms ease;
    }

.radio-tile-M {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    border: 2px solid yellow;
    border-radius: 5px;
    padding: 1rem;
    transition: transform 300ms ease;
}
.radio-tile-L {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    border: 2px solid green;
    border-radius: 5px;
    padding: 1rem;
    transition: transform 300ms ease;
}
    .radio-button-H:checked + .radio-tile-H {
        background-color: red;
        border: 2px solid red;
        color: white;
        transform: scale(1.1, 1.1);
    }

    .radio-tile-label-H {
        color: white;
        background-color: red;
    }

    .radio-button-M:checked + .radio-tile-M {
        background-color: yellow;
        border: 2px solid yellow;
        color: white;
        transform: scale(1.1, 1.1);
    }

    .radio-tile-label-M {
        color: black;
        background-color: yellow;
    }

    .radio-button-L:checked + .radio-tile-L {
        background-color: green;
        border: 2px solid green;
        color: white;
        transform: scale(1.1, 1.1);
    }

    .radio-tile-label-L {
        color: white;
        background-color: green;
    }