您当前的位置: 首页 >  ui

寒冰屋

暂无认证

  • 1浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

在Blazor中构建数据库应用程序——第5部分——查看组件——UI中的CRUD列表操作

寒冰屋 发布时间:2021-09-12 23:25:19 ,浏览量:1

目录

介绍

存储库和数据库

列表功能

基本表单

分页和排序

分页器

分页器数据

分页器控件

排序控件

UIDataTableHeaderColumn

天气预报列表表单

视图

WeatherForecastComponent

RouteViews(又名页面)

总结

介绍

本文是构建Blazor数据库应用程序系列中的第五篇。到目前为止的文章是:

  1. 项目结构和框架——一些介绍。
  2. 服务——构建CRUD数据层。
  3. 查看组件——UI中的CRUD编辑和查看操作。
  4. UI组件——构建 HTML/CSS控件。
  5. 查看组件——UI中的CRUD列表操作。

本文详细介绍了构建可重用的List UI组件并将它们部署在Server和WASM项目中。

存储库和数据库

文章的存储库已移至Blazor.Database存储库。较旧的存储库现已过时,将很快被删除。

存储库中的/SQL中有一个用于构建数据库的SQL脚本。

您可以在同一站点上看到在此处运行的项目的Server和WASM版本。

列表功能

列表组件比其他CRUD组件面临更多挑战。生产级清单控制中预期的功能包括:

* 分页——处理大型数据集

* 列格式——控制列宽和数据溢出

* 排序——在列上

* 过滤——此处未涵盖。

基本表单

ListFormBase是所有列表的基本抽象形式。它继承自ComponentBase,并包含所有样板代码。 TRecord是它操作的数据类。该表格使用代码如下所示。

public abstract class ListFormBase : ComponentBase, IDisposable where TRecord : class, IDbRecord, new()
{
    /// Callbacks for Edit/View/New/Exit Actions
    [Parameter] public EventCallback EditRecord { get; set; }
    [Parameter] public EventCallback ViewRecord { get; set; }
    [Parameter] public EventCallback NewRecord { get; set; }
    [Parameter] public EventCallback ExitAction { get; set; }

    /// Controller Data Service
    [Inject] protected IFactoryControllerService Service { get; set; }
    [Inject] protected NavigationManager NavManager { get; set; }

    /// Booleans for Service and Recordlist state
    protected bool IsLoaded => this.Service?.HasRecords ?? false;
    protected bool HasService => this.Service != null;

    protected override async Task OnInitializedAsync()
    {
        if (this.HasService)
        {
            await this.Service.GetRecordsAsync();
            this.Service.ListHasChanged += OnListChanged;
        }
    }

    /// Call StatehasChanged if list changed
    protected void OnListChanged(object sender, EventArgs e)
        => this.InvokeAsync(this.StateHasChanged);

    /// Event handlers to call EventCallbacks
    protected virtual void Edit(int id)
        => this.EditRecord.InvokeAsync(id);
    protected virtual void View(int id)
        => this.ViewRecord.InvokeAsync(id);
    protected virtual void New()
        => this.NewRecord.InvokeAsync();
    protected virtual void Exit()
    {
        if (ExitAction.HasDelegate)
            ExitAction.InvokeAsync();
        else
            this.NavManager.NavigateTo("/");
    }

    /// IDisosable Interface implementation
    public void Dispose()
        => this.Service.ListHasChanged -= OnListChanged;
}

分页和排序

分页和排序由驻留在ControllerService中的Paginator类实现。有与Paginator:PaginatorControl和SortControl交互的UI组件。

您可以在列表表单中看到PaginatorControl在使用——此处位于表单底部按钮行的左侧


    
        
            
        
        
            New Record
            Exit
        
    

并且SortControl在列表表单的标题行中起作用。


    
        ID
        Date
        ...
    

分页器

控制器服务持有列表表单使用的Paginator实例。代码是不言自明的,提供了分页操作的功能。它被传递给数据服务以通过PaginatorData类检索正确的排序页面。

public class Paginator
{
    public int Page { get; set; } = 1;
    public int PageSize { get; set; } = 25;
    public int BlockSize { get; set; } = 10;
    public int RecordCount { get; set; } = 0;
    public event EventHandler PageChanged;
    public string SortColumn
    {
        get => (!string.IsNullOrWhiteSpace(_sortColumn)) ? _sortColumn : DefaultSortColumn;
        set => _sortColumn = value;
    }
    private string _sortColumn = string.Empty;
    public string DefaultSortColumn { get; set; } = "ID";
    public bool SortDescending { get; set; }

    public int LastPage => (int)((RecordCount / PageSize) + 0.5);
    public int LastBlock => (int)((LastPage / BlockSize) + 1.5);
    public int CurrentBlock => (int)((Page / BlockSize) + 1.5);
    public int StartBlockPage => ((CurrentBlock - 1) * BlockSize) + 1;
    public int EndBlockPage => StartBlockPage + BlockSize;
    public bool HasBlocks => ((RecordCount / (PageSize * BlockSize)) + 0.5) > 1;
    public bool HasPagination => (RecordCount / PageSize) > 1;


    public Paginator(int pageSize, int blockSize)
    {
        this.BlockSize = blockSize;
        this.PageSize = pageSize;
    }

    public void ToPage(int page, bool forceUpdate = false)
    {
        if ((forceUpdate | !this.Page.Equals(page)) && page > 0)
        {
            this.Page = page;
            this.PageChanged?.Invoke(this, EventArgs.Empty);
        }
    }

    public void NextPage()
        => this.ToPage(this.Page + 1);

    public void PreviousPage()
                => this.ToPage(this.Page - 1);
    public void ToStart()
        => this.ToPage(1);

    public void ToEnd()
        => this.ToPage((int)((RecordCount / PageSize) + 0.5));

    public void NextBlock()
    {
        if (CurrentBlock != LastBlock)
        {
            var calcpage = (CurrentBlock * PageSize * BlockSize) + 1;
            this.Page = calcpage > LastPage ? LastPage : LastPage;
            this.PageChanged?.Invoke(this, EventArgs.Empty);
        }
    }
    public void PreviousBlock()
    {
        if (CurrentBlock != 1)
        {
            this.Page = ((CurrentBlock - 1) * PageSize * BlockSize) - 1;
            this.PageChanged?.Invoke(this, EventArgs.Empty);
        }
    }

    public void NotifySortingChanged()
        => this.ToPage(1, true);

    public PaginatorData GetData => new PaginatorData()
    {
        Page = this.Page,
        PageSize = this.PageSize,
        BlockSize = this.BlockSize,
        RecordCount = this.RecordCount,
        SortColumn = this.SortColumn,
        SortDescending = this.SortDescending
    };
}

分页器数据

这是用于将数据传递到dat服务的类。这必须通过json通过api传递,所以“保持简单”/

public class PaginatorData
{
    public int Page { get; set; } = 1;
    public int PageSize { get; set; } = 25;
    public int BlockSize { get; set; } = 10;
    public int RecordCount { get; set; } = 0;
    public string SortColumn { get; set; } = string.Empty;
    public bool SortDescending { get; set; } = false;
}

分页器控件

代码再次是不言自明的,构建了一个Bootstrap ButtonGroup。我一直避免使用图标,如果你愿意,你可以。

@namespace Blazor.SPA.Components

@if (this.hasPaginator)
{
    
        
  • |@pageNo
  • } } @if (this.Paginator.HasBlocks) {
  • >>
  • }
  • >|
} @code { [Parameter] public Paginator Paginator { get; set; } private bool hasPaginator => this.Paginator != null && this.Paginator.HasPagination; }

排序控件

SortControl是在列表头使用。它自身级联并通过一组公共辅助方法为标题列提供到分页器的接口。

@namespace Blazor.SPA.Components


    @ChildContent


@code {
    [Parameter] public RenderFragment ChildContent { get; set; }
    [Parameter] public string NotSortedClass { get; set; } = "sort-column oi oi-resize-height";
    [Parameter] public string AscendingClass { get; set; } = "sort-column oi oi-sort-ascending";
    [Parameter] public string DescendingClass { get; set; } = "sort-column oi oi-sort-descending";
    [Parameter] public EventCallback Sort { get; set; }
    [Parameter] public Paginator Paginator { get; set; }
    public string SortColumm { get; private set; } = string.Empty;
    public bool Descending { get; private set; } = false;

    public string GetIcon(string columnName)
        => !this.SortColumm.Equals(columnName)
        ? this.NotSortedClass
        : this.Descending
            ? this.AscendingClass
            : this.DescendingClass;

    public void NotifySortingChanged(string sortColumn, bool descending = false)
    {
        this.SortColumm = sortColumn;
        this.Descending = descending;
        this.Notify();
    }

    public void NotifySortingDirectionChanged()
    {
        this.Descending = !this.Descending;
        this.Notify();
    }

    private void Notify()
    {
        if (Paginator != null)
            {
            Paginator.SortDescending = this.Descending;
            Paginator.SortColumn = this.SortColumm;
            Paginator.NotifySortingChanged();
            }
        var args = SortingEventArgs.Get(this.SortColumm, this.Descending);
        if (Sort.HasDelegate) this.Sort.InvokeAsync(args);
    }
}

UIDataTableHeaderColumn

这是在列表中构建每个标题列的UI控件。它为标题构建了razor和Css类,并在任何鼠标单击事件上通知捕获的SortControl。

@namespace Blazor.SPA.Components

@if (_isSortField)
{
    
        
        @this.ChildContent
    
}
else
{
    
        @this.ChildContent
    
}

@code {

    [CascadingParameter] public SortControl SortControl { get; set; }
    [Parameter] public RenderFragment ChildContent { get; set; }
    [Parameter] public string SortField { get; set; } = string.Empty;
    [Parameter(CaptureUnmatchedValues = true)] public IDictionary UserAttributes { get; set; } = new Dictionary();
    private bool _hasSortControl => this.SortControl != null;
    private bool _isSortField => !string.IsNullOrWhiteSpace(this.SortField);
    private string _iconclass => _hasSortControl && _isSortField ? this.SortControl.GetIcon(SortField) : string.Empty;

    private string CssClass => CSSBuilder.Class("grid-col")
        .AddClass("cursor-hand", _isSortField)
        .AddClassFromAttributes(this.UserAttributes)
        .Build();

    private void SortClick(MouseEventArgs e)
    {
        if (this.SortControl.SortColumm.Equals(this.SortField))
            this.SortControl.NotifySortingDirectionChanged();
        else
            this.SortControl.NotifySortingChanged(this.SortField);
    }
}

天气预报列表表单

解决方案中有三种列表表单。它们展示了不同的UI方法。

  1. 为记录查看器和编辑器使用不同的RouteView(页面)的经典网页方法。
  2. 模态对话框方法——在列表RouteView中打开和关闭模态对话框。
  3. 内嵌对话方法——在RouteView中打开和关闭一个部分以显示/编辑记录。

标准WeatherForecastListForm看起来像这样。它继承自ListFormBase, WeatherForecast作为 TRecord。它将WeatherForecastControllerService分配给基接口IFactoryControllerService属性Service。请注意,它有一个组件Css文件,用于定义组件中使用的自定义Css。

// Blazor.Database/Components/Forms/WeatherForecast/WeatherForecastListForm.razor.cs
public partial class WeatherForecastListForm : ListFormBase
{
    [Inject] private WeatherForecastControllerService ControllerService { get; set; }
    [Parameter] public bool IsModal {get; set;}
    private BaseModalDialog Modal { get; set; }

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

    protected override async void Edit(int id)
    {
        if (this.IsModal)
        {
            var options = new ModalOptions();
            options.Set("Id", id);
            await this.Modal.ShowAsync(options);
        }
        else
            base.Edit(id);
    }
    protected override async void View(int id)
    {
        if (this.IsModal)
        {
            var options = new ModalOptions();
            options.Set("Id", id);
            await this.Modal.ShowAsync(options);
        }
        else
            base.View(id);
    }

    protected override async void New()
    {
        if (this.IsModal)
        {
            var options = new ModalOptions();
            options.Set("Id", -1);
            await this.Modal.ShowAsync(options);
        }
        else
            base.New();
    }
}

razor标记。注意:

  1. 头部中的SortControl和使用排序构建头部的UIDataTableHeaderColumn组件。
  2. 链接到Service.Paginator的按钮行中的PaginatorControl。分页是事件驱动的。PaginatorControl分页请求由Paginator控制器服务直接处理。更新触发服务中的ListChanged事件,该事件触发列表表单中的UI更新。
  3. 该表单是否使用模式对话框添加BaseModalDialog。

// Blazor.Database/Components/Forms/WeatherForecast/WeatherForecastListForm.razor.cs
public partial class WeatherForecastListForm : ListFormBase
{
    [Inject] private WeatherForecastControllerService ControllerService { get; set; }
    [Parameter] public bool IsModal {get; set;}
    private BaseModalDialog Modal { get; set; }

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

    protected override async void Edit(int id)
    {
        if (this.IsModal)
        {
            var options = new ModalOptions();
            options.Set("Id", id);
            await this.Modal.ShowAsync(options);
        }
        else
            base.Edit(id);
    }
    protected override async void View(int id)
    {
        if (this.IsModal)
        {
            var options = new ModalOptions();
            options.Set("Id", id);
            await this.Modal.ShowAsync(options);
        }
        else
            base.View(id);
    }

    protected override async void New()
    {
        if (this.IsModal)
        {
            var options = new ModalOptions();
            options.Set("Id", -1);
            await this.Modal.ShowAsync(options);
        }
        else
            base.New();
    }
}

视图

应用程序为列表表单声明了一组中间视图。这些对于WASM和服务器SPA都是通用的。

WeatherForecastComponent

这是多RouteView实现。事件处理程序连接到WeatherForecastListForm,通过NavigationManager路由到不同的RouteView。

@namespace Blazor.Database.Components



@code {

    [Inject] NavigationManager NavManager { get; set; }

    protected override Task OnInitializedAsync()
    {
        return base.OnInitializedAsync();
    }

    public void GoToEditor(int id)
    => this.NavManager.NavigateTo($"/weather/edit/{id}");

    public void GoToNew()
    => this.NavManager.NavigateTo($"/weather/edit/-1");

    public void GoToViewer(int id)
    => this.NavManager.NavigateTo($"/weather/view/{id}");

}

模态实现很简单。它已经通过启用IsModal.。您实际上并不需要它,因为您可以直接在RouteView中声明WeatherForecastListForm。

@namespace Blazor.Database.Components

内联对话框是最复杂的。它使用Id通过UIBase显示/隐藏编辑器/查看器。

@namespace Blazor.Database.Components


    


    


@code {

    [Inject] NavigationManager NavManager { get; set; }

    private int editorId = 0;
    private int viewerId = 0;

    private bool ShowViewer => this.viewerId != 0;
    private bool ShowEditor => this.editorId != 0;

    public void GoToEditor(int id)
        => SetIds(id, 0);

    public void GoToNew()
        => SetIds(-1, 0);

    public void GoToViewer(int id)
        => SetIds(0, id);

    public void CloseDialog()
        => SetIds(0, 0);

    public void Exit()
        => this.NavManager.NavigateTo("/");

    private void SetIds(int editorId, int viewerId)
    {
        this.editorId = editorId;
        this.viewerId = viewerId;
    }
}
RouteViews(又名页面)

这些只是声明路由和顶级表单组件。

  • Blazor.Database.WASM/RouteViews/Weather/xxx.razor
  • Blazor.Database.Server/RouteViews/Weather/xxx.razor

@page "/fetchdata"

@page "/fetchdataInline"

@page "/fetchdataModal"
总结

这篇文章到此结束。需要注意的一些关键点:

  1. Blazor服务器和Blazor WASM代码库之间没有区别。
  2. 90%以上的功能在库组件中作为样板通用代码实现。大多数应用程序代码是用于单个记录表单的Razor标记。
  3. 异步功能贯穿始终。

如果您在未来阅读本文,请查看存储库中的自述文件以获取文章集的最新版本。

Building a Database Application in Blazor - Part 5 - View Components - CRUD List Operations in the UI - CodeProject

关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.0490s