modal-dialogblazorparameter-passing

Pass parameters to modal popup


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

Solution

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

    enter image description here

    The code will temporarily be here - https://github.com/ShaunCurtis/SO73617831