Adventures with Blazor: My First Component, a Busy Button

blazor

I recently challenged myself to take one of my side projects, on of my many multi-million dollar ideas, and develop it using some technologies that I am not fully accustomed to.

For the back-end I am going to use Logic Apps, Azure Functions (JavaScript), Cosmos DB and for the front-end I am going to use Blazor.

What is Blazor?

I am glad you asked.

According to the Blazor Docs, Blazor lets you build interactive web UIs using C# instead of JavaScript. Blazor apps are composed of reusable web UI components implemented using C#, HTML, and CSS. Both client and server code is written in C#, allowing you to share code and libraries.

Blazor is a feature of ASP.NET, the popular web development framework that extends the .NET developer platform with tools and libraries for building web apps.

Prior to learning about Blazor, most of the user interfaces I created were done with React. I like React for a variety of reasons, but the biggest reason was React takes a component approach to building an interactive user interface without all the opinions, such as, Angular.

One of the components I had in my React app was a BusyButton component.

The BusyButton component allowed you to set some style properties, e.g. Caption, Icon and Color.

When the user clicked the BusyButton it would disable itself, preventing multiple click and then switch the text letting the user know something was happening

You could also set an OnClickCallback event callback if you wanted to respond to the click event of the button yourself, as opposed to it’s default behavior, e.g. a submit button.

<button class="btn btn-@Color" type="submit" disabled="@IsBusy" onclick="@OnClickCallback" @onclick:preventDefault="@PreventDefault"><i class="fal fa-@Icon"></i><span>@DisplayCaption</span></button>

@code {
    [Parameter]
    public string Icon { get; set; }

    [Parameter]
    public string Color { get; set; }

    [Parameter]
    public bool IsBusy { get; set; }

    private string DisplayCaption
    {
        get
        {
            if (IsBusy) return IsBusyCaption;
            return Caption;
        }
    }

    [Parameter]
    public string Caption { get; set; }

    [Parameter]
    public string IsBusyCaption { get; set; }

    [Parameter]
    public EventCallback<MouseEventArgs> OnClickCallback { get; set; }

    private bool PreventDefault
    {
        get
        {
            return OnClickCallback.HasDelegate;
        }
    }

    public BusyButton()
    {
        Color = "primary";
        IsBusy = false;
    }
}

The initial build out of the component in Blazor was fairly quick, surprisingly quick, the only tricky part was figuring out how to keep events from propagating if an OnClickCallback was provided.

Eventually I figured it out.

The differences can be seen in a Create and Delete operation.

@page "/organizations/create"

@using Teamaloo.WebApp.Components
@using Teamaloo.WebApp.Data

@inject AppState appState
@inject NavigationManager navigationManager
@inject OrganizationService organizationService

<h1>Add Organization</h1>

<EditForm Model="@organizationAddOptions" OnValidSubmit="(async () => await OnValidSubmitAsync())">

    <DataAnnotationsValidator />
    <ValidationSummary />

    <div class="form-group">
        <label for="name">Name</label>
        <InputText class="form-control" @bind-Value="organizationAddOptions.Name" placeholder="The name of the organization..." />
    </div>

    <BusyButton Caption="Save" IsBusyCaption="Saving..." Icon="check" IsBusy="@isBusy" />
    <a class="btn btn-link" href="@string.Format("/organizations")">Cancel</a>

</EditForm>

@code {
    private bool isBusy = false;

    private OrganizationAddOptions organizationAddOptions = new OrganizationAddOptions();

    private async Task OnValidSubmitAsync()
    {
        isBusy = true;

        var organization =
            await organizationService.AddAsync(organizationAddOptions);

        appState.AddOrUpdateOrganization(organization);

        isBusy = false;

        navigationManager.NavigateTo($"/organizations/{organization.Id}");
    }
}

In my Create operation, I have a local variable called isBusy which I bind to the IsBusy property of the BusyButton component. I do not provide an OnClientCallback event handler, the default submit behavior works for this use case.

When the Save button is clicked, the button disables and sets the text, in my case, to Saving....

@page "/organizations/{id:guid}/delete"

@using Teamaloo.WebApp.Components
@using Teamaloo.WebApp.Data

@inject AppState appState
@inject NavigationManager navigationManager
@inject OrganizationService organizationService

<h1>Organization</h1>

@if (isLoading)
{
    <p>Loading...</p>
}
else
{
    <form>

        <div class="form-group">
            <label for="name">Name</label>
            <p class="form-control-plaintext">@organization.Name</p>
        </div>

        <BusyButton Caption="Delete" IsBusyCaption="Deleting..." Icon="trash" color="danger" IsBusy="@isBusy" OnClickCallback="@(async () => await OnValidSubmitAsync())" />
        <a class="btn btn-link" href="@string.Format("/organizations/{0}", id)">Cancel</a>

    </form>
}

@code {
    [Parameter]
    public Guid id { get; set; }

    private bool isBusy = false;
    private bool isLoading = true;

    private Organization organization;

    protected override async Task OnParametersSetAsync()
    {
        isLoading = true;

        organization =
            await organizationService.GetByIdAsync(id);

        isLoading = false;
    }

    private async Task OnValidSubmitAsync()
    {
        await organizationService.DeleteByIdAsync(id);

        appState.DeleteOrganizationById(id);

        navigationManager.NavigateTo("/organizations");
    }
}

In my Delete operation I need to handle what happens when the BusyButton is clicked, to do this, I provide a reference to my OnValidateSubmitAsync method to the OnClientCallback property.

Like the Create operation, when the Delete button is clicked it disables the button, changes the text, but in this case, instead of using the button’s default behavior it calls my OnValidateSubmitAsync method.

This is where I had to include the option to set @onclick:preventDefault to true or the Delete operation would occur again, this time causing an error because the record no longer existed in the database.

So far, really liking Blazor, very easy to use, and the fact I don’t have to write JavaScript or TypeScript, makes it even more appealing.

Related Links

Leave a Reply

Your email address will not be published.