Summary
Support for Location change event is a very popular ask from our users. Unfortunately, given the nature of the event adding support for it that uses browser intrinsic is going to be difficult / impossible. #24417 attempted to solve this by hijacking browser navigation and managing it via Blazor. This is a fairly involved solution, with possible significant perf overhead, and we'd like to determine if using a narrower solution would suffice here.
A popular theme in the issue focused on preventing navigation to avoid losing unsaved changes in a component. A well-upvoted comment provides a sample for just this - https://github.com/ShaunCurtis/Blazr.Demo.EditForm/tree/master/Blazr.NavigationLocker. This spec is a way to determine if this is something we could get away with.
Motivation and goals
- Most popular issue for Blazor at the time of writing.
In-scope
- Add an API that allows components to prevent navigation in a platform-independent way.
- Provide turnkey solution that integrates with EditForm
Out-of-scope
- Not a general purpose location changing event handler.
Proposed solution
We introduce an API on NavigationManager that allows users to confirm when a navigation is to occur.AddNavigationConfirmationAsync returns an IAsyncDisposable which prompts for a confirmation until the returned value is disposed.
public class NavigationManager
{
+ public ValueTask<IAsyncDisposable> AddNavigationConfirmationAsync(string message, CancellationToken cancellationToken);
}
Here's an example of it in use:
@inject NavigationManager Nav
<form @oninput="OnInput" @onvalidsubmit="OnSubmit">
...
<button type="submit">Submit</button>
</form>
@code {
IAsyncDisposable? _navigationConfirmation;
async Task OnInput()
{
_navigationConfirmation ??= await Nav.AddNavigationConfirmationAsync("Are you sure you want to exit without saving?");
}
async Task OnSubmit() => await RemoveNavigationConfirmationAsync();
ValueTask IAsyncDisposable.DisposeAsync() => RemoveNavigationConfirmationAsync();
ValueTask ClearConfirmationAsync() => await (_navigationConfirmation?.DisposeAsync() ?? default);
}
The implementation relies on window.confirm for a navigation that is handled via Blazor's routing and beforeunload event for all other navigation. The message parameter is used as part of the confirm dialog, but is likely to be ignored by the unload event since browsers do not consistently support specifying it.
In addition to this, we introduce a LockNavigationWhenDirty component that uses EditContext's dirty state (IsModified() / MarkAsUnmodified()`) to add / remove navigation confirmations. Here is what this component looks like:
public class LockNavigationWhenDirty : IAsyncDisposable
{
public LockNavigationWhenDirty(NavigationManager navigationManager);
[EditorRequired, Parameter] public string Message { get; set; }
}
Usage:
<EditForm ... @onvalidsubmit="OnValidSubmit">
<LockNavigationWhenDirty />
...
</EditForm>
@code {
async OnValidSubmit()
{
// Save content
repository.SaveAsync(..);
// Release the lock now that we're satisfied
editContext.MarkAsUnmodified();
}
}
Summary
Support for Location change event is a very popular ask from our users. Unfortunately, given the nature of the event adding support for it that uses browser intrinsic is going to be difficult / impossible. #24417 attempted to solve this by hijacking browser navigation and managing it via Blazor. This is a fairly involved solution, with possible significant perf overhead, and we'd like to determine if using a narrower solution would suffice here.
A popular theme in the issue focused on preventing navigation to avoid losing unsaved changes in a component. A well-upvoted comment provides a sample for just this - https://github.com/ShaunCurtis/Blazr.Demo.EditForm/tree/master/Blazr.NavigationLocker. This spec is a way to determine if this is something we could get away with.
Motivation and goals
In-scope
Out-of-scope
Proposed solution
We introduce an API on
NavigationManagerthat allows users to confirm when a navigation is to occur.AddNavigationConfirmationAsyncreturns anIAsyncDisposablewhich prompts for a confirmation until the returned value is disposed.public class NavigationManager { + public ValueTask<IAsyncDisposable> AddNavigationConfirmationAsync(string message, CancellationToken cancellationToken); }Here's an example of it in use:
The implementation relies on window.confirm for a navigation that is handled via Blazor's routing and beforeunload event for all other navigation. The
messageparameter is used as part of theconfirmdialog, but is likely to be ignored by the unload event since browsers do not consistently support specifying it.In addition to this, we introduce a
LockNavigationWhenDirtycomponent that usesEditContext's dirty state (IsModified() /MarkAsUnmodified()`) to add / remove navigation confirmations. Here is what this component looks like:Usage: