diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs b/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs
index f9efa1a..14176c9 100644
--- a/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs
+++ b/AyCode.Blazor.Components/Components/Grids/MgGridBase.cs
@@ -61,12 +61,36 @@ public interface IMgGridBase : IGrid
/// Whether the grid/wrapper is currently in fullscreen mode
///
bool IsFullscreen { get; }
- string LayoutStorageKey { get; }
+
+ ///
+ /// Storage key for automatic layout persistence
+ ///
+ string AutomaticLayoutStorageKey { get; }
///
/// Toggles fullscreen mode for the grid (or wrapper if available)
///
void ToggleFullscreen();
+
+ ///
+ /// Saves the current layout to user storage (manual save)
+ ///
+ Task SaveUserLayoutAsync();
+
+ ///
+ /// Loads layout from user storage (manual load)
+ ///
+ Task LoadUserLayoutAsync();
+
+ ///
+ /// Resets the layout by clearing auto-saved layout and reloading the page
+ ///
+ Task ResetLayoutAsync();
+
+ ///
+ /// Checks if a user-saved layout exists without loading it
+ ///
+ Task HasUserLayoutAsync();
}
public abstract class MgGridBase : DxGrid, IMgGridBase, IAsyncDisposable
@@ -81,6 +105,7 @@ public abstract class MgGridBase? _dataSourceParam = [];
@@ -107,7 +132,12 @@ public abstract class MgGridBase
+ /// Gets the user layout storage key (replaces AutoSave with UserSave)
+ ///
+ private string UserLayoutStorageKey => AutomaticLayoutStorageKey.Replace("_AutoSave_", "_UserSave_");
+
+ public string AutomaticLayoutStorageKey
{
get
{
@@ -184,6 +214,7 @@ public abstract class MgGridBase
protected virtual int GetLayoutUserId() => 0;
+ ///
+ /// Stores the default layout (before any saved layout is loaded) for reset functionality
+ ///
+ private string? _defaultLayoutJson = null;
+
+ ///
+ /// Checks if a layout exists in localStorage without loading its content
+ ///
+ protected virtual async Task GetStorageItem(string localStorageKey)
+ {
+ try
+ {
+ return await JSRuntime.InvokeAsync("localStorage.getItem", localStorageKey);
+ }
+ catch
+ {
+ // Mute exceptions for the server prerender stage
+ }
+
+ return null;
+ }
+
private async Task Grid_LayoutAutoLoading(GridPersistentLayoutEventArgs e)
{
- e.Layout = await LoadLayoutFromLocalStorageAsync(LayoutStorageKey);
+ // Save the default layout before loading any saved layout
+ _defaultLayoutJson ??= JsonSerializer.Serialize(SaveLayout());
+
+ e.Layout = await LoadLayoutFromLocalStorageAsync(AutomaticLayoutStorageKey);
}
private async Task Grid_LayoutAutoSaving(GridPersistentLayoutEventArgs e)
{
- await SaveLayoutToLocalStorageAsync(e.Layout, LayoutStorageKey);
+ await SaveLayoutToLocalStorageAsync(e.Layout, AutomaticLayoutStorageKey);
}
protected virtual async Task LoadLayoutFromLocalStorageAsync(string localStorageKey)
{
try
{
- var json = await JSRuntime.InvokeAsync("localStorage.getItem", localStorageKey);
+ var json = await GetStorageItem(localStorageKey);
if (!string.IsNullOrWhiteSpace(json))
return JsonSerializer.Deserialize(json);
@@ -749,10 +810,75 @@ public abstract class MgGridBase
+ public async Task SaveUserLayoutAsync()
+ {
+ var layout = SaveLayout();
+
+ await SaveLayoutToLocalStorageAsync(layout, UserLayoutStorageKey);
+ await SaveLayoutToLocalStorageAsync(layout, AutomaticLayoutStorageKey);
+ }
+
+ ///
+ public async Task LoadUserLayoutAsync()
+ {
+ var layout = await LoadLayoutFromLocalStorageAsync(UserLayoutStorageKey);
+ if (layout != null)
+ {
+ LoadLayout(layout);
+ }
+ }
+
+ ///
+ public async Task ResetLayoutAsync()
+ {
+ await RemoveLayoutFromLocalStorageAsync(AutomaticLayoutStorageKey);
+
+ // Restore the default layout if available
+ if (!string.IsNullOrWhiteSpace(_defaultLayoutJson))
+ {
+ var defaultLayout = JsonSerializer.Deserialize(_defaultLayoutJson);
+ if (defaultLayout != null)
+ LoadLayout(defaultLayout);
+ }
+ }
+
+
+ ///
+ public async Task HasUserLayoutAsync()
+ {
+ return !(await GetStorageItem(UserLayoutStorageKey)).IsNullOrWhiteSpace();
+ }
+
#endregion
//public Task AddDataItem(TDataItem dataItem, int messageTag) => PostDataToServerAsync(dataItem, messageTag, TrackingState.Add);
+
+ ///
+ /// Force grid re-initialization
+ ///
+ ///
+ public async Task ForceRenderAsync()
+ {
+ // Force grid re-initialization by changing the render key
+ _gridRenderKey = Guid.NewGuid();
+
+ await InvokeAsync(StateHasChanged);
+ }
+
public Task UpdateDataItem(TDataItem dataItem) => _dataSource.Update(dataItem, true);
public Task UpdateDataItemAsync(TDataItem dataItem)
diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridToolbarTemplate.razor b/AyCode.Blazor.Components/Components/Grids/MgGridToolbarTemplate.razor
index f358824..53abc06 100644
--- a/AyCode.Blazor.Components/Components/Grids/MgGridToolbarTemplate.razor
+++ b/AyCode.Blazor.Components/Components/Grids/MgGridToolbarTemplate.razor
@@ -14,6 +14,13 @@
@if (!OnlyGridEditTools)
{
+
+
+
+
+
+
+
@@ -28,7 +35,7 @@
}
-@code {
+ @code {
[Parameter] public bool OnlyGridEditTools { get; set; } = false;
[Parameter] public IMgGridBase Grid { get; set; } = null!;
[Parameter] public RenderFragment? ToolbarItemsExtended { get; set; }
@@ -38,6 +45,7 @@
public MgGridToolbarBase GridToolbar { get; set; } = null!;
const string ExportFileName = "ExportResult";
+ private bool _hasUserLayout;
private bool _isReloadInProgress;
///
@@ -70,8 +78,9 @@
///
private string FullscreenIconCssClass => IsFullscreenMode ? "grid-fullscreen-exit" : "grid-fullscreen";
- protected override void OnInitialized()
+ protected override async Task OnInitializedAsync()
{
+ _hasUserLayout = await Grid.HasUserLayoutAsync();
}
async Task ReloadData_Click(ToolbarItemClickEventArgs e)
@@ -151,4 +160,20 @@
{
await Grid.ExportToPdfAsync(ExportFileName);
}
+
+ async Task LoadLayout_Click()
+ {
+ await Grid.LoadUserLayoutAsync();
+ }
+
+ async Task SaveLayout_Click()
+ {
+ await Grid.SaveUserLayoutAsync();
+ _hasUserLayout = true;
+ }
+
+ async Task ResetLayout_Click()
+ {
+ await Grid.ResetLayoutAsync();
+ }
}
\ No newline at end of file
diff --git a/AyCode.Blazor.Components/Components/Grids/MgGridWithInfoPanel.razor b/AyCode.Blazor.Components/Components/Grids/MgGridWithInfoPanel.razor
index d4bff51..90441e9 100644
--- a/AyCode.Blazor.Components/Components/Grids/MgGridWithInfoPanel.razor
+++ b/AyCode.Blazor.Components/Components/Grids/MgGridWithInfoPanel.razor
@@ -116,7 +116,7 @@
}
private string GetStorageKey() => _currentGrid != null
- ? $"Splitter_{_currentGrid.LayoutStorageKey}"
+ ? $"Splitter_{_currentGrid.AutomaticLayoutStorageKey}"
: null!;
private async Task LoadSavedSizeAsync()