Introduce Add, Edit, and Delete buttons with Bootstrap Icons in the toolbars of CatalogsGrid and MassDataGrid. Hide the default command column by overriding ShowCommandColumn. Update BandGridBase to conditionally render the command column. Track focused row index for toolbar actions, enabling row operations via toolbar instead of the grid's built-in command column. Add Bootstrap Icons stylesheet to App.razor.
413 lines
16 KiB
Plaintext
413 lines
16 KiB
Plaintext
@inherits BandGridBase<CatalogReadDto>
|
|
@inject CatalogApiClient Api
|
|
|
|
@if (!string.IsNullOrWhiteSpace(errorMessage))
|
|
{
|
|
<div class="alert alert-danger" role="alert">@errorMessage</div>
|
|
}
|
|
else if (!string.IsNullOrWhiteSpace(infoMessage))
|
|
{
|
|
<div class="alert alert-success" role="alert">@infoMessage</div>
|
|
}
|
|
|
|
@if (!hasLoaded || isLoading)
|
|
{
|
|
<div class="loading-container">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Lade...</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
else if (items.Count == 0)
|
|
{
|
|
<p>Keine Einträge vorhanden.</p>
|
|
}
|
|
else
|
|
{
|
|
<BandEditor Bands="@bandLayout.Bands"
|
|
BandOptions="@bandOptions"
|
|
Columns="@ColumnDefinitions"
|
|
GetColumnBand="GetColumnBand"
|
|
CanSave="@CanSaveBandLayout"
|
|
OnAddBand="AddBand"
|
|
OnSaveLayout="SaveLayoutWithFeedbackAsync"
|
|
OnResetLayout="ResetLayoutWithFeedbackAsync"
|
|
OnRemoveBand="RemoveBand"
|
|
OnBandCaptionChanged="@(args => UpdateBandCaption(args.Band, args.Value))"
|
|
OnColumnBandChanged="@(args => UpdateColumnBand(args.FieldName, args.BandId))" />
|
|
|
|
<div class="grid-section">
|
|
<DxGrid Data="@items"
|
|
ColumnChooserButtonDisplayMode="GridColumnChooserButtonDisplayMode.Always"
|
|
TItem="CatalogReadDto"
|
|
KeyFieldName="@nameof(CatalogReadDto.Guid)"
|
|
SizeMode="@_sizeMode"
|
|
ShowGroupPanel="true"
|
|
ShowGroupedColumns="true"
|
|
AllowGroup="true"
|
|
FilterMenuButtonDisplayMode="GridFilterMenuButtonDisplayMode.Always"
|
|
AllowColumnResize="true"
|
|
ColumnResizeMode="GridColumnResizeMode.ColumnsContainer"
|
|
AllowColumnReorder="true"
|
|
PageSize="10"
|
|
CssClass="mb-4 catalog-grid"
|
|
EditMode="GridEditMode.PopupEditForm"
|
|
PopupEditFormCssClass="catalog-edit-popup"
|
|
PopupEditFormHeaderText="@popupHeaderText"
|
|
CustomizeEditModel="OnCustomizeEditModel"
|
|
EditModelSaving="OnEditModelSaving"
|
|
DataItemDeleting="OnDataItemDeleting"
|
|
FocusedRowEnabled="true"
|
|
@bind-FocusedRowKey="focusedRowKey"
|
|
RowClick="@(args => _focusedVisibleIndex = args.VisibleIndex)"
|
|
@ref="gridRef">
|
|
<ToolbarTemplate>
|
|
<DxToolbar>
|
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Left">
|
|
<Template Context="_">
|
|
<DxButton IconCssClass="bi bi-plus-lg"
|
|
RenderStyle="ButtonRenderStyle.Secondary"
|
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
|
Click="@(() => gridRef!.StartEditNewRowAsync())" />
|
|
</Template>
|
|
</DxToolbarItem>
|
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Left">
|
|
<Template Context="_">
|
|
<DxButton IconCssClass="bi bi-pencil"
|
|
RenderStyle="ButtonRenderStyle.Secondary"
|
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
|
Click="EditFocusedRow" />
|
|
</Template>
|
|
</DxToolbarItem>
|
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Left">
|
|
<Template Context="_">
|
|
<DxButton IconCssClass="bi bi-trash"
|
|
RenderStyle="ButtonRenderStyle.Secondary"
|
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
|
Click="DeleteFocusedRow" />
|
|
</Template>
|
|
</DxToolbarItem>
|
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Right">
|
|
<Template Context="_">
|
|
<DxButton Text="Spalten"
|
|
RenderStyle="ButtonRenderStyle.Secondary"
|
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
|
Click="@(() => gridRef!.ShowColumnChooser())" />
|
|
</Template>
|
|
</DxToolbarItem>
|
|
<DxToolbarItem Alignment="ToolbarItemAlignment.Right">
|
|
<Template Context="_">
|
|
<DxDropDownButton Text="@FormatSizeText(_sizeMode)"
|
|
RenderStyle="ButtonRenderStyle.Secondary"
|
|
RenderStyleMode="ButtonRenderStyleMode.Text"
|
|
ItemClick="OnSizeChange">
|
|
<Items>
|
|
@foreach (var size in _sizeModes)
|
|
{
|
|
<DxDropDownButtonItem Text="@FormatSizeText(size)" Id="@size.ToString()" />
|
|
}
|
|
</Items>
|
|
</DxDropDownButton>
|
|
</Template>
|
|
</DxToolbarItem>
|
|
</DxToolbar>
|
|
</ToolbarTemplate>
|
|
<Columns>
|
|
@RenderColumns()
|
|
</Columns>
|
|
<EditFormTemplate Context="editFormContext">
|
|
@{
|
|
SetEditContext(editFormContext.EditContext); var editModel = (CatalogEditModel)editFormContext.EditModel; SetPopupHeaderText(editModel.IsNew);
|
|
}
|
|
<DxFormLayout ColCount="2">
|
|
<DxFormLayoutItem Caption="Titel">
|
|
<DxTextBox @bind-Text="editModel.CatTitle" Width="100%" />
|
|
</DxFormLayoutItem>
|
|
<DxFormLayoutItem Caption="Kennung">
|
|
<DxTextBox @bind-Text="editModel.CatString" Width="100%" />
|
|
</DxFormLayoutItem>
|
|
@if (!editModel.IsNew)
|
|
{
|
|
<DxFormLayoutItem Caption="Update-Prozedur">
|
|
<DxComboBox Data="@procedureOptions"
|
|
TData="ProcedureOption"
|
|
TValue="int"
|
|
TextFieldName="Text"
|
|
ValueFieldName="Value"
|
|
@bind-Value="editModel.UpdateProcedure"
|
|
Width="100%" />
|
|
</DxFormLayoutItem>
|
|
}
|
|
<DxFormLayoutItem ColSpanMd="12">
|
|
<ValidationSummary />
|
|
</DxFormLayoutItem>
|
|
</DxFormLayout>
|
|
</EditFormTemplate>
|
|
</DxGrid>
|
|
</div>
|
|
}
|
|
|
|
@code {
|
|
private List<CatalogReadDto> items = new();
|
|
private bool isLoading;
|
|
private bool hasLoaded;
|
|
private string? errorMessage;
|
|
private string? infoMessage;
|
|
private EditContext? editContext;
|
|
private ValidationMessageStore? validationMessageStore;
|
|
private int? focusedRowKey;
|
|
private string popupHeaderText = "Edit";
|
|
|
|
protected override string LayoutKey => "CatalogsGrid";
|
|
protected override bool ShowCommandColumn => false;
|
|
|
|
protected override List<ColumnDefinition> ColumnDefinitions { get; } = new()
|
|
{
|
|
new() { FieldName = nameof(CatalogReadDto.Guid), Caption = "Id", Width = "140px", FilterType = ColumnFilterType.Text },
|
|
new() { FieldName = nameof(CatalogReadDto.CatTitle), Caption = "Titel", FilterType = ColumnFilterType.Text },
|
|
new() { FieldName = nameof(CatalogReadDto.CatString), Caption = "String", FilterType = ColumnFilterType.Text },
|
|
new() { FieldName = nameof(CatalogReadDto.AddedWho), Caption = "Angelegt von", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
|
new() { FieldName = nameof(CatalogReadDto.AddedWhen), Caption = "Angelegt am", ReadOnly = true, FilterType = ColumnFilterType.Date },
|
|
new() { FieldName = nameof(CatalogReadDto.ChangedWho), Caption = "Geändert von", ReadOnly = true, FilterType = ColumnFilterType.Text },
|
|
new() { FieldName = nameof(CatalogReadDto.ChangedWhen),Caption = "Geändert am", ReadOnly = true, FilterType = ColumnFilterType.Date }
|
|
};
|
|
|
|
private readonly List<ProcedureOption> procedureOptions = new()
|
|
{
|
|
new() { Value = 0, Text = "PRTBMY_CATALOG_UPDATE" },
|
|
new() { Value = 1, Text = "PRTBMY_CATALOG_SAVE" }
|
|
};
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
await InitializeBandLayoutAsync();
|
|
await LoadCatalogs();
|
|
}
|
|
|
|
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
{
|
|
await ApplyGridLayoutAfterRenderAsync();
|
|
}
|
|
|
|
private async Task LoadCatalogs()
|
|
{
|
|
isLoading = true;
|
|
errorMessage = null;
|
|
try
|
|
{
|
|
items = await Api.GetAllAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Kataloge konnten nicht geladen werden: {ex.Message}";
|
|
}
|
|
finally
|
|
{
|
|
isLoading = false;
|
|
hasLoaded = true;
|
|
StateHasChanged();
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
var editModel = (CatalogEditModel)e.EditModel;
|
|
if (!ValidateEditModel(editModel, e.IsNew))
|
|
{
|
|
e.Cancel = true;
|
|
return;
|
|
}
|
|
|
|
var dto = new CatalogWriteDto
|
|
{
|
|
CatTitle = editModel.CatTitle,
|
|
CatString = editModel.CatString,
|
|
UpdateProcedure = editModel.UpdateProcedure
|
|
};
|
|
|
|
try
|
|
{
|
|
if (e.IsNew)
|
|
{
|
|
var created = await Api.CreateAsync(dto);
|
|
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;
|
|
}
|
|
else
|
|
{
|
|
var updated = await Api.UpdateAsync(editModel.Guid, dto);
|
|
if (!updated.Success)
|
|
{
|
|
errorMessage = updated.Error ?? "Aktualisierung fehlgeschlagen.";
|
|
e.Cancel = true;
|
|
return;
|
|
}
|
|
infoMessage = "Katalog aktualisiert.";
|
|
focusedRowKey = editModel.Guid;
|
|
}
|
|
|
|
await LoadCatalogs();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Fehler beim Speichern: {ex.Message}";
|
|
e.Cancel = true;
|
|
}
|
|
}
|
|
|
|
private void AddValidationError(CatalogEditModel editModel, string fieldName, string 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 (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;
|
|
}
|
|
|
|
private async Task OnDataItemDeleting(GridDataItemDeletingEventArgs e)
|
|
{
|
|
errorMessage = null;
|
|
infoMessage = null;
|
|
var item = (CatalogReadDto)e.DataItem;
|
|
try
|
|
{
|
|
var deleted = await Api.DeleteAsync(item.Guid);
|
|
if (!deleted.Success)
|
|
{
|
|
errorMessage = deleted.Error ?? "Löschen fehlgeschlagen.";
|
|
e.Cancel = true;
|
|
return;
|
|
}
|
|
infoMessage = "Katalog gelöscht.";
|
|
await LoadCatalogs();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Fehler beim Löschen: {ex.Message}";
|
|
e.Cancel = true;
|
|
}
|
|
}
|
|
|
|
private sealed class CatalogEditModel
|
|
{
|
|
public int Guid { get; set; }
|
|
public string CatTitle { get; set; } = string.Empty;
|
|
public string CatString { get; set; } = string.Empty;
|
|
public int UpdateProcedure { get; set; }
|
|
public string OriginalCatTitle { get; set; } = string.Empty;
|
|
public bool IsNew { get; set; }
|
|
}
|
|
|
|
private sealed class ProcedureOption
|
|
{
|
|
public int Value { get; set; }
|
|
public string Text { get; set; } = string.Empty;
|
|
}
|
|
|
|
private int _focusedVisibleIndex;
|
|
|
|
private async Task EditFocusedRow()
|
|
=> await gridRef!.StartEditRowAsync(_focusedVisibleIndex);
|
|
|
|
private Task DeleteFocusedRow()
|
|
{
|
|
gridRef!.ShowRowDeleteConfirmation(_focusedVisibleIndex);
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
private async Task SaveLayoutWithFeedbackAsync()
|
|
{
|
|
try
|
|
{
|
|
await SaveLayoutAsync();
|
|
infoMessage = "Layout gespeichert.";
|
|
errorMessage = null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
errorMessage = $"Layout konnte nicht gespeichert werden: {ex.Message}";
|
|
}
|
|
}
|
|
|
|
private async Task ResetLayoutWithFeedbackAsync()
|
|
{
|
|
await ResetLayoutAsync();
|
|
infoMessage = "Layout zurückgesetzt.";
|
|
errorMessage = null;
|
|
}
|
|
} |