I would like to know if we can pass id and class of button as parameters to modal, so that I could display dynamic data in my modal page.
Navbar.razor:
<li><button class ="firstButtonDialog" id="mm" @onclick="() => _modal.Open()">mm</button></li>
<li><button class ="secondButtonDialog" id="cm" @onclick="() => _modal.Open()">cm</button</li>
<li><button class ="thirdButtonDialog" id="meter" @onclick="() =>_modal.Open()">meter</button></li>meter</button></li>
private Modal _modal { get; set; }
public string _min { get; set; }
public string _max { get; set; }
private void OnModalMin(string min)
{
_min = min;
}
private void OnModalMax(string max)
{
_max = max;
}
'Title' and 'Measurement' need to be dynamic in modal page. I would like to use passed parameters to display dynamic data here.
Modal.razor:
<h5 class="modal-title">firstButtonDialog</h5> //title need to be dynamic based on button clicked
<div class="modal-body">
<label for="Min">Enter Min</label>
<input @bind="min" type="text" id="Min" name="Min">[mm]<br> // [mm] measurement need to be dynamic based on button clicked
<label for="Max">Enter Max:</label>
<input @bind="max" type="text" id="Max" name="Max">[mm]<br>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" @onclick="() => Done()">Done</button>
</div>
@code {
private string min;
private string max;
public Guid Guid = Guid.NewGuid();
public string ModalDisplay = "none;";
public string ModalClass = "";
[Parameter] public EventCallback<string> OnDoneCallback1 { get; set; }
[Parameter] public EventCallback<string> OnDoneCallback2 { get; set; }
public void Open()
{
ModalDisplay = "block;";
ModalClass = "Show";
ShowBackdrop = true;
StateHasChanged();
}
public void Close()
{
ModalDisplay = "none";
ModalClass = "";
ShowBackdrop = false;
StateHasChanged();
}
public async Task Done()
{
await InvokeAsync(() => OnDoneCallback1.InvokeAsync(min));
await InvokeAsync(() => OnDoneCallback2.InvokeAsync(max));
}
}
There is a simple answer to this question, or a longer more comprehensive one that demonstrates how to build a "basic" Modal Dialog framework. This is the later.
You need to separate out the Modal Dialog from the EditForm. You should be able to host any Form in a Modal Dialog.
First, define an IModalDialog
interface so we can have more than one implementation of Modal Dialog - say a simple clean Css one or a Bootstrap one.
public interface IModalDialog
{
public ModalRequest ModalRequest { get; }
public bool IsActive { get; }
public bool Display { get; }
public Task<ModalResult> ShowAsync<TModal>(ModalRequest modalRequest) where TModal : IComponent;
public Task<bool> SwitchAsync<TModal>(ModalRequest modalRequest) where TModal : IComponent;
public void Update(ModalRequest? modalRequest);
public void Dismiss();
public void Close(ModalResult result);
}
We pass in a ModalRequest
:
public record ModalRequest
{
public IDictionary<string, object> Parameters { get; init; } = new Dictionary<string, object>();
public object? InData { get; init; } = null;
}
and get back out a ModalResult
public class ModalResult
{
public ModalResultType ResultType { get; private set; } = ModalResultType.NoSet;
public object? Data { get; set; } = null;
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 enum ModalResultType { NoSet, OK, Cancel, Exit }
}
Our basic implementation - ModalDialog.razor. It operates in an async context using a TaskCompletionSource
. You open the dialog by calling ShowAsync<TModal>
setting TModal
to be the component you want hosted in the form and providing settings and data through a ModalRequest
instance. You then await the provide Task
that is set to complete when you exit the form. We use DynamicComponent
to create TModal
. Note that we cascade the Modal instance to the hosted component.
@implements IModalDialog
@if (this.Display)
{
<CascadingValue Value="(IModalDialog)this">
<div class="base-modal-background" @onclick="OnBackClick">
<div class="base-modal-content" @onclick:stopPropagation="true">
<DynamicComponent Type=this.ModalContentType Parameters=this.ModalRequest.Parameters />
</div>
</div>
</CascadingValue>
}
@code {
[Parameter] public bool ExitOnBackGroundClick { get; set; } = false;
public ModalRequest ModalRequest { get; private set; } = new ModalRequest();
public object? InData { get; } = null;
public bool Display { get; protected set; } = false;
protected TaskCompletionSource<ModalResult> _ModalTask { get; set; } = new TaskCompletionSource<ModalResult>();
protected Type? ModalContentType = null;
public bool IsActive
=> this.ModalContentType is not null;
public Task<ModalResult> ShowAsync<TModal>(ModalRequest modalRequest) where TModal : IComponent
{
this.ModalRequest = modalRequest;
this.ModalContentType = typeof(TModal);
this._ModalTask = new TaskCompletionSource<ModalResult>();
this.Display = true;
InvokeAsync(StateHasChanged);
return this._ModalTask.Task;
}
public async Task<bool> SwitchAsync<TModal>(ModalRequest modalRequest) where TModal : IComponent
{
this.ModalRequest = modalRequest;
this.ModalContentType = typeof(TModal);
await InvokeAsync(StateHasChanged);
return true;
}
public void Update(ModalRequest? modalRequest = null)
{
this.ModalRequest = modalRequest ?? this.ModalRequest;
InvokeAsync(StateHasChanged);
}
private void OnBackClick(MouseEventArgs e)
{
if (ExitOnBackGroundClick)
this.Close(ModalResult.Exit());
}
public async void Dismiss()
=> await this.Reset(ModalResult.Cancel());
public async void Close(ModalResult result)
=> await this.Reset(result);
private async Task Reset(ModalResult result)
{
_ = this._ModalTask.TrySetResult(ModalResult.Cancel());
this.Display = false;
this.ModalContentType = null;
await InvokeAsync(StateHasChanged);
}
}
And it's component css - ModalDialog.razor.css
div.base-modal-background {
display: block;
position: fixed;
z-index: 101; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
div.base-modal-content {
background-color: #fefefe;
margin: 10% auto;
padding: 10px;
border: 2px solid #888;
width: 90%;
}
Your data class:
public class MyData
{
public int Min { get; set; }
public int Max { get; set; }
}
And form - MyForm.razor. This captures the cascaded IModalDialog
and interacts with IModalDialog
to get any provided data and close the dialog.
<div class="modal-title border-bottom border-secondary">
<h5>@this.Title</h5>
</div>
<div class="modal-body">
<label class="form-label" for="Min">Enter Min [mm]</label>
<input class="form-control" @bind=model.Min type="number">
<label class="form-label" for="Max">Enter Max [mm]:</label>
<input class="form-control" @bind=model.Max type="number">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" @onclick="() => Done()">Done</button>
</div>
@code {
private MyData model = new MyData();
[Parameter] public string Title { get; set; } = "I Need a Title!";
[CascadingParameter] private IModalDialog? modalDialog { get; set; }
private ModalRequest modalRequest
=> modalDialog?.ModalRequest ?? new ModalRequest();
protected override void OnInitialized()
{
if (modalDialog is null)
throw new NullReferenceException("You must cascade a IModalDialog to use this Form");
model = (MyData)(modalDialog?.ModalRequest.InData ?? new MyData());
}
private void Done()
=> modalDialog?.Close(ModalResult.OK(model));
}
And finally a demo page. It hosts the ModalDialog
component and interacts with it to open the dialog.
@page "/"
<PageTitle>Modal Dialog Demo</PageTitle>
<div class="m-2 b-2">
<button class="btn btn-primary" @onclick=OpenDialog1>Edit Model 1</button>
</div>
<div class="alert alert-primary">
<strong>Model 1</strong> Min: @this.model1.Min Max: @this.model1.Max
</div>
<div class="m-2 b-2">
<button class="btn btn-dark" @onclick=OpenDialog2>Edit Model 2</button>
</div>
<div class="alert alert-dark">
<strong>Model 2</strong> Min: @this.model2.Min Max: @this.model2.Max
</div>
<ModalDialog @ref=modalDialog ExitOnBackGroundClick=false />
@code {
private MyData model1 = new MyData { Max = 20, Min = -10 };
private MyData model2 = new MyData { Max = 50, Min = -50 };
private IModalDialog? modalDialog;
private async Task OpenDialog1()
{
var parameters = new Dictionary<string, object> { { "Title", "Modal Form 1" } };
var request = new ModalRequest { InData = this.model1, Parameters = parameters };
if (this.modalDialog is not null)
await modalDialog.ShowAsync<MyForm>(request);
// This won't complete until the dialog closes and the Task is complete.
// We can use any return data at this point
// and this component will render as part of the ComponentBase UI event handling code.
}
private async Task OpenDialog2()
{
var parameters = new Dictionary<string, object> { { "Title", "Modal Form 2" } };
var request = new ModalRequest { InData = this.model2 };
if (this.modalDialog is not null)
await modalDialog.ShowAsync<MyForm>(request);
}
}
Here's a screen shot of one of the dialogs:
The code will temporarily be here - https://github.com/ShaunCurtis/SO73617831