Search code examples
eventsblazor-server-sidesemaphore

Is there a way to stop in a Blazor component method until a boolean is set?


This is for Blazor Server version 7. I have a situation where the user is deciding to navigate to another page on the site when they have entered fields on a form and have not saved them.

I am using <NavigationLock ConfirmExternalNavigation="@_hasUnsavedChanges" OnBeforeInternalNavigation="OnBeforeInternalNavigation" /> to ask the user "Are you sure?" in OnBeforeInternalNavigation().

In that method I display a popup component. Once it is displayed, I want to stop execution in that method until the user clicks the Yes or No button. Once that is clicked I want to then resume execution in that method , calling context.PreventNavigation() if they clicked No.

First off, is this a bad idea? The page logically is synchronous at this point where the only thing they can do on the page is click yes or no.

Second, how can I do this? Are events the best approach?


Solution

  • Here's a single page demonstration using a Bootstrap modal and the TaskCompletionSource class.

    @page "/"
    
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    
    Welcome to your new app.
    
    <SurveyPrompt Title="How is Blazor working for you?" />
    
    <div class="@_dialogCss" tabindex="-1">
        <div class="modal-dialog">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title">Exit Page</h5>
                    <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" @onclick=this.Cancel></button>
                </div>
                <div class="modal-body">
                    <p>Do you really want to leave me?</p>
                </div>
                <div class="modal-footer">
                    <button type="button" class="btn btn-danger" @onclick=this.Navigate>Navigate</button>
                    <button type="button" class="btn btn-primary" @onclick=this.Cancel>Cancel</button>
                </div>
            </div>
        </div>
    </div>
    
    <NavigationLock OnBeforeInternalNavigation=this.OnNavigation />
    
    <style>
        .show-dialog {display:block; }
    </style>
    
    @code {
        private bool _showDialog;
        private string _dialogCss => _showDialog ? "modal show-dialog" : "modal";
    
        private TaskCompletionSource<bool> _taskCompletionSource = new();
    
        private async Task OnNavigation(LocationChangingContext context)
        {
            if (!await this.ShowDialogAsync())
                context.PreventNavigation();
        }
    
        private Task<bool> ShowDialogAsync()
        {
            _showDialog = true;
            _taskCompletionSource = new();
            // Queue a Render Request
            StateHasChanged();
            // returns the Task associated with the TaskCompletionSource instance
            // this is a running Task that the caller can await
            return _taskCompletionSource.Task;
        }
    
        private Task CloseDialogAsync(bool navigate)
        {
            _showDialog = false;
            // Sets the Task to completed
            _taskCompletionSource.SetResult(navigate);
            // Queue a Render Request
            StateHasChanged();
            return _taskCompletionSource.Task;
        }
    
        private Task Navigate()
            => CloseDialogAsync(true);
    
        private Task Cancel()
            => CloseDialogAsync(false);
    }