Welcome to mirror list, hosted at ThFree Co, Russian Federation.

NavigationLock.cs « Routing « src « Web « Components « src - github.com/dotnet/aspnetcore.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 622ab43a18656866e5d5cbcfd2c3522cfd6e7ef0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Globalization;
using Microsoft.JSInterop;

namespace Microsoft.AspNetCore.Components.Routing;

/// <summary>
/// A component that can be used to intercept navigation events. 
/// </summary>
public sealed class NavigationLock : IComponent, IHandleAfterRender, IAsyncDisposable
{
    private readonly string _id = Guid.NewGuid().ToString("D", CultureInfo.InvariantCulture);

    private RenderHandle _renderHandle;
    private IDisposable? _locationChangingRegistration;
    private bool _hasLocationChangingHandler;
    private bool _confirmExternalNavigation;

    private bool HasLocationChangingHandler => OnBeforeInternalNavigation.HasDelegate;

    [Inject]
    private IJSRuntime JSRuntime { get; set; } = default!;

    [Inject]
    private NavigationManager NavigationManager { get; set; } = default!;

    /// <summary>
    /// Gets or sets a callback to be invoked when an internal navigation event occurs.
    /// </summary>
    [Parameter]
    public EventCallback<LocationChangingContext> OnBeforeInternalNavigation { get; set; }

    /// <summary>
    /// Gets or sets whether a browser dialog should prompt the user to either confirm or cancel
    /// external navigations.
    /// </summary>
    [Parameter]
    public bool ConfirmExternalNavigation { get; set; }

    void IComponent.Attach(RenderHandle renderHandle)
    {
        _renderHandle = renderHandle;
    }

    Task IComponent.SetParametersAsync(ParameterView parameters)
    {
        foreach (var parameter in parameters)
        {
            if (parameter.Name.Equals(nameof(OnBeforeInternalNavigation), StringComparison.OrdinalIgnoreCase))
            {
                OnBeforeInternalNavigation = (EventCallback<LocationChangingContext>)parameter.Value;
            }
            else if (parameter.Name.Equals(nameof(ConfirmExternalNavigation), StringComparison.OrdinalIgnoreCase))
            {
                ConfirmExternalNavigation = (bool)parameter.Value;
            }
            else
            {
                throw new ArgumentException($"The component '{nameof(NavigationLock)}' does not accept a parameter with the name '{parameter.Name}'.");
            }
        }

        if (_hasLocationChangingHandler != HasLocationChangingHandler ||
            _confirmExternalNavigation != ConfirmExternalNavigation)
        {
            _renderHandle.Render(static builder => { });
        }

        return Task.CompletedTask;
    }

    async Task IHandleAfterRender.OnAfterRenderAsync()
    {
        if (_hasLocationChangingHandler != HasLocationChangingHandler)
        {
            _hasLocationChangingHandler = HasLocationChangingHandler;
            _locationChangingRegistration?.Dispose();
            _locationChangingRegistration = _hasLocationChangingHandler
                ? NavigationManager.RegisterLocationChangingHandler(OnLocationChanging)
                : null;
        }

        if (_confirmExternalNavigation != ConfirmExternalNavigation)
        {
            _confirmExternalNavigation = ConfirmExternalNavigation;
            if (_confirmExternalNavigation)
            {
                await JSRuntime.InvokeVoidAsync(NavigationLockInterop.EnableNavigationPrompt, _id);
            }
            else
            {
                await JSRuntime.InvokeVoidAsync(NavigationLockInterop.DisableNavigationPrompt, _id);
            }
        }
    }

    private async ValueTask OnLocationChanging(LocationChangingContext context)
    {
        await OnBeforeInternalNavigation.InvokeAsync(context);
    }

    async ValueTask IAsyncDisposable.DisposeAsync()
    {
        _locationChangingRegistration?.Dispose();

        if (_confirmExternalNavigation)
        {
            try
            {
                await JSRuntime.InvokeVoidAsync(NavigationLockInterop.DisableNavigationPrompt, _id);
            }
            catch (JSDisconnectedException)
            {
                // If the browser is gone, we don't need it to clean up any browser-side state
            }
        }
    }
}