The code below demonstrates how to implement a generic modal dialog i.e. a wrapper for any component/page that you want to display in modal mode. The concrete implementation of IModalDialog
shows how to implement a Bootstrap version.
I use a more complex version of this. My edit/View components are written to run in modal or full page mode.
Support Classes
First three support classes that are fairly self-evident:
public class ModalResult
{
public ModalResultType ResultType { get; private set; } = ModalResultType.NoSet;
// Whatever object you wish to pass back
public object? Data { get; set; } = null;
// A set of static methods to build a BootstrapModalResult
public static ModalResult OK() => new ModalResult() { ResultType = ModalResultType.OK };
public static ModalResult Exit() => new ModalResult() { ResultType = ModalResultType.Exit };
public static ModalResult Cancel() => new ModalResult() { ResultType = ModalResultType.Cancel };
public static ModalResult OK(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.OK };
public static ModalResult Exit(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.Exit };
public static ModalResult Cancel(object data) => new ModalResult() { Data = data, ResultType = ModalResultType.Cancel };
}
public class ModalOptions
{
// Whatever data you want to pass
// my complex version uses a <string, object> dictionary
}
public enum ModalResultType
{
NoSet,
OK,
Cancel,
Exit
}
IModalDialog Interface
Now the modal dialog Interface
.
using Microsoft.AspNetCore.Components;
public interface IModalDialog
{
ModalOptions Options { get; }
// Method to display a Modal Dialog
Task<ModalResult> ShowAsync<TModal>(ModalOptions options) where TModal : IComponent;
// Method to update the Modal Dialog during display
void Update(ModalOptions? options = null);
// Method to dismiss - normally called by the dismiss button in the header bar
void Dismiss();
// Method to close the dialog - normally called by the child component TModal
void Close(ModalResult result);
}
The Base Bootstrap Implementation
- There's no JS.
- I use a
TaskCompletionSource
to make the open/close an async process.
- The component to display is passed as part of the open call.
@inherits ComponentBase
@implements IModalDialog
@if (this.Display)
{
<CascadingValue Value="(IModalDialog)this">
<div class="modal" tabindex="-1" style="display:block;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modal title</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick="() => Close(ModalResult.Exit())"></button>
</div>
<div class="modal-body">
@this.Content
</div>
</div>
</div>
</div>
</CascadingValue>
}
@code {
public ModalOptions Options { get; protected set; } = new ModalOptions();
public bool Display { get; protected set; }
protected RenderFragment? Content { get; set; }
protected TaskCompletionSource<ModalResult> _ModalTask { get; set; } = new TaskCompletionSource<ModalResult>();
public Task<ModalResult> ShowAsync<TModal>(ModalOptions options) where TModal : IComponent
{
this.Options = options ??= this.Options;
this._ModalTask = new TaskCompletionSource<ModalResult>();
this.Content = new RenderFragment(builder =>
{
builder.OpenComponent(1, typeof(TModal));
builder.CloseComponent();
});
this.Display = true;
InvokeAsync(StateHasChanged);
return this._ModalTask.Task;
}
public void Update(ModalOptions? options = null)
{
this.Options = options ??= this.Options;
InvokeAsync(StateHasChanged);
}
public async void Dismiss()
{
_ = this._ModalTask.TrySetResult(ModalResult.Cancel());
this.Display = false;
this.Content = null;
await InvokeAsync(StateHasChanged);
}
public async void Close(ModalResult result)
{
_ = this._ModalTask.TrySetResult(result);
this.Display = false;
this.Content = null;
await InvokeAsync(StateHasChanged);
}
}
Demo
A simple "Edit" component:
@inject NavigationManager NavManager
<h3>EditForm</h3>
<div class="p-3">
<button class="btn btn-success" @onclick=Close>Close</button>
</div>
@code {
[CascadingParameter] private IModalDialog? modal { get; set; }
private void Close()
{
if (modal is not null)
modal.Close(ModalResult.OK());
else
this.NavManager.NavigateTo("/");
}
}
And a test page:
@page "/"
<div class="p-2">
<button class="btn btn-primary" @onclick=OpenDialog>Click</button>
</div>
<div class="p-2">
result: @Value
</div>
<ModalDialog @ref=this.modal />
@code {
private string Value { get; set; } = "Fred";
private IModalDialog? modal;
private IModalDialog Modal => modal!;
private async void OpenDialog()
{
var options = new ModalOptions();
var ret = await Modal.ShowAsync<EditorForm>(new ModalOptions());
if (ret is not null)
{
Value = ret.ResultType.ToString();
}
StateHasChanged();
}
}