Skip to content

[Blazor] Add support for confirming navigations #40149

Closed
@pranavkm

Description

@pranavkm

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

Metadata

Metadata

Assignees

Labels

Priority:1Work that is critical for the release, but we could probably ship withoutarea-blazorIncludes: Blazor, Razor ComponentsenhancementThis issue represents an ask for new feature or an enhancement to an existing one

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions