How to use Bootstrap modal in Blazor client app?
Asked Answered
C

10

48

I am trying to show bootstrap modal then bind its buttons. But I cannot pass the first step showing the modal. I am using Blazor client template of .net core 3.1. I have a page named Modal.razor which contains the bootstrap modal I found from getbootstrap.com.

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

An I called the modal in the index.razor file

@page "/"

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

<Modal Show="switchModal"/>

@code{
    bool switchModal = false;
}

You might say StateHasChanged should be called here. But even if I copy and paste the modal code in the index.razor, I won't see anything.

Comyns answered 9/12, 2019 at 21:19 Comment(2)
The problem with your code is, that all you're doing is switching whether the HTML is being sent to the client or not, but with bootstrap, the modal HTML is always on the page, and is either triggered with javascript with $('#modal').modal() or with a data-toggle and data-target tag on the button that should open it.Sirocco
You can use this Nuget package: nuget.org/packages/Majorsoft.Blazor.Components.Modal It supports backdrop and animation as well... Docs: github.com/majorimi/blazor-components/blob/master/.github/docs/…Stagger
S
90

There is likely a better way to do this, but here's a working example to get you started:

Page:

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

Component:

<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();
    }
}

Another option to go about this, would be to use JSInterop to call $('#modalId').modal()

You could have each version of the component have a unique id by doing something like this: <div id="bootstrap-modal-@Guid" then use the saved ID to call .modal() with jQuery.

Sirocco answered 9/12, 2019 at 21:49 Comment(16)
Great, works well. Do you know how can I make the background except modal window dark?Comyns
@Comyns I updated my answer to fix that. Bootstrap 4 looks like it's adding a <div class="modal-backdrop fade show"></div> to darken behind the modal, so I used a boolean to show or hide it.Sirocco
Could you exemplify how to get the user's answer from the modal dialog into some variable in the modal-test page? I'm still not real clear on blazor data binding.Rarebit
This has worked great for me for most of my modals, but now I have a really long modal and I would like it to scroll like this one: getbootstrap.com/docs/4.4/components/modal/… Instead this is too big for the window and only the content behind the modal scrolls from mouse wheel. Any suggestions?Sikorsky
Great, but not work with fade class and why use you Guid ? i mean public Guid Guid = Guid.NewGuid();Hire
Hi @ZanyarJ.Ahmed The reason for using a Guid in this example component is explained in the last line. It is only if you need a unique id on the modal to do something with it with jquery.Sirocco
@ZanyarJ.Ahmed Can you elaborate on "not work with fade class"? What issue are you having?Sirocco
I mean your code not detected fade css class Not work animation and if use large Model not work scrollHire
Great easy way to this problem. Good that no external nuget package is used. Thanks @Kyle.Columbian
@ZanyarJ.Ahmed Have you found the solution to why the fade class is not using?Charinile
yep i also tried alot but still no fade efect - does someone have any solution?Pustulant
Has anyone had any luck closing this modal with the escape key? I believe Bootstrap's modal should close when escape is pressed by default, but this doesn't seem to work in my project.Cisco
@MattHamilton you can use a @onkeydown or @onkeyup handler and check the key thats pressed, if its escape then run Close()Sirocco
Hey @Kyle! Thanks for the reply. If I have inputs inside my modal, would I have to add then @onkeydown handler to each of them? Or can I somehow add it to one place and have it trigger before the inputs?Cisco
How Cal I add a method saveButton in modal and use in the page? <button type="button" class="btn btn-primary" @onclick="() => Save()">Save changes</button> Save() in this case is inside component and I want to save the information in another page.Rootless
Hi Kyle. Why do you use Open and Close instead of the built-in ShowAsync()?Prospector
S
49

Building on Kyle's answer, this is my first experiment with Blazor: Making the modal dialog component take any markup or component.

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; }
}
Scruff answered 23/7, 2020 at 19:19 Comment(5)
fade effect also does not work here - does you have any solution?Pustulant
What is FetchData?Beatty
@JensMander: FetchData is the default Razor component that shows a table with the WeatherForecast. I suppose it's there just for testingAguste
@grammophone, how to get the modal dialog of entire page, when I launch the modal dialog from inside the Tab, it is not modal dialog of entire page, it is only on Tab. like here:[1]: i.sstatic.net/Cu9Es.pngQueer
I am able to fix the problem by adding the modal as part of the page and pass it to another component as a parameter.Queer
B
13

Also building on Kyle's answer, you can sustain the bootstrap fade effect if you place a short delay between the display and class adjustments.

@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();
    }
}

I also applied the ModalClass and ModalDisplay variables to the backdrop element too

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

I believe bootstrap can better identify the state change that triggers the animation this way

Bathetic answered 8/2, 2021 at 10:50 Comment(1)
can you please post working example with working fade effect ? for me addidng the delay is not causing animation - just delay a bit showing of backdrop . thanksPustulant
A
9

With Kyle solution my Dialog do not close when i click on the backdrop.

I saw that it is a problem of z-index: the modal div has a z-index of 1050, and the backdrop div has 1040, in this way i was not able to click my backdrop.

I have moved the backdrop inside the dialog div and added to the modal-dialog div z-index > 1040 (ES: 1055)

I also added data-dismiss="modal" @onclick="() => Close()" to the backdrop div and now it works as well as the "Close" button.

<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>
Archerfish answered 26/3, 2021 at 14:49 Comment(3)
your solution is nice but not working using <div class="modal-dialog modal-dialog-centered"> any idea why?Abscess
No need to move around backdrop. Just remove onclick-handler from the backdrop and use it on div having css class "modal" instead.Prodigy
Forget it ;-) With this dialog will close not only if you click outside but also if you click inside the dialog.Prodigy
J
3

As an alternative you can use Bootstrap Blazor which is an open-source and very nice implementation of bootstrap integrated with blazor.

Juieta answered 12/5, 2022 at 8:7 Comment(1)
Got me barking up the right tree. The link is broken though - should be here: demos.blazorbootstrap.com/modalsNamedropper
H
2

for backdrop shadow only add fade class:

<div class="modal fade @ModalClass" tabindex="-1" role="dialog" 
     style="display:@ModalDisplay">
Houppelande answered 16/2, 2021 at 8:47 Comment(1)
this is not always workingAbscess
A
1

Kyle's components work well but does anyone know how to add draggable and resizable features to a bootstrap modal using the jqueryUi draggable()/resizeable() functions?

I have this link to a pure javascript solution: DRAG AND RESIZE BOOTSTRAP MODAL that essentially calls the resizeable and draggable functions on the modal divs

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

I've tried adding this script to my _Host.cshtml page but it has no effect. Any advice on how to do this would be gratefully received...

David

Updated with answer

The answer is to explicitly call a javascript function in the OnAfterRenderAsync override to apply the JQuery UI functions to the modal divs.

E.g.

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

where setModalDraggableAndResizable is a javascript function in the _Hosts.cshtml:

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

And the modal is now draggable and resizable...

Modal example image

Akeyla answered 17/3, 2021 at 11:4 Comment(0)
U
1

Update: I've converted this answer into a service which can be found here.

I adjusted Kyles and grammophones answers to support our beloved Alert, Prompt, and Confirm from both C# and JavaScript. Tested in the lastest Blazor Server release with 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();
    }
}

Markup Usage

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

Code Usage

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 Usage

With promise support we can get a response from Prompt and Confirm right from JavaScript. To avoid declaring our Modal as static we need to setup a 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 Note: Polyfil recommended for promise support in older browsers

Unleash answered 11/5, 2022 at 20:30 Comment(2)
why do you need to use the Js here?Abscess
@Abscess There is no need for JS here. But if you happen to be writing JavaScript for a component, the last part of my answer just describes how you could replace the standard JS Alert, Prompt, and Confirm boxes so that all your modals match throughout your application. If you don't need to call the modal from JS just remove [JSInvokable("Show")] and JS.InvokeVoidAsync("MODAL.SetDotnetReference", DotNetObjectReference.Create(this));.Unleash
M
1

To include fade in and slide down effects, it would be needed simply to add some CSS. For example, this could be as I did in my code.

My modal blazor component is the following:

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

Please note "fadeInAnimation" class. This will be added to the div element when "isDisplayed" is true. In CSS, I wrote the following code to implement fade in and slide down effects:

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

I hope this will work for you.

Massorete answered 21/1, 2023 at 17:1 Comment(0)
E
1

Based on answers of Kyles and Nenad Savic, If someone is looking for a complete solution that uses Bootstrap 5 with similar fadeIn animation + the possibility to hook into callback action when confirm button is clicked;

Confirmation.razor

<div class="modal @(_modalOpen ? "fadeInAnimation" : "")" tabindex="-1" aria-labelledby="confirmationModalLabel" aria-hidden="true" style="display:@(_modalOpen ? " block" : "none")">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="confirmationModalLabel">@Title</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="CloseModal"></button>
        </div>
            <div class="modal-body">
                <p>@Content</p>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-dismiss="modal" @onclick="CloseModal">@CancelButtonLabel</button>
                <button type="button" class="btn btn-danger" @onclick="ConfirmDeleteAsync">@ConfirmButtonLabel</button>
            </div>
        </div>
    </div>
</div>

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

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

    [Parameter]
    public required string Content { get; set; }

    [Parameter]
    public required string ConfirmButtonLabel { get; set; }

    [Parameter]
    public required string CancelButtonLabel { get; set; } = "Cancel";

    [Parameter]
    public EventCallback<bool> ConfirmedCallback { get; set; }

    private bool _modalOpen = false;

    public void OpenModal()
    {
        _modalOpen = true;
        StateHasChanged();
    }

    public void CloseModal()
    {
        _modalOpen = false;
        StateHasChanged();
    }

    private async Task ConfirmDeleteAsync()
    {
        CloseModal();
        await ConfirmedCallback.InvokeAsync();
    }
}

Confirmation.razor.css

@keyframes fade-in-slide-down {
    from {
        margin-top: -70px;
    }

    to {
         margin-top: 0px;
    }
}

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

And then in your Page.razor where you want to use the component.

<Confirmation @ref="_confirmationModal" Title="Delete record" Content="Are you sure you want to delete this record?" ConfirmButtonLabel="Delete" ConfirmedCallback="DeleteRecordAsync" />

<button @onclick="() => _confirmationModal?.OpenModal()" class="btn btn-outline-danger">Delete</button>

....

@code {
    private Confirmation? _confirmationModal;

    private async Task DeleteRecordAsync()
    {
        await MyService.DeleteAsync(...);
        ...
    }
}
Eliza answered 7/4 at 15:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.