Refactor: decouple InfoPanel using MgGridWithInfoPanel
Major refactor to decouple InfoPanel logic from grid base. Introduces MgGridWithInfoPanel wrapper component to manage grid and InfoPanel layout and communication. InfoPanels are now customizable via Razor templates with named slots (header, footer, etc.), and grid-to-InfoPanel communication is routed through the wrapper. Removes legacy C#-only InfoPanel base classes and parameters from grid base. This improves flexibility, composability, and maintainability of grid+InfoPanel UIs.
This commit is contained in:
parent
112d633590
commit
739d0fa808
|
|
@ -23,6 +23,7 @@ public interface IMgGridBase : IGrid
|
|||
bool IsSyncing { get; }
|
||||
|
||||
string Caption { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Current edit state of the grid (None, New, Edit)
|
||||
/// </summary>
|
||||
|
|
@ -49,14 +50,9 @@ public interface IMgGridBase : IGrid
|
|||
void StepNextRow();
|
||||
|
||||
/// <summary>
|
||||
/// Whether this grid shows an InfoPanel
|
||||
/// InfoPanel instance for displaying row details (from wrapper)
|
||||
/// </summary>
|
||||
bool ShowInfoPanel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// InfoPanel instance for displaying row details
|
||||
/// </summary>
|
||||
IInfoPanelBase? InfoPanelInstance { get; set; }
|
||||
IInfoPanelBase? InfoPanelInstance { get; }
|
||||
}
|
||||
|
||||
public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable
|
||||
|
|
@ -97,90 +93,27 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
return current;
|
||||
}
|
||||
|
||||
[Parameter] public bool ShowInfoPanel { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Custom InfoPanel component type. Must inherit from MgInfoPanelTemplateBase.
|
||||
/// If not set, the default MgGridInfoPanel is used.
|
||||
/// Reference to the wrapper component for grid-InfoPanel communication
|
||||
/// </summary>
|
||||
[Parameter] public Type? InfoPanelType { get; set; }
|
||||
[CascadingParameter]
|
||||
public MgGridWithInfoPanel? GridWrapper { get; set; }
|
||||
|
||||
private object _focusedDataItem;
|
||||
|
||||
/// <summary>
|
||||
/// InfoPanel instance for displaying row details
|
||||
/// InfoPanel instance for displaying row details (from wrapper or direct)
|
||||
/// </summary>
|
||||
public IInfoPanelBase? InfoPanelInstance { get; set; }
|
||||
public IInfoPanelBase? InfoPanelInstance
|
||||
{
|
||||
get => GridWrapper?.InfoPanelInstance;
|
||||
set { /* Set through wrapper */ }
|
||||
}
|
||||
|
||||
public MgGridBase() : base()
|
||||
{
|
||||
}
|
||||
|
||||
//protected override RenderFragment<RenderFragment> CreateRootComponent()
|
||||
//{
|
||||
// System.Diagnostics.Debug.WriteLine($"[MgGridBase] CreateRootComponent - ShowInfoPanel: {ShowInfoPanel}, GridName: {GridName}");
|
||||
|
||||
// if (!ShowInfoPanel)
|
||||
// {
|
||||
// // Ha nincs InfoPanel, használjuk az alapértelmezett renderelést
|
||||
// System.Diagnostics.Debug.WriteLine("[MgGridBase] Using base CreateRootComponent");
|
||||
|
||||
// return base.CreateRootComponent();
|
||||
// }
|
||||
|
||||
// // Ha van InfoPanel, akkor splitter-rel burkoljuk be
|
||||
// System.Diagnostics.Debug.WriteLine("[MgGridBase] Creating splitter wrapper");
|
||||
|
||||
// return (RenderFragment<RenderFragment>)(content => (RenderFragment)(builder =>
|
||||
// {
|
||||
// var seq = 0;
|
||||
|
||||
// // DxSplitter
|
||||
// builder.OpenComponent<DxSplitter>(seq++);
|
||||
// builder.AddAttribute(seq++, "Width", "100%");
|
||||
// builder.AddAttribute(seq++, "Height", "100%");
|
||||
|
||||
// // Panes
|
||||
// builder.AddAttribute(seq++, "Panes", (RenderFragment)(panesBuilder =>
|
||||
// {
|
||||
// var paneSeq = 0;
|
||||
|
||||
// // Bal pane - Grid
|
||||
// panesBuilder.OpenComponent<DxSplitterPane>(paneSeq++);
|
||||
|
||||
// panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(gridBuilder =>
|
||||
// {
|
||||
// // A grid eredeti content-jét rendereljük
|
||||
// var baseRootComponent = base.CreateRootComponent();
|
||||
// baseRootComponent(content)(gridBuilder);
|
||||
// }));
|
||||
|
||||
// panesBuilder.CloseComponent();
|
||||
|
||||
// // Jobb pane - Egyelőre üres
|
||||
// panesBuilder.OpenComponent<DxSplitterPane>(paneSeq++);
|
||||
|
||||
// panesBuilder.AddAttribute(paneSeq++, "Size", "0px");
|
||||
// panesBuilder.AddAttribute(paneSeq++, "MinSize", "0px");
|
||||
// panesBuilder.AddAttribute(paneSeq++, "AllowCollapse", true);
|
||||
// panesBuilder.AddAttribute(paneSeq++, "Collapsed", true);
|
||||
|
||||
// panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(infoPanelBuilder =>
|
||||
// {
|
||||
// infoPanelBuilder.OpenElement(0, "div");
|
||||
// infoPanelBuilder.AddAttribute(1, "style", "padding: 1rem;");
|
||||
// infoPanelBuilder.AddContent(2, "Info Panel - Coming soon...");
|
||||
// infoPanelBuilder.CloseElement();
|
||||
// }));
|
||||
|
||||
// panesBuilder.CloseComponent();
|
||||
|
||||
// }));
|
||||
|
||||
// builder.CloseComponent();
|
||||
// }));
|
||||
//}
|
||||
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
var seq = 0;
|
||||
|
|
@ -189,71 +122,12 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
builder.OpenComponent<CascadingValue<IMgGridBase>>(seq++);
|
||||
builder.AddAttribute(seq++, "Value", (IMgGridBase)this);
|
||||
builder.AddAttribute(seq++, "ChildContent", (RenderFragment)(contentBuilder =>
|
||||
{
|
||||
// Nested grids or root without InfoPanel: render base grid only
|
||||
if (ParentGrid != null || !ShowInfoPanel)
|
||||
{
|
||||
base.BuildRenderTree(contentBuilder);
|
||||
return;
|
||||
}));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
|
||||
// Root grid with InfoPanel enabled: render splitter with grid + InfoPanel
|
||||
var innerSeq = 0;
|
||||
|
||||
contentBuilder.OpenComponent<DxSplitter>(innerSeq++);
|
||||
contentBuilder.AddAttribute(innerSeq++, "Width", "100%");
|
||||
contentBuilder.AddAttribute(innerSeq++, "CssClass", "mg-grid-splitter");
|
||||
contentBuilder.AddAttribute(innerSeq++, "Orientation", Orientation.Horizontal);
|
||||
|
||||
contentBuilder.AddAttribute(innerSeq++, "Panes", (RenderFragment)(panesBuilder =>
|
||||
{
|
||||
var paneSeq = 0;
|
||||
|
||||
// Left pane - Grid
|
||||
panesBuilder.OpenComponent<DxSplitterPane>(paneSeq++);
|
||||
panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(gridBuilder =>
|
||||
{
|
||||
base.BuildRenderTree(gridBuilder);
|
||||
}));
|
||||
panesBuilder.CloseComponent();
|
||||
|
||||
// Right pane - InfoPanel
|
||||
panesBuilder.OpenComponent<DxSplitterPane>(paneSeq++);
|
||||
panesBuilder.AddAttribute(paneSeq++, "Size", "400px");
|
||||
panesBuilder.AddAttribute(paneSeq++, "MinSize", "0px");
|
||||
panesBuilder.AddAttribute(paneSeq++, "MaxSize", "100%");
|
||||
panesBuilder.AddAttribute(paneSeq++, "AllowCollapse", true);
|
||||
panesBuilder.AddAttribute(paneSeq++, "CssClass", "mg-info-panel-pane");
|
||||
|
||||
panesBuilder.AddAttribute(paneSeq++, "ChildContent", (RenderFragment)(infoPanelBuilder =>
|
||||
{
|
||||
var infoPanelSeq = 0;
|
||||
|
||||
// Use custom InfoPanel type if provided, otherwise use default MgGridInfoPanel
|
||||
var panelType = InfoPanelType ?? typeof(MgGridInfoPanel);
|
||||
infoPanelBuilder.OpenComponent(infoPanelSeq++, panelType);
|
||||
infoPanelBuilder.AddComponentReferenceCapture(infoPanelSeq++, instance =>
|
||||
{
|
||||
InfoPanelInstance = instance as IInfoPanelBase;
|
||||
});
|
||||
infoPanelBuilder.CloseComponent();
|
||||
}));
|
||||
|
||||
panesBuilder.CloseComponent();
|
||||
}));
|
||||
|
||||
contentBuilder.CloseComponent();
|
||||
}));
|
||||
builder.CloseComponent(); // Close CascadingValue
|
||||
}
|
||||
|
||||
//protected override Task RaiseFocusedRowChangedAsync(GridFocusedRowChangedEventArgsBase args)
|
||||
//{
|
||||
// _focusedDataItem = args.DataItem;
|
||||
// InvokeAsync(StateHasChanged);
|
||||
// return base.RaiseFocusedRowChangedAsync(args);
|
||||
//}
|
||||
|
||||
protected bool HasIdValue(TDataItem dataItem) => HasIdValue(dataItem.Id);
|
||||
protected bool HasIdValue(TId id) => !_equalityComparerId.Equals(id, default);
|
||||
protected bool IdEquals(TId id1, TId id2) => _equalityComparerId.Equals(id1, id2);
|
||||
|
|
@ -373,12 +247,10 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
throw new NullReferenceException($"[{GetType().Name}] SignalRClient == null");
|
||||
}
|
||||
|
||||
ShowInfoPanel = IsMasterGrid;
|
||||
var crudTags = new SignalRCrudTags(GetAllMessageTag, GetItemMessageTag, AddMessageTag, UpdateMessageTag, RemoveMessageTag);
|
||||
|
||||
_dataSource = (TSignalRDataSource)Activator.CreateInstance(typeof(TSignalRDataSource), SignalRClient, crudTags, ContextIds)!;
|
||||
_dataSource.FilterText = FilterText;
|
||||
//_dataSource = new SignalRDataSource<TDataItem>(SignalRClient, crudTags, ContextIds) { FilterText = FilterText };
|
||||
|
||||
SetGridData(_dataSource.GetReferenceInnerList());
|
||||
|
||||
|
|
@ -514,11 +386,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
await OnGridCustomizeEditModel.InvokeAsync(e);
|
||||
|
||||
// Update InfoPanel to edit mode
|
||||
var rootGrid = GetRootGrid();
|
||||
if (rootGrid.ShowInfoPanel && rootGrid.InfoPanelInstance != null)
|
||||
{
|
||||
rootGrid.InfoPanelInstance.SetEditMode(editModel);
|
||||
}
|
||||
InfoPanelInstance?.SetEditMode(editModel);
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
|
@ -532,12 +400,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
{
|
||||
_focusedDataItem = e.DataItem;
|
||||
|
||||
var rootGrid = GetRootGrid();
|
||||
|
||||
if (!rootGrid.ShowInfoPanel) return;
|
||||
|
||||
// Get the root grid's InfoPanel (the visible one)
|
||||
var infoPanelInstance = rootGrid.InfoPanelInstance;
|
||||
var infoPanelInstance = InfoPanelInstance;
|
||||
|
||||
if (infoPanelInstance != null && e.DataItem != null)
|
||||
{
|
||||
|
|
@ -549,7 +412,6 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
}
|
||||
|
||||
// Frissítjük az InfoPanel-t az új sor adataival
|
||||
// The InfoPanel is now non-generic and works with any data type!
|
||||
infoPanelInstance.RefreshData(this, e.DataItem, e.VisibleIndex);
|
||||
}
|
||||
|
||||
|
|
@ -585,11 +447,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
|
||||
GridEditState = MgGridEditState.None;
|
||||
|
||||
var rootGrid = GetRootGrid();
|
||||
if (rootGrid.ShowInfoPanel && rootGrid.InfoPanelInstance != null)
|
||||
{
|
||||
rootGrid.InfoPanelInstance.ClearEditMode();
|
||||
}
|
||||
InfoPanelInstance?.ClearEditMode();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
|
@ -598,11 +456,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
{
|
||||
GridEditState = MgGridEditState.None;
|
||||
|
||||
var rootGrid = GetRootGrid();
|
||||
if (rootGrid.ShowInfoPanel && rootGrid.InfoPanelInstance != null)
|
||||
{
|
||||
rootGrid.InfoPanelInstance.ClearEditMode();
|
||||
}
|
||||
InfoPanelInstance?.ClearEditMode();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,63 +6,47 @@
|
|||
@* Header *@
|
||||
@if (HeaderTemplate != null)
|
||||
{
|
||||
@HeaderTemplate
|
||||
@HeaderTemplate(GetActiveDataItem())
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="dxbl-grid-header-panel px-3 py-2 border-bottom">
|
||||
<span class="fw-semibold">@((_currentGrid as IMgGridBase)?.Caption ?? "")</span>
|
||||
<span class="fw-semibold">@(_currentGrid?.Caption ?? "")</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@* Toolbar *@
|
||||
@if (_currentGrid != null)
|
||||
{
|
||||
<div class="mg-info-panel-toolbar border-bottom">
|
||||
<MgGridToolbarTemplate Grid="_currentGrid as IMgGridBase" OnlyGridEditTools="true" ShowOnlyIcon="true" />
|
||||
<MgGridToolbarTemplate Grid="_currentGrid" OnlyGridEditTools="true" ShowOnlyIcon="true" />
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@* Content *@
|
||||
<div class="mg-info-panel-content">
|
||||
@if (GetActiveDataItem() != null && _currentGrid != null)
|
||||
{
|
||||
var dataItem = GetActiveDataItem()!;
|
||||
var dataItemType = dataItem.GetType();
|
||||
@* Before Columns *@
|
||||
@if (BeforeColumnsTemplate != null)
|
||||
{
|
||||
@BeforeColumnsTemplate(GetActiveDataItem())
|
||||
}
|
||||
|
||||
@* Columns *@
|
||||
@if (ColumnsTemplate != null)
|
||||
{
|
||||
@ColumnsTemplate
|
||||
@ColumnsTemplate(GetActiveDataItem())
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="mg-info-panel-grid">
|
||||
@foreach (var column in GetVisibleColumns())
|
||||
{
|
||||
var displayText = GetDisplayTextFromGrid(column);
|
||||
var value = GetCellValue(column);
|
||||
var settingsType = GetEditSettingsType(column);
|
||||
var isReadOnly = !_isEditMode || column.ReadOnly;
|
||||
@RenderDefaultColumns()
|
||||
}
|
||||
|
||||
<div class="mg-info-panel-item">
|
||||
<div class="dxbl-form-layout-item">
|
||||
<label class="dxbl-fl-lc @GetCaptionCssClass(isReadOnly) d-block mb-1 small">
|
||||
@GetColumnCaption(column)
|
||||
</label>
|
||||
<div class="dxbl-fl-ec">
|
||||
@if (_isEditMode && !column.ReadOnly)
|
||||
@* After Columns *@
|
||||
@if (AfterColumnsTemplate != null)
|
||||
{
|
||||
@RenderEditableCell(column, dataItem, dataItemType, value, displayText, settingsType)
|
||||
}
|
||||
else
|
||||
{
|
||||
@RenderCellContent(column, value, displayText, settingsType)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@AfterColumnsTemplate(GetActiveDataItem())
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -76,25 +60,83 @@
|
|||
@* Footer *@
|
||||
@if (FooterTemplate != null)
|
||||
{
|
||||
@FooterTemplate
|
||||
@FooterTemplate(GetActiveDataItem())
|
||||
}
|
||||
</div>
|
||||
|
||||
@code {
|
||||
/// <summary>
|
||||
/// Gets the columns to display based on edit mode and ShowReadOnlyFieldsInEditMode setting
|
||||
/// Custom header template. Receives the current data item.
|
||||
/// </summary>
|
||||
private IEnumerable<DxGridDataColumn> GetVisibleColumns()
|
||||
[Parameter] public RenderFragment<object?>? HeaderTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content to render before the columns.
|
||||
/// </summary>
|
||||
[Parameter] public RenderFragment<object?>? BeforeColumnsTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Custom columns template. If not set, columns are auto-generated.
|
||||
/// </summary>
|
||||
[Parameter] public RenderFragment<object?>? ColumnsTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Content to render after the columns.
|
||||
/// </summary>
|
||||
[Parameter] public RenderFragment<object?>? AfterColumnsTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Custom footer template.
|
||||
/// </summary>
|
||||
[Parameter] public RenderFragment<object?>? FooterTemplate { get; set; }
|
||||
|
||||
private RenderFragment RenderDefaultColumns() => builder =>
|
||||
{
|
||||
if (!_isEditMode || ShowReadOnlyFieldsInEditMode)
|
||||
var dataItem = GetActiveDataItem()!;
|
||||
var dataItemType = dataItem.GetType();
|
||||
var seq = 0;
|
||||
|
||||
builder.OpenElement(seq++, "div");
|
||||
builder.AddAttribute(seq++, "class", "mg-info-panel-grid");
|
||||
|
||||
foreach (var column in GetVisibleColumns())
|
||||
{
|
||||
return _allDataColumns;
|
||||
var displayText = GetDisplayTextFromGrid(column);
|
||||
var value = GetCellValue(column);
|
||||
var settingsType = GetEditSettingsType(column);
|
||||
var isReadOnly = !_isEditMode || column.ReadOnly;
|
||||
|
||||
builder.OpenElement(seq++, "div");
|
||||
builder.AddAttribute(seq++, "class", "mg-info-panel-item");
|
||||
|
||||
builder.OpenElement(seq++, "div");
|
||||
builder.AddAttribute(seq++, "class", "dxbl-form-layout-item");
|
||||
|
||||
builder.OpenElement(seq++, "label");
|
||||
builder.AddAttribute(seq++, "class", $"dxbl-fl-lc {GetCaptionCssClass(isReadOnly)} d-block mb-1 small");
|
||||
builder.AddContent(seq++, GetColumnCaption(column));
|
||||
builder.CloseElement();
|
||||
|
||||
builder.OpenElement(seq++, "div");
|
||||
builder.AddAttribute(seq++, "class", "dxbl-fl-ec");
|
||||
|
||||
if (_isEditMode && !column.ReadOnly)
|
||||
{
|
||||
RenderEditableCell(column, dataItem, dataItemType, value, displayText, settingsType)(builder);
|
||||
}
|
||||
else
|
||||
{
|
||||
RenderCellContent(column, value, displayText, settingsType)(builder);
|
||||
}
|
||||
|
||||
// In edit mode with ShowReadOnlyFieldsInEditMode=false, hide readonly columns
|
||||
return _allDataColumns.Where(c => !c.ReadOnly);
|
||||
builder.CloseElement();
|
||||
builder.CloseElement();
|
||||
builder.CloseElement();
|
||||
}
|
||||
|
||||
builder.CloseElement();
|
||||
};
|
||||
|
||||
private static string GetColumnCaption(DxGridDataColumn column)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(column.Caption) ? column.Caption : column.FieldName;
|
||||
|
|
|
|||
|
|
@ -27,32 +27,23 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
[Parameter] public bool ShowReadOnlyFieldsInEditMode { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Custom header template. If not set, default header is used.
|
||||
/// Reference to the wrapper component - automatically registers this InfoPanel
|
||||
/// </summary>
|
||||
[Parameter] public RenderFragment? HeaderTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Custom columns template. If not set, columns are auto-generated.
|
||||
/// </summary>
|
||||
[Parameter] public RenderFragment? ColumnsTemplate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Custom footer template. If not set, no footer is rendered.
|
||||
/// </summary>
|
||||
[Parameter] public RenderFragment? FooterTemplate { get; set; }
|
||||
[CascadingParameter]
|
||||
public MgGridWithInfoPanel? GridWrapper { get; set; }
|
||||
|
||||
private ElementReference _panelElement;
|
||||
private bool _isJsInitialized;
|
||||
private const int DefaultTopOffset = 300; // Increased from 180 to account for header + tabs + toolbar
|
||||
|
||||
private IMgGridBase? _currentGrid;
|
||||
private object? _currentDataItem;
|
||||
private int _focusedRowVisibleIndex = -1;
|
||||
private List<DxGridDataColumn> _allDataColumns = [];
|
||||
protected IMgGridBase? _currentGrid;
|
||||
protected object? _currentDataItem;
|
||||
protected int _focusedRowVisibleIndex = -1;
|
||||
protected List<DxGridDataColumn> _allDataColumns = [];
|
||||
|
||||
// Edit mode state
|
||||
private bool _isEditMode;
|
||||
private object? _editModel;
|
||||
protected bool _isEditMode;
|
||||
protected object? _editModel;
|
||||
|
||||
// Cache for edit settings to avoid repeated lookups
|
||||
private readonly Dictionary<string, IEditSettings?> _editSettingsCache = [];
|
||||
|
|
@ -61,6 +52,9 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
{
|
||||
if (firstRender)
|
||||
{
|
||||
// Register this InfoPanel with the wrapper
|
||||
GridWrapper?.RegisterInfoPanel(this);
|
||||
|
||||
await InitializeStickyAsync();
|
||||
}
|
||||
}
|
||||
|
|
@ -95,11 +89,12 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
_focusedRowVisibleIndex = visibleIndex;
|
||||
_editSettingsCache.Clear();
|
||||
|
||||
// Clear edit mode when refreshing with new data
|
||||
_isEditMode = false;
|
||||
_editModel = null;
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - _currentDataItem is null: {_currentDataItem == null}, cast success: {dataItem != null && _currentDataItem != null}");
|
||||
|
||||
// Ha nem vagyunk edit módban, frissítjük az oszlopokat
|
||||
if (!_isEditMode)
|
||||
{
|
||||
if (_currentGrid != null && _currentDataItem != null)
|
||||
{
|
||||
_allDataColumns = GetAllDataColumns(_currentGrid);
|
||||
|
|
@ -110,7 +105,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
_allDataColumns = [];
|
||||
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - cleared columns (grid or dataItem is null)");
|
||||
}
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
System.Diagnostics.Debug.WriteLine($"[InfoPanel] RefreshData - StateHasChanged called");
|
||||
|
|
@ -121,6 +115,8 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
/// </summary>
|
||||
public void SetEditMode(object editModel)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[InfoPanel] SetEditMode called - editModel type: {editModel?.GetType().Name ?? "null"}, grid.GridEditState: {_currentGrid?.GridEditState}");
|
||||
|
||||
_editModel = editModel;
|
||||
_isEditMode = true;
|
||||
_currentDataItem = _editModel;
|
||||
|
|
@ -130,7 +126,8 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
_allDataColumns = GetAllDataColumns(_currentGrid);
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
InvokeAsync(StateHasChanged);
|
||||
System.Diagnostics.Debug.WriteLine($"[InfoPanel] SetEditMode - InvokeAsync(StateHasChanged) called, _isEditMode: {_isEditMode}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -138,10 +135,13 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
/// </summary>
|
||||
public void ClearEditMode()
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[InfoPanel] ClearEditMode called, grid.GridEditState: {_currentGrid?.GridEditState}");
|
||||
|
||||
_isEditMode = false;
|
||||
_editModel = null;
|
||||
_editSettingsCache.Clear();
|
||||
StateHasChanged();
|
||||
InvokeAsync(StateHasChanged);
|
||||
System.Diagnostics.Debug.WriteLine($"[InfoPanel] ClearEditMode - InvokeAsync(StateHasChanged) called");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -177,13 +177,13 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
/// <summary>
|
||||
/// Gets the data item to display/edit (EditModel in edit mode, otherwise CurrentDataItem)
|
||||
/// </summary>
|
||||
private object? GetActiveDataItem() => _isEditMode && _editModel != null ? _editModel : _currentDataItem;
|
||||
protected object? GetActiveDataItem() => _isEditMode && _editModel != null ? _editModel : _currentDataItem;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the display text for a field using the grid's internal formatting.
|
||||
/// For ComboBox columns, tries to get the text from the lookup data source.
|
||||
/// </summary>
|
||||
private string GetDisplayTextFromGrid(DxGridDataColumn column)
|
||||
protected string GetDisplayTextFromGrid(DxGridDataColumn column)
|
||||
{
|
||||
var dataItem = GetActiveDataItem();
|
||||
if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
|
||||
|
|
@ -310,7 +310,37 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
};
|
||||
}
|
||||
|
||||
private static List<DxGridDataColumn> GetAllDataColumns(IMgGridBase grid)
|
||||
/// <summary>
|
||||
/// Gets the columns to display based on edit mode and ShowReadOnlyFieldsInEditMode setting
|
||||
/// </summary>
|
||||
protected IEnumerable<DxGridDataColumn> GetVisibleColumns()
|
||||
{
|
||||
if (!_isEditMode || ShowReadOnlyFieldsInEditMode)
|
||||
{
|
||||
return _allDataColumns;
|
||||
}
|
||||
|
||||
// In edit mode with ShowReadOnlyFieldsInEditMode=false, hide readonly columns
|
||||
return _allDataColumns.Where(c => !c.ReadOnly);
|
||||
}
|
||||
|
||||
protected object? GetCellValue(DxGridDataColumn column)
|
||||
{
|
||||
var dataItem = GetActiveDataItem();
|
||||
if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return _currentGrid.GetDataItemValue(dataItem, column.FieldName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected static List<DxGridDataColumn> GetAllDataColumns(IMgGridBase grid)
|
||||
{
|
||||
var columns = new List<DxGridDataColumn>();
|
||||
|
||||
|
|
@ -342,22 +372,6 @@ public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPan
|
|||
return columns;
|
||||
}
|
||||
|
||||
private object? GetCellValue(DxGridDataColumn column)
|
||||
{
|
||||
var dataItem = GetActiveDataItem();
|
||||
if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
return _currentGrid.GetDataItemValue(dataItem, column.FieldName);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the EditSettings type for rendering logic
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
@using DevExpress.Blazor
|
||||
|
||||
<CascadingValue Value="this">
|
||||
@if (ShowInfoPanel)
|
||||
{
|
||||
<DxSplitter Width="100%" CssClass="mg-grid-with-info-panel" Orientation="Orientation.Horizontal">
|
||||
<Panes>
|
||||
<DxSplitterPane>
|
||||
@GridContent
|
||||
</DxSplitterPane>
|
||||
<DxSplitterPane Size="@InfoPanelSize" MinSize="0px" MaxSize="100%" AllowCollapse="true" CssClass="mg-info-panel-pane">
|
||||
@if (ChildContent != null)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
else
|
||||
{
|
||||
<MgGridInfoPanel />
|
||||
}
|
||||
</DxSplitterPane>
|
||||
</Panes>
|
||||
</DxSplitter>
|
||||
}
|
||||
else
|
||||
{
|
||||
@GridContent
|
||||
}
|
||||
</CascadingValue>
|
||||
|
||||
@code {
|
||||
private IInfoPanelBase? _infoPanelInstance;
|
||||
|
||||
/// <summary>
|
||||
/// The grid content to display in the left pane
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? GridContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// InfoPanel content (e.g., GridShippingDocumentInfoPanel) to display in the right pane.
|
||||
/// If not set, the default MgGridInfoPanel is used.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initial size of the InfoPanel pane. Default is "400px".
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string InfoPanelSize { get; set; } = "400px";
|
||||
|
||||
/// <summary>
|
||||
/// Whether to show the InfoPanel. Default is true.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public bool ShowInfoPanel { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the InfoPanel instance for grid-InfoPanel communication
|
||||
/// </summary>
|
||||
public IInfoPanelBase? InfoPanelInstance
|
||||
{
|
||||
get => _infoPanelInstance;
|
||||
set => _infoPanelInstance = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an InfoPanel instance (called by child InfoPanel components)
|
||||
/// </summary>
|
||||
public void RegisterInfoPanel(IInfoPanelBase infoPanel)
|
||||
{
|
||||
_infoPanelInstance = infoPanel;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AyCode.Blazor.Components.Components.Grids;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for custom InfoPanel templates.
|
||||
/// Inherit from this class to create a custom InfoPanel for a specific grid.
|
||||
/// </summary>
|
||||
public abstract class MgInfoPanelTemplateBase : MgGridInfoPanel
|
||||
{
|
||||
/// <summary>
|
||||
/// Override this to provide custom header content.
|
||||
/// Return null to use default header.
|
||||
/// </summary>
|
||||
protected virtual RenderFragment? GetHeaderTemplate() => null;
|
||||
|
||||
/// <summary>
|
||||
/// Override this to provide custom columns/content.
|
||||
/// Return null to use auto-generated columns.
|
||||
/// </summary>
|
||||
protected virtual RenderFragment? GetColumnsTemplate() => null;
|
||||
|
||||
/// <summary>
|
||||
/// Override this to provide custom footer content.
|
||||
/// Return null to hide footer.
|
||||
/// </summary>
|
||||
protected virtual RenderFragment? GetFooterTemplate() => null;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
SetTemplates();
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
SetTemplates();
|
||||
base.OnParametersSet();
|
||||
}
|
||||
|
||||
private void SetTemplates()
|
||||
{
|
||||
HeaderTemplate = GetHeaderTemplate();
|
||||
ColumnsTemplate = GetColumnsTemplate();
|
||||
FooterTemplate = GetFooterTemplate();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue