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

45

我正在尝试展示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 中,我也看不到任何东西。
你可能会说StateHasChanged应该在这里被调用。但是,即使我将模态框的代码复制并粘贴到index.razor文件中,我仍然看不到任何内容。

你的代码问题在于,你只是切换HTML是否发送到客户端,但使用Bootstrap时,模态框的HTML始终存在于页面上,并且可以通过JavaScript中的$('#modal').modal()或者在应该打开它的按钮上使用data-toggle和data-target标签来触发。 - Kyle
1
你可以使用这个Nuget包:https://www.nuget.org/packages/Majorsoft.Blazor.Components.Modal/ 它支持背景和动画... 文档:https://github.com/majorimi/blazor-components/blob/master/.github/docs/Modal.md - Major
9个回答

86

可能有更好的方法来完成这个任务,但以下是一个可行的示例,可以帮助你入门:

页面:

@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()。


1
@Sorush 我更新了我的答案来修复这个问题。Bootstrap 4似乎正在添加一个<div class="modal-backdrop fade show"></div>来使模态框后面变暗,所以我使用了一个布尔值来显示或隐藏它。 - Kyle
1
很好,但不能与 fade 类一起使用,为什么要使用 Guid?我的意思是 public Guid Guid = Guid.NewGuid(); - Zanyar Jalal
1
我的意思是你的代码没有检测到 fade 的 CSS 类,因此动画不起作用。如果使用大型的 Model,滚动也无法正常工作。 - Zanyar Jalal
2
@ZanyarJ.Ahmed,你找到为什么 fade 类没有被使用的解决方案了吗? - TanvirArjel
1
@MattHamilton,你可以使用@onkeydown@onkeyup处理程序并检查按下的键,如果是Esc键,则运行Close()。 - Kyle
显示剩余10条评论

43

在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; }
}

1
淡入淡出效果在这里也不起作用 - 你有什么解决方案吗? - d00lar
什么是FetchData? - Jens Mander
1
@JensMander:FetchData是默认的Razor组件,显示带有WeatherForecast表格的内容。我想它只是用于测试。 - Christoph B
@grammophone,如何获取整个页面的模态对话框?当我从选项卡内部启动模态对话框时,它不是整个页面的模态对话框,而仅在选项卡上。就像这里:[1]:https://i.stack.imgur.com/Cu9Es.png - Snekithan
我可以通过将模态框作为页面的一部分并将其作为参数传递给另一个组件来解决问题。 - Snekithan

11

除了基于Kyle的回答,如果你在显示和类调整之间设置短延迟,你可以维持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>

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


你能否请提供一个带有工作淡入效果的示例代码?对我来说,添加延迟并不会导致动画 - 只是稍微延迟了背景的显示。谢谢。 - d00lar

9

通过Kyle的解决方案,我点击背景时对话框不会关闭。

我发现这是z-index的问题:模态div具有1050的z-index,而背景div只有1040,这样我就无法单击背景。

我已将背景放入对话框div中,并向modal-dialog div添加了z-index>1040(ES:1055)。

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

<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>

你的解决方案很好,但是在使用<div class="modal-dialog modal-dialog-centered">时无法工作。你有什么想法为什么会这样? - Emil

2

仅为背景阴影添加淡入效果,请添加淡入类:

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

这并不总是有效。 - Emil

2
作为替代方案,您可以使用Bootstrap Blazor,它是一个与Blazor集成的开源且非常好的Bootstrap实现。请注意保留HTML标签。

1

更新:我将这个答案转化成了一个服务,可以在这里找到。

我修改了Kylesgrammophones的答案,以支持我们所喜爱的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


你为什么需要在这里使用JS? - Emil
@Emil 这里没有必要使用JS。但是如果你恰好在为组件编写JavaScript,我回答的最后一部分描述了如何替换标准的JS AlertPromptConfirm框,以便所有模态框在应用程序中匹配。如果您不需要从JS调用模态框,只需删除[JSInvokable("Show")]JS.InvokeVoidAsync("MODAL.SetDotnetReference", DotNetObjectReference.Create(this)); - clamchoda

1

要包含淡入和下滑效果,只需要添加一些CSS即可。例如,可以像我在代码中所做的那样。

我的模态Blazor组件如下:

<div class="modal fade show"
     id="trxModal"
     style="display: @(IsDisplayed ? "block" : "none"); background-color: rgba(10,10,10,.8);"
     aria-modal="true"
     role="dialog">
        <div class="modal-dialog @(IsDisplayed ? "fadeInAnimation" : "")">
            <div class="modal-content">
                <div class="modal-header">
                    <h4 class="modal-title">@Title</h4>
                    <button type="button" class="trx-btn btn-text" @onclick="onCancelClick">&times;</button>
                </div>
                <div class="modal-body">
                    <p>@Text</p>
                </div>
                <div class="modal-footer">
                    @if (CancelText != null)
                    {
                        <button type="button" class="trx-btn trx-btn-cancel" @onclick="onCancelClick">
                            @CancelText
                        </button>
                    }

                    @if (OkText != null)
                    {
                        <button type="button" class="trx-btn trx-btn-primary" @onclick="onOkClick">
                            @OkText
                        </button>
                    }
                </div>
            </div>
        </div>
</div>

请注意 "fadeInAnimation" 类。当 "isDisplayed" 为真时,它将被添加到 div 元素中。在 CSS 中,我编写了以下代码来实现淡入和滑动效果:
@keyframes fade-in-slide-down {
    from {
        opacity: 0.1;
        margin-top: 0;
    }

    to {
        opacity: 1;
        margin-top: 75px;
    }
}

.fadeInAnimation {
    animation-name: fade-in-slide-down;
    animation-duration: 500ms;
    animation-fill-mode: forwards;
}

希望这对你有用。


1
Kyle的组件效果很好,但是否有人知道如何使用jqueryUi的draggable()/resizeable()函数为Bootstrap模态框添加可拖动和可调整大小的功能?
我有这个链接提供一个纯JavaScript解决方案: DRAG AND RESIZE BOOTSTRAP MODAL,它本质上是在模态框div上调用resizeable和draggable函数。
<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>

现在模态框可以拖动和调整大小...

模态框示例图


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接