65.9K
CodeProject 正在变化。 阅读更多。
Home

Blazor 内联对话框控件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.43/5 (3投票s)

2021 年 3 月 17 日

CPOL

4分钟阅读

viewsIcon

9595

一个 Blazor 内联对话框控件,用于锁定页面上的所有控件,除了表单内的控件。

内联对话框控件

这是本系列文章的第三篇,介绍了 Blazor 的一系列有用的编辑控件,它们解决了开箱即用的编辑体验中存在的一些不足之处,而无需购买昂贵的工具包。

本文介绍如何构建一个组件,该组件可以禁用链接、按钮、URL 栏……:除了组件内的内容之外的所有内容。虽然它无法阻止用户通过浏览器控件进行导航,但它会开启浏览器的 beforeunload 事件,以强制弹出“您确定要离开此网站吗?”对话框。所有这些都通过一个相对简单的标准 Blazor 组件和一个小型 js 文件实现。

Inline Dialog

链接

此存储库包含一个项目,其中实现了本系列所有文章的控件。您可以在此处找到它。

示例网站位于https://cec-blazor-database.azurewebsites.net/

本文末尾描述的示例表单可以在 https://cec-blazor-database.azurewebsites.net//inlinedialogeditor 找到。

之前的文章

概述

如果您想查看该组件的实际运行效果,请访问我演示站点的 此页面。这是一个基础模型,用于演示功能,并扩展了前两篇文章中使用的表单。有一个典型的编辑表单,包含前两篇文章介绍的两个额外控件

  1. EditFormState 监控 Model 数据的编辑状态
  2. ValidationFormState - 表单验证器

关键的动作是连接 InlineDialog 控件的 Lock 到表单状态。 EditFormState 监控表单状态,并在每次发生更改时调用 EventCallback EditStateChanged。页面 EditStateChanged 事件处理程序已注册到 EditFormState.EditStateChanged,并在状态更改时更新 _isDirty。如果 EditFormState' 脏了,则 InlineDialog 会被锁定。

@using Blazor.Database.Data
@page "/inlinedialogEditor"

    <InlineDialog Lock="this._isDirty" Transparent="false">
        <EditForm Model="@Model" OnValidSubmit="@HandleValidSubmit" class="p-3">
            <EditFormState @ref="editFormState" EditStateChanged="this.EditStateChanged">
            </EditFormState>
            <ValidationFormState @ref="validationFormState"></ValidationFormState>

            <label class="form-label">ID:</label> 
            <InputNumber class="form-control" @bind-Value="Model.ID" />
            <label class="form-label">Date:</label> 
            <InputDate class="form-control" @bind-Value="Model.Date" />
            <ValidationMessage For="@(() => Model.Date)" />
            <label class="form-label">Temp C:</label>
            <InputNumber class="form-control" @bind-Value="Model.TemperatureC" />
            <ValidationMessage For="@(() => Model.TemperatureC)" />
            <label class="form-label">Summary:</label>
            <InputText class="form-control" @bind-Value="Model.Summary" />
            <ValidationMessage For="@(() => Model.Summary)" />

            <div class="mt-2">
                <div>Validation Messages:</div>
                <ValidationSummary />
            </div>

            <div class="text-right mt-2">
                <button class="btn @btnStateColour" disabled>@btnStateText</button>
                <button class="btn @btnValidColour" disabled>@btnValidText</button>
                <button class="btn btn-primary" type="submit" 
                 disabled="@_btnSubmitDisabled">Submit</button>
            </div>

        </EditForm>
    </InlineDialog>
}
@code {
    protected bool _isDirty = false;
    protected bool _isValid => validationFormState?.IsValid ?? true;
    protected string btnStateColour => _isDirty ? "btn-danger" : "btn-success";
    protected string btnStateText => _isDirty ? "Dirty" : "Clean";
    protected string btnValidColour => !_isValid ? "btn-danger" : "btn-success";
    protected string btnValidText => !_isValid ? "Invalid" : "Valid";
    protected bool _btnSubmitDisabled => !(_isValid && _isDirty);

    protected EditFormState editFormState { get; set; }
    protected ValidationFormState validationFormState { get; set; }

    private WeatherForecast Model = new WeatherForecast()
    {
        ID = 1,
        Date = DateTime.Now,
        TemperatureC = 22,
        Summary = "Balmy"
    };

    private void HandleValidSubmit()
    {
        this.editFormState.UpdateState();
    }

    private void EditStateChanged(bool editstate)
        => this._isDirty = editstate;
}

内联对话框

让我们先看看参数和 public 属性。

  1. 我们捕获添加的属性,尽管我们只使用 class
  2. Cascade 打开/关闭 this 的参数级联,即 InlineDialog 的实例。默认值为 true
  3. Transparent 将背景设置为透明或半透明。演示设置为半透明,以便您可以看到它的切换。
  4. ChildContent<InlineDialog></InlineDialog> 之间的内容。
  5. IsLocked 是一个只读属性,用于检查组件状态。
[Parameter(CaptureUnmatchedValues = true)] 
public IDictionary<string, object> 
  AdditionalAttributes { get; set; } = new Dictionary<string, object>();
[Parameter] public bool Cascade { get; set; } = true;
[Parameter] public bool Transparent { get; set; } = true;
[Parameter] public RenderFragment ChildContent { get; set; }
public bool IsLocked => this._isLocked;

private 属性

  1. 注入 IJSRuntime 以访问 JavaScript Interop 并设置/取消设置浏览器的 BeforeUnload 事件。
  2. CssClass 构建组件的 HTML class 属性,将输入的任何类与组件构建的类相结合。
  3. CSS 属性定义了 class 的各种 CSS 选项。
  4. _isLocked 是用于控制锁定状态的 private 字段。
[Inject] private IJSRuntime _js { get; set; }

private string CssClass => (AdditionalAttributes != null && 
        AdditionalAttributes.TryGetValue("class", out var obj))
    ? $"{this.frontcss}
    { Convert.ToString(obj, CultureInfo.InvariantCulture)}"
    : this.frontcss;

private string backcss = string.Empty;
private string frontcss = string.Empty;
private string _backcss => this.Transparent ? "back-block-transparent" : "back-block";
private string _frontcss => this.Transparent ? "fore-block-transparent" : "fore-block";
private string __backcss => string.Empty;
private string __frontcss => string.Empty;
private bool _isLocked;

有两个 public 方法:LockUnlock。它们更改 CSS 类。 SetPageExitCheck 与 JavaScript 函数进行交互,以添加或删除 Window 上的 beforeunload 事件。代码如下所示

public void Lock()
{
    this._isLocked = true;
    this.backcss = this._backcss;
    this.frontcss = this._frontcss;
    this.SetPageExitCheck(true);
    this.InvokeAsync(StateHasChanged);
}

public void Unlock()
{
    this._isLocked = false;
    this.backcss = this.__backcss;
    this.frontcss = this.__frontcss;
    this.SetPageExitCheck(false);
    this.InvokeAsync(StateHasChanged);
}

private void SetPageExitCheck(bool action)
    => _js.InvokeAsync<bool>("cecblazor_setEditorExitCheck", action);

site.js 中的 JavaScript

window.cecblazor_setEditorExitCheck = function (show) {
    if (show) {
        window.addEventListener("beforeunload", cecblazor_showExitDialog);
    }
    else {
        window.removeEventListener("beforeunload", cecblazor_showExitDialog);
    }
}

window.cecblazor_showExitDialog = function (event) {
    event.preventDefault();
    event.returnValue = "There are unsaved changes on this page.  Do you want to leave?";
}

继续组件的 Razor

  1. 我们添加一个带有 CSS 类 _backcssdiv:当 Locked 时,它为 back-block-transparentback-block,当 Unlocked 时为空。
  2. 我们添加一个带有 Css 类 _frontcssdiv:当 Locked 时,它为 fore-block-transparentfore-block,当 Unlocked 时为空,并结合我们添加到组件的任何 class 属性值。
  3. 如果 Cascadetrue,我们级联 this
<div class="@this.backcss"></div>

<div class="@this.CssClass">
    @if (this.Cascade)
    {
        <CascadingValue Value="this">
            @this.ChildContent
        </CascadingValue>
    }
    else
    {
        @this.ChildContent
    }
</div>

继续组件的 CSS,魔法就发生在这里。我们实现了与模态对话框中使用的相似的 CSS 技术,在页面内容之上添加一个透明或半透明的层来 block 该层下方的内容,并将 InlineDialog 的内容放在该层的前面。如果您在应用程序中使用了大量 z-index 层,您可能需要调整 Z-index 以确保它置于最顶层。

div.back-block {
    display: block;
    position: fixed;
    z-index: 1; /* Sit on top */
    left: 0;
    top: 0;
    width: 100; /* Full width */
    height: 100; /* Full height */
    overflow: auto; /* Enable scroll if needed */
    background-color: RGBA(224, 224, 224, 0.4);/* the translucent effect*/
}

div.back-block-transparent {
    display: block;
    position: fixed;
    z-index: 1;          /* Sit on top */
    left: 0;
    top: 0;
    width: 100%;    /* Full width */
    height: 100%;   /* Full height */
    overflow: auto;  * Enable scroll if needed */
    background-color: transparent; 
}

div.fore-block-transparent {
    display: block;
    position: relative;
    z-index: 2;      /* Sit on top */
}

div.fore-block {
    display: block;
    position: relative;
    z-index: 2; /* Sit on top */
    background-color: RGB(255, 255, 255);/* need to set the colour, adjust as necessary */
}

总结

此解决方案使用了与模态对话框相同的技术,在页面控件和控件内容之间放置了一个屏障。它是一个就地的模态对话框。 Lock 插入屏障,Unlock 移除它。我们添加了 JavaScript Interop 来打开和关闭浏览器上的 beforeunload 事件。选择透明或半透明层,或者编写自己的 CSS。

在开发了许多解决此问题的方案并撰写了相关文章后,我终于找到了一个如此简单的解决方案,这让我有点不知所措。最好的解决方案总是最简单的!

如果您在很久以后读到这篇文章,最新版本的文章将在此处:here

历史

  • 2021年3月17日:初始版本
© . All rights reserved.