Building database applications in Blazor - Part 4 - UI controls

catalogue

introduce

Repositories and databases

assembly

RouteViews

form

UI control

UIBase

Some examples

UIButton

UIColumn

UILoader

UIContainer/UIRow/UIColumn

summary

introduce

This article is the fourth in a series on building Blazor database applications. This article focuses on the components we use in the UI, and then focuses on how to build common UI components from HTML and CSS.

  1. Project structure and framework.
  2. Services - building the CRUD data layer.
  3. View component - CRUD editing and viewing operations in the UI.
  4. UI components -- building HTML/CSS controls.
  5. View component - CRUD list operation in UI.

Repositories and databases

The repository for the article has been moved to CEC.Blazor.SPA repository.   CEC.Blazor GitHub repository Obsolete and will be deleted.

There is an SQL script in / SQL in the repository to build the database.

You can see the Server and WASM versions of the projects running here on the same site.

assembly

To learn more about components, please read my article Learn more about Blazor components.

Everything in the Blazor UI (except the start page) is a component. Yes, applications, routers... They are all components. Not all components emit Html.

You can divide components into four categories:

  1. RouteViews - these are top-level components. The view is combined with the layout to form a display window.
  2. Layouts -- the combination of layout and view forms the display window.
  3. Forms -- a form is a logical collection of controls. Edit form, display form, list form and data entry wizard are classic forms. The form contains controls -- not HTML.
  4. Controls -- controls either display something -- emit HTML -- or execute some unit of work. Text box, drop-down menu, button and grid are classic HTML emission controls. Applications, routers, and authentication are controls that perform units of work.

RouteViews

RouteViews is application specific. The only difference between RouteView and Form is that RouteView declares one or more routes through the @ Page instruction. The Router component declared in the root App will be set to a specific code assembly AppAssembly. This is the route that searches for all assemblies that claim routes at startup.

In the application, RouteViews are declared in the WASM application library.  

The weather forecast viewer and list view are shown below.

// Blazor.Database/RouteViews/Weather/WeatherViewer.cs
@page "/weather/view/{ID:int}"

<WeatherForecastViewerForm ID="this.ID" ExitAction="this.ExitToList"></WeatherForecastViewerForm>

@code {
    [Parameter] public int ID { get; set; }

    [Inject] public NavigationManager NavManager { get; set; }

    private void ExitToList()
        => this.NavManager.NavigateTo("/fetchdata");
}
// Blazor.Database/RouteViews/Weather/FetchData.cs
@page "/fetchdata"

<WeatherForecastComponent></WeatherForecastComponent>

form

We saw the form in the last article. They are application specific.

The following code shows the weather viewer. Are UI controls without HTML tags.

// Blazor.Database/Components/Forms/WeatherForecastViewerForm.razor
@namespace Blazor.Database.Components
@inherits RecordFormBase<WeatherForecast>

<UIContainer>
    <UIFormRow>
        <UIColumn>
            <h2>Weather Forecast Viewer</h2>
        </UIColumn>
    </UIFormRow>
</UIContainer>
<UILoader Loaded="this.IsLoaded">
    <UIContainer>
        <UIFormRow>
            <UILabelColumn>
                Date
            </UILabelColumn>
            <UIInputColumn Cols="3">
                <InputReadOnlyText Value="@this.ControllerService.Record.Date.ToShortDateString()"></InputReadOnlyText>
            </UIInputColumn>
            <UIColumn Cols="7"></UIColumn>
        </UIFormRow>
        <UIFormRow>
            <UILabelColumn>
                Temperature °C
            </UILabelColumn>
            <UIInputColumn Cols="2">
                <InputReadOnlyText Value="@this.ControllerService.Record.TemperatureC.ToString()"></InputReadOnlyText>
            </UIInputColumn>
            <UIColumn Cols="8"></UIColumn>
        </UIFormRow>
        <UIFormRow>
            <UILabelColumn>
                Temperature °f
            </UILabelColumn>
            <UIInputColumn Cols="2">
                <InputReadOnlyText Value="@this.ControllerService.Record.TemperatureF.ToString()"></InputReadOnlyText>
            </UIInputColumn>
            <UIColumn Cols="8"></UIColumn>
        </UIFormRow>
        <UIFormRow>
            <UILabelColumn>
                Summary
            </UILabelColumn>
            <UIInputColumn Cols="9">
                <InputReadOnlyText Value="@this.ControllerService.Record.Summary"></InputReadOnlyText>
            </UIInputColumn>
        </UIFormRow>
    </UIContainer>
</UILoader>
<UIContainer>
    <UIFormRow>
        <UIButtonColumn>
            <UIButton AdditionalClasses="btn-secondary" ClickEvent="this.Exit">Exit</UIButton>
        </UIButtonColumn>
    </UIFormRow>
</UIContainer>

The code behind the page is relatively simple -- the complex is the template code in the parent class. It loads record specific controller services.

// Blazor.Database/Components/Forms/WeatherForecastViewerForm.razor.cs
public partial class WeatherForecastViewerForm : RecordFormBase<WeatherForecast>
{

    [Inject] private WeatherForecastControllerService ControllerService { get; set; }

    protected async override Task OnInitializedAsync()
    {
        this.Service = this.ControllerService;
        await base.OnInitializedAsync();
    }
}

UI control

UI controls emit HTML and CSS tags. All controls here are based on the Bootstrap CSS framework. All controls inherit from ComponentBase and UI controls inherit from UIBase.

UIBase

UIBase inherits from Component  . It builds an HTML DIV block that can be turned on or off.

Let's look at some UIBase in detail.

You can set HTML block tags using the Tag parameter. It can only be set by inherited classes.

protected virtual string HtmlTag => "div";

Control CSS classes are built using the CssBuilder class. Inheritance classes can set a primary CSS value and add any number of secondary values they want. CSS classes can be added through the AdditionalClasses parameter or by defining the class attribute.

[Parameter] public virtual string AdditionalClasses { get; set; } = string.Empty;
protected virtual string PrimaryClass => string.Empty;
protected List<string> SecondaryClass { get; private set; } = new List<string>();

protected string CssClass
=> CSSBuilder.Class(this.PrimaryClass)
    .AddClass(SecondaryClass)
    .AddClass(AdditionalClasses)
    .AddClassFromAttributes(this.UserAttributes)
    .Build();

You can hide or disable the control with two parameters. Displays ChildContent when Show is true. If Show is false, HideContent is displayed. If it is not null, nothing is displayed.

[Parameter] public bool Show { get; set; } = true;
[Parameter] public bool Disabled { get; set; } = false;
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public RenderFragment HideContent { get; set; }

Finally, the control captures any additional attributes and adds them to the markup element.

[Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary<string, object> UserAttributes { get; set; } = new Dictionary<string, object>();

This control builds the input RenderTree code.

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
    if (this.Show)
    {
        builder.OpenElement(0, this.HtmlTag);
        if (!string.IsNullOrWhiteSpace(this.CssClass)) builder.AddAttribute(1, "class", this.CssClass);
        if (Disabled) builder.AddAttribute(2, "disabled");
        builder.AddMultipleAttributes(3, this.UserAttributes);
        if (this.ChildContent != null) builder.AddContent(4, ChildContent);
        else if (this.HideContent != null) builder.AddContent(5, HideContent);
        builder.CloseElement();
    }
}

Some examples

The rest of this article covers some UI controls in more detail.

UIButton

This is a standard Bootstrap button.  

  1. Type sets the button type.
  2. PrimaryClass   set up.
  3. ButtonClick   Handle the button click event and call EventCallback.
  4. Show and Disabled handle button status.

// Blazor.SPA/Components/UIComponents/Base/UIButtons.cs
@namespace Blazor.SPA.Components
@inherits UIBase
@if (this.Show)
{
    <button class="@this.CssClass" @onclick="ButtonClick" type="@Type" disabled="@this.Disabled" @attributes="UserAttributes">
        @this.ChildContent
    </button>
}
@code {
    [Parameter] public string Type { get; set; } = "button";
    [Parameter] public EventCallback<MouseEventArgs> ClickEvent { get; set; }
    protected override string PrimaryClass => "btn mr-1";
    protected async Task ButtonClick(MouseEventArgs e) => await this.ClickEvent.InvokeAsync(e);
}

Here's some code that shows the control you're using.

<UIButton Show="true" Disabled="this._dirtyExit" AdditionalClasses="btn-dark" ClickEvent="() => this.Exit()">Exit</UIButton>

UIColumn

This is a standard Bootstrap column.  

  1. Cols   Define number of columns
  2. PrimaryCss is built from Cols.
  3. Base   RenderTreeBuilder builds the control as a div.  

// Blazor.SPA/Components/UIControls/Base/UIColumn.cs
public class UIColumn : UIBase
{
    [Parameter] public virtual int Cols { get; set; } = 0;
    protected override string PrimaryClass => this.Cols > 0 ? $"col-{this.Cols}" : $"col";
}

UILoader

This is a wrapper control designed to save in child content for error checking. It renders its children only when IsLoaded is true. This control saves a lot of error checking in child content.

@namespace Blazor.SPA.Components
@inherits UIBase

@if (this.Loaded)
{
    @this.ChildContent
}
else
{
    <div>Loading....</div>
}

@code {
    [Parameter] public bool Loaded { get; set; }
}

You can see the controls you are using in the edit and view forms.

UIContainer/UIRow/UIColumn

These controls create a BootStrap grid system -- containers, rows, and columns -- by building DIV with the correct Css.

public class UIContainer : UIBase
{
    protected override string PrimaryClass => "container-fluid";
}

class UIRow : UIBase
{
    protected override string PrimaryClass => "row";
}

public class UIColumn : UIBase
{
    [Parameter] public virtual int Cols { get; set; } = 0;
    protected override string PrimaryClass => this.Cols > 0 ? $"col-{this.Cols}" : $"col";
}
// CEC.Blazor/Components/UIControls/UIBootstrapContainer/UILabelColumn.cs
public class UILabelColumn : UIColumn
{
    protected override string _BaseCss => $"col-{Columns} col-form-label";
}

Here's some code that shows the control you're using.

<UIContainer>
    <UIRow>
        <UILabelColumn Columns="2">
            Date
        </UILabelColumn>
        ............
    </UIRow>
..........
</UIContainer>

summary

This article provided an overview of how to build UI controls using components, and examined some sample components in detail. You can see all the library UIControls in the GitHub Repository

Some key points to note:

  1. UI controls enable you to abstract tags from higher-level components, such as forms and views.
  2. UI controls give you control and apply some rules to HTML and CSS tags.
  3. View and Form components are clearer and easier to view.
  4. Use as little or as much abstraction as possible.
  5. Controls, such as UILoader, make life easier!

If you read this article in the future, check the readme file in the repository for the latest version of the article set.

https://www.codeproject.com/Articles/5280090/Building-a-Database-Application-in-Blazor-Part-4-U

Keywords: ASP.NET Blazor

Added by tefuzz on Sun, 12 Sep 2021 20:33:36 +0300