44

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.

Sorush
  • 3,275
  • 4
  • 28
  • 42
  • 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. – Kyle Dec 09 '19 at 21:29
  • 1
    You can use this Nuget package: https://www.nuget.org/packages/Majorsoft.Blazor.Components.Modal/ It supports backdrop and animation as well... Docs: https://github.com/majorimi/blazor-components/blob/master/.github/docs/Modal.md – Major Nov 16 '20 at 19:31

9 Answers9

82

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.

Kyle
  • 32,731
  • 39
  • 134
  • 184
  • Great, works well. Do you know how can I make the background except modal window dark? – Sorush Dec 10 '19 at 18:18
  • 1
    @Sorush I updated my answer to fix that. Bootstrap 4 looks like it's adding a `` to darken behind the modal, so I used a boolean to show or hide it. – Kyle Dec 10 '19 at 18:31
  • 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. – Linus Proxy Feb 12 '20 at 18:12
  • 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: https://getbootstrap.com/docs/4.4/components/modal/#scrolling-long-content Instead this is too big for the window and only the content behind the modal scrolls from mouse wheel. Any suggestions? – Matt Sanders Mar 18 '20 at 09:24
  • 1
    Great, but not work with `fade` class and why use you `Guid` ? i mean `public Guid Guid = Guid.NewGuid();` – Zanyar Jalal Jul 02 '20 at 20:59
  • 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. – Kyle Jul 06 '20 at 01:06
  • @ZanyarJ.Ahmed Can you elaborate on "not work with fade class"? What issue are you having? – Kyle Jul 06 '20 at 01:06
  • 1
    I mean your code not detected `fade` css class *Not work animation* and if use large `Model` not work scroll – Zanyar Jalal Jul 06 '20 at 07:02
  • Great easy way to this problem. Good that no external nuget package is used. Thanks @Kyle. – Tajveer Singh Nijjar Aug 10 '20 at 02:53
  • 2
    @ZanyarJ.Ahmed Have you found the solution to why the `fade` class is not using? – TanvirArjel Dec 07 '20 at 03:11
  • yep i also tried alot but still no fade efect - does someone have any solution? – d00lar Feb 03 '21 at 11:24
  • 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. – Matt Hamilton Feb 21 '22 at 23:58
  • 1
    @MattHamilton you can use a `@onkeydown` or `@onkeyup` handler and check the key thats pressed, if its escape then run Close() – Kyle Feb 23 '22 at 23:22
  • 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? – Matt Hamilton Feb 25 '22 at 01:14
  • How Cal I add a method saveButton in modal and use in the page? Save() in this case is inside component and I want to save the information in another page. – Rodrigo Porcionato Jan 12 '23 at 18:01
39

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; }
}
grammophone
  • 601
  • 6
  • 8
  • 1
    fade effect also does not work here - does you have any solution? – d00lar Feb 03 '21 at 11:46
  • What is FetchData? – Jens Mander Jul 07 '21 at 00:02
  • 1
    @JensMander: FetchData is the default Razor component that shows a table with the WeatherForecast. I suppose it's there just for testing – Christoph B Aug 26 '21 at 05:24
  • @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]: https://i.stack.imgur.com/Cu9Es.png – Snekithan Dec 03 '21 at 23:27
  • 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. – Snekithan Dec 04 '21 at 00:46
11

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

Daniel
  • 123
  • 1
  • 7
  • 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 . thanks – d00lar Mar 29 '22 at 11:49
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>
2

for backdrop shadow only add fade class:

<div class="modal fade @ModalClass" tabindex="-1" role="dialog" 
     style="display:@ModalDisplay">
Ali Belyani
  • 43
  • 1
  • 7
2

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

Kaveh Naseri
  • 1,102
  • 2
  • 15
  • 24
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

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

clamchoda
  • 4,411
  • 2
  • 36
  • 74
  • why do you need to use the Js here? – Emil Nov 10 '22 at 10:23
  • @Emil 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));`. – clamchoda Nov 10 '22 at 17:36
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.

Nenad Savic
  • 135
  • 2
  • 9