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