'How to use Bootstrap modal in Blazor client app?
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">×</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.
Solution 1:[1]
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">×</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.
Solution 2:[2]
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">×</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; }
}
Solution 3:[3]
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
Solution 4:[4]
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>
Solution 5:[5]
for backdrop shadow only add fade class:
<div class="modal fade @ModalClass" tabindex="-1" role="dialog"
style="display:@ModalDisplay">
Solution 6:[6]
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...
Solution 7:[7]
As an alternative you can use Bootstrap Blazor which is a open-source and very nice implementation of bootstrap integrated with blazor.
Solution 8:[8]
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">×</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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | grammophone |
Solution 3 | Daniel |
Solution 4 | Daniele Ranaldi |
Solution 5 | Ali Belyani |
Solution 6 | |
Solution 7 | Kaveh Naseri |
Solution 8 |