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:
@@ -1,6 +1,5 @@
|
||||
@inject CatalogApiClient Api
|
||||
@inject LayoutApiClient LayoutApi
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject BandLayoutService BandLayoutService
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
||||
{
|
||||
@@ -101,7 +100,9 @@ else
|
||||
@RenderColumns()
|
||||
</Columns>
|
||||
<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">
|
||||
<DxFormLayoutItem Caption="Titel">
|
||||
<DxTextBox @bind-Text="editModel.CatTitle" Width="100%" />
|
||||
@@ -143,13 +144,13 @@ else
|
||||
private string popupHeaderText = "Edit";
|
||||
private const string LayoutType = "GRID_BANDS";
|
||||
private const string LayoutKey = "CatalogsGrid";
|
||||
private const string LayoutUserStorageKey = "layoutUser";
|
||||
private string? layoutUser;
|
||||
private BandLayout bandLayout = new();
|
||||
private Dictionary<string, string> columnBandAssignments = new();
|
||||
private List<BandOption> bandOptions = new();
|
||||
private Dictionary<string, ColumnDefinition> columnLookup = new();
|
||||
private readonly JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web);
|
||||
private bool gridLayoutApplied;
|
||||
|
||||
private List<ColumnDefinition> columnDefinitions = new()
|
||||
{
|
||||
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 gridLayoutApplied;
|
||||
|
||||
private DevExpress.Blazor.SizeMode _sizeMode = DevExpress.Blazor.SizeMode.Medium;
|
||||
private static readonly List<DevExpress.Blazor.SizeMode> _sizeModes =
|
||||
Enum.GetValues<DevExpress.Blazor.SizeMode>().ToList();
|
||||
private SizeMode _sizeMode = SizeMode.Medium;
|
||||
private static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
|
||||
|
||||
private string FormatSizeText(DevExpress.Blazor.SizeMode size) => size switch
|
||||
private string FormatSizeText(SizeMode size) => size switch
|
||||
{
|
||||
DevExpress.Blazor.SizeMode.Small => "Klein",
|
||||
DevExpress.Blazor.SizeMode.Medium => "Mittel",
|
||||
DevExpress.Blazor.SizeMode.Large => "Groß",
|
||||
SizeMode.Small => "Klein",
|
||||
SizeMode.Medium => "Mittel",
|
||||
SizeMode.Large => "Groß",
|
||||
_ => size.ToString()
|
||||
};
|
||||
|
||||
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()
|
||||
{
|
||||
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||
await EnsureLayoutUserAsync();
|
||||
await LoadBandLayoutAsync();
|
||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||
layoutUser = await BandLayoutService.EnsureLayoutUserAsync();
|
||||
bandLayout = await BandLayoutService.LoadBandLayoutAsync(LayoutType, LayoutKey, layoutUser, columnLookup);
|
||||
columnBandAssignments = BandLayoutService.BuildAssignmentsFromLayout(bandLayout);
|
||||
ApplyColumnLayoutFromStorage();
|
||||
_sizeMode = bandLayout.SizeMode;
|
||||
UpdateBandOptions();
|
||||
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()
|
||||
{
|
||||
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)
|
||||
{
|
||||
errorMessage = null;
|
||||
infoMessage = null;
|
||||
|
||||
validationMessageStore?.Clear();
|
||||
editContext?.NotifyValidationStateChanged();
|
||||
|
||||
@@ -320,18 +476,12 @@ else
|
||||
if (!created.Success || created.Value == null)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(created.Error))
|
||||
{
|
||||
AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), created.Error);
|
||||
}
|
||||
else
|
||||
{
|
||||
errorMessage = "Anlegen fehlgeschlagen.";
|
||||
}
|
||||
|
||||
e.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
infoMessage = "Katalog angelegt.";
|
||||
focusedRowKey = created.Value.Guid;
|
||||
}
|
||||
@@ -344,7 +494,6 @@ else
|
||||
e.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
infoMessage = "Katalog aktualisiert.";
|
||||
focusedRowKey = editModel.Guid;
|
||||
}
|
||||
@@ -360,30 +509,20 @@ else
|
||||
|
||||
private void AddValidationError(CatalogEditModel editModel, string fieldName, string message)
|
||||
{
|
||||
if (editContext == null || validationMessageStore == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var field = new FieldIdentifier(editModel, fieldName);
|
||||
validationMessageStore.Add(field, message);
|
||||
if (editContext == null || validationMessageStore == null) return;
|
||||
validationMessageStore.Add(new FieldIdentifier(editModel, fieldName), message);
|
||||
editContext.NotifyValidationStateChanged();
|
||||
}
|
||||
|
||||
private bool ValidateEditModel(CatalogEditModel editModel, bool isNew)
|
||||
{
|
||||
if (isNew)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isNew) return true;
|
||||
if (editModel.UpdateProcedure == 0 &&
|
||||
!string.Equals(editModel.CatTitle, editModel.OriginalCatTitle, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
AddValidationError(editModel, nameof(CatalogEditModel.CatTitle), "Titel kann nicht geändert werden.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -391,9 +530,7 @@ else
|
||||
{
|
||||
errorMessage = null;
|
||||
infoMessage = null;
|
||||
|
||||
var item = (CatalogReadDto)e.DataItem;
|
||||
|
||||
try
|
||||
{
|
||||
var deleted = await Api.DeleteAsync(item.Guid);
|
||||
@@ -403,350 +540,16 @@ else
|
||||
e.Cancel = true;
|
||||
return;
|
||||
}
|
||||
|
||||
infoMessage = "Katalog gelöscht.";
|
||||
await LoadCatalogs();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
errorMessage = $"Fehler beim Löschen: {ex.Message}";
|
||||
errorMessage = $"Fehler beim Löschen: {ex.Message}";
|
||||
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
|
||||
{
|
||||
public int Guid { get; set; }
|
||||
@@ -762,10 +565,4 @@ else
|
||||
public int Value { get; set; }
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
@inject MassDataApiClient Api
|
||||
@inject LayoutApiClient LayoutApi
|
||||
@inject IJSRuntime JsRuntime
|
||||
@inject BandLayoutService BandLayoutService
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
||||
{
|
||||
@@ -113,7 +112,9 @@ else
|
||||
@RenderColumns()
|
||||
</Columns>
|
||||
<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">
|
||||
<DxFormLayoutItem Caption="CustomerName">
|
||||
<DxTextBox @bind-Text="editModel.CustomerName" Width="100%" />
|
||||
@@ -171,13 +172,13 @@ else
|
||||
private int? focusedRowKey;
|
||||
private const string LayoutType = "GRID_BANDS";
|
||||
private const string LayoutKey = "MassDataGrid";
|
||||
private const string LayoutUserStorageKey = "layoutUser";
|
||||
private string? layoutUser;
|
||||
private BandLayout bandLayout = new();
|
||||
private Dictionary<string, string> columnBandAssignments = new();
|
||||
private List<BandOption> bandOptions = new();
|
||||
private Dictionary<string, ColumnDefinition> columnLookup = new();
|
||||
private readonly JsonSerializerOptions jsonOptions = new(JsonSerializerDefaults.Web);
|
||||
private bool gridLayoutApplied;
|
||||
|
||||
private List<ColumnDefinition> columnDefinitions = new()
|
||||
{
|
||||
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 }
|
||||
};
|
||||
|
||||
private bool CanSaveBandLayout => !string.IsNullOrWhiteSpace(layoutUser);
|
||||
|
||||
private readonly List<PageSizeOption> pageSizeOptions = new()
|
||||
{
|
||||
new() { Value = 100, Text = "100" },
|
||||
new() { Value = 1000, Text = "1.000" },
|
||||
new() { Value = 10000, Text = "10.000" },
|
||||
new() { Value = 100, Text = "100" },
|
||||
new() { Value = 1000, Text = "1.000" },
|
||||
new() { Value = 10000, Text = "10.000" },
|
||||
new() { Value = 100000, Text = "100.000" },
|
||||
new() { Value = null, Text = "Alle" }
|
||||
new() { Value = null, Text = "Alle" }
|
||||
};
|
||||
|
||||
private readonly List<ProcedureOption> procedureOptions = new()
|
||||
@@ -205,33 +204,46 @@ else
|
||||
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 static readonly List<DevExpress.Blazor.SizeMode> _sizeModes =
|
||||
Enum.GetValues<DevExpress.Blazor.SizeMode>().ToList();
|
||||
private SizeMode _sizeMode = SizeMode.Medium;
|
||||
private static readonly List<SizeMode> _sizeModes = Enum.GetValues<SizeMode>().ToList();
|
||||
|
||||
private string FormatSizeText(DevExpress.Blazor.SizeMode size) => size switch
|
||||
private string FormatSizeText(SizeMode size) => size switch
|
||||
{
|
||||
DevExpress.Blazor.SizeMode.Small => "Klein",
|
||||
DevExpress.Blazor.SizeMode.Medium => "Mittel",
|
||||
DevExpress.Blazor.SizeMode.Large => "Groß",
|
||||
SizeMode.Small => "Klein",
|
||||
SizeMode.Medium => "Mittel",
|
||||
SizeMode.Large => "Groß",
|
||||
_ => size.ToString()
|
||||
};
|
||||
|
||||
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()
|
||||
{
|
||||
columnLookup = columnDefinitions.ToDictionary(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||
await EnsureLayoutUserAsync();
|
||||
await LoadBandLayoutAsync();
|
||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||
layoutUser = await BandLayoutService.EnsureLayoutUserAsync();
|
||||
bandLayout = await BandLayoutService.LoadBandLayoutAsync(LayoutType, LayoutKey, layoutUser, columnLookup);
|
||||
columnBandAssignments = BandLayoutService.BuildAssignmentsFromLayout(bandLayout);
|
||||
ApplyColumnLayoutFromStorage();
|
||||
_sizeMode = bandLayout.SizeMode;
|
||||
UpdateBandOptions();
|
||||
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)
|
||||
{
|
||||
isLoading = true;
|
||||
@@ -242,7 +254,6 @@ else
|
||||
var effectivePageSize = pageSize ?? (total == 0 ? 1 : total);
|
||||
pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)effectivePageSize));
|
||||
pageIndex = Math.Clamp(page, 0, pageCount - 1);
|
||||
|
||||
var skip = pageSize.HasValue ? pageIndex * pageSize.Value : 0;
|
||||
items = await Api.GetAllAsync(skip, pageSize);
|
||||
}
|
||||
@@ -258,10 +269,7 @@ else
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnPageChanged(int index)
|
||||
{
|
||||
await LoadPage(index);
|
||||
}
|
||||
private async Task OnPageChanged(int index) => await LoadPage(index);
|
||||
|
||||
private async Task OnPageSizeChanged(int? size)
|
||||
{
|
||||
@@ -269,61 +277,15 @@ else
|
||||
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()
|
||||
{
|
||||
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
|
||||
});
|
||||
await BandLayoutService.SaveBandLayoutAsync(LayoutType, LayoutKey, layoutUser, bandLayout);
|
||||
infoMessage = "Layout gespeichert.";
|
||||
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()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(layoutUser))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await LayoutApi.DeleteAsync(LayoutType, LayoutKey, layoutUser);
|
||||
await BandLayoutService.ResetBandLayoutAsync(LayoutType, LayoutKey, layoutUser);
|
||||
|
||||
bandLayout = new BandLayout();
|
||||
columnBandAssignments.Clear();
|
||||
@@ -377,49 +310,57 @@ else
|
||||
column.Width = null;
|
||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
_sizeMode = DevExpress.Blazor.SizeMode.Medium;
|
||||
_sizeMode = SizeMode.Medium;
|
||||
|
||||
if (gridRef != null)
|
||||
gridRef.LoadLayout(new GridPersistentLayout());
|
||||
gridLayoutApplied = false;
|
||||
|
||||
infoMessage = "Layout zur\u00fcckgesetzt.";
|
||||
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(column => column.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||
columnLookup = columnDefinitions.ToDictionary(c => c.FieldName, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private void AddBand()
|
||||
{
|
||||
bandLayout.Bands.Add(new BandDefinition
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
Caption = "Band"
|
||||
});
|
||||
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);
|
||||
}
|
||||
foreach (var key in columnBandAssignments.Where(p => p.Value == band.Id).Select(p => p.Key).ToList())
|
||||
columnBandAssignments.Remove(key);
|
||||
UpdateBandOptions();
|
||||
SyncBandsFromAssignments();
|
||||
}
|
||||
@@ -433,77 +374,31 @@ else
|
||||
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;
|
||||
}
|
||||
=> 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)
|
||||
.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(band => new BandOption { Id = band.Id, Caption = band.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;
|
||||
bandOptions.AddRange(bandLayout.Bands.Select(b => new BandOption { Id = b.Id, Caption = b.Caption }));
|
||||
}
|
||||
|
||||
private RenderFragment RenderColumns() => builder =>
|
||||
@@ -513,18 +408,13 @@ else
|
||||
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)))
|
||||
{
|
||||
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;
|
||||
}
|
||||
if (band.Columns.Count == 0) continue;
|
||||
|
||||
builder.OpenComponent<DxGridBandColumn>(seq++);
|
||||
builder.AddAttribute(seq++, "Caption", band.Caption);
|
||||
@@ -534,9 +424,7 @@ else
|
||||
foreach (var columnName in band.Columns)
|
||||
{
|
||||
if (columnLookup.TryGetValue(columnName, out var column))
|
||||
{
|
||||
BuildDataColumn(bandBuilder, ref bandSeq, column);
|
||||
}
|
||||
}
|
||||
}));
|
||||
builder.CloseComponent();
|
||||
@@ -549,35 +437,19 @@ else
|
||||
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 == context) return;
|
||||
if (editContext != null)
|
||||
{
|
||||
editContext.OnFieldChanged -= OnEditFieldChanged;
|
||||
}
|
||||
|
||||
editContext = context;
|
||||
validationMessageStore = new ValidationMessageStore(editContext);
|
||||
editContext.OnFieldChanged += OnEditFieldChanged;
|
||||
@@ -585,10 +457,7 @@ else
|
||||
|
||||
private void OnEditFieldChanged(object? sender, FieldChangedEventArgs e)
|
||||
{
|
||||
if (validationMessageStore == null || editContext == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (validationMessageStore == null || editContext == null) return;
|
||||
|
||||
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.UpdateProcedure))
|
||||
{
|
||||
@@ -599,16 +468,12 @@ else
|
||||
|
||||
if (e.FieldIdentifier.FieldName == nameof(MassDataEditModel.CustomerName))
|
||||
{
|
||||
var field = new FieldIdentifier(editContext.Model, nameof(MassDataEditModel.CustomerName));
|
||||
validationMessageStore.Clear(field);
|
||||
validationMessageStore.Clear(new FieldIdentifier(editContext.Model, nameof(MassDataEditModel.CustomerName)));
|
||||
editContext.NotifyValidationStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void SetPopupHeaderText(bool isNew)
|
||||
{
|
||||
popupHeaderText = isNew ? "Neu" : "Edit";
|
||||
}
|
||||
private void SetPopupHeaderText(bool isNew) => popupHeaderText = isNew ? "Neu" : "Edit";
|
||||
|
||||
private async Task OnCustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
||||
{
|
||||
@@ -638,7 +503,6 @@ else
|
||||
{
|
||||
errorMessage = null;
|
||||
infoMessage = null;
|
||||
|
||||
validationMessageStore?.Clear();
|
||||
editContext?.NotifyValidationStateChanged();
|
||||
|
||||
@@ -685,13 +549,8 @@ else
|
||||
|
||||
private void AddValidationError(MassDataEditModel editModel, string fieldName, string message)
|
||||
{
|
||||
if (editContext == null || validationMessageStore == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var field = new FieldIdentifier(editModel, fieldName);
|
||||
validationMessageStore.Add(field, message);
|
||||
if (editContext == null || validationMessageStore == null) return;
|
||||
validationMessageStore.Add(new FieldIdentifier(editModel, fieldName), message);
|
||||
editContext.NotifyValidationStateChanged();
|
||||
}
|
||||
|
||||
@@ -703,55 +562,6 @@ else
|
||||
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
|
||||
{
|
||||
public int Id { get; set; }
|
||||
@@ -770,21 +580,9 @@ else
|
||||
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
|
||||
{
|
||||
public int? Value { get; set; }
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using System.Text.Json
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Rendering
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@@ -11,10 +12,9 @@
|
||||
@using DbFirst.BlazorWebApp
|
||||
@using DbFirst.BlazorWebApp.Components
|
||||
@using DbFirst.BlazorWebApp.Models
|
||||
@using DbFirst.BlazorWebApp.Models.Grid
|
||||
@using DbFirst.BlazorWebApp.Services
|
||||
@using DevExpress.Blazor
|
||||
@using DevExpress.DashboardBlazor
|
||||
@using DevExpress.DashboardWeb
|
||||
@using DevExpress.Data.Filtering
|
||||
@using DbFirst.BlazorWebApp
|
||||
@using System.Text.Json
|
||||
@using DevExpress.Data.Filtering
|
||||
43
DbFirst.BlazorWebApp/Models/Grid/BandLayoutModels.cs
Normal file
43
DbFirst.BlazorWebApp/Models/Grid/BandLayoutModels.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ builder.Services.AddRazorComponents()
|
||||
|
||||
builder.Services.AddDevExpressBlazor(options => options.BootstrapVersion = BootstrapVersion.v5);
|
||||
builder.Services.AddScoped<ThemeState>();
|
||||
builder.Services.AddScoped<BandLayoutService>();
|
||||
|
||||
var apiBaseUrl = builder.Configuration["ApiBaseUrl"];
|
||||
if (!string.IsNullOrWhiteSpace(apiBaseUrl))
|
||||
|
||||
103
DbFirst.BlazorWebApp/Services/BandLayoutService.cs
Normal file
103
DbFirst.BlazorWebApp/Services/BandLayoutService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user