tracks form changes and warns users before navigation
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
BackButtonUrl="/events" />
|
BackButtonUrl="/events" />
|
||||||
|
|
||||||
<EditForm id="create-event-form" Model="EventDefinition" OnValidSubmit="OnValidSubmit" Enhance>
|
<EditForm id="create-event-form" Model="EventDefinition" OnValidSubmit="OnValidSubmit" Enhance>
|
||||||
|
<FormChangeTracker @ref="_formChangeTracker" />
|
||||||
<AntiforgeryToken />
|
<AntiforgeryToken />
|
||||||
<DataAnnotationsValidator />
|
<DataAnnotationsValidator />
|
||||||
<MudGrid>
|
<MudGrid>
|
||||||
@@ -48,17 +49,27 @@
|
|||||||
|
|
||||||
<FormActions>
|
<FormActions>
|
||||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Form="create-event-form">Create</MudButton>
|
<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>
|
</FormActions>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[SupplyParameterFromForm]
|
[SupplyParameterFromForm]
|
||||||
private EventDefinition EventDefinition { get; set; } = new();
|
private EventDefinition EventDefinition { get; set; } = new();
|
||||||
|
|
||||||
|
private FormChangeTracker? _formChangeTracker;
|
||||||
|
|
||||||
private void OnValidSubmit()
|
private void OnValidSubmit()
|
||||||
{
|
{
|
||||||
|
_formChangeTracker?.AllowNavigation();
|
||||||
|
|
||||||
context.Events.Add(EventDefinition);
|
context.Events.Add(EventDefinition);
|
||||||
context.SaveChanges();
|
context.SaveChanges();
|
||||||
NavigationManager.NavigateTo("/events");
|
NavigationManager.NavigateTo("/events");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleCancel()
|
||||||
|
{
|
||||||
|
_formChangeTracker?.AllowNavigation();
|
||||||
|
NavigationManager.NavigateTo("/events");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
BackButtonUrl="/events" />
|
BackButtonUrl="/events" />
|
||||||
|
|
||||||
<EditForm id="edit-event-form" Model="EventDefinition" OnValidSubmit="OnValidSubmit" Enhance>
|
<EditForm id="edit-event-form" Model="EventDefinition" OnValidSubmit="OnValidSubmit" Enhance>
|
||||||
|
<FormChangeTracker @ref="_formChangeTracker" />
|
||||||
<AntiforgeryToken />
|
<AntiforgeryToken />
|
||||||
<DataAnnotationsValidator />
|
<DataAnnotationsValidator />
|
||||||
<MudGrid>
|
<MudGrid>
|
||||||
@@ -55,7 +56,7 @@
|
|||||||
|
|
||||||
<FormActions>
|
<FormActions>
|
||||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Form="edit-event-form">Save</MudButton>
|
<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>
|
</FormActions>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -65,6 +66,8 @@
|
|||||||
[SupplyParameterFromForm]
|
[SupplyParameterFromForm]
|
||||||
private EventDefinition? EventDefinition { get; set; }
|
private EventDefinition? EventDefinition { get; set; }
|
||||||
|
|
||||||
|
private FormChangeTracker? _formChangeTracker;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
EventDefinition ??= await context.Events.FirstOrDefaultAsync(m => m.Id == Id);
|
EventDefinition ??= await context.Events.FirstOrDefaultAsync(m => m.Id == Id);
|
||||||
@@ -84,6 +87,8 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
context.SaveChangesAsync();
|
context.SaveChangesAsync();
|
||||||
|
_formChangeTracker?.AllowNavigation();
|
||||||
|
NavigationManager.NavigateTo("/events");
|
||||||
}
|
}
|
||||||
catch (DbUpdateConcurrencyException)
|
catch (DbUpdateConcurrencyException)
|
||||||
{
|
{
|
||||||
@@ -96,7 +101,11 @@
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCancel()
|
||||||
|
{
|
||||||
|
_formChangeTracker?.AllowNavigation();
|
||||||
NavigationManager.NavigateTo("/events");
|
NavigationManager.NavigateTo("/events");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
BackButtonUrl="/students" />
|
BackButtonUrl="/students" />
|
||||||
|
|
||||||
<EditForm id="create-student-form" Model="Student" OnValidSubmit="OnValidSubmit" Enhance>
|
<EditForm id="create-student-form" Model="Student" OnValidSubmit="OnValidSubmit" Enhance>
|
||||||
|
<FormChangeTracker @ref="_formChangeTracker" />
|
||||||
<AntiforgeryToken />
|
<AntiforgeryToken />
|
||||||
<DataAnnotationsValidator />
|
<DataAnnotationsValidator />
|
||||||
<MudGrid>
|
<MudGrid>
|
||||||
@@ -29,18 +30,27 @@
|
|||||||
|
|
||||||
<FormActions>
|
<FormActions>
|
||||||
<MudButton ButtonType="ButtonType.Submit" Variant="Variant.Filled" Color="Color.Primary" Form="create-student-form">Create</MudButton>
|
<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>
|
</FormActions>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[SupplyParameterFromForm]
|
[SupplyParameterFromForm]
|
||||||
private Student Student { get; set; } = new() { TsaYear = 1 };
|
private Student Student { get; set; } = new() { TsaYear = 1 };
|
||||||
|
|
||||||
|
private FormChangeTracker? _formChangeTracker;
|
||||||
|
|
||||||
private void OnValidSubmit(EditContext context)
|
private void OnValidSubmit(EditContext context)
|
||||||
{
|
{
|
||||||
|
_formChangeTracker?.AllowNavigation();
|
||||||
|
|
||||||
Context.Students.Add(Student);
|
Context.Students.Add(Student);
|
||||||
Context.SaveChanges();
|
Context.SaveChanges();
|
||||||
NavigationManager.NavigateTo("/students");
|
NavigationManager.NavigateTo("/students");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void HandleCancel()
|
||||||
|
{
|
||||||
|
_formChangeTracker?.AllowNavigation();
|
||||||
|
NavigationManager.NavigateTo("/students");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
@* https://www.mudblazor.com/components/form *@
|
@* https://www.mudblazor.com/components/form *@
|
||||||
@* https://medium.com/@husainalbar/applying-mudblazor-for-crud-operations-in-our-blazor-project-a343037a52ef *@
|
@* https://medium.com/@husainalbar/applying-mudblazor-for-crud-operations-in-our-blazor-project-a343037a52ef *@
|
||||||
<EditForm method="post" Model="Student" OnValidSubmit="UpdateStudent" FormName="edit" Enhance>
|
<EditForm method="post" Model="Student" OnValidSubmit="UpdateStudent" FormName="edit" Enhance>
|
||||||
|
<FormChangeTracker @ref="_formChangeTracker" />
|
||||||
<AntiforgeryToken />
|
<AntiforgeryToken />
|
||||||
<DataAnnotationsValidator/>
|
<DataAnnotationsValidator/>
|
||||||
<MudGrid>
|
<MudGrid>
|
||||||
@@ -49,7 +50,7 @@
|
|||||||
|
|
||||||
<FormActions>
|
<FormActions>
|
||||||
<MudButton OnClick="UpdateStudent" Variant="Variant.Filled" Color="Color.Primary">Save</MudButton>
|
<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>
|
</FormActions>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -62,6 +63,8 @@
|
|||||||
[SupplyParameterFromForm]
|
[SupplyParameterFromForm]
|
||||||
private Student? Student { get; set; }
|
private Student? Student { get; set; }
|
||||||
|
|
||||||
|
private FormChangeTracker? _formChangeTracker;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
Student ??= await Context.Students.FirstOrDefaultAsync(m => m.Id == Id);
|
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.
|
// 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.
|
// For more information, see https://learn.microsoft.com/aspnet/core/blazor/forms/#mitigate-overposting-attacks.
|
||||||
private async Task UpdateStudent()
|
private async Task UpdateStudent()
|
||||||
{
|
{
|
||||||
if (Student?.OfficerRole == 0)
|
if (Student?.OfficerRole == 0)
|
||||||
Student.OfficerRole = null;
|
Student.OfficerRole = null;
|
||||||
|
|
||||||
@@ -84,6 +87,8 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Context.SaveChangesAsync();
|
await Context.SaveChangesAsync();
|
||||||
|
_formChangeTracker?.AllowNavigation();
|
||||||
|
NavigationManager.NavigateTo(ReturnUrl ?? "/students");
|
||||||
}
|
}
|
||||||
catch (DbUpdateConcurrencyException)
|
catch (DbUpdateConcurrencyException)
|
||||||
{
|
{
|
||||||
@@ -96,7 +101,11 @@
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCancel()
|
||||||
|
{
|
||||||
|
_formChangeTracker?.AllowNavigation();
|
||||||
NavigationManager.NavigateTo(ReturnUrl ?? "/students");
|
NavigationManager.NavigateTo(ReturnUrl ?? "/students");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
BackButtonUrl="/teams" />
|
BackButtonUrl="/teams" />
|
||||||
|
|
||||||
<EditForm method="post" Model="Team" OnValidSubmit="AddTeam" FormName="create" Enhance>
|
<EditForm method="post" Model="Team" OnValidSubmit="AddTeam" FormName="create" Enhance>
|
||||||
|
<FormChangeTracker @ref="_formChangeTracker" />
|
||||||
<AntiforgeryToken />
|
<AntiforgeryToken />
|
||||||
<DataAnnotationsValidator />
|
<DataAnnotationsValidator />
|
||||||
<MudGrid>
|
<MudGrid>
|
||||||
@@ -67,13 +68,14 @@
|
|||||||
|
|
||||||
<FormActions>
|
<FormActions>
|
||||||
<MudButton OnClick="AddTeam" Variant="Variant.Filled" Color="Color.Primary">Add</MudButton>
|
<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>
|
</FormActions>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
[SupplyParameterFromForm]
|
[SupplyParameterFromForm]
|
||||||
private Team Team { get; set; } = new();
|
private Team Team { get; set; } = new();
|
||||||
|
|
||||||
|
private FormChangeTracker? _formChangeTracker;
|
||||||
private List<EventDefinition>? _events;
|
private List<EventDefinition>? _events;
|
||||||
private List<Student> _students = [];
|
private List<Student> _students = [];
|
||||||
private IEnumerable<Student> _selectedStudents = [];
|
private IEnumerable<Student> _selectedStudents = [];
|
||||||
@@ -159,6 +161,13 @@
|
|||||||
Context.Teams.Add(Team);
|
Context.Teams.Add(Team);
|
||||||
|
|
||||||
await Context.SaveChangesAsync();
|
await Context.SaveChangesAsync();
|
||||||
|
_formChangeTracker?.AllowNavigation();
|
||||||
|
NavigationManager.NavigateTo("/teams");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCancel()
|
||||||
|
{
|
||||||
|
_formChangeTracker?.AllowNavigation();
|
||||||
NavigationManager.NavigateTo("/teams");
|
NavigationManager.NavigateTo("/teams");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
BackButtonUrl="/teams" />
|
BackButtonUrl="/teams" />
|
||||||
|
|
||||||
<EditForm method="post" Model="Team" OnValidSubmit="UpdateTeam" FormName="edit" Enhance>
|
<EditForm method="post" Model="Team" OnValidSubmit="UpdateTeam" FormName="edit" Enhance>
|
||||||
|
<FormChangeTracker @ref="_formChangeTracker" />
|
||||||
<AntiforgeryToken />
|
<AntiforgeryToken />
|
||||||
<DataAnnotationsValidator/>
|
<DataAnnotationsValidator/>
|
||||||
<MudGrid>
|
<MudGrid>
|
||||||
@@ -38,7 +39,7 @@
|
|||||||
|
|
||||||
<FormActions>
|
<FormActions>
|
||||||
<MudButton OnClick="UpdateTeam" Variant="Variant.Filled" Color="Color.Primary">Save</MudButton>
|
<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>
|
</FormActions>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
@@ -48,6 +49,7 @@
|
|||||||
[SupplyParameterFromForm]
|
[SupplyParameterFromForm]
|
||||||
private Team? Team { get; set; }
|
private Team? Team { get; set; }
|
||||||
|
|
||||||
|
private FormChangeTracker? _formChangeTracker;
|
||||||
private IEnumerable<Student>? _selectedStudents = [];
|
private IEnumerable<Student>? _selectedStudents = [];
|
||||||
private List<Student> _students = [];
|
private List<Student> _students = [];
|
||||||
|
|
||||||
@@ -100,6 +102,8 @@
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Context.SaveChangesAsync();
|
await Context.SaveChangesAsync();
|
||||||
|
_formChangeTracker?.AllowNavigation();
|
||||||
|
NavigationManager.NavigateTo("/teams");
|
||||||
}
|
}
|
||||||
catch (DbUpdateConcurrencyException)
|
catch (DbUpdateConcurrencyException)
|
||||||
{
|
{
|
||||||
@@ -112,7 +116,11 @@
|
|||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleCancel()
|
||||||
|
{
|
||||||
|
_formChangeTracker?.AllowNavigation();
|
||||||
NavigationManager.NavigateTo("/teams");
|
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
|
@namespace WebApp.Components.Shared.Components
|
||||||
@using MudBlazor
|
|
||||||
|
|
||||||
<PageTitle>@GetPageTitle()</PageTitle>
|
<PageTitle>@GetPageTitle()</PageTitle>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user