Write a standard query form page using MASA.Blazor

foreword

Hello everyone, I am one of the main developers of the open source project MASA Blazor . If you don't know MASA Blazor yet, you can visit our official website and blog "Getting to Know MASA Blazor" to find out. Simply put, MASA Blazor is a Blazor component library based on the Material Design design language. DotNET developers only need or even don't need to know javascript to develop an enterprise-level middle and background system.

The theme I am sharing this time is "Developing a Standard Query Form Page Using MASA Blazor". I will start by creating a project to create a query form page without any skills, and then I will share some skills and packaged components to achieve Rapid development.

Manual query form page

Create an application

For instructions on how to install the MASA Blazor template, go to the MASA.Blazor Quick Start .

  1. First, through the default Server project of the MASA Blazor template, the project is named MasaBlazorStandardTablePage .

    dotnet new --install MASA.Template
    dotnet new masab -o MasaBlazorStandardTablePage
    
  2. Run the application via the CLI, or start the project directly via vs.

    cd MasaBlazorStandardTablePage
    dotnet run
    
  3. After successful startup, switch to the Fetch data page, which shows MDataTablea .

    default-fetch-data.png

Supports a single query condition and search

Let's start with the simplest single conditional query.

Replace random data with simulated data

  1. Modified WeatherForecastServiceto replace random data with dead data in order to support query functions. The code below updates the data source and GetForecastAsyncquery method.

        public class WeatherForecastService
        {
            private readonly List<WeatherForecast> _data = new()
            {
                new() { Date = DateTime.Now.AddDays(-1), TemperatureC = 23, Summary = "Freezing" },
                new() { Date = DateTime.Now.AddDays(-1), TemperatureC = -10, Summary = "Bracing" },
                new() { Date = DateTime.Now.AddDays(-1), TemperatureC = 37, Summary = "Chilly" },
                new() { Date = DateTime.Now.AddDays(-2), TemperatureC = 29, Summary = "Cool" },
                new() { Date = DateTime.Now.AddDays(-3), TemperatureC = 11, Summary = "Mild" },
                new() { Date = DateTime.Now.AddDays(-4), TemperatureC = 35, Summary = "Warm" },
                new() { Date = DateTime.Now.AddDays(-5), TemperatureC = 41, Summary = "Balmy" },
                new() { Date = DateTime.Now.AddDays(-5), TemperatureC = -13, Summary = "Hot" },
                new() { Date = DateTime.Now.AddDays(-6), TemperatureC = 23, Summary = "Sweltering" },
                new() { Date = DateTime.Now.AddDays(-7), TemperatureC = 2, Summary = "Scorching" },
            };
    
            public Task<WeatherForecast[]> GetForecastAsync()
            {
                IEnumerable<WeatherForecast> res = _data.AsQueryable();
    
                return Task.FromResult(res.ToArray());
            }
        }
    
  2. Also modify FetchData.razorWeatherForecastService.GetForecastAsync() because the input startDateparameter is deleted.

    protected override async Task OnInitializedAsync()
    {
        await Task.Delay(1000); // 模拟真实环境,触发Loading效果
        forecasts = await ForecastService.GetForecastAsync(); // here
    }
    

Add query input box and search button

  1. Add the following code under the tag in the FetchData.razor<p> page

    <MRow Class="pb-3">
        <MCol Cols="12">
            <MTextField @bind-Value="summary"
                        Dense
                        HideDetails="@("auto")"
                        Label="Summary"
                        Outlined>
            </MTextField>
        </MCol>
        <MCol Cols="12" Class="d-flex py-0 pb-3">
            <MSpacer></MSpacer>
            <MButton Color="primary" OnClick="OnSearch">搜索</MButton>
        </MCol>
    </MRow>
    
    @code {
        private string summary;
    
        private async Task OnSearch()
        {
            forecasts = await ForecastService.GetForecastAsync(summary);
        }
    }
    
    • Line 3,17

      defines stringa summaryvariable of type named , bound in both directions to the MTextFieldcomponent . MTextFiledIn addition to the @bind-Valueproperties used to set two-way binding, please read the documentation for the meaning of other properties .

    • Line 12

      A search button is defined to trigger the query.

  2. WeatherForecastService.GetForecastAsyncModify summarythe method, add input parameters, and support query.

    public Task<WeatherForecast[]> GetForecastAsync(string? summary = null)
    {
        IEnumerable<WeatherForecast> res = _data.AsQueryable();
    
        if (!string.IsNullOrEmpty(summary))
        {
            res = res.Where(item => item.Summary.Contains(summary));
        }
    
        return Task.FromResult(res.ToArray());
    }
    

summary-query.gif

Support multiple query conditions and reset

Now let's add another high temperature warning selection box to query the data of different high temperature warning states.

Updated WeatherForecastServiceto support filtering data based on high temperature warnings

public Task<WeatherForecast[]> GetForecastAsync(string? summary = null, WarningSigns? warningSigns = null)
{
    IEnumerable<WeatherForecast> res = _data.AsQueryable();

    if (!string.IsNullOrEmpty(summary))
    {
        res = res.Where(item => item.Summary.Contains(summary));
    }

    if (warningSigns.HasValue)
    {
        res = warningSigns switch
        {
            WarningSigns.Yellow => res.Where(item => item.TemperatureC >= 35 && item.TemperatureC < 37),
            WarningSigns.Orange => res.Where(item => item.TemperatureC >= 37 && item.TemperatureC < 39),
            WarningSigns.Red => res.Where(item => item.TemperatureC >= 39),
            _ => res
        };
    }

    return Task.FromResult(res.ToArray());
}

Add high temperature warning selection box

  1. Add an the Data directory.WarningSigns

    public enum WarningSigns
    {
        [Description("高温黄色预警 35℃")]
        Yellow = 1,
        [Description("高温橙色预警 37℃")]
        Orange,
        [Description("高温红色预警 39℃")]
        Red
    }
    
  2. Introduces the Masa.Utils.Enumspackage , which provides GetEnumObjectListmethods to easily use enums Descriptionand enum values ​​for MSelectcomponents Items.

    dotnet add package Masa.Utils.Enums
    
  3. Update FetchData.razor .

    <MRow Class="pb-3">
            <MCol Cols="12" Sm="6">
            <MTextField @bind-Value="@summary"
                        Label="Summary"
                        Dense
                        HideDetails="@("auto")"
                        Outlined>
            </MTextField>
        </MCol>
        <MCol Cols="12" Sm="6">
            <MSelect @bind-Value="warningSigns"
                    Items="@(Enum<WarningSigns>.GetEnumObjectList<WarningSigns>())"
                    ItemText="item => item.Name"
                    ItemValue="item => item.Value"
                    TValue="WarningSigns?"
                    TItem="EnumObject<WarningSigns>"
                    TItemValue="WarningSigns"
                    Label="高温警告"
                    Clearable
                    Dense
                    HideDetails="@("auto")"
                    Outlined>
            </MSelect>
        </MCol>
        <MCol Cols="12" Class="d-flex py-0 pb-3">
            <MSpacer></MSpacer>
            <MButton Class="mr-2" OnClick="OnReset">重置</MButton>
            <MButton Color="primary" OnClick="OnSearch">搜索</MButton>
        </MCol>
    </MRow>
    
    @code {
        private WarningSigns? warningSigns;
    
        private Task OnReset()
        {
            summary = null;
            warningSigns = null;
            return OnSearch();
        }
    
        private async Task OnSearch()
        {
            forecasts = await ForecastService.GetForecastAsync(summary, warningSigns);
        }
    }
    
    • Line 2,10

    By Sm="6"setting , when the screen size is greater than 768px, two lines are occupied MCol, so that MTextFieldand MSelectare displayed side by side.

    • Line 11-23,33,44

    Line 33 defines the warningSignsvariable to receive MSelectthe selected value, and of course, the MSelectselected the value, as long as the @bind-Valuetwo-way binding is set, just like line 11. Line 12 uses the Masa.Utils.Enumsprovided method, which returns a list containing Name (Description) and Value (enumeration value), which is assigned to MSelect.Items. Line 44 passes to warningSignsthe query interface.

    • Line 27,35-40

    A reset button is defined here to clear the contents of all query input boxes and refresh the table.

select-query.gif

Supports triggering queries after typing enter or selecting

Later, the test lady said that you are too difficult to use, you can't trigger the search by pressing Enter, and you can't trigger the search after selecting it. Alright alright, let's add it now.

Triggered after typing enter

The principle is to capture the OnKeyDownevent whether the Enterkey is clicked.

<MTextField @bind-Value="@summary"
            OnKeyDown="HandleOnKeyDown"
            Label="Summary"
            Dense
            HideDetails="@("auto")"
            Outlined>
</MTextField>

@code {
    private async Task HandleOnKeyDown(KeyboardEventArgs args)
    {
        if (args.Code == "Enter")
        {
            // 等待156毫秒,预防输入的值在更新到变量之前按下Enter键
            await Task.Delay(156);

            await OnSearch();
        }
    }
}
  • Line 2

    HandleOnKeyDownBind to MTextFieldthe OnKeyDownevent of .

  • Line 10-17

    Trigger the search by determining whether KeyboardEventArgsthe valueCode of is . EnterLine 14 waits 156 milliseconds to wait for summarya value that is already entered.

Trigger query after selection

<MSelect @bind-Value="warningSigns"
    	 Items="@(Enum<WarningSigns>.GetEnumObjectList<WarningSigns>())"
    	 ItemText="item => item.Name"
    	 ItemValue="item => item.Value"
    	 TValue="WarningSigns?"
    	 TItem="EnumObject<WarningSigns>"
    	 TItemValue="WarningSigns"
    	 Label="高温警告"
    	 OnSelectedItemUpdate="OnSearch"
    	 Clearable
         Dense
         HideDetails="@("auto")"
    	 Outlined>
</MSelect>
  • Line 9

    When the selection is updated ( OnSelectedItemUpdate) directly calls the OnSearchmethod to trigger the query. There is no need to wait for 156 milliseconds OnKeyDownas , because OnSelectedItemUpdateis triggered after the warningSignsupdate .

Click the clear icon to trigger the query

It's as simple MTextFieldas MSelectadding the following properties to the and components:

Clearable
OnClearClick="OnSearch"

enter-query.gif

How about adding some Loading animations?

it is good!

<MButton Color="primary" 
    	 Loading="searching" 
    	 OnClick="HandleOnSearch">
    搜索
</MButton>
...

<MDataTable Headers="_headers" Items="forecasts" 
            Loading="loading" 
            ItemsPerPage="5" Class="elevation-1">
...
    
@code {
    private bool loading;
    private bool searching;
    
    private async Task HandleOnSearch()
    {
        searching = true;

        await OnSearch();

        searching = false;
    }
    
    private async Task OnSearch()
    {
        loading = true;
        
        await Task.Delay(1000);
        forecasts = await ForecastService.GetForecastAsync(summary, warningSigns);

        loading = false;
    }
}
  • Line 2-3,15,17-24

    The new searchingvariable is used to control the Loadingstate of the search button, and the new is added to HandleOnSearchreplace original OnSearchto control the animation of clicking the search button separately.

  • Line 9,14,28,33

    Added loadingvariables to control MDataTablethe Loadingstate of . TheOnSearch value set in the method block before and after the interface request .loading

loading.gif

Row Actions and Custom Column Styles for Tables

Due to space limitations, I will not post the codes one by one. For specific codes, please refer to the source code Next, I will write some common codes for Table, such as row operations and custom column styles.

table.gif

Packaging Components and Tips

I should have written the process of refactoring the above example using the content shared in this section, but it feels like it would make this article too long. I will also upload the refactored code to Github .

Package components

Just imagine, when you are assigned to several modules, and each module has at least one query form page, how would you develop? You'd probably say copy the most appropriate code file, then rename the file, rename the corresponding variable, and you're done. Of course this is a method, but not elegant. What is that elegant way, is encapsulation. I have been developing the MASA.Blazor component library full-time for a while , and later I was assigned to the IoT project to help the development of the Blazor backend system and the practice of MASA.Blazor due to business needs . When developing IoT projects, it is often seen that the same code is distributed in the same classes. I tried to optimize and refactor these codes, and extracted and encapsulated the following components from the query table page:

  • Filters : Receive OnSearchparameter proxy queries, by contextproviding onEnterand onSearchmethods for use by individual query components.
  • PageHeader : A standard page header that includes title, subtitle, search button, and provides Filterscomponent capabilities.
  • Actions : Provides a set of action buttons, the first two are displayed by default, and the latter buttons will be moved to the MMenumiddle.
  • BlockText : Displays two data of the same type side by side.
  • ColorChip : Provides a limited list of colors to generate with light fonts MChip.
  • CopyableText : Provides an icon button after the text that can copy the content.
  • DateTimePicker : Improve the popup layer time picker with hour, minute and second picker.
  • EllipsisText : Automatically truncate text based on the width of the parent box.
  • GenericColumnRender : Renders DateTime, enum, bool, and other types of values. can be used MDataTablefor ItemColContentand Definitions.DetailContent

PageHeaderThe components have been released as part of the MASA.Blazor presets, the other mentioned components have not been incorporated into the main MASA.Blazor library. If you want to use or reference, you can visit MASA.Blazor.Experimental.Components . Articles about the detailed introduction and use of preset components and experimental components will be written and published by other colleagues later, please pay more attention!

MASA.Blazor.Experimental.Components is an experimental component library, which means that the library's API and functionality may be redesigned. However, as the functions of the experimental components continue to improve and stabilize, they will be merged into the main library as soon as the MASA.Blazor version is updated.

Skill

Make good use of base classes

A Blazor component is actually a class that inherits from by default ComponentBaseand provides many virtual methods that we can override to affect the behavior of the application. And these methods are used by all Blazor components through inheritance mechanism.

In actual development, I will find that almost every page will inject NavigationManager, IJsRunTimeand other possible business services, or will use some common components, then we ComponentBasecan write another page that has used these services and The base class for components.

According to the architecture, it is possible to create specific for @pagecomponents PageComponentBaseand pure encapsulation functions PureComponentBase. Classification by business depends on the situation, because the business is more specific, and there are usually injections HttpClientor , as well as any common code.

SetParametersAsync

SetParametersAsync sets parameters supplied by the component's parent in the render tree or from route parameters.

Just know that this method will be executed whenever the parent renders. This means it's the right place to specify default parameter values. Taking the previous example, MTextFieldthe MSelectfollowing code is set to maintain the same appearance and behavior when using and :

Clearable
Dense
HideDetails="@("auto")"
Outlined

So instead of writing it every time, it is better to set these default parameters in advance using SetParametersAsyncthe features of :

public class DefaultTextField<TValue> : MTextField<TValue>
{
    public override async Task SetParametersAsync(ParameterView parameters)
    {
        Clearable = true;
        Dense = true;
        HideDetails = "auto";
        Outlined = true;

        await base.SetParametersAsync(parameters);
    }
}

public class DefaultSelect<TItem, TItemValue, TValue> : MSelect<TItem, TItemValue, TValue>
{
    public override async Task SetParametersAsync(ParameterView parameters)
    {
        Clearable = true;
        Dense = true;
        HideDetails = "auto";
        Outlined = true;
        
        await base.SetParametersAsync(parameters);
    }
}
<DefaultTextField @bind-Value="@summary"
                  OnKeyDown="@context.onEnter"
                  OnClearClick="@context.onSearch"
                  Label="Summary">
</DefaultTextField>

<DefaultSelect @bind-Value="warningSigns"
               Items="@(Enum<WarningSigns>.GetEnumObjectList<WarningSigns>())"
               ItemText="item => item.Name"
               ItemValue="item => item.Value"
               TValue="WarningSigns?"
               TItem="EnumObject<WarningSigns>"
               TItemValue="WarningSigns"
               Label="高温警告"
               OnSelectedItemUpdate="@context.onSearch"
               OnClearClick="@context.onSearch">
</DefaultSelect>

after.gif

future plans

In the future, our team will continue to optimize the performance of each component, complete missing components, solve bugs, improve documentation, etc. In addition, we also plan to publish Blazor related tutorials and sharing articles, so stay tuned.

Thanks for reading!

resource

open source address

MASA.BuildingBlocks :https://github.com/masastack/MASA.BuildingBlocks

MASA.Contrib :https://github.com/masastack/MASA.Contrib

MASA.Utils :https://github.com/masastack/MASA.Utils

MASA.EShop :https://github.com/masalabs/MASA.EShop

MASA.Blazor :https://github.com/BlazorComponent/MASA.Blazor

If you are interested in our MASA Framework, whether it is code contribution, use, issue, please contact us

16373211753064.png

{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/5447363/blog/5481319