如何在 Blazor 客户端应用程序中使用 Bootstrap 模式?

How to use Bootstrap modal in Blazor client app?

我正在尝试显示 bootstrap 模式然后绑定其按钮。但是我无法通过显示模态的第一步。我正在使用 .net core 3.1 的 Blazor 客户端模板。我有一个名为 Modal.razor 的页面,其中包含我从 getbootstrap.com 中找到的 bootstrap 模式。

@if (Show)
{
    <div class="modal" tabindex="-1" role="dialog">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Modal title</h5>
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="modal-body">
                    <p>Modal body text goes here.</p>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-primary">Save changes</button>
                    <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                </div>
            </div>
        </div>
    </div>
}
@code {
    [Parameter]
    public bool Show { get; set; } = false;
}

我在 index.razor 文件中调用了模态

@page "/"

<button @onclick="(()=>switchModal=!switchModal)">Switch Modal</button>

<Modal Show="switchModal"/>

@code{
    bool switchModal = false;
}

你可能会说应该在这里调用StateHasChanged。但是即使我复制并粘贴 index.razor 中的模态代码,我也看不到任何东西。

可能有更好的方法来执行此操作,但这里有一个可以帮助您入门的工作示例:

页数:

@page "/modal-test"

<BlazorApp1.Components.Modal @ref="Modal"></BlazorApp1.Components.Modal>

<button @onclick="() => Modal.Open()">Open Modal</button>

@code {
    private BlazorApp1.Components.Modal Modal { get; set; }
}

组件:

<div class="modal @ModalClass" tabindex="-1" role="dialog" style="display:@ModalDisplay">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">Modal title</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                <p>Modal body text goes here.</p>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-primary">Save changes</button>
                <button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="() => Close()">Close</button>
            </div>
        </div>
    </div>
</div>


@if (ShowBackdrop)
{
    <div class="modal-backdrop fade show"></div>
}


@code {


  public Guid Guid = Guid.NewGuid();
    public string ModalDisplay = "none;";
    public string ModalClass = "";
    public bool ShowBackdrop = false;

    public void Open()
    {
        ModalDisplay = "block;";
        ModalClass = "Show";
        ShowBackdrop = true;
        StateHasChanged();
    }

    public void Close()
    {
        ModalDisplay = "none";
        ModalClass = "";
        ShowBackdrop = false;
        StateHasChanged();
    }
}

解决此问题的另一种选择是使用 JSInterop 调用 $('#modalId').modal()

您可以通过执行以下操作让组件的每个版本都有一个唯一的 ID: <div id="bootstrap-modal-@Guid" 然后使用保存的 ID 以 jQuery.

调用 .modal()

基于 Kyle 的回答,这是我对 Blazor 的第一个实验:使模态对话框组件采用任何标记或组件。

Modal.razor

<div class="modal @modalClass" tabindex="-1" role="dialog" style="display:@modalDisplay; overflow-y: auto;">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title">@Title</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close" @onclick="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                @Body
            </div>
            <div class="modal-footer">
                @Footer
            </div>
        </div>
    </div>
</div>

@if (showBackdrop)
{
    <div class="modal-backdrop fade show"></div>
}

@code {
    [Parameter]
    public RenderFragment Title { get; set; }

    [Parameter]
    public RenderFragment Body { get; set; }

    [Parameter]
    public RenderFragment Footer { get; set; }

    private string modalDisplay = "none;";
    private string modalClass = "";
    private bool showBackdrop = false;

    public void Open()
    {
        modalDisplay = "block;";
        modalClass = "show";
        showBackdrop = true;
    }

    public void Close()
    {
        modalDisplay = "none";
        modalClass = "";
        showBackdrop = false;
    }
}

Index.razor

@page "/"

<h1>Hello, world!</h1>

Welcome to your new app.
<button class="btn btn-primary" @onclick="() => modal.Open()">Modal!</button>

<Modal @ref="modal">
    <Title>This is a <em>Title!</em></Title>
    <Body>
        <p>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Omnes enim iucundum motum, quo sensus hilaretur.
            <i>Quis istud possit, inquit, negare?</i>
            <mark>Ego vero isti, inquam, permitto.</mark> Duo Reges: constructio interrete.
        </p>
        <FetchData />
        <dl>
            <dt><dfn>Stoici scilicet.</dfn></dt>
            <dd>An hoc usque quaque, aliter in vita?</dd>
            <dt><dfn>Erat enim Polemonis.</dfn></dt>
            <dd>Quod cum accidisset ut alter alterum necopinato videremus, surrexit statim.</dd>
        </dl>
    </Body>
    <Footer>
        <button type="button" class="btn btn-primary">Save changes</button>
        <button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="() => modal.Close()">Close</button>
    </Footer>
</Modal>

@code {
    private Modal modal { get; set; }
}

同样基于 Kyle 的回答,如果您在显示和 class 调整之间放置一个短暂的延迟,您可以 维持 bootstrap 淡入淡出效果

@code {

    ...

    public async Task OpenModal()
    {
        ModalDisplay = "block;";
        await Task.Delay(100);//Delay allows bootstrap to perform nice fade animation
        ModalClass = "show";
        StateHasChanged();
    }

    public async Task CloseModal()
    {
        ModalClass = "";
        await Task.Delay(250);
        ModalDisplay = "none;";
        StateHasChanged();
    }
}

我也将 ModalClass 和 ModalDisplay 变量应用到背景元素

<div class="modal-backdrop fade @ModalClass" style="display: @ModalDisplay"></div>

相信bootstrap这样可以更好的识别触发动画的状态变化

只为背景阴影添加淡入淡出class:

<div class="modal fade @ModalClass" tabindex="-1" role="dialog" 
     style="display:@ModalDisplay">

Kyle 的组件运行良好,但有谁知道如何使用 jqueryUi draggable()/resizeable() 函数向 bootstrap 模态添加可拖动和可调整大小的功能?

我有这个 link 到一个纯 javascript 解决方案:DRAG AND RESIZE BOOTSTRAP MODAL 本质上调用模态 div 上的可调整大小和可拖动功能

<script src="https://code.jquery.com/ui/1.11.3/jquery-ui.min.js"></script>
<script type="text/javascript">
    $('.modal-content').resizable({
        //alsoResize: ".modal-dialog",
        minHeight: 300,
        minWidth: 300
    });
    $('.modal-dialog').draggable();
</script>

我试过将此脚本添加到我的 _Host.cshtml 页面,但没有效果。如有任何关于如何执行此操作的建议,我们将不胜感激...

大卫

更新了答案

答案是在 OnAfterRenderAsync 重写中显式调用 javascript 函数以将 JQuery UI 函数应用于模式 div。

例如

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await jsRuntime.InvokeVoidAsync("setModalDraggableAndResizable");
        await base.OnAfterRenderAsync(firstRender);
    }

其中 setModalDraggableAndResizable 是 _Hosts.cshtml 中的一个 javascript 函数:

    <script src="https://code.jquery.com/ui/1.11.3/jquery-ui.min.js"></script>
    <script type="text/javascript">
        function setModalDraggableAndResizable() {
            $('.modal-content').resizable({
                //alsoResize: ".modal-dialog",
                minHeight: 300,
                minWidth: 300
            });
            $('.modal-dialog').draggable();
        }
    </script>

模态窗口现在可以拖动和调整大小了...

Modal example image

使用 Kyle 解决方案,当我单击背景时我的对话框不会关闭。

我看到是z-index的问题:模态div的z-index是1050,背景div是1040,这样我就没办法了点击我的背景。

我已将背景移动到对话框中 div 并添加到模态对话框中 div z-index > 1040 (ES: 1055)

我还在背景中添加了 data-dismiss="modal" @onclick="() => Close()" div,现在它和“关闭”按钮一样有效。

<div class="modal @ModalClass" tabindex="-1" role="dialog" style="display:@ModalDisplay">

    <div class="modal-dialog" role="document" style="z-index:1055">
       ...
    </div>    

@if (ShowBackdrop)
{
    <div class="modal-backdrop fade show"  data-dismiss="modal" @onclick="() => Close()"></div>
}

</div>

更新: 我已将此答案转换为可以找到的服务

我调整了 and 答案以支持我们心爱的 AlertPromptConfirm 来自 C# 和 JavaScript。在最新的 Blazor Server 版本中测试 Bootstrap 5.

ProjectName.Components.Modal.razor

@using Microsoft.JSInterop
<div class="modal @ModalClass" tabindex="-1" role="dialog" style="display:@ModalDisplay; overflow-y: auto;">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title w-100 text-center" style="padding-left:31px">@Title</h5>
                <button type="button" class="close border-0 bg-white" data-dismiss="modal" aria-label="Close"  @onclick="() => Close(true)">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body mx-auto text-center">
                @Body
                @if (MType == ModalType.Prompt){ 
                    <input type="text" class="form-control text-center my-2" @bind-value="PromptValue" style="max-width:400px"></input> 
                }
            </div>
            <div class="modal-footer justify-content-center">
                @if (MType == ModalType.Prompt || MType == ModalType.Confirm)
                {
                    <button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="() => Close(false)">OK</button>
                    <button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="() => Close(true)">Cancel</button>
                }
                else
                {
                    <button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="() => Close(false)">Close</button>
                }
            </div>
        </div>
    </div>
</div>

@if (ShowBackdrop)
{
    <div class="modal-backdrop fade show"></div>
}

@code {

    [Inject] IJSRuntime JS { get; set; }

    public enum ModalType
    {
        Alert,
        Prompt,
        Confirm
    }

    /// <summary>
    /// (Optional) We can setup an instance of this .net object to call directly from JavaScript. See JavaScript Usage section.
    /// </summary>
    /// <returns></returns>
    protected override async Task OnInitializedAsync()
    {
        JS.InvokeVoidAsync("MODAL.SetDotnetReference", DotNetObjectReference.Create(this));
    }

    private string Title { get; set; }
    private string Body { get; set; }


    public Guid Guid = Guid.NewGuid();
    public string ModalDisplay = "none;";
    public string ModalClass = "";
    public bool ShowBackdrop = false;


    private string PromptValue { get; set; }
    private bool ConfirmValue { get; set; }
    private ModalType MType { get; set; }



    private List<string> MsgIds = new List<string>();
    [JSInvokable("Show")]
    public async Task<dynamic> Show(ModalType mType, string title, string body)
    {
        // The JavaScript call MODAL.DotNetReference.invokeMethodAsync is non-blocking
        // This means multiple calls to show the modal using invokeMethodAsync will only show the modal once.
        // We can solve this by making sure each message waits in line.

        string msgId = Guid.NewGuid().ToString();

        if (!MsgIds.Contains(msgId))
            MsgIds.Add(msgId);

        // If multiple messages are being processed, wait for this msgs turn.
        while (MsgIds.Count > 1 && MsgIds.IndexOf(msgId) != 0)
            await Task.Delay(250);

        Title = title;
        Body = body;
        ModalDisplay = "block;";
        ModalClass = "Show";
        MType = mType;
        ShowBackdrop = true;
        StateHasChanged();

        while (ShowBackdrop)
            await Task.Delay(250);

         switch(mType)
        {
            default:
            case ModalType.Alert:
                MsgIds.Remove(msgId);
                return string.Empty;
            case ModalType.Confirm:
                bool confirmResponse = ConfirmValue;
                MsgIds.Remove(msgId);
                return confirmResponse;
            case ModalType.Prompt:
                string promptResponse = PromptValue;
                MsgIds.Remove(msgId);
                return promptResponse;
        }

    }

    private void Close(bool isCancel)
    {
        // Determine returned values.
        PromptValue = isCancel ? string.Empty : PromptValue;
        ConfirmValue = isCancel ? false : true;

        ModalDisplay = "none";
        ModalClass = "";
        ShowBackdrop = false;
        StateHasChanged();
    }
}

标记用法

<Modal @ref="Modal"></Modal>
<button @onclick='() => Modal.Show(Modal.ModalType.Alert, "Title goes here","Body goes here")'>Open Modal</button>

代码用法

if (await Modal.Show(Modal.ModalType.Confirm,"Save Settings", "Are you sure you want to save settings?"))
{
    string fileName = await Modal.Show(Modal.ModalType.Prompt, "File Name", "Please enter a filename");
    if (!string.IsNullOrEmpty(fileName))
        await Modal.Show(Modal.ModalType.Alert, "File Saved", $"File Saved as {fileName}");
}

JavaScript 用法

借助 promise 支持,我们可以直接从 JavaScript 获得 PromptConfirm 的响应。为了避免将我们的 Modal 声明为静态的,我们需要设置一个 DotNetReference.

// Defined somewhere globally
var MODAL = {};
MODAL.DotNetReference = null;
MODAL.SetDotnetReference = function (pDotNetReference) {
    MODAL.DotNetReference = pDotNetReference;
};
MODAL.MType = {
    Alert: 0,
    Prompt:1,
    Confirm: 2,
};

// Called from wherever
MODAL.DotNetReference.invokeMethodAsync('Show', MODAL.MType.Prompt, `Title goes here`, `Body goes here`)
.then(data => {
    console.log(`Prompt Response`, data);
});

JavaScript 注意:建议使用 Polyfil 以在旧版浏览器中提供 promise 支持

作为替代方案,您可以使用 Bootstrap Blazor,它是 open-source 的 bootstrap 与 blazor 集成的非常好的实现。