radzenhq / radzen-blazor

Radzen Blazor is a set of 90+ free native Blazor UI components packed with DataGrid, Scheduler, Charts and robust theming including Material design and FluentUI.
https://www.radzen.com
MIT License
3.61k stars 807 forks source link

DialogService OpenAsync & Close in a second/nested Dialog causes reinitialization and state loss of first/parent Dialog #174

Closed Matthewsre closed 3 months ago

Matthewsre commented 3 years ago

I have a form that opens a Dialog using this code:

        [Inject]
        public DialogService _dialogService { get; set; }

        ......

        [Parameter]
        public int? Width { get; set; }

        ......

        private DialogOptions _options;
        private dynamic _currentModal;

        ......

        public async Task OpenAsync()
        {
            if(Width != null)
            {
                _options = new DialogOptions {
                    Width = $"{Width}px"
                };
            }
            _currentModal = await _dialogService.OpenAsync(Title, ds => ChildContent, _options);
        }

        public Task CloseAsync()
        {
            _dialogService.Close(_currentModal);
            return Task.CompletedTask;
        }

image

I have that same code in a separate child Blazor component used as a confirmation:

image

The problem is when opening and closing the second Dialog, the first Dialog completely loses all state and re-initializes a new component.

If someone clicks cancel or closes the second Dialog the state of the first Dialog should be preserved.

enchev commented 3 years ago

Hey @Matthewsre,

Dialogs are rendered in a container (RadzenDialog) in the MainLayout. Not sure if preserving state is possible at all, still if you find a way we will gladly test a pull request.

Matthewsre commented 3 years ago

@enchev, I was able to get this working with one of the other methods:

        // State of first modal is NOT lost when opening second modal
        public async Task OpenAsync<T>(Dictionary<string, object> parameters = null) where T : ComponentBase
        {
            SetOptions();
            _currentModal = await _dialogService.OpenAsync<T>(Title, parameters, _options);
        }

        // State of first modal is lost when opening second modal
        public async Task OpenAsync()
        {
            SetOptions();
            _currentModal = await _dialogService.OpenAsync(Title, ds => ChildContent, _options);
        }

        ......

        private void SetOptions()
        {
            if (Width != null)
            {
                _options = new DialogOptions {
                    Width = $"{Width}px"
                };
            }
        }

When using the OpenAsync option that takes a dictionary of parameters this is working as expected, but the behavior of the second non-generic method is not. It seems like there is something specific that causes this behavior to be different for the second option and somehow links all of the dialogs opened with it the same. Is this keyed on the Type passed in?

I noticed the type is always "object" when using the second option and the dictionary value passed in is null.

Would be great if these could both work the same since I would prefer to not use the Dictionary for specifying component parameters.

MoriLuca commented 2 years ago

Hi, I'm facing a similar problem here. I'm opening a component via

await _dialog.OpenAsync<Cmp_RichiestaMateriale>($"Ordine {_ordineSelezionato.NumeroOrdine}",
    new Dictionary<string, object>() { { "Ordine", _ordineSelezionato}},
    new DialogOptions() { Width = "90%", Height = "95%", Resizable = false, Draggable = false });

Then inside this component I open a confirm dialog by

var dialogRes = await _dialog.Confirm(dialogMessage, "Conferma", new ConfirmOptions()
  { OkButtonText = "Yes", CancelButtonText = "No" });

When the confirm dialog is created, the OnParameterSetAsync() function of the father component get executed as well causing data loss. Is there any fix or work around to handle this behaviour?

gd738429 commented 1 year ago

Hi @MoriLuca ~ Could you provide complete code for your question? Cause I tried the solution as well as yours, I can show the nested Dialog in my project. Maybe we can find out the different between yours and mine, since you've provided the complete code.

ghhv commented 1 year ago

I found this because I have the same issue as @MoriLuca.. A button on an OpenSideAsync should open a dialog.confirm but doesn't. I'm doing this with Maui Blazor and it actually crashes the app - Android shows the app unresponsive. Now to see if I can use @Matthewsre's solution to make it work..

Did you find a fix @MoriLuca ?

Schullebernd commented 1 year ago

Same issue here. It's a strange thing because it seems to depend on the size of the screen. The problem occured when I wanted to test the app on an iPhone. When I open a second dialog (inside another one) the app crashes. 1st dialog size 700x512px 2nd dialog size 700x650px On normal desktop screens sizes everything works perfect. But testing it on smaller screen sizes, where the dialog fills the screen width (due to the responsive mechanism) and the content of the dialogs need to be scrolled, the following Exception occures, when closing the second dialog:

[2023-03-26T21:04:35.346Z] Error: Microsoft.JSInterop.JSException: Cannot read properties of null (reading 'getBoundingClientRect')
TypeError: Cannot read properties of null (reading 'getBoundingClientRect')
    at Object.clientRect (https://.../_content/Radzen.Blazor/Radzen.Blazor.js:1408:19)
    at https://.../_framework/blazor.server.js:1:3506
    at new Promise (<anonymous>)
    at Ft.beginInvokeJSFromDotNet (.../_framework/blazor.server.js:1:3480)
    at Ft._invokeClientMethod (.../_framework/blazor.server.js:1:75066)
    at Ft._processIncomingData (.../_framework/blazor.server.js:1:72690)
    at Ft.connection.onreceive (.../_framework/blazor.server.js:1:67003)
    at i.onmessage (.../_framework/blazor.server.js:1:51316)
   at Microsoft.JSInterop.JSRuntime.InvokeAsync[TValue](Int64 targetInstanceId, String identifier, Object[] args)
   at Radzen.Blazor.Rendering.DialogContainer.OnDragStart(DraggableEventArgs args)
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Radzen.Blazor.Rendering.Draggable.OnMouseDown(MouseEventArgs args)
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)

Some additional info. On my dev machine (windows 11 with chrome browser) I can not get it to an exception, even if I simulate the smaller screen sizes with chrome. Hosted on azure cloud with a linux appservice and testing it with a chrome browser on windows 11 or on an iPhone it's reproduceable. Just open chrome --> F12 --> set view to iPhone XR --> 100% and try to open and close the second dialog several times.

Anyone else can confirm this?

ghhv commented 1 year ago

My quick fix was to close the dialogue completely and pass back a flag in the result which allowed the main screen to pop up the confirmation. A workaround..

Schullebernd commented 1 year ago

Thanks @ghhv. I considered to try your solution but... I looked a little bit into the details of the exception and then searched for the source code in Radzen, where the exception gets thrown. It's here:

Radzen.Blazor/Rendering/DialogContainer.razor

async Task OnDragStart(DraggableEventArgs args)
    {
        clientX = args.ClientX;
        clientY = args.ClientY;

        shouldRender = false;
        var rect = await JSRuntime.InvokeAsync<Rect>("Radzen.clientRect", dialog);
        height = $"height: {rect.Height}px;";
        width = $"width: {rect.Width}px;";

        shouldRender = true;
    }

It seems that the OnDragStart(...) method gets called when closing a dialog on a small screen. Now, after I set the Draggable Option to false. Everything is working perfectly.

I should have created a new/dedicated issue because it seems not to be the same problem like the original one in the post here...

LlamaNL commented 1 year ago

I had a similar issue where the proceeding dialogs were reinitialized upon closing a higher dialog. I solved it by injecting a singleton service into the page OpenAsync openened, which held my object.

enchev commented 3 months ago

Dragging is now made completely client-side to avoid unnecessary content rendering.

franklupo commented 1 month ago

OpenSideAsync nested not work, close previous Side dialog.

How can I solve the problem?