Refactor: centralize grid band layout logic in service

Introduce BandLayoutService and shared models to manage grid band layouts across components. Refactor CatalogsGrid and MassDataGrid to use the new service, removing duplicated layout logic. Update _Imports.razor and register the service in Program.cs for improved maintainability and code reuse.
This commit is contained in:
OlgunR
2026-03-25 17:04:19 +01:00
parent 566c3b3276
commit dc74d21426
6 changed files with 472 additions and 730 deletions

View File

@@ -1,6 +1,5 @@
@inject CatalogApiClient Api @inject CatalogApiClient Api
@inject LayoutApiClient LayoutApi @inject BandLayoutService BandLayoutService
@inject IJSRuntime JsRuntime
@if (!string.IsNullOrWhiteSpace(errorMessage)) @if (!string.IsNullOrWhiteSpace(errorMessage))
{ {
@@ -101,7 +100,9 @@ else
@RenderColumns() @RenderColumns()
</Columns> </Columns>
<EditFormTemplate Context="editFormContext"> <EditFormTemplate Context="editFormContext">
@{ SetEditContext(editFormContext.EditContext); var editModel = (CatalogEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew); } @{
SetEditContext(editFormContext.EditContext); var editModel = (CatalogEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew);
}
<DxFormLayout ColCount="2"> <DxFormLayout ColCount="2">
<DxFormLayoutItem Caption="Titel"> <DxFormLayoutItem Caption="Titel">
<DxTextBox @bind-Text="editModel.CatTitle" Width="100%" /> <DxTextBox @bind-Text="editModel.CatTitle" Width="100%" />
@@ -143,13 +144,13 @@ else
private string popupHeaderText = "Edit"; private string popupHeaderText = "Edit";
private const string LayoutType = "GRID_BANDS"; private const string LayoutType = "GRID_BANDS";
private const string LayoutKey = "CatalogsGrid"; private const string LayoutKey = "CatalogsGrid";
private const string LayoutUserStorageKey = "layoutUser";
private string? layoutUser; private string? layoutUser;
private BandLayout bandLayout = new(); private BandLayout bandLayout = new();
private Dictionary<string, string> columnBandAssignments = new(); private Dictionary<string, string> columnBandAssignments = new();
private List<BandOption> bandOptions = new(); private List<BandOption> bandOptions = new();
private Dictionary<string, ColumnDefinition> columnLookup = new(); private Dictionary<string, ColumnDefinition> columnLookup = new();
private readonly JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web); private bool gridLayoutApplied;
private List<ColumnDefinition> columnDefinitions = new() private List<ColumnDefinition> columnDefinitions = new()
{ {
new() { FieldName = nameof(CatalogReadDto.Guid), Caption = "Id", Width = "140px", FilterType = ColumnFilterType.Text }, new() { FieldName = nameof(CatalogReadDto.Guid), Caption = "Id", Width = "140px", FilterType = ColumnFilterType.Text },
@@ -168,30 +169,32 @@ else
}; };
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser); private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
private bool gridLayoutApplied;
private DevExpress.Blazor.SizeMode _sizeMode = DevExpress.Blazor.SizeMode.Medium; private SizeMode _sizeMode = SizeMode.Medium;
private static readonly List<DevExpress.Blazor.SizeMode> _sizeModes = private static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
Enum.GetValues<DevExpress.Blazor.SizeMode>().ToList();
private string FormatSizeText(DevExpress.Blazor.SizeMode size) => size switch private string FormatSizeText(SizeMode size) => size switch
{ {
DevExpress.Blazor.SizeMode.Small => "Klein", SizeMode.Small => "Klein",
DevExpress.Blazor.SizeMode.Medium => "Mittel", SizeMode.Medium => "Mittel",
DevExpress.Blazor.SizeMode.Large => "Groß", SizeMode.Large => "Groß",
_ => size.ToString() _ => size.ToString()
}; };
private void OnSizeChange(DropDownButtonItemClickEventArgs args) private void OnSizeChange(DropDownButtonItemClickEventArgs args)
{ {
_sizeMode = Enum.Parse<DevExpress.Blazor.SizeMode>(args.ItemInfo.Id); _sizeMode = Enum.Parse<SizeMode>(args.ItemInfo.Id);
} }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase); columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
await EnsureLayoutUserAsync(); layoutUser = await BandLayoutService.EnsureLayoutUserAsync();
await LoadBandLayoutAsync(); bandLayout = await BandLayoutService.LoadBandLayoutAsync(LayoutType, LayoutKey, layoutUser, columnLookup);
columnBandAssignments = BandLayoutService.BuildAssignmentsFromLayout(bandLayout);
ApplyColumnLayoutFromStorage();
_sizeMode = bandLayout.SizeMode;
UpdateBandOptions();
await LoadCatalogs(); await LoadCatalogs();
} }
@@ -205,71 +208,6 @@ else
} }
} }
private void SetEditContext(EditContext context)
{
if (editContext == context)
{
return;
}
if (editContext != null)
{
editContext.OnFieldChanged -= OnEditFieldChanged;
}
editContext = context;
validationMessageStore = new ValidationMessageStore(editContext);
editContext.OnFieldChanged += OnEditFieldChanged;
}
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (validationMessageStore == null || editContext == null)
{
return;
}
if (e.FieldIdentifier.FieldName == nameof(CatalogEditModel.UpdateProcedure))
{
validationMessageStore.Clear();
editContext.NotifyValidationStateChanged();
return;
}
if (e.FieldIdentifier.FieldName == nameof(CatalogEditModel.CatTitle))
{
var field = new FieldIdentifier(editContext.Model, nameof(CatalogEditModel.CatTitle));
validationMessageStore.Clear(field);
editContext.NotifyValidationStateChanged();
}
}
private void SetPopupHeaderText(bool isNew)
{
popupHeaderText = isNew ? "Neu" : "Edit";
}
private void OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
{
popupHeaderText = e.IsNew ? "Neu" : "Edit";
if (e.IsNew)
{
e.EditModel = new CatalogEditModel { IsNew = true };
return;
}
var item = (CatalogReadDto)e.DataItem;
e.EditModel = new CatalogEditModel
{
Guid = item.Guid,
CatTitle = item.CatTitle,
CatString = item.CatString,
UpdateProcedure = 0,
OriginalCatTitle = item.CatTitle,
IsNew = false
};
}
private async Task LoadCatalogs() private async Task LoadCatalogs()
{ {
isLoading = true; isLoading = true;
@@ -290,11 +228,229 @@ else
} }
} }
private async Task SaveLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
return;
try
{
CaptureColumnLayoutFromGrid();
await BandLayoutService.SaveBandLayoutAsync(LayoutType, LayoutKey, layoutUser, bandLayout);
infoMessage = "Layout gespeichert.";
errorMessage = null;
}
catch (Exception ex)
{
errorMessage = $"Layout konnte nicht gespeichert werden: {ex.Message}";
}
}
private async Task ResetLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
return;
await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser);
bandLayout = new BandLayout();
columnBandAssignments.Clear();
UpdateBandOptions();
foreach (var column in columnDefinitions)
column.Width = null;
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
_sizeMode = SizeMode.Medium;
if (gridRef != null)
gridRef.LoadLayout(new GridPersistentLayout());
gridLayoutApplied = false;
infoMessage = "Layout zurückgesetzt.";
errorMessage = null;
}
private void CaptureColumnLayoutFromGrid()
{
if (gridRef == null)
return;
var layout = gridRef.SaveLayout();
bandLayout.GridLayout = layout;
bandLayout.SizeMode = _sizeMode;
var orderedColumns = layout.Columns
.Where(c => !string.IsNullOrWhiteSpace(c.FieldName))
.OrderBy(c => c.VisibleIndex)
.ToList();
bandLayout.ColumnOrder = orderedColumns.Select(c => c.FieldName).ToList();
bandLayout.ColumnWidths = orderedColumns
.Where(c => !string.IsNullOrWhiteSpace(c.Width))
.ToDictionary(c => c.FieldName, c => c.Width, StringComparer.OrdinalIgnoreCase);
}
private void ApplyColumnLayoutFromStorage()
{
foreach (var column in columnDefinitions)
{
if (bandLayout.ColumnWidths.TryGetValue(column.FieldName, out var width) && !string.IsNullOrWhiteSpace(width))
column.Width = width;
}
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
}
private void AddBand()
{
bandLayout.Bands.Add(new BandDefinition { Id = Guid.NewGuid().ToString("N"), Caption = "Band" });
UpdateBandOptions();
}
private void RemoveBand(BandDefinition band)
{
bandLayout.Bands.Remove(band);
foreach (var key in columnBandAssignments.Where(p => p.Value == band.Id).Select(p => p.Key).ToList())
columnBandAssignments.Remove(key);
UpdateBandOptions();
SyncBandsFromAssignments();
}
private void UpdateBandCaption(BandDefinition band, string value)
{
band.Caption = value;
UpdateBandOptions();
}
private void UpdateColumnBand(string fieldName, string? bandId)
{
if (string.IsNullOrWhiteSpace(bandId))
columnBandAssignments.Remove(fieldName);
else
columnBandAssignments[fieldName] = bandId;
SyncBandsFromAssignments();
}
private string GetColumnBand(string fieldName)
=> columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
private void SyncBandsFromAssignments()
{
foreach (var band in bandLayout.Bands)
{
band.Columns = columnDefinitions
.Where(c => columnBandAssignments.TryGetValue(c.FieldName, out var id) && id == band.Id)
.Select(c => c.FieldName)
.ToList();
}
StateHasChanged();
}
private void UpdateBandOptions()
{
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption }));
}
private RenderFragment RenderColumns() => builder =>
{
var seq = 0;
builder.OpenComponent<DxGridCommandColumn>(seq++);
builder.AddAttribute(seq++, "Width", "120px");
builder.CloseComponent();
var grouped = bandLayout.Bands.SelectMany(b => b.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var column in columnDefinitions.Where(c => !grouped.Contains(c.FieldName)))
BuildDataColumn(builder, ref seq, column);
foreach (var band in bandLayout.Bands)
{
if (band.Columns.Count == 0) continue;
builder.OpenComponent<DxGridBandColumn>(seq++);
builder.AddAttribute(seq++, "Caption", band.Caption);
builder.AddAttribute(seq++, "Columns", (RenderFragment)(bandBuilder =>
{
var bandSeq = 0;
foreach (var columnName in band.Columns)
{
if (columnLookup.TryGetValue(columnName, out var column))
BuildDataColumn(bandBuilder, ref bandSeq, column);
}
}));
builder.CloseComponent();
}
};
private void BuildDataColumn(RenderTreeBuilder builder, ref int seq, ColumnDefinition column)
{
builder.OpenComponent<DxGridDataColumn>(seq++);
builder.AddAttribute(seq++, "FieldName", column.FieldName);
builder.AddAttribute(seq++, "Caption", column.Caption);
if (!string.IsNullOrWhiteSpace(column.Width))
builder.AddAttribute(seq++, "Width", column.Width);
if (!string.IsNullOrWhiteSpace(column.DisplayFormat))
builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat);
if (column.ReadOnly)
builder.AddAttribute(seq++, "ReadOnly", true);
builder.CloseComponent();
}
private void SetEditContext(EditContext context)
{
if (editContext == context) return;
if (editContext != null)
editContext.OnFieldChanged -= OnEditFieldChanged;
editContext = context;
validationMessageStore = new ValidationMessageStore(editContext);
editContext.OnFieldChanged += OnEditFieldChanged;
}
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
{
if (validationMessageStore == null || editContext == null) return;
if (e.FieldIdentifier.FieldName == nameof(CatalogEditModel.UpdateProcedure))
{
validationMessageStore.Clear();
editContext.NotifyValidationStateChanged();
return;
}
if (e.FieldIdentifier.FieldName == nameof(CatalogEditModel.CatTitle))
{
validationMessageStore.Clear(new FieldIdentifier(editContext.Model, nameof(CatalogEditModel.CatTitle)));
editContext.NotifyValidationStateChanged();
}
}
private void SetPopupHeaderText(bool isNew) => popupHeaderText = isNew ? "Neu" : "Edit";
private void OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
{
popupHeaderText = e.IsNew ? "Neu" : "Edit";
if (e.IsNew)
{
e.EditModel = new CatalogEditModel { IsNew = true };
return;
}
var item = (CatalogReadDto)e.DataItem;
e.EditModel = new CatalogEditModel
{
Guid = item.Guid,
CatTitle = item.CatTitle,
CatString = item.CatString,
UpdateProcedure = 0,
OriginalCatTitle = item.CatTitle,
IsNew = false
};
}
private async Task OnEditModelSaving(GridEditModelSavingEventArgs e) private async Task OnEditModelSaving(GridEditModelSavingEventArgs e)
{ {
errorMessage = null; errorMessage = null;
infoMessage = null; infoMessage = null;
validationMessageStore?.Clear(); validationMessageStore?.Clear();
editContext?.NotifyValidationStateChanged(); editContext?.NotifyValidationStateChanged();
@@ -320,18 +476,12 @@ else
if (!created.Success || created.Value == null) if (!created.Success || created.Value == null)
{ {
if (!string.IsNullOrWhiteSpace(created.Error)) if (!string.IsNullOrWhiteSpace(created.Error))
{
AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), created.Error); AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), created.Error);
}
else else
{
errorMessage = "Anlegen fehlgeschlagen."; errorMessage = "Anlegen fehlgeschlagen.";
}
e.Cancel = true; e.Cancel = true;
return; return;
} }
infoMessage = "Katalog angelegt."; infoMessage = "Katalog angelegt.";
focusedRowKey = created.Value.Guid; focusedRowKey = created.Value.Guid;
} }
@@ -344,7 +494,6 @@ else
e.Cancel = true; e.Cancel = true;
return; return;
} }
infoMessage = "Katalog aktualisiert."; infoMessage = "Katalog aktualisiert.";
focusedRowKey = editModel.Guid; focusedRowKey = editModel.Guid;
} }
@@ -360,30 +509,20 @@ else
private void AddValidationError(CatalogEditModel editModel, string fieldName, string message) private void AddValidationError(CatalogEditModel editModel, string fieldName, string message)
{ {
if (editContext == null || validationMessageStore == null) if (editContext == null || validationMessageStore == null) return;
{ validationMessageStore.Add(new FieldIdentifier(editModel, fieldName), message);
return;
}
var field = new FieldIdentifier(editModel, fieldName);
validationMessageStore.Add(field, message);
editContext.NotifyValidationStateChanged(); editContext.NotifyValidationStateChanged();
} }
private bool ValidateEditModel(CatalogEditModel editModel, bool isNew) private bool ValidateEditModel(CatalogEditModel editModel, bool isNew)
{ {
if (isNew) if (isNew) return true;
{
return true;
}
if (editModel.UpdateProcedure == 0 && if (editModel.UpdateProcedure == 0 &&
!string.Equals(editModel.CatTitle, editModel.OriginalCatTitle, StringComparison.OrdinalIgnoreCase)) !string.Equals(editModel.CatTitle, editModel.OriginalCatTitle, StringComparison.OrdinalIgnoreCase))
{ {
AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), "Titel kann nicht geändert werden."); AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), "Titel kann nicht geändert werden.");
return false; return false;
} }
return true; return true;
} }
@@ -391,9 +530,7 @@ else
{ {
errorMessage = null; errorMessage = null;
infoMessage = null; infoMessage = null;
var item = (CatalogReadDto)e.DataItem; var item = (CatalogReadDto)e.DataItem;
try try
{ {
var deleted = await Api.DeleteAsync(item.Guid); var deleted = await Api.DeleteAsync(item.Guid);
@@ -403,350 +540,16 @@ else
e.Cancel = true; e.Cancel = true;
return; return;
} }
infoMessage = "Katalog gelöscht."; infoMessage = "Katalog gelöscht.";
await LoadCatalogs(); await LoadCatalogs();
} }
catch (Exception ex) catch (Exception ex)
{ {
errorMessage = $"Fehler beim L&#246;schen: {ex.Message}"; errorMessage = $"Fehler beim Löschen: {ex.Message}";
e.Cancel = true; e.Cancel = true;
} }
} }
private async Task EnsureLayoutUserAsync()
{
layoutUser = await JsRuntime.InvokeAsync<string?>("localStorage.getItem", LayoutUserStorageKey);
if (string.IsNullOrWhiteSpace(layoutUser))
{
layoutUser = Guid.NewGuid().ToString("N");
await JsRuntime.InvokeVoidAsync("localStorage.setItem", LayoutUserStorageKey, layoutUser);
}
}
private async Task SaveLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
{
return;
}
try
{
CaptureColumnLayoutFromGrid();
var layoutData = JsonSerializer.Serialize(bandLayout, jsonOptions);
await LayoutApi.UpsertAsync(new LayoutDto
{
LayoutType = LayoutType,
LayoutKey = LayoutKey,
UserName = layoutUser,
LayoutData = layoutData
});
infoMessage = "Layout gespeichert.";
errorMessage = null;
}
catch (Exception ex)
{
errorMessage = $"Layout konnte nicht gespeichert werden: {ex.Message}";
}
}
private void CaptureColumnLayoutFromGrid()
{
if (gridRef == null)
{
return;
}
var layout = gridRef.SaveLayout();
bandLayout.GridLayout = layout;
bandLayout.SizeMode = _sizeMode;
var orderedColumns = layout.Columns
.Where(column => !string.IsNullOrWhiteSpace(column.FieldName))
.OrderBy(column => column.VisibleIndex)
.ToList();
bandLayout.ColumnOrder = orderedColumns
.Select(column => column.FieldName)
.ToList();
bandLayout.ColumnWidths = orderedColumns
.Where(column => !string.IsNullOrWhiteSpace(column.Width))
.ToDictionary(column => column.FieldName, column => column.Width, StringComparer.OrdinalIgnoreCase);
}
private async Task LoadBandLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
{
bandLayout = new BandLayout();
UpdateBandOptions();
return;
}
var stored = await LayoutApi.GetAsync(LayoutType, LayoutKey, layoutUser);
if (stored != null && !string.IsNullOrWhiteSpace(stored.LayoutData))
{
var parsed = JsonSerializer.Deserialize<BandLayout>(stored.LayoutData, jsonOptions);
bandLayout = NormalizeBandLayout(parsed);
}
else
{
bandLayout = new BandLayout();
}
columnBandAssignments = BuildAssignmentsFromLayout(bandLayout);
ApplyColumnLayoutFromStorage();
_sizeMode = bandLayout.SizeMode;
UpdateBandOptions();
}
private async Task ResetLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
{
return;
}
await LayoutApi.DeleteAsync(LayoutType, LayoutKey, layoutUser);
bandLayout = new BandLayout();
columnBandAssignments.Clear();
UpdateBandOptions();
foreach (var column in columnDefinitions)
column.Width = null;
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
_sizeMode = DevExpress.Blazor.SizeMode.Medium;
if (gridRef != null)
gridRef.LoadLayout(new GridPersistentLayout());
gridLayoutApplied = false;
infoMessage = "Layout zur\u00fcckgesetzt.";
errorMessage = null;
}
private void ApplyColumnLayoutFromStorage()
{
foreach (var column in columnDefinitions)
{
if (bandLayout.ColumnWidths.TryGetValue(column.FieldName, out var width) && !string.IsNullOrWhiteSpace(width))
{
column.Width = width;
}
}
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
}
private void AddBand()
{
bandLayout.Bands.Add(new BandDefinition
{
Id = Guid.NewGuid().ToString("N"),
Caption = "Band"
});
UpdateBandOptions();
}
private void RemoveBand(BandDefinition band)
{
bandLayout.Bands.Remove(band);
var removedColumns = columnBandAssignments.Where(pair => pair.Value == band.Id)
.Select(pair => pair.Key)
.ToList();
foreach (var column in removedColumns)
{
columnBandAssignments.Remove(column);
}
UpdateBandOptions();
SyncBandsFromAssignments();
}
private void UpdateBandCaption(BandDefinition band, string value)
{
band.Caption = value;
UpdateBandOptions();
}
private void UpdateColumnBand(string fieldName, string? bandId)
{
if (string.IsNullOrWhiteSpace(bandId))
{
columnBandAssignments.Remove(fieldName);
}
else
{
columnBandAssignments[fieldName] = bandId;
}
SyncBandsFromAssignments();
}
private string GetColumnBand(string fieldName)
{
return columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
}
private void SyncBandsFromAssignments()
{
foreach (var band in bandLayout.Bands)
{
band.Columns = columnDefinitions
.Where(column => columnBandAssignments.TryGetValue(column.FieldName, out var bandId) && bandId == band.Id)
.Select(column => column.FieldName)
.ToList();
}
StateHasChanged();
}
private void UpdateBandOptions()
{
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
bandOptions.AddRange(bandLayout.Bands.Select(band => new BandOption { Id = band.Id, Caption = band.Caption }));
}
private BandLayout NormalizeBandLayout(BandLayout? layout)
{
layout ??= new BandLayout();
layout.Bands ??= new List<BandDefinition>();
layout.ColumnOrder ??= new List<string>();
layout.ColumnWidths ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
foreach (var band in layout.Bands)
{
if (string.IsNullOrWhiteSpace(band.Id))
{
band.Id = Guid.NewGuid().ToString("N");
}
if (string.IsNullOrWhiteSpace(band.Caption))
{
band.Caption = "Band";
}
band.Columns = band.Columns?.Where(columnLookup.ContainsKey).ToList() ?? new List<string>();
}
return layout;
}
private Dictionary<string, string> BuildAssignmentsFromLayout(BandLayout layout)
{
var assignments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var band in layout.Bands)
{
foreach (var column in band.Columns)
{
assignments[column] = band.Id;
}
}
return assignments;
}
private RenderFragment RenderColumns() => builder =>
{
var seq = 0;
builder.OpenComponent<DxGridCommandColumn>(seq++);
builder.AddAttribute(seq++, "Width", "120px");
builder.CloseComponent();
var grouped = bandLayout.Bands.SelectMany(band => band.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var column in columnDefinitions.Where(column => !grouped.Contains(column.FieldName)))
{
BuildDataColumn(builder, ref seq, column);
}
foreach (var band in bandLayout.Bands)
{
if (band.Columns.Count == 0)
{
continue;
}
builder.OpenComponent<DxGridBandColumn>(seq++);
builder.AddAttribute(seq++, "Caption", band.Caption);
builder.AddAttribute(seq++, "Columns", (RenderFragment)(bandBuilder =>
{
var bandSeq = 0;
foreach (var columnName in band.Columns)
{
if (columnLookup.TryGetValue(columnName, out var column))
{
BuildDataColumn(bandBuilder, ref bandSeq, column);
}
}
}));
builder.CloseComponent();
}
};
private void BuildDataColumn(RenderTreeBuilder builder, ref int seq, ColumnDefinition column)
{
builder.OpenComponent<DxGridDataColumn>(seq++);
builder.AddAttribute(seq++, "FieldName", column.FieldName);
builder.AddAttribute(seq++, "Caption", column.Caption);
if (!string.IsNullOrWhiteSpace(column.Width))
{
builder.AddAttribute(seq++, "Width", column.Width);
}
if (!string.IsNullOrWhiteSpace(column.DisplayFormat))
{
builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat);
}
if (column.ReadOnly)
{
builder.AddAttribute(seq++, "ReadOnly", true);
}
builder.CloseComponent();
}
private sealed class BandLayout
{
public List<BandDefinition> Bands { get; set; } = new();
public List<string> ColumnOrder { get; set; } = new();
public Dictionary<string, string?> ColumnWidths { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public GridPersistentLayout? GridLayout { get; set; }
public DevExpress.Blazor.SizeMode SizeMode { get; set; } = DevExpress.Blazor.SizeMode.Medium;
}
private sealed class BandDefinition
{
public string Id { get; set; } = string.Empty;
public string Caption { get; set; } = string.Empty;
public List<string> Columns { get; set; } = new();
}
private sealed class BandOption
{
public string Id { get; set; } = string.Empty;
public string Caption { get; set; } = string.Empty;
}
private sealed class ColumnDefinition
{
public string FieldName { get; init; } = string.Empty;
public string Caption { get; init; } = string.Empty;
public string? Width { get; set; }
public string? DisplayFormat { get; init; }
public bool ReadOnly { get; init; }
public ColumnFilterType FilterType { get; init; }
}
private enum ColumnFilterType
{
Text,
Date
}
private sealed class CatalogEditModel private sealed class CatalogEditModel
{ {
public int Guid { get; set; } public int Guid { get; set; }
@@ -762,10 +565,4 @@ else
public int Value { get; set; } public int Value { get; set; }
public string Text { get; set; } = string.Empty; public string Text { get; set; } = string.Empty;
} }
}
private sealed class FilterOperatorOption
{
public string Value { get; set; } = string.Empty;
public string Text { get; set; } = string.Empty;
}
}

View File

@@ -1,6 +1,5 @@
@inject MassDataApiClient Api @inject MassDataApiClient Api
@inject LayoutApiClient LayoutApi @inject BandLayoutService BandLayoutService
@inject IJSRuntime JsRuntime
@if (!string.IsNullOrWhiteSpace(errorMessage)) @if (!string.IsNullOrWhiteSpace(errorMessage))
{ {
@@ -113,7 +112,9 @@ else
@RenderColumns() @RenderColumns()
</Columns> </Columns>
<EditFormTemplate Context="editFormContext"> <EditFormTemplate Context="editFormContext">
@{ SetEditContext(editFormContext.EditContext); var editModel = (MassDataEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew); } @{
SetEditContext(editFormContext.EditContext); var editModel = (MassDataEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew);
}
<DxFormLayout ColCount="2"> <DxFormLayout ColCount="2">
<DxFormLayoutItem Caption="CustomerName"> <DxFormLayoutItem Caption="CustomerName">
<DxTextBox @bind-Text="editModel.CustomerName" Width="100%" /> <DxTextBox @bind-Text="editModel.CustomerName" Width="100%" />
@@ -171,13 +172,13 @@ else
private int? focusedRowKey; private int? focusedRowKey;
private const string LayoutType = "GRID_BANDS"; private const string LayoutType = "GRID_BANDS";
private const string LayoutKey = "MassDataGrid"; private const string LayoutKey = "MassDataGrid";
private const string LayoutUserStorageKey = "layoutUser";
private string? layoutUser; private string? layoutUser;
private BandLayout bandLayout = new(); private BandLayout bandLayout = new();
private Dictionary<string, string> columnBandAssignments = new(); private Dictionary<string, string> columnBandAssignments = new();
private List<BandOption> bandOptions = new(); private List<BandOption> bandOptions = new();
private Dictionary<string, ColumnDefinition> columnLookup = new(); private Dictionary<string, ColumnDefinition> columnLookup = new();
private readonly JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web); private bool gridLayoutApplied;
private List<ColumnDefinition> columnDefinitions = new() private List<ColumnDefinition> columnDefinitions = new()
{ {
new() { FieldName = nameof(MassDataReadDto.Id), Caption = "Id", Width = "90px", ReadOnly = true, FilterType = ColumnFilterType.Text }, new() { FieldName = nameof(MassDataReadDto.Id), Caption = "Id", Width = "90px", ReadOnly = true, FilterType = ColumnFilterType.Text },
@@ -189,15 +190,13 @@ else
new() { FieldName = nameof(MassDataReadDto.ChangedWhen), Caption = "Changed", ReadOnly = true, FilterType = ColumnFilterType.Date } new() { FieldName = nameof(MassDataReadDto.ChangedWhen), Caption = "Changed", ReadOnly = true, FilterType = ColumnFilterType.Date }
}; };
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
private readonly List<PageSizeOption> pageSizeOptions = new() private readonly List<PageSizeOption> pageSizeOptions = new()
{ {
new() { Value = 100, Text = "100" }, new() { Value = 100, Text = "100" },
new() { Value = 1000, Text = "1.000" }, new() { Value = 1000, Text = "1.000" },
new() { Value = 10000, Text = "10.000" }, new() { Value = 10000, Text = "10.000" },
new() { Value = 100000, Text = "100.000" }, new() { Value = 100000, Text = "100.000" },
new() { Value = null, Text = "Alle" } new() { Value = null, Text = "Alle" }
}; };
private readonly List<ProcedureOption> procedureOptions = new() private readonly List<ProcedureOption> procedureOptions = new()
@@ -205,33 +204,46 @@ else
new() { Value = 0, Text = "PRMassdata_UpsertByCustomerName" } new() { Value = 0, Text = "PRMassdata_UpsertByCustomerName" }
}; };
private bool gridLayoutApplied; private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
private DevExpress.Blazor.SizeMode _sizeMode = DevExpress.Blazor.SizeMode.Medium; private SizeMode _sizeMode = SizeMode.Medium;
private static readonly List<DevExpress.Blazor.SizeMode> _sizeModes = private static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
Enum.GetValues<DevExpress.Blazor.SizeMode>().ToList();
private string FormatSizeText(DevExpress.Blazor.SizeMode size) => size switch private string FormatSizeText(SizeMode size) => size switch
{ {
DevExpress.Blazor.SizeMode.Small => "Klein", SizeMode.Small => "Klein",
DevExpress.Blazor.SizeMode.Medium => "Mittel", SizeMode.Medium => "Mittel",
DevExpress.Blazor.SizeMode.Large => "Groß", SizeMode.Large => "Groß",
_ => size.ToString() _ => size.ToString()
}; };
private void OnSizeChange(DropDownButtonItemClickEventArgs args) private void OnSizeChange(DropDownButtonItemClickEventArgs args)
{ {
_sizeMode = Enum.Parse<DevExpress.Blazor.SizeMode>(args.ItemInfo.Id); _sizeMode = Enum.Parse<SizeMode>(args.ItemInfo.Id);
} }
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase); columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
await EnsureLayoutUserAsync(); layoutUser = await BandLayoutService.EnsureLayoutUserAsync();
await LoadBandLayoutAsync(); bandLayout = await BandLayoutService.LoadBandLayoutAsync(LayoutType, LayoutKey, layoutUser, columnLookup);
columnBandAssignments = BandLayoutService.BuildAssignmentsFromLayout(bandLayout);
ApplyColumnLayoutFromStorage();
_sizeMode = bandLayout.SizeMode;
UpdateBandOptions();
await LoadPage(0); await LoadPage(0);
} }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!gridLayoutApplied && gridRef != null && bandLayout.GridLayout != null)
{
gridRef.LoadLayout(bandLayout.GridLayout);
gridLayoutApplied = true;
await InvokeAsync(StateHasChanged);
}
}
private async Task LoadPage(int page) private async Task LoadPage(int page)
{ {
isLoading = true; isLoading = true;
@@ -242,7 +254,6 @@ else
var effectivePageSize = pageSize ?? (total == 0 ? 1 : total); var effectivePageSize = pageSize ?? (total == 0 ? 1 : total);
pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)effectivePageSize)); pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)effectivePageSize));
pageIndex = Math.Clamp(page, 0, pageCount - 1); pageIndex = Math.Clamp(page, 0, pageCount - 1);
var skip = pageSize.HasValue ? pageIndex * pageSize.Value : 0; var skip = pageSize.HasValue ? pageIndex * pageSize.Value : 0;
items = await Api.GetAllAsync(skip, pageSize); items = await Api.GetAllAsync(skip, pageSize);
} }
@@ -258,10 +269,7 @@ else
} }
} }
private async Task OnPageChanged(int index) private async Task OnPageChanged(int index) => await LoadPage(index);
{
await LoadPage(index);
}
private async Task OnPageSizeChanged(int? size) private async Task OnPageSizeChanged(int? size)
{ {
@@ -269,61 +277,15 @@ else
await LoadPage(0); await LoadPage(0);
} }
private async Task EnsureLayoutUserAsync()
{
layoutUser = await JsRuntime.InvokeAsync<string?>("localStorage.getItem", LayoutUserStorageKey);
if (string.IsNullOrWhiteSpace(layoutUser))
{
layoutUser = Guid.NewGuid().ToString("N");
await JsRuntime.InvokeVoidAsync("localStorage.setItem", LayoutUserStorageKey, layoutUser);
}
}
private async Task LoadBandLayoutAsync()
{
if (string.IsNullOrWhiteSpace(layoutUser))
{
bandLayout = new BandLayout();
UpdateBandOptions();
return;
}
var stored = await LayoutApi.GetAsync(LayoutType, LayoutKey, layoutUser);
if (stored != null && !string.IsNullOrWhiteSpace(stored.LayoutData))
{
var parsed = JsonSerializer.Deserialize<BandLayout>(stored.LayoutData, jsonOptions);
bandLayout = NormalizeBandLayout(parsed);
}
else
{
bandLayout = new BandLayout();
}
columnBandAssignments = BuildAssignmentsFromLayout(bandLayout);
ApplyColumnLayoutFromStorage();
_sizeMode = bandLayout.SizeMode;
UpdateBandOptions();
}
private async Task SaveLayoutAsync() private async Task SaveLayoutAsync()
{ {
if (string.IsNullOrWhiteSpace(layoutUser)) if (string.IsNullOrWhiteSpace(layoutUser))
{
return; return;
}
try try
{ {
CaptureColumnLayoutFromGrid(); CaptureColumnLayoutFromGrid();
await BandLayoutService.SaveBandLayoutAsync(LayoutType, LayoutKey, layoutUser, bandLayout);
var layoutData = JsonSerializer.Serialize(bandLayout, jsonOptions);
await LayoutApi.UpsertAsync(new LayoutDto
{
LayoutType = LayoutType,
LayoutKey = LayoutKey,
UserName = layoutUser,
LayoutData = layoutData
});
infoMessage = "Layout gespeichert."; infoMessage = "Layout gespeichert.";
errorMessage = null; errorMessage = null;
} }
@@ -333,41 +295,12 @@ else
} }
} }
private void CaptureColumnLayoutFromGrid()
{
if (gridRef == null)
{
return;
}
var layout = gridRef.SaveLayout();
bandLayout.GridLayout = layout;
bandLayout.SizeMode = _sizeMode;
var orderedColumns = layout.Columns
.Where(column => !string.IsNullOrWhiteSpace(column.FieldName))
.OrderBy(column => column.VisibleIndex)
.ToList();
bandLayout.ColumnOrder = orderedColumns
.Select(column => column.FieldName)
.ToList();
bandLayout.ColumnWidths = orderedColumns
.Where(column => !string.IsNullOrWhiteSpace(column.Width))
.ToDictionary(column => column.FieldName, column => column.Width, StringComparer.OrdinalIgnoreCase);
bandLayout.SizeMode = _sizeMode;
}
private async Task ResetLayoutAsync() private async Task ResetLayoutAsync()
{ {
if (string.IsNullOrWhiteSpace(layoutUser)) if (string.IsNullOrWhiteSpace(layoutUser))
{
return; return;
}
await LayoutApi.DeleteAsync(LayoutType, LayoutKey, layoutUser); await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser);
bandLayout = new BandLayout(); bandLayout = new BandLayout();
columnBandAssignments.Clear(); columnBandAssignments.Clear();
@@ -377,49 +310,57 @@ else
column.Width = null; column.Width = null;
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase); columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
_sizeMode = DevExpress.Blazor.SizeMode.Medium; _sizeMode = SizeMode.Medium;
if (gridRef != null) if (gridRef != null)
gridRef.LoadLayout(new GridPersistentLayout()); gridRef.LoadLayout(new GridPersistentLayout());
gridLayoutApplied = false; gridLayoutApplied = false;
infoMessage = "Layout zur\u00fcckgesetzt."; infoMessage = "Layout zurückgesetzt.";
errorMessage = null; errorMessage = null;
} }
private void CaptureColumnLayoutFromGrid()
{
if (gridRef == null)
return;
var layout = gridRef.SaveLayout();
bandLayout.GridLayout = layout;
bandLayout.SizeMode = _sizeMode;
var orderedColumns = layout.Columns
.Where(c => !string.IsNullOrWhiteSpace(c.FieldName))
.OrderBy(c => c.VisibleIndex)
.ToList();
bandLayout.ColumnOrder = orderedColumns.Select(c => c.FieldName).ToList();
bandLayout.ColumnWidths = orderedColumns
.Where(c => !string.IsNullOrWhiteSpace(c.Width))
.ToDictionary(c => c.FieldName, c => c.Width, StringComparer.OrdinalIgnoreCase);
}
private void ApplyColumnLayoutFromStorage() private void ApplyColumnLayoutFromStorage()
{ {
foreach (var column in columnDefinitions) foreach (var column in columnDefinitions)
{ {
if (bandLayout.ColumnWidths.TryGetValue(column.FieldName, out var width) && !string.IsNullOrWhiteSpace(width)) if (bandLayout.ColumnWidths.TryGetValue(column.FieldName, out var width) && !string.IsNullOrWhiteSpace(width))
{
column.Width = width; column.Width = width;
}
} }
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
} }
private void AddBand() private void AddBand()
{ {
bandLayout.Bands.Add(new BandDefinition bandLayout.Bands.Add(new BandDefinition { Id = Guid.NewGuid().ToString("N"), Caption = "Band" });
{
Id = Guid.NewGuid().ToString("N"),
Caption = "Band"
});
UpdateBandOptions(); UpdateBandOptions();
} }
private void RemoveBand(BandDefinition band) private void RemoveBand(BandDefinition band)
{ {
bandLayout.Bands.Remove(band); bandLayout.Bands.Remove(band);
var removedColumns = columnBandAssignments.Where(pair => pair.Value == band.Id) foreach (var key in columnBandAssignments.Where(p => p.Value == band.Id).Select(p => p.Key).ToList())
.Select(pair => pair.Key) columnBandAssignments.Remove(key);
.ToList();
foreach (var column in removedColumns)
{
columnBandAssignments.Remove(column);
}
UpdateBandOptions(); UpdateBandOptions();
SyncBandsFromAssignments(); SyncBandsFromAssignments();
} }
@@ -433,77 +374,31 @@ else
private void UpdateColumnBand(string fieldName, string? bandId) private void UpdateColumnBand(string fieldName, string? bandId)
{ {
if (string.IsNullOrWhiteSpace(bandId)) if (string.IsNullOrWhiteSpace(bandId))
{
columnBandAssignments.Remove(fieldName); columnBandAssignments.Remove(fieldName);
}
else else
{
columnBandAssignments[fieldName] = bandId; columnBandAssignments[fieldName] = bandId;
}
SyncBandsFromAssignments(); SyncBandsFromAssignments();
} }
private string GetColumnBand(string fieldName) private string GetColumnBand(string fieldName)
{ => columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
return columnBandAssignments.TryGetValue(fieldName, out var bandId) ? bandId : string.Empty;
}
private void SyncBandsFromAssignments() private void SyncBandsFromAssignments()
{ {
foreach (var band in bandLayout.Bands) foreach (var band in bandLayout.Bands)
{ {
band.Columns = columnDefinitions band.Columns = columnDefinitions
.Where(column => columnBandAssignments.TryGetValue(column.FieldName, out var bandId) && bandId == band.Id) .Where(c => columnBandAssignments.TryGetValue(c.FieldName, out var id) && id == band.Id)
.Select(column => column.FieldName) .Select(c => c.FieldName)
.ToList(); .ToList();
} }
StateHasChanged(); StateHasChanged();
} }
private void UpdateBandOptions() private void UpdateBandOptions()
{ {
bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } }; bandOptions = new List<BandOption> { new() { Id = string.Empty, Caption = "Ohne Band" } };
bandOptions.AddRange(bandLayout.Bands.Select(band => new BandOption { Id = band.Id, Caption = band.Caption })); bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption }));
}
private Dictionary<string, string> BuildAssignmentsFromLayout(BandLayout layout)
{
var assignments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var band in layout.Bands)
{
foreach (var column in band.Columns)
{
assignments[column] = band.Id;
}
}
return assignments;
}
private BandLayout NormalizeBandLayout(BandLayout? layout)
{
layout ??= new BandLayout();
layout.Bands ??= new List<BandDefinition>();
layout.ColumnOrder ??= new List<string>();
layout.ColumnWidths ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
foreach (var band in layout.Bands)
{
if (string.IsNullOrWhiteSpace(band.Id))
{
band.Id = Guid.NewGuid().ToString("N");
}
if (string.IsNullOrWhiteSpace(band.Caption))
{
band.Caption = "Band";
}
band.Columns = band.Columns?.Where(columnLookup.ContainsKey).ToList() ?? new List<string>();
}
return layout;
} }
private RenderFragment RenderColumns() => builder => private RenderFragment RenderColumns() => builder =>
@@ -513,18 +408,13 @@ else
builder.AddAttribute(seq++, "Width", "120px"); builder.AddAttribute(seq++, "Width", "120px");
builder.CloseComponent(); builder.CloseComponent();
var grouped = bandLayout.Bands.SelectMany(band => band.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase); var grouped = bandLayout.Bands.SelectMany(b => b.Columns).ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var column in columnDefinitions.Where(column => !grouped.Contains(column.FieldName))) foreach (var column in columnDefinitions.Where(c => !grouped.Contains(c.FieldName)))
{
BuildDataColumn(builder, ref seq, column); BuildDataColumn(builder, ref seq, column);
}
foreach (var band in bandLayout.Bands) foreach (var band in bandLayout.Bands)
{ {
if (band.Columns.Count == 0) if (band.Columns.Count == 0) continue;
{
continue;
}
builder.OpenComponent<DxGridBandColumn>(seq++); builder.OpenComponent<DxGridBandColumn>(seq++);
builder.AddAttribute(seq++, "Caption", band.Caption); builder.AddAttribute(seq++, "Caption", band.Caption);
@@ -534,9 +424,7 @@ else
foreach (var columnName in band.Columns) foreach (var columnName in band.Columns)
{ {
if (columnLookup.TryGetValue(columnName, out var column)) if (columnLookup.TryGetValue(columnName, out var column))
{
BuildDataColumn(bandBuilder, ref bandSeq, column); BuildDataColumn(bandBuilder, ref bandSeq, column);
}
} }
})); }));
builder.CloseComponent(); builder.CloseComponent();
@@ -549,35 +437,19 @@ else
builder.AddAttribute(seq++, "FieldName", column.FieldName); builder.AddAttribute(seq++, "FieldName", column.FieldName);
builder.AddAttribute(seq++, "Caption", column.Caption); builder.AddAttribute(seq++, "Caption", column.Caption);
if (!string.IsNullOrWhiteSpace(column.Width)) if (!string.IsNullOrWhiteSpace(column.Width))
{
builder.AddAttribute(seq++, "Width", column.Width); builder.AddAttribute(seq++, "Width", column.Width);
}
if (!string.IsNullOrWhiteSpace(column.DisplayFormat)) if (!string.IsNullOrWhiteSpace(column.DisplayFormat))
{
builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat); builder.AddAttribute(seq++, "DisplayFormat", column.DisplayFormat);
}
if (column.ReadOnly) if (column.ReadOnly)
{
builder.AddAttribute(seq++, "ReadOnly", true); builder.AddAttribute(seq++, "ReadOnly", true);
}
builder.CloseComponent(); builder.CloseComponent();
} }
private void SetEditContext(EditContext context) private void SetEditContext(EditContext context)
{ {
if (editContext == context) if (editContext == context) return;
{
return;
}
if (editContext != null) if (editContext != null)
{
editContext.OnFieldChanged -= OnEditFieldChanged; editContext.OnFieldChanged -= OnEditFieldChanged;
}
editContext = context; editContext = context;
validationMessageStore = new ValidationMessageStore(editContext); validationMessageStore = new ValidationMessageStore(editContext);
editContext.OnFieldChanged += OnEditFieldChanged; editContext.OnFieldChanged += OnEditFieldChanged;
@@ -585,10 +457,7 @@ else
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e) private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
{ {
if (validationMessageStore == null || editContext == null) if (validationMessageStore == null || editContext == null) return;
{
return;
}
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.UpdateProcedure)) if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.UpdateProcedure))
{ {
@@ -599,16 +468,12 @@ else
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.CustomerName)) if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.CustomerName))
{ {
var field = new FieldIdentifier(editContext.Model, nameof(MassDataEditModel.CustomerName)); validationMessageStore.Clear(new FieldIdentifier(editContext.Model, nameof(MassDataEditModel.CustomerName)));
validationMessageStore.Clear(field);
editContext.NotifyValidationStateChanged(); editContext.NotifyValidationStateChanged();
} }
} }
private void SetPopupHeaderText(bool isNew) private void SetPopupHeaderText(bool isNew) => popupHeaderText = isNew ? "Neu" : "Edit";
{
popupHeaderText = isNew ? "Neu" : "Edit";
}
private async Task OnCustomizeEditModel(GridCustomizeEditModelEventArgs e) private async Task OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
{ {
@@ -638,7 +503,6 @@ else
{ {
errorMessage = null; errorMessage = null;
infoMessage = null; infoMessage = null;
validationMessageStore?.Clear(); validationMessageStore?.Clear();
editContext?.NotifyValidationStateChanged(); editContext?.NotifyValidationStateChanged();
@@ -685,13 +549,8 @@ else
private void AddValidationError(MassDataEditModel editModel, string fieldName, string message) private void AddValidationError(MassDataEditModel editModel, string fieldName, string message)
{ {
if (editContext == null || validationMessageStore == null) if (editContext == null || validationMessageStore == null) return;
{ validationMessageStore.Add(new FieldIdentifier(editModel, fieldName), message);
return;
}
var field = new FieldIdentifier(editModel, fieldName);
validationMessageStore.Add(field, message);
editContext.NotifyValidationStateChanged(); editContext.NotifyValidationStateChanged();
} }
@@ -703,55 +562,6 @@ else
return Task.CompletedTask; return Task.CompletedTask;
} }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!gridLayoutApplied && gridRef != null && bandLayout.GridLayout != null)
{
gridRef.LoadLayout(bandLayout.GridLayout);
gridLayoutApplied = true;
await InvokeAsync(StateHasChanged);
}
}
private sealed class BandLayout
{
public List<BandDefinition> Bands { get; set; } = new();
public List<string> ColumnOrder { get; set; } = new();
public Dictionary<string, string?> ColumnWidths { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public GridPersistentLayout? GridLayout { get; set; }
public DevExpress.Blazor.SizeMode SizeMode { get; set; } = DevExpress.Blazor.SizeMode.Medium;
}
private sealed class BandDefinition
{
public string Id { get; set; } = string.Empty;
public string Caption { get; set; } = string.Empty;
public List<string> Columns { get; set; } = new();
}
private sealed class BandOption
{
public string Id { get; set; } = string.Empty;
public string Caption { get; set; } = string.Empty;
}
private sealed class ColumnDefinition
{
public string FieldName { get; init; } = string.Empty;
public string Caption { get; init; } = string.Empty;
public string? Width { get; set; }
public string? DisplayFormat { get; init; }
public bool ReadOnly { get; init; }
public ColumnFilterType FilterType { get; init; }
}
private enum ColumnFilterType
{
Text,
Bool,
Date
}
private sealed class MassDataEditModel private sealed class MassDataEditModel
{ {
public int Id { get; set; } public int Id { get; set; }
@@ -770,21 +580,9 @@ else
public string Text { get; set; } = string.Empty; public string Text { get; set; } = string.Empty;
} }
private sealed class BoolFilterOption
{
public bool? Value { get; set; }
public string Text { get; set; } = string.Empty;
}
private sealed class PageSizeOption private sealed class PageSizeOption
{ {
public int? Value { get; set; } public int? Value { get; set; }
public string Text { get; set; } = string.Empty; public string Text { get; set; } = string.Empty;
} }
}
private sealed class FilterOperatorOption
{
public string Value { get; set; } = string.Empty;
public string Text { get; set; } = string.Empty;
}
}

View File

@@ -1,5 +1,6 @@
@using System.Net.Http @using System.Net.Http
@using System.Net.Http.Json @using System.Net.Http.Json
@using System.Text.Json
@using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Rendering @using Microsoft.AspNetCore.Components.Rendering
@using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Routing
@@ -11,10 +12,9 @@
@using DbFirst.BlazorWebApp @using DbFirst.BlazorWebApp
@using DbFirst.BlazorWebApp.Components @using DbFirst.BlazorWebApp.Components
@using DbFirst.BlazorWebApp.Models @using DbFirst.BlazorWebApp.Models
@using DbFirst.BlazorWebApp.Models.Grid
@using DbFirst.BlazorWebApp.Services @using DbFirst.BlazorWebApp.Services
@using DevExpress.Blazor @using DevExpress.Blazor
@using DevExpress.DashboardBlazor @using DevExpress.DashboardBlazor
@using DevExpress.DashboardWeb @using DevExpress.DashboardWeb
@using DevExpress.Data.Filtering @using DevExpress.Data.Filtering
@using DbFirst.BlazorWebApp
@using System.Text.Json

View File

@@ -0,0 +1,43 @@
using DevExpress.Blazor;
namespace DbFirst.BlazorWebApp.Models.Grid
{
public class BandLayout
{
public List<BandDefinition> Bands { get; set; } = new();
public List<string> ColumnOrder { get; set; } = new();
public Dictionary<string, string?> ColumnWidths { get; set; } = new(StringComparer.OrdinalIgnoreCase);
public GridPersistentLayout? GridLayout { get; set; }
public SizeMode SizeMode { get; set; } = SizeMode.Medium;
}
public class BandDefinition
{
public string Id { get; set; } = string.Empty;
public string Caption { get; set; } = string.Empty;
public List<string> Columns { get; set; } = new();
}
public class BandOption
{
public string Id { get; set; } = string.Empty;
public string Caption { get; set; } = string.Empty;
}
public class ColumnDefinition
{
public string FieldName { get; init; } = string.Empty;
public string Caption { get; init; } = string.Empty;
public string? Width { get; set; }
public string? DisplayFormat { get; init; }
public bool ReadOnly { get; init; }
public ColumnFilterType FilterType { get; init; }
}
public enum ColumnFilterType
{
Text,
Bool,
Date
}
}

View File

@@ -10,6 +10,7 @@ builder.Services.AddRazorComponents()
builder.Services.AddDevExpressBlazor(options => options.BootstrapVersion = BootstrapVersion.v5); builder.Services.AddDevExpressBlazor(options => options.BootstrapVersion = BootstrapVersion.v5);
builder.Services.AddScoped<ThemeState>(); builder.Services.AddScoped<ThemeState>();
builder.Services.AddScoped<BandLayoutService>();
var apiBaseUrl = builder.Configuration["ApiBaseUrl"]; var apiBaseUrl = builder.Configuration["ApiBaseUrl"];
if (!string.IsNullOrWhiteSpace(apiBaseUrl)) if (!string.IsNullOrWhiteSpace(apiBaseUrl))

View File

@@ -0,0 +1,103 @@
using DbFirst.BlazorWebApp.Models;
using DbFirst.BlazorWebApp.Models.Grid;
using Microsoft.JSInterop;
using System.Text.Json;
namespace DbFirst.BlazorWebApp.Services
{
public class BandLayoutService(LayoutApiClient layoutApi, IJSRuntime jsRuntime)
{
private const string LayoutUserStorageKey = "layoutUser";
private readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
public async Task<string> EnsureLayoutUserAsync()
{
var layoutUser = await jsRuntime.InvokeAsync<string?>("localStorage.getItem", LayoutUserStorageKey);
if (string.IsNullOrWhiteSpace(layoutUser))
{
layoutUser = Guid.NewGuid().ToString("N");
await jsRuntime.InvokeVoidAsync("localStorage.setItem", LayoutUserStorageKey, layoutUser);
}
return layoutUser;
}
public async Task<BandLayout> LoadBandLayoutAsync(
string layoutType,
string layoutKey,
string layoutUser,
Dictionary<string, ColumnDefinition> columnLookup)
{
if (string.IsNullOrWhiteSpace(layoutUser))
return new BandLayout();
var stored = await layoutApi.GetAsync(layoutType, layoutKey, layoutUser);
if (stored != null && !string.IsNullOrWhiteSpace(stored.LayoutData))
{
var parsed = JsonSerializer.Deserialize<BandLayout>(stored.LayoutData, _jsonOptions);
return NormalizeBandLayout(parsed, columnLookup);
}
return new BandLayout();
}
public async Task SaveBandLayoutAsync(
string layoutType,
string layoutKey,
string layoutUser,
BandLayout bandLayout)
{
var layoutData = JsonSerializer.Serialize(bandLayout, _jsonOptions);
await layoutApi.UpsertAsync(new LayoutDto
{
LayoutType = layoutType,
LayoutKey = layoutKey,
UserName = layoutUser,
LayoutData = layoutData
});
}
public async Task ResetBandLayoutAsync(
string layoutType,
string layoutKey,
string layoutUser)
{
await layoutApi.DeleteAsync(layoutType, layoutKey, layoutUser);
}
public Dictionary<string, string> BuildAssignmentsFromLayout(BandLayout layout)
{
var assignments = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var band in layout.Bands)
{
foreach (var column in band.Columns)
{
assignments[column] = band.Id;
}
}
return assignments;
}
public BandLayout NormalizeBandLayout(
BandLayout? layout,
Dictionary<string, ColumnDefinition> columnLookup)
{
layout ??= new BandLayout();
layout.Bands ??= new List<BandDefinition>();
layout.ColumnOrder ??= new List<string>();
layout.ColumnWidths ??= new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
foreach (var band in layout.Bands)
{
if (string.IsNullOrWhiteSpace(band.Id))
band.Id = Guid.NewGuid().ToString("N");
if (string.IsNullOrWhiteSpace(band.Caption))
band.Caption = "Band";
band.Columns = band.Columns?.Where(columnLookup.ContainsKey).ToList() ?? new List<string>();
}
return layout;
}
}
}