tracks form changes and warns users before navigation
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
BackButtonUrl="/events" />
|
||||
|
||||
<EditForm id="create-event-form" Model="EventDefinition" OnValidSubmit="OnValidSubmit" Enhance>
|
||||
<FormChangeTracker @ref="_formChangeTracker" />
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator />
|
||||
<MudGrid>
|
||||
@@ -48,17 +49,27 @@
|
||||
|
||||
<FormActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Form="create-event-form">Create</MudButton>
|
||||
<MudButton Href="/events" Variant="Variant.Text">Cancel</MudButton>
|
||||
<MudButton OnClick="HandleCancel" Variant="Variant.Text">Cancel</MudButton>
|
||||
</FormActions>
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromForm]
|
||||
private EventDefinition EventDefinition { get; set; } = new();
|
||||
|
||||
private FormChangeTracker? _formChangeTracker;
|
||||
|
||||
private void OnValidSubmit()
|
||||
{
|
||||
_formChangeTracker?.AllowNavigation();
|
||||
|
||||
context.Events.Add(EventDefinition);
|
||||
context.SaveChanges();
|
||||
NavigationManager.NavigateTo("/events");
|
||||
}
|
||||
|
||||
private void HandleCancel()
|
||||
{
|
||||
_formChangeTracker?.AllowNavigation();
|
||||
NavigationManager.NavigateTo("/events");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
BackButtonUrl="/events" />
|
||||
|
||||
<EditForm id="edit-event-form" Model="EventDefinition" OnValidSubmit="OnValidSubmit" Enhance>
|
||||
<FormChangeTracker @ref="_formChangeTracker" />
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator />
|
||||
<MudGrid>
|
||||
@@ -55,7 +56,7 @@
|
||||
|
||||
<FormActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Form="edit-event-form">Save</MudButton>
|
||||
<MudButton Href="/events" Variant="Variant.Text">Cancel</MudButton>
|
||||
<MudButton OnClick="HandleCancel" Variant="Variant.Text">Cancel</MudButton>
|
||||
</FormActions>
|
||||
|
||||
@code {
|
||||
@@ -65,6 +66,8 @@
|
||||
[SupplyParameterFromForm]
|
||||
private EventDefinition? EventDefinition { get; set; }
|
||||
|
||||
private FormChangeTracker? _formChangeTracker;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
EventDefinition ??= await context.Events.FirstOrDefaultAsync(m => m.Id == Id);
|
||||
@@ -84,6 +87,8 @@
|
||||
try
|
||||
{
|
||||
context.SaveChangesAsync();
|
||||
_formChangeTracker?.AllowNavigation();
|
||||
NavigationManager.NavigateTo("/events");
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
@@ -96,7 +101,11 @@
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCancel()
|
||||
{
|
||||
_formChangeTracker?.AllowNavigation();
|
||||
NavigationManager.NavigateTo("/events");
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
BackButtonUrl="/students" />
|
||||
|
||||
<EditForm id="create-student-form" Model="Student" OnValidSubmit="OnValidSubmit" Enhance>
|
||||
<FormChangeTracker @ref="_formChangeTracker" />
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator />
|
||||
<MudGrid>
|
||||
@@ -29,18 +30,27 @@
|
||||
|
||||
<FormActions>
|
||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Form="create-student-form">Create</MudButton>
|
||||
<MudButton Href="/students" Variant="Variant.Text">Cancel</MudButton>
|
||||
<MudButton OnClick="HandleCancel" Variant="Variant.Text">Cancel</MudButton>
|
||||
</FormActions>
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromForm]
|
||||
private Student Student { get; set; } = new() { TsaYear = 1 };
|
||||
|
||||
private FormChangeTracker? _formChangeTracker;
|
||||
|
||||
private void OnValidSubmit(EditContext context)
|
||||
{
|
||||
_formChangeTracker?.AllowNavigation();
|
||||
|
||||
Context.Students.Add(Student);
|
||||
Context.SaveChanges();
|
||||
NavigationManager.NavigateTo("/students");
|
||||
}
|
||||
|
||||
private void HandleCancel()
|
||||
{
|
||||
_formChangeTracker?.AllowNavigation();
|
||||
NavigationManager.NavigateTo("/students");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
@* https://www.mudblazor.com/components/form *@
|
||||
@* https://medium.com/@husainalbar/applying-mudblazor-for-crud-operations-in-our-blazor-project-a343037a52ef *@
|
||||
<EditForm method="post" Model="Student" OnValidSubmit="UpdateStudent" FormName="edit" Enhance>
|
||||
<FormChangeTracker @ref="_formChangeTracker" />
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator/>
|
||||
<MudGrid>
|
||||
@@ -49,7 +50,7 @@
|
||||
|
||||
<FormActions>
|
||||
<MudButton OnClick="UpdateStudent" Variant="Variant.Filled" Color="Color.Primary">Save</MudButton>
|
||||
<MudButton Href="@(ReturnUrl ?? "/students")" Variant="Variant.Text">Cancel</MudButton>
|
||||
<MudButton OnClick="HandleCancel" Variant="Variant.Text">Cancel</MudButton>
|
||||
</FormActions>
|
||||
|
||||
@code {
|
||||
@@ -62,6 +63,8 @@
|
||||
[SupplyParameterFromForm]
|
||||
private Student? Student { get; set; }
|
||||
|
||||
private FormChangeTracker? _formChangeTracker;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
Student ??= await Context.Students.FirstOrDefaultAsync(m => m.Id == Id);
|
||||
@@ -75,7 +78,7 @@
|
||||
// To protect from overposting attacks, enable the specific properties you want to bind to.
|
||||
// For more information, see https://learn.microsoft.com/aspnet/core/blazor/forms/#mitigate-overposting-attacks.
|
||||
private async Task UpdateStudent()
|
||||
{
|
||||
{
|
||||
if (Student?.OfficerRole == 0)
|
||||
Student.OfficerRole = null;
|
||||
|
||||
@@ -84,6 +87,8 @@
|
||||
try
|
||||
{
|
||||
await Context.SaveChangesAsync();
|
||||
_formChangeTracker?.AllowNavigation();
|
||||
NavigationManager.NavigateTo(ReturnUrl ?? "/students");
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
@@ -96,7 +101,11 @@
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCancel()
|
||||
{
|
||||
_formChangeTracker?.AllowNavigation();
|
||||
NavigationManager.NavigateTo(ReturnUrl ?? "/students");
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
BackButtonUrl="/teams" />
|
||||
|
||||
<EditForm method="post" Model="Team" OnValidSubmit="AddTeam" FormName="create" Enhance>
|
||||
<FormChangeTracker @ref="_formChangeTracker" />
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator />
|
||||
<MudGrid>
|
||||
@@ -67,13 +68,14 @@
|
||||
|
||||
<FormActions>
|
||||
<MudButton OnClick="AddTeam" Variant="Variant.Filled" Color="Color.Primary">Add</MudButton>
|
||||
<MudButton Href="/teams" Variant="Variant.Text">Cancel</MudButton>
|
||||
<MudButton OnClick="HandleCancel" Variant="Variant.Text">Cancel</MudButton>
|
||||
</FormActions>
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromForm]
|
||||
private Team Team { get; set; } = new();
|
||||
|
||||
private FormChangeTracker? _formChangeTracker;
|
||||
private List<EventDefinition>? _events;
|
||||
private List<Student> _students = [];
|
||||
private IEnumerable<Student> _selectedStudents = [];
|
||||
@@ -159,6 +161,13 @@
|
||||
Context.Teams.Add(Team);
|
||||
|
||||
await Context.SaveChangesAsync();
|
||||
_formChangeTracker?.AllowNavigation();
|
||||
NavigationManager.NavigateTo("/teams");
|
||||
}
|
||||
|
||||
private void HandleCancel()
|
||||
{
|
||||
_formChangeTracker?.AllowNavigation();
|
||||
NavigationManager.NavigateTo("/teams");
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,7 @@
|
||||
BackButtonUrl="/teams" />
|
||||
|
||||
<EditForm method="post" Model="Team" OnValidSubmit="UpdateTeam" FormName="edit" Enhance>
|
||||
<FormChangeTracker @ref="_formChangeTracker" />
|
||||
<AntiforgeryToken />
|
||||
<DataAnnotationsValidator/>
|
||||
<MudGrid>
|
||||
@@ -38,7 +39,7 @@
|
||||
|
||||
<FormActions>
|
||||
<MudButton OnClick="UpdateTeam" Variant="Variant.Filled" Color="Color.Primary">Save</MudButton>
|
||||
<MudButton Href="/teams" Variant="Variant.Text">Cancel</MudButton>
|
||||
<MudButton OnClick="HandleCancel" Variant="Variant.Text">Cancel</MudButton>
|
||||
</FormActions>
|
||||
|
||||
@code {
|
||||
@@ -48,6 +49,7 @@
|
||||
[SupplyParameterFromForm]
|
||||
private Team? Team { get; set; }
|
||||
|
||||
private FormChangeTracker? _formChangeTracker;
|
||||
private IEnumerable<Student>? _selectedStudents = [];
|
||||
private List<Student> _students = [];
|
||||
|
||||
@@ -100,6 +102,8 @@
|
||||
try
|
||||
{
|
||||
await Context.SaveChangesAsync();
|
||||
_formChangeTracker?.AllowNavigation();
|
||||
NavigationManager.NavigateTo("/teams");
|
||||
}
|
||||
catch (DbUpdateConcurrencyException)
|
||||
{
|
||||
@@ -112,7 +116,11 @@
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleCancel()
|
||||
{
|
||||
_formChangeTracker?.AllowNavigation();
|
||||
NavigationManager.NavigateTo("/teams");
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
@namespace WebApp.Components.Shared.Components
|
||||
@implements IDisposable
|
||||
@inject IDialogService DialogService
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
@code {
|
||||
[CascadingParameter]
|
||||
private EditContext? EditContext { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public EditContext? ExplicitEditContext { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
private bool _isDirty = false;
|
||||
private bool _allowNavigation = false;
|
||||
private IDisposable? _navigationRegistration;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var contextToUse = ExplicitEditContext ?? EditContext;
|
||||
|
||||
if (contextToUse == null)
|
||||
{
|
||||
throw new InvalidOperationException("FormChangeTracker requires an EditContext. Either provide it as a cascading parameter or through the ExplicitEditContext parameter.");
|
||||
}
|
||||
|
||||
// Subscribe to field changes to detect when form becomes dirty
|
||||
contextToUse.OnFieldChanged += HandleFieldChanged;
|
||||
|
||||
// Register navigation handler to intercept navigation attempts
|
||||
_navigationRegistration = NavigationManager.RegisterLocationChangingHandler(OnLocationChanging);
|
||||
}
|
||||
|
||||
private void HandleFieldChanged(object? sender, FieldChangedEventArgs e)
|
||||
{
|
||||
if (Enabled)
|
||||
{
|
||||
_isDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask OnLocationChanging(LocationChangingContext context)
|
||||
{
|
||||
// Allow navigation if form isn't dirty, navigation is explicitly allowed, or tracking is disabled
|
||||
if (!_isDirty || _allowNavigation || !Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Show confirmation dialog
|
||||
var result = await DialogService.ShowMessageBox(
|
||||
"Unsaved Changes",
|
||||
"You have unsaved changes. Are you sure you want to leave this page?",
|
||||
yesText: "Leave",
|
||||
cancelText: "Stay");
|
||||
|
||||
// If user chooses to stay (result is null or false), prevent navigation
|
||||
if (result != true)
|
||||
{
|
||||
context.PreventNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this method before programmatic navigation to bypass the unsaved changes warning.
|
||||
/// Use this when the form has been successfully saved or when the user explicitly cancels.
|
||||
/// </summary>
|
||||
public void AllowNavigation()
|
||||
{
|
||||
_allowNavigation = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reset the dirty state of the form. This marks the form as clean and resets the navigation flag.
|
||||
/// </summary>
|
||||
public void MarkClean()
|
||||
{
|
||||
_isDirty = false;
|
||||
_allowNavigation = false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
var contextToUse = ExplicitEditContext ?? EditContext;
|
||||
if (contextToUse != null)
|
||||
{
|
||||
contextToUse.OnFieldChanged -= HandleFieldChanged;
|
||||
}
|
||||
_navigationRegistration?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
@namespace WebApp.Components.Shared.Components
|
||||
@using MudBlazor
|
||||
|
||||
<PageTitle>@GetPageTitle()</PageTitle>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user