Compare commits
No commits in common. "15776ca53767dd929ff67f28679cb326c9fd0160" and "920bc299aa2ee511317ed0b201f4cbdc4e17ca57" have entirely different histories.
15776ca537
...
920bc299aa
|
|
@ -1,22 +0,0 @@
|
||||||
namespace AyCode.Blazor.Components.Components.Grids;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Represents the current edit state of the MgGrid
|
|
||||||
/// </summary>
|
|
||||||
public enum MgGridEditState
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// No edit operation in progress
|
|
||||||
/// </summary>
|
|
||||||
None,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adding a new row
|
|
||||||
/// </summary>
|
|
||||||
New,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Editing an existing row
|
|
||||||
/// </summary>
|
|
||||||
Edit
|
|
||||||
}
|
|
||||||
|
|
@ -8,10 +8,8 @@ using AyCode.Services.SignalRs;
|
||||||
using AyCode.Utils.Extensions;
|
using AyCode.Utils.Extensions;
|
||||||
using DevExpress.Blazor;
|
using DevExpress.Blazor;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.AspNetCore.Components.Rendering;
|
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using DevExpress.Blazor.Internal;
|
|
||||||
|
|
||||||
namespace AyCode.Blazor.Components.Components.Grids;
|
namespace AyCode.Blazor.Components.Components.Grids;
|
||||||
|
|
||||||
|
|
@ -22,47 +20,10 @@ public interface IMgGridBase : IGrid
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool IsSyncing { get; }
|
bool IsSyncing { get; }
|
||||||
|
|
||||||
string Caption { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Current edit state of the grid (None, New, Edit)
|
/// Event fired when synchronization state changes (true = syncing started, false = syncing ended)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
MgGridEditState GridEditState { get; }
|
event Action<bool>? OnSyncingStateChanged;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Parent grid in nested grid hierarchy (null if this is a root grid)
|
|
||||||
/// </summary>
|
|
||||||
IMgGridBase? ParentGrid { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the root grid in the hierarchy
|
|
||||||
/// </summary>
|
|
||||||
IMgGridBase GetRootGrid();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Navigates to the previous row in the grid
|
|
||||||
/// </summary>
|
|
||||||
void StepPrevRow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Navigates to the next row in the grid
|
|
||||||
/// </summary>
|
|
||||||
void StepNextRow();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// InfoPanel instance for displaying row details (from wrapper)
|
|
||||||
/// </summary>
|
|
||||||
IInfoPanelBase? InfoPanelInstance { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the grid/wrapper is currently in fullscreen mode
|
|
||||||
/// </summary>
|
|
||||||
bool IsFullscreen { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Toggles fullscreen mode for the grid (or wrapper if available)
|
|
||||||
/// </summary>
|
|
||||||
void ToggleFullscreen();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable
|
public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable
|
||||||
|
|
@ -86,118 +47,12 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
public bool IsSyncing => _dataSource?.IsSyncing ?? false;
|
public bool IsSyncing => _dataSource?.IsSyncing ?? false;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public MgGridEditState GridEditState { get; private set; } = MgGridEditState.None;
|
public event Action<bool>? OnSyncingStateChanged;
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
[CascadingParameter]
|
|
||||||
public IMgGridBase? ParentGrid { get; set; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IMgGridBase GetRootGrid()
|
|
||||||
{
|
|
||||||
var current = (IMgGridBase)this;
|
|
||||||
while (current.ParentGrid != null)
|
|
||||||
{
|
|
||||||
current = current.ParentGrid;
|
|
||||||
}
|
|
||||||
return current;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reference to the wrapper component for grid-InfoPanel communication
|
|
||||||
/// </summary>
|
|
||||||
[CascadingParameter]
|
|
||||||
public MgGridWithInfoPanel? GridWrapper { get; set; }
|
|
||||||
|
|
||||||
private object _focusedDataItem;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// InfoPanel instance for displaying row details (from wrapper or direct)
|
|
||||||
/// </summary>
|
|
||||||
public IInfoPanelBase? InfoPanelInstance
|
|
||||||
{
|
|
||||||
get => GridWrapper?.InfoPanelInstance;
|
|
||||||
set { /* Set through wrapper */ }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsFullscreen => GridWrapper?.IsFullscreen ?? _isStandaloneFullscreen;
|
|
||||||
|
|
||||||
private bool _isStandaloneFullscreen;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public void ToggleFullscreen()
|
|
||||||
{
|
|
||||||
if (GridWrapper != null)
|
|
||||||
{
|
|
||||||
// Ha van wrapper, azt váltjuk fullscreen-be
|
|
||||||
GridWrapper.ToggleFullscreen();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Ha nincs wrapper, saját fullscreen állapotot használunk
|
|
||||||
_isStandaloneFullscreen = !_isStandaloneFullscreen;
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MgGridBase() : base()
|
public MgGridBase() : base()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
|
||||||
{
|
|
||||||
var seq = 0;
|
|
||||||
|
|
||||||
// Wrap everything in a CascadingValue to provide this grid as ParentGrid to nested grids
|
|
||||||
builder.OpenComponent<CascadingValue<IMgGridBase>>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Value", (IMgGridBase)this);
|
|
||||||
builder.AddAttribute(seq++, "ChildContent", (RenderFragment)(contentBuilder =>
|
|
||||||
{
|
|
||||||
if (_isStandaloneFullscreen && GridWrapper == null)
|
|
||||||
{
|
|
||||||
// Standalone fullscreen mode - Bootstrap 5 fullscreen overlay
|
|
||||||
contentBuilder.OpenElement(0, "div");
|
|
||||||
contentBuilder.AddAttribute(1, "class", "mg-fullscreen-overlay");
|
|
||||||
|
|
||||||
// Header
|
|
||||||
contentBuilder.OpenElement(2, "div");
|
|
||||||
contentBuilder.AddAttribute(3, "class", "mg-fullscreen-header");
|
|
||||||
|
|
||||||
contentBuilder.OpenElement(4, "span");
|
|
||||||
contentBuilder.AddAttribute(5, "class", "mg-fullscreen-title");
|
|
||||||
contentBuilder.AddContent(6, Caption);
|
|
||||||
contentBuilder.CloseElement(); // span
|
|
||||||
|
|
||||||
contentBuilder.OpenElement(7, "button");
|
|
||||||
contentBuilder.AddAttribute(8, "type", "button");
|
|
||||||
contentBuilder.AddAttribute(9, "class", "btn-close btn-close-white");
|
|
||||||
contentBuilder.AddAttribute(10, "aria-label", "Close");
|
|
||||||
contentBuilder.AddAttribute(11, "onclick", EventCallback.Factory.Create<Microsoft.AspNetCore.Components.Web.MouseEventArgs>(this, () =>
|
|
||||||
{
|
|
||||||
_isStandaloneFullscreen = false;
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
}));
|
|
||||||
contentBuilder.CloseElement(); // button
|
|
||||||
|
|
||||||
contentBuilder.CloseElement(); // header div
|
|
||||||
|
|
||||||
// Body
|
|
||||||
contentBuilder.OpenElement(12, "div");
|
|
||||||
contentBuilder.AddAttribute(13, "class", "mg-fullscreen-body");
|
|
||||||
base.BuildRenderTree(contentBuilder);
|
|
||||||
contentBuilder.CloseElement(); // body div
|
|
||||||
|
|
||||||
contentBuilder.CloseElement(); // overlay div
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
base.BuildRenderTree(contentBuilder);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool HasIdValue(TDataItem dataItem) => HasIdValue(dataItem.Id);
|
protected bool HasIdValue(TDataItem dataItem) => HasIdValue(dataItem.Id);
|
||||||
protected bool HasIdValue(TId id) => !_equalityComparerId.Equals(id, default);
|
protected bool HasIdValue(TId id) => !_equalityComparerId.Equals(id, default);
|
||||||
protected bool IdEquals(TId id1, TId id2) => _equalityComparerId.Equals(id1, id2);
|
protected bool IdEquals(TId id1, TId id2) => _equalityComparerId.Equals(id1, id2);
|
||||||
|
|
@ -208,8 +63,6 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
[Parameter] public string? KeyFieldNameToParentId { get; set; }
|
[Parameter] public string? KeyFieldNameToParentId { get; set; }
|
||||||
[Parameter] public object[]? ContextIds { get; set; }
|
[Parameter] public object[]? ContextIds { get; set; }
|
||||||
|
|
||||||
[Parameter] public string Caption { get; set; } = typeof(TDataItem).Name;
|
|
||||||
|
|
||||||
public bool IsMasterGrid => ParentDataItem == null;
|
public bool IsMasterGrid => ParentDataItem == null;
|
||||||
protected PropertyInfo? KeyFieldPropertyInfoToParent;
|
protected PropertyInfo? KeyFieldPropertyInfoToParent;
|
||||||
|
|
||||||
|
|
@ -251,9 +104,6 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
protected new EventCallback<GridCustomizeEditModelEventArgs> CustomizeEditModel { get; set; }
|
protected new EventCallback<GridCustomizeEditModelEventArgs> CustomizeEditModel { get; set; }
|
||||||
[Parameter] public EventCallback<GridCustomizeEditModelEventArgs> OnGridCustomizeEditModel { get; set; }
|
[Parameter] public EventCallback<GridCustomizeEditModelEventArgs> OnGridCustomizeEditModel { get; set; }
|
||||||
|
|
||||||
protected new EventCallback<GridFocusedRowChangedEventArgs> FocusedRowChanged { get; set; }
|
|
||||||
[Parameter] public EventCallback<GridFocusedRowChangedEventArgs> OnGridFocusedRowChanged { get; set; }
|
|
||||||
|
|
||||||
[Parameter] public EventCallback<IList<TDataItem>> OnDataSourceChanged { get; set; }
|
[Parameter] public EventCallback<IList<TDataItem>> OnDataSourceChanged { get; set; }
|
||||||
[Parameter] public EventCallback<GridDataItemChangingEventArgs<TDataItem>> OnGridItemChanging { get; set; }
|
[Parameter] public EventCallback<GridDataItemChangingEventArgs<TDataItem>> OnGridItemChanging { get; set; }
|
||||||
|
|
||||||
|
|
@ -321,6 +171,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
|
|
||||||
_dataSource = (TSignalRDataSource)Activator.CreateInstance(typeof(TSignalRDataSource), SignalRClient, crudTags, ContextIds)!;
|
_dataSource = (TSignalRDataSource)Activator.CreateInstance(typeof(TSignalRDataSource), SignalRClient, crudTags, ContextIds)!;
|
||||||
_dataSource.FilterText = FilterText;
|
_dataSource.FilterText = FilterText;
|
||||||
|
//_dataSource = new SignalRDataSource<TDataItem>(SignalRClient, crudTags, ContextIds) { FilterText = FilterText };
|
||||||
|
|
||||||
SetGridData(_dataSource.GetReferenceInnerList());
|
SetGridData(_dataSource.GetReferenceInnerList());
|
||||||
|
|
||||||
|
|
@ -336,7 +187,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
if (_isDisposed) return;
|
if (_isDisposed) return;
|
||||||
|
|
||||||
// Forward the event to external subscribers
|
// Forward the event to external subscribers
|
||||||
//OnSyncingStateChanged?.Invoke(isSyncing);
|
OnSyncingStateChanged?.Invoke(isSyncing);
|
||||||
|
|
||||||
// Trigger UI update
|
// Trigger UI update
|
||||||
InvokeAsync(StateHasChanged);
|
InvokeAsync(StateHasChanged);
|
||||||
|
|
@ -366,8 +217,12 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
|
|
||||||
Logger.Debug($"{_gridLogName} OnDataSourceLoaded; Count: {_dataSource?.Count}");
|
Logger.Debug($"{_gridLogName} OnDataSourceLoaded; Count: {_dataSource?.Count}");
|
||||||
|
|
||||||
SetGridData(_dataSource!.GetReferenceInnerList());
|
//if(_dataSourceParam.GetType() == typeof()AcObservableCollection<TDataItem>)
|
||||||
|
|
||||||
|
SetGridData(_dataSource!.GetReferenceInnerList());
|
||||||
|
//else Reload();
|
||||||
|
|
||||||
|
//_dataSource.LoadItem(_dataSource.First().Id).Forget();
|
||||||
if (!_isDisposed)
|
if (!_isDisposed)
|
||||||
{
|
{
|
||||||
await OnDataSourceChanged.InvokeAsync(_dataSource);
|
await OnDataSourceChanged.InvokeAsync(_dataSource);
|
||||||
|
|
@ -450,44 +305,29 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
e.EditModel = editModel;
|
e.EditModel = editModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set edit state
|
|
||||||
GridEditState = e.IsNew ? MgGridEditState.New : MgGridEditState.Edit;
|
|
||||||
|
|
||||||
await OnGridCustomizeEditModel.InvokeAsync(e);
|
await OnGridCustomizeEditModel.InvokeAsync(e);
|
||||||
|
|
||||||
// Update InfoPanel to edit mode
|
|
||||||
InfoPanelInstance?.SetEditMode(editModel);
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnEditStart(GridEditStartEventArgs e)
|
private async Task OnEditStart(GridEditStartEventArgs e)
|
||||||
{
|
{
|
||||||
|
var dataItem = (e.DataItem as TDataItem)!;
|
||||||
|
|
||||||
await OnGridEditStart.InvokeAsync(e);
|
await OnGridEditStart.InvokeAsync(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual async Task OnFocusedRowChanged(GridFocusedRowChangedEventArgs e)
|
//void Grid_CustomizeEditModel(GridCustomizeEditModelEventArgs e)
|
||||||
{
|
//{
|
||||||
_focusedDataItem = e.DataItem;
|
// var model = e.EditModel as EditableWorkOrder;
|
||||||
|
// if (model == null)
|
||||||
|
// {
|
||||||
|
// model = new EditableWorkOrder();
|
||||||
|
|
||||||
var infoPanelInstance = InfoPanelInstance;
|
// model.WorkOrderNum = "123";
|
||||||
|
// model.Description = "hey";
|
||||||
if (infoPanelInstance != null && e.DataItem != null)
|
|
||||||
{
|
|
||||||
// Ha edit módban vagyunk, de a felhasználó egy másik sorra kattintott,
|
|
||||||
// akkor kilépünk az edit módból
|
|
||||||
if (GridEditState != MgGridEditState.None)
|
|
||||||
{
|
|
||||||
infoPanelInstance.ClearEditMode();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Frissítjük az InfoPanel-t az új sor adataival
|
|
||||||
infoPanelInstance.RefreshData(this, e.DataItem, e.VisibleIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
await OnGridFocusedRowChanged.InvokeAsync(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// e.EditModel = model;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
private async Task OnItemSaving(GridEditModelSavingEventArgs e)
|
private async Task OnItemSaving(GridEditModelSavingEventArgs e)
|
||||||
{
|
{
|
||||||
var dataItem = (e.EditModel as TDataItem)!;
|
var dataItem = (e.EditModel as TDataItem)!;
|
||||||
|
|
@ -495,6 +335,15 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
if (e.IsNew)
|
if (e.IsNew)
|
||||||
{
|
{
|
||||||
if (!HasIdValue(dataItem)) SetNewId(dataItem);
|
if (!HasIdValue(dataItem)) SetNewId(dataItem);
|
||||||
|
|
||||||
|
//if (ParentDataItem != null && !KeyFieldNameToParentId.IsNullOrWhiteSpace())
|
||||||
|
//{
|
||||||
|
// Type examType = typeof(TDataItem);
|
||||||
|
|
||||||
|
// // Change the static property value.
|
||||||
|
// PropertyInfo piShared = examType.GetProperty(KeyFieldNameToParentId);
|
||||||
|
// piShared.SetValue(dataItem, ParentDataItem.Id);
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
var logText = e.IsNew ? "add" : "update";
|
var logText = e.IsNew ? "add" : "update";
|
||||||
|
|
@ -515,20 +364,11 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
}
|
}
|
||||||
else await UpdateDataItemAsync(dataItem);
|
else await UpdateDataItemAsync(dataItem);
|
||||||
|
|
||||||
GridEditState = MgGridEditState.None;
|
//var equalityComparer = EqualityComparer<TId>.Default;
|
||||||
|
//var index = CollectionExtensions.FindIndex(_dataSource, x => equalityComparer.Equals(x.Id, dataItem.Id));
|
||||||
|
//_dataSource.UpdateCollectionByIndex(index, dataItem, false);
|
||||||
|
|
||||||
InfoPanelInstance?.ClearEditMode();
|
//_dataSource.UpdateCollectionById<TId>(dataItem.Id, false);
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task OnEditCanceling(GridEditCancelingEventArgs e)
|
|
||||||
{
|
|
||||||
GridEditState = MgGridEditState.None;
|
|
||||||
|
|
||||||
InfoPanelInstance?.ClearEditMode();
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task SaveChangesToServerAsync()
|
private Task SaveChangesToServerAsync()
|
||||||
|
|
@ -591,27 +431,6 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
e.Column.Visible = AcDomain.IsDeveloperVersion;
|
e.Column.Visible = AcDomain.IsDeveloperVersion;
|
||||||
e.Column.ShowInColumnChooser = AcDomain.IsDeveloperVersion;
|
e.Column.ShowInColumnChooser = AcDomain.IsDeveloperVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply edit mode background to the row being edited
|
|
||||||
if (e.ElementType == GridElementType.DataRow && GridEditState != MgGridEditState.None)
|
|
||||||
{
|
|
||||||
if (e.VisibleIndex == GetFocusedRowIndex())
|
|
||||||
{
|
|
||||||
e.Style = string.IsNullOrEmpty(e.Style)
|
|
||||||
? "background-color: #fffbeb;"
|
|
||||||
: e.Style + " background-color: #fffbeb;";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Apply edit mode background to cells in the edited row
|
|
||||||
else if (e.ElementType == GridElementType.DataCell && GridEditState != MgGridEditState.None)
|
|
||||||
{
|
|
||||||
if (e.VisibleIndex == GetFocusedRowIndex())
|
|
||||||
{
|
|
||||||
e.Style = string.IsNullOrEmpty(e.Style)
|
|
||||||
? "background-color: #fffbeb;"
|
|
||||||
: e.Style + " background-color: #fffbeb;";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override async Task SetParametersAsyncCore(ParameterView parameters)
|
protected override async Task SetParametersAsyncCore(ParameterView parameters)
|
||||||
|
|
@ -626,9 +445,8 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
base.DataItemDeleting = EventCallback.Factory.Create<GridDataItemDeletingEventArgs>(this, OnItemDeleting);
|
base.DataItemDeleting = EventCallback.Factory.Create<GridDataItemDeletingEventArgs>(this, OnItemDeleting);
|
||||||
base.EditModelSaving = EventCallback.Factory.Create<GridEditModelSavingEventArgs>(this, OnItemSaving);
|
base.EditModelSaving = EventCallback.Factory.Create<GridEditModelSavingEventArgs>(this, OnItemSaving);
|
||||||
base.CustomizeEditModel = EventCallback.Factory.Create<GridCustomizeEditModelEventArgs>(this, OnCustomizeEditModel);
|
base.CustomizeEditModel = EventCallback.Factory.Create<GridCustomizeEditModelEventArgs>(this, OnCustomizeEditModel);
|
||||||
base.FocusedRowChanged = EventCallback.Factory.Create<GridFocusedRowChangedEventArgs>(this, OnFocusedRowChanged);
|
//base.customizecel= EventCallback.Factory.Create<GridCustomizeEditModelEventArgs>(this, OnCustomizeEditModel);
|
||||||
base.EditStart = EventCallback.Factory.Create<GridEditStartEventArgs>(this, OnEditStart);
|
base.EditStart = EventCallback.Factory.Create<GridEditStartEventArgs>(this, OnEditStart);
|
||||||
base.EditCanceling = EventCallback.Factory.Create<GridEditCancelingEventArgs>(this, OnEditCanceling);
|
|
||||||
|
|
||||||
CustomizeElement += OnCustomizeElement;
|
CustomizeElement += OnCustomizeElement;
|
||||||
|
|
||||||
|
|
@ -642,6 +460,16 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
HighlightRowOnHover = true;
|
HighlightRowOnHover = true;
|
||||||
AutoCollapseDetailRow = true;
|
AutoCollapseDetailRow = true;
|
||||||
AutoExpandAllGroupRows = false;
|
AutoExpandAllGroupRows = false;
|
||||||
|
//KeyboardNavigationEnabled = true;
|
||||||
|
|
||||||
|
//var dataColumns = GetDataColumns();
|
||||||
|
|
||||||
|
//var idColumn = dataColumns.FirstOrDefault(x => x.FieldName == nameof(IId<TId>.Id));
|
||||||
|
//if (idColumn != null)
|
||||||
|
//{
|
||||||
|
// idColumn.ShowInColumnChooser = AcDomain.IsDeveloperVersion;
|
||||||
|
// idColumn.Visible = !AcDomain.IsDeveloperVersion;
|
||||||
|
//}
|
||||||
|
|
||||||
IsFirstInitializeParameterCore = true;
|
IsFirstInitializeParameterCore = true;
|
||||||
}
|
}
|
||||||
|
|
@ -689,31 +517,6 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
||||||
return _dataSource.LoadDataSourceAsync(false);
|
return _dataSource.LoadDataSourceAsync(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Navigates to the previous row in the grid
|
|
||||||
/// </summary>
|
|
||||||
public void StepPrevRow()
|
|
||||||
{
|
|
||||||
var currentIndex = GetFocusedRowIndex();
|
|
||||||
if (currentIndex > 0)
|
|
||||||
{
|
|
||||||
SetFocusedRowIndex(currentIndex - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Navigates to the next row in the grid
|
|
||||||
/// </summary>
|
|
||||||
public void StepNextRow()
|
|
||||||
{
|
|
||||||
var currentIndex = GetFocusedRowIndex();
|
|
||||||
var visibleRowCount = GetVisibleRowCount();
|
|
||||||
if (currentIndex >= 0 && currentIndex < visibleRowCount - 1)
|
|
||||||
{
|
|
||||||
SetFocusedRowIndex(currentIndex + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
if (_isDisposed) return;
|
if (_isDisposed) return;
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
using DevExpress.Blazor;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
|
|
||||||
namespace AyCode.Blazor.Components.Components.Grids;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Extended DxGridDataColumn with additional parameters for InfoPanel support.
|
|
||||||
/// </summary>
|
|
||||||
public class MgGridDataColumn : DxGridDataColumn
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this column should be visible in the InfoPanel. Default is true.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter]
|
|
||||||
public bool ShowInInfoPanel { get; set; } = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Custom display format for InfoPanel (overrides DisplayFormat if set).
|
|
||||||
/// </summary>
|
|
||||||
[Parameter]
|
|
||||||
public string? InfoPanelDisplayFormat { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Column order in InfoPanel (lower = earlier). Default is int.MaxValue.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter]
|
|
||||||
public int InfoPanelOrder { get; set; } = int.MaxValue;
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace AyCode.Blazor.Components.Components.Grids
|
|
||||||
{
|
|
||||||
internal class MgGridHelper
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,383 +0,0 @@
|
||||||
@using DevExpress.Blazor
|
|
||||||
@using Microsoft.AspNetCore.Components.Rendering
|
|
||||||
@using System.Reflection
|
|
||||||
|
|
||||||
<div @ref="_panelElement" class="mg-grid-info-panel @(_isEditMode ? "edit-mode" : "") @GetColumnCountClass()">
|
|
||||||
@* Header *@
|
|
||||||
@if (HeaderTemplate != null)
|
|
||||||
{
|
|
||||||
@HeaderTemplate(GetActiveDataItem())
|
|
||||||
}
|
|
||||||
else if (_currentGrid != null)
|
|
||||||
{
|
|
||||||
<div class="mg-info-panel-header">@_currentGrid.Caption</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@* Toolbar *@
|
|
||||||
@if (_currentGrid != null)
|
|
||||||
{
|
|
||||||
<div class="mg-info-panel-toolbar">
|
|
||||||
<MgGridToolbarTemplate Grid="_currentGrid" OnlyGridEditTools="true" ShowOnlyIcon="true" />
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
@* Content *@
|
|
||||||
<div class="mg-info-panel-content">
|
|
||||||
@if (GetActiveDataItem() != null && _currentGrid != null)
|
|
||||||
{
|
|
||||||
@if (BeforeColumnsTemplate != null)
|
|
||||||
{
|
|
||||||
@BeforeColumnsTemplate(GetActiveDataItem())
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (ColumnsTemplate != null)
|
|
||||||
{
|
|
||||||
@ColumnsTemplate(GetActiveDataItem())
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@RenderDefaultColumns()
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (AfterColumnsTemplate != null)
|
|
||||||
{
|
|
||||||
@AfterColumnsTemplate(GetActiveDataItem())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<div class="mg-info-panel-empty">
|
|
||||||
<p>Válasszon ki egy sort az adatok megtekintéséhez</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* Footer *@
|
|
||||||
@if (FooterTemplate != null)
|
|
||||||
{
|
|
||||||
@FooterTemplate(GetActiveDataItem())
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter] public RenderFragment<object?>? HeaderTemplate { get; set; }
|
|
||||||
[Parameter] public RenderFragment<object?>? BeforeColumnsTemplate { get; set; }
|
|
||||||
[Parameter] public RenderFragment<object?>? ColumnsTemplate { get; set; }
|
|
||||||
[Parameter] public RenderFragment<object?>? AfterColumnsTemplate { get; set; }
|
|
||||||
[Parameter] public RenderFragment<object?>? FooterTemplate { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Called when the data item changes (row selection changed)
|
|
||||||
/// </summary>
|
|
||||||
[Parameter] public EventCallback<object?> OnDataItemChanged { get; set; }
|
|
||||||
|
|
||||||
private string GetColumnCountClass() => FixedColumnCount switch
|
|
||||||
{
|
|
||||||
1 => "mg-columns-1",
|
|
||||||
2 => "mg-columns-2",
|
|
||||||
3 => "mg-columns-3",
|
|
||||||
4 => "mg-columns-4",
|
|
||||||
_ => ""
|
|
||||||
};
|
|
||||||
|
|
||||||
private RenderFragment RenderDefaultColumns() => builder =>
|
|
||||||
{
|
|
||||||
var dataItem = GetActiveDataItem();
|
|
||||||
if (dataItem == null) return;
|
|
||||||
|
|
||||||
var dataItemType = dataItem.GetType();
|
|
||||||
var seq = 0;
|
|
||||||
|
|
||||||
builder.OpenElement(seq++, "div");
|
|
||||||
builder.AddAttribute(seq++, "class", "mg-info-panel-grid");
|
|
||||||
|
|
||||||
foreach (var column in GetVisibleColumns())
|
|
||||||
{
|
|
||||||
var displayText = GetDisplayTextFromGrid(column);
|
|
||||||
var value = GetCellValue(column);
|
|
||||||
var settingsType = GetEditSettingsType(column);
|
|
||||||
var isEditable = _isEditMode && !column.ReadOnly;
|
|
||||||
|
|
||||||
builder.OpenElement(seq++, "div");
|
|
||||||
builder.AddAttribute(seq++, "class", "mg-info-panel-item");
|
|
||||||
|
|
||||||
builder.OpenElement(seq++, "label");
|
|
||||||
builder.AddAttribute(seq++, "class", isEditable ? "mg-info-panel-label editable" : "mg-info-panel-label");
|
|
||||||
builder.AddContent(seq++, GetColumnCaption(column));
|
|
||||||
builder.CloseElement();
|
|
||||||
|
|
||||||
builder.OpenElement(seq++, "div");
|
|
||||||
if (isEditable)
|
|
||||||
{
|
|
||||||
RenderEditableCell(column, dataItem, dataItemType, value, displayText, settingsType)(builder);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
RenderCellContent(value, displayText)(builder);
|
|
||||||
}
|
|
||||||
builder.CloseElement();
|
|
||||||
|
|
||||||
builder.CloseElement();
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.CloseElement();
|
|
||||||
};
|
|
||||||
|
|
||||||
private static string GetColumnCaption(DxGridDataColumn column) =>
|
|
||||||
!string.IsNullOrWhiteSpace(column.Caption) ? column.Caption : column.FieldName;
|
|
||||||
|
|
||||||
private RenderFragment RenderEditableCell(DxGridDataColumn column, object dataItem, Type dataItemType, object? value, string displayText, EditSettingsType settingsType)
|
|
||||||
{
|
|
||||||
return builder =>
|
|
||||||
{
|
|
||||||
var seq = 0;
|
|
||||||
var propertyInfo = dataItemType.GetProperty(column.FieldName);
|
|
||||||
|
|
||||||
if (propertyInfo == null)
|
|
||||||
{
|
|
||||||
RenderCellContent(value, displayText)(builder);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var underlyingType = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
|
|
||||||
|
|
||||||
if (settingsType == EditSettingsType.ComboBox && GetEditSettings(column.FieldName) is DxComboBoxSettings comboSettings)
|
|
||||||
{
|
|
||||||
RenderComboBoxEditor(builder, ref seq, dataItem, propertyInfo, comboSettings);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (underlyingType == typeof(bool)) RenderCheckBoxEditor(builder, ref seq, dataItem, propertyInfo);
|
|
||||||
else if (underlyingType == typeof(DateTime)) RenderDateTimeEditor(builder, ref seq, dataItem, propertyInfo, column.DisplayFormat);
|
|
||||||
else if (underlyingType == typeof(DateOnly)) RenderDateOnlyEditor(builder, ref seq, dataItem, propertyInfo, column.DisplayFormat);
|
|
||||||
else if (underlyingType == typeof(int)) RenderSpinIntEditor(builder, ref seq, dataItem, propertyInfo);
|
|
||||||
else if (underlyingType == typeof(decimal)) RenderSpinDecimalEditor(builder, ref seq, dataItem, propertyInfo);
|
|
||||||
else if (underlyingType == typeof(double)) RenderSpinDoubleEditor(builder, ref seq, dataItem, propertyInfo);
|
|
||||||
else if (settingsType == EditSettingsType.Memo) RenderMemoEditor(builder, ref seq, dataItem, propertyInfo);
|
|
||||||
else RenderTextBoxEditor(builder, ref seq, dataItem, propertyInfo);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderCheckBoxEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxCheckBox<bool>>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Checked", (bool)(propertyInfo.GetValue(dataItem) ?? false));
|
|
||||||
builder.AddAttribute(seq++, "CheckedChanged", EventCallback.Factory.Create<bool>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderDateTimeEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, string? displayFormat)
|
|
||||||
{
|
|
||||||
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
|
||||||
var value = propertyInfo.GetValue(dataItem);
|
|
||||||
|
|
||||||
if (isNullable)
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxDateEdit<DateTime?>>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Date", (DateTime?)value);
|
|
||||||
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateTime?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxDateEdit<DateTime>>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Date", (DateTime)(value ?? DateTime.MinValue));
|
|
||||||
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateTime>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
}
|
|
||||||
builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd HH:mm");
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderDateOnlyEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, string? displayFormat)
|
|
||||||
{
|
|
||||||
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
|
||||||
var value = propertyInfo.GetValue(dataItem);
|
|
||||||
|
|
||||||
if (isNullable)
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxDateEdit<DateOnly?>>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Date", (DateOnly?)value);
|
|
||||||
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateOnly?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxDateEdit<DateOnly>>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Date", (DateOnly)(value ?? DateOnly.MinValue));
|
|
||||||
builder.AddAttribute(seq++, "DateChanged", EventCallback.Factory.Create<DateOnly>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
}
|
|
||||||
builder.AddAttribute(seq++, "DisplayFormat", displayFormat ?? "yyyy-MM-dd");
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderSpinIntEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
|
|
||||||
{
|
|
||||||
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
|
||||||
var value = propertyInfo.GetValue(dataItem);
|
|
||||||
|
|
||||||
if (isNullable)
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxSpinEdit<int?>>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Value", (int?)value);
|
|
||||||
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxSpinEdit<int>>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Value", (int)(value ?? 0));
|
|
||||||
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<int>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
}
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderSpinDecimalEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
|
|
||||||
{
|
|
||||||
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
|
||||||
var value = propertyInfo.GetValue(dataItem);
|
|
||||||
|
|
||||||
if (isNullable)
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxSpinEdit<decimal?>>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Value", (decimal?)value);
|
|
||||||
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<decimal?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxSpinEdit<decimal>>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Value", (decimal)(value ?? 0m));
|
|
||||||
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<decimal>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
}
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderSpinDoubleEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
|
|
||||||
{
|
|
||||||
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
|
||||||
var value = propertyInfo.GetValue(dataItem);
|
|
||||||
|
|
||||||
if (isNullable)
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxSpinEdit<double?>>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Value", (double?)value);
|
|
||||||
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<double?>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxSpinEdit<double>>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Value", (double)(value ?? 0d));
|
|
||||||
builder.AddAttribute(seq++, "ValueChanged", EventCallback.Factory.Create<double>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
}
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderTextBoxEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxTextBox>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Text", propertyInfo.GetValue(dataItem)?.ToString() ?? "");
|
|
||||||
builder.AddAttribute(seq++, "TextChanged", EventCallback.Factory.Create<string>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderMemoEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo)
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxMemo>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Text", propertyInfo.GetValue(dataItem)?.ToString() ?? "");
|
|
||||||
builder.AddAttribute(seq++, "TextChanged", EventCallback.Factory.Create<string>(this, v => { propertyInfo.SetValue(dataItem, v); InvokeAsync(StateHasChanged); }));
|
|
||||||
builder.AddAttribute(seq++, "Rows", 3);
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderComboBoxEditor(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings)
|
|
||||||
{
|
|
||||||
var value = propertyInfo.GetValue(dataItem);
|
|
||||||
var underlyingType = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
|
|
||||||
var itemType = settings.Data?.GetType().GetGenericArguments().FirstOrDefault() ?? typeof(object);
|
|
||||||
|
|
||||||
if (underlyingType == typeof(int))
|
|
||||||
RenderComboBoxInt(builder, ref seq, dataItem, propertyInfo, settings, itemType, value);
|
|
||||||
else if (underlyingType == typeof(long))
|
|
||||||
RenderComboBoxLong(builder, ref seq, dataItem, propertyInfo, settings, itemType, value);
|
|
||||||
else if (underlyingType == typeof(Guid))
|
|
||||||
RenderComboBoxGuid(builder, ref seq, dataItem, propertyInfo, settings, itemType, value);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.OpenComponent<DxTextBox>(seq++);
|
|
||||||
builder.AddAttribute(seq++, "Text", ResolveComboBoxDisplayText(settings, value ?? new object()) ?? value?.ToString() ?? "");
|
|
||||||
builder.AddAttribute(seq++, "ReadOnly", true);
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderComboBoxInt(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue)
|
|
||||||
{
|
|
||||||
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
|
||||||
var comboType = isNullable ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int?)) : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(int));
|
|
||||||
|
|
||||||
builder.OpenComponent(seq++, comboType);
|
|
||||||
builder.AddAttribute(seq++, "Data", settings.Data);
|
|
||||||
builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName);
|
|
||||||
builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName);
|
|
||||||
builder.AddAttribute(seq++, "Value", isNullable ? currentValue as int? : (currentValue is int intVal ? intVal : 0));
|
|
||||||
builder.AddAttribute(seq++, "ValueChanged", isNullable
|
|
||||||
? EventCallback.Factory.Create<int?>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); })
|
|
||||||
: EventCallback.Factory.Create<int>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); }));
|
|
||||||
builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderComboBoxLong(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue)
|
|
||||||
{
|
|
||||||
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
|
||||||
var comboType = isNullable ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long?)) : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(long));
|
|
||||||
|
|
||||||
builder.OpenComponent(seq++, comboType);
|
|
||||||
builder.AddAttribute(seq++, "Data", settings.Data);
|
|
||||||
builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName);
|
|
||||||
builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName);
|
|
||||||
builder.AddAttribute(seq++, "Value", isNullable ? currentValue as long? : (currentValue is long longVal ? longVal : 0L));
|
|
||||||
builder.AddAttribute(seq++, "ValueChanged", isNullable
|
|
||||||
? EventCallback.Factory.Create<long?>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); })
|
|
||||||
: EventCallback.Factory.Create<long>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); }));
|
|
||||||
builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RenderComboBoxGuid(RenderTreeBuilder builder, ref int seq, object dataItem, PropertyInfo propertyInfo, DxComboBoxSettings settings, Type itemType, object? currentValue)
|
|
||||||
{
|
|
||||||
var isNullable = Nullable.GetUnderlyingType(propertyInfo.PropertyType) != null;
|
|
||||||
var comboType = isNullable ? typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid?)) : typeof(DxComboBox<,>).MakeGenericType(itemType, typeof(Guid));
|
|
||||||
|
|
||||||
builder.OpenComponent(seq++, comboType);
|
|
||||||
builder.AddAttribute(seq++, "Data", settings.Data);
|
|
||||||
builder.AddAttribute(seq++, "ValueFieldName", settings.ValueFieldName);
|
|
||||||
builder.AddAttribute(seq++, "TextFieldName", settings.TextFieldName);
|
|
||||||
builder.AddAttribute(seq++, "Value", isNullable ? currentValue as Guid? : (currentValue is Guid guidVal ? guidVal : Guid.Empty));
|
|
||||||
builder.AddAttribute(seq++, "ValueChanged", isNullable
|
|
||||||
? EventCallback.Factory.Create<Guid?>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); })
|
|
||||||
: EventCallback.Factory.Create<Guid>(this, v => { propertyInfo.SetValue(dataItem, v); StateHasChanged(); }));
|
|
||||||
builder.AddAttribute(seq++, "ClearButtonDisplayMode", DataEditorClearButtonDisplayMode.Auto);
|
|
||||||
builder.CloseComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private RenderFragment RenderCellContent(object? value, string displayText)
|
|
||||||
{
|
|
||||||
return builder =>
|
|
||||||
{
|
|
||||||
var seq = 0;
|
|
||||||
builder.OpenElement(seq++, "span");
|
|
||||||
builder.AddAttribute(seq++, "class", "mg-info-panel-value");
|
|
||||||
builder.AddAttribute(seq++, "title", displayText);
|
|
||||||
|
|
||||||
if (value is bool boolValue)
|
|
||||||
{
|
|
||||||
builder.OpenElement(seq++, "span");
|
|
||||||
builder.AddAttribute(seq++, "class", boolValue ? "dx-icon dx-icon-check" : "dx-icon dx-icon-close");
|
|
||||||
builder.CloseElement();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
builder.AddContent(seq++, displayText);
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.CloseElement();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,410 +0,0 @@
|
||||||
using DevExpress.Blazor;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
using Microsoft.JSInterop;
|
|
||||||
|
|
||||||
namespace AyCode.Blazor.Components.Components.Grids;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Interface for InfoPanel to support grid access
|
|
||||||
/// </summary>
|
|
||||||
public interface IInfoPanelBase
|
|
||||||
{
|
|
||||||
void ClearEditMode();
|
|
||||||
void SetEditMode(object editModel);
|
|
||||||
void RefreshData(IMgGridBase grid, object? dataItem, int visibleIndex = -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// InfoPanel component for displaying and editing grid row details
|
|
||||||
/// </summary>
|
|
||||||
public partial class MgGridInfoPanel : ComponentBase, IAsyncDisposable, IInfoPanelBase
|
|
||||||
{
|
|
||||||
[Inject] private IJSRuntime JSRuntime { get; set; } = null!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether to show readonly fields when in edit mode. Default is false.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter] public bool ShowReadOnlyFieldsInEditMode { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Minimum width for 2 columns layout. Default is 500px.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter] public int TwoColumnBreakpoint { get; set; } = 400;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Minimum width for 3 columns layout. Default is 800px.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter] public int ThreeColumnBreakpoint { get; set; } = 800;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Minimum width for 4 columns layout. Default is 1200px.
|
|
||||||
/// </summary>
|
|
||||||
[Parameter] public int FourColumnBreakpoint { get; set; } = 1300;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fixed column count. If set (1-4), overrides responsive breakpoints. Default is null (responsive).
|
|
||||||
/// </summary>
|
|
||||||
[Parameter] public int? FixedColumnCount { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Reference to the wrapper component - automatically registers this InfoPanel
|
|
||||||
/// </summary>
|
|
||||||
[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
|
|
||||||
|
|
||||||
protected IMgGridBase? _currentGrid;
|
|
||||||
protected object? _currentDataItem;
|
|
||||||
protected int _focusedRowVisibleIndex = -1;
|
|
||||||
protected List<DxGridDataColumn> _allDataColumns = [];
|
|
||||||
|
|
||||||
// Edit mode state
|
|
||||||
protected bool _isEditMode;
|
|
||||||
protected object? _editModel;
|
|
||||||
|
|
||||||
// Cache for edit settings to avoid repeated lookups
|
|
||||||
private readonly Dictionary<string, IEditSettings?> _editSettingsCache = [];
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
// Register this InfoPanel with the wrapper
|
|
||||||
GridWrapper?.RegisterInfoPanel(this);
|
|
||||||
|
|
||||||
await InitializeStickyAsync();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task InitializeStickyAsync()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await JSRuntime.InvokeVoidAsync(
|
|
||||||
"MgGridInfoPanel.initSticky",
|
|
||||||
_panelElement,
|
|
||||||
DefaultTopOffset);
|
|
||||||
_isJsInitialized = true;
|
|
||||||
}
|
|
||||||
catch (JSException)
|
|
||||||
{
|
|
||||||
// JS might not be loaded yet, ignore
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Refreshes the InfoPanel with data from the specified grid row (view mode)
|
|
||||||
/// </summary>
|
|
||||||
public void RefreshData(IMgGridBase grid, object? dataItem, int visibleIndex = -1)
|
|
||||||
{
|
|
||||||
ArgumentNullException.ThrowIfNull(grid);
|
|
||||||
|
|
||||||
_currentGrid = grid;
|
|
||||||
_currentDataItem = dataItem;
|
|
||||||
_focusedRowVisibleIndex = visibleIndex;
|
|
||||||
_editSettingsCache.Clear();
|
|
||||||
|
|
||||||
// Clear edit mode when refreshing with new data
|
|
||||||
_isEditMode = false;
|
|
||||||
_editModel = null;
|
|
||||||
|
|
||||||
if (_currentGrid != null && _currentDataItem != null)
|
|
||||||
{
|
|
||||||
_allDataColumns = GetAllDataColumns(_currentGrid);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_allDataColumns = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
StateHasChanged();
|
|
||||||
|
|
||||||
// Notify subscribers that data item changed
|
|
||||||
_ = OnDataItemChanged.InvokeAsync(dataItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets the InfoPanel to edit mode with the given edit model
|
|
||||||
/// </summary>
|
|
||||||
public void SetEditMode(object editModel)
|
|
||||||
{
|
|
||||||
_editModel = editModel;
|
|
||||||
_isEditMode = true;
|
|
||||||
_currentDataItem = _editModel;
|
|
||||||
|
|
||||||
if (_currentGrid != null)
|
|
||||||
{
|
|
||||||
_allDataColumns = GetAllDataColumns(_currentGrid);
|
|
||||||
}
|
|
||||||
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears edit mode and returns to view mode
|
|
||||||
/// </summary>
|
|
||||||
public void ClearEditMode()
|
|
||||||
{
|
|
||||||
_isEditMode = false;
|
|
||||||
_editModel = null;
|
|
||||||
_editSettingsCache.Clear();
|
|
||||||
InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clears the InfoPanel completely
|
|
||||||
/// </summary>
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
_currentGrid = null;
|
|
||||||
_currentDataItem = null;
|
|
||||||
_focusedRowVisibleIndex = -1;
|
|
||||||
_allDataColumns = [];
|
|
||||||
_editSettingsCache.Clear();
|
|
||||||
_isEditMode = false;
|
|
||||||
_editModel = null;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async ValueTask DisposeAsync()
|
|
||||||
{
|
|
||||||
if (_isJsInitialized)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await JSRuntime.InvokeVoidAsync("MgGridInfoPanel.disposeSticky", _panelElement);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Ignore disposal errors
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the data item to display/edit (EditModel in edit mode, otherwise CurrentDataItem)
|
|
||||||
/// </summary>
|
|
||||||
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>
|
|
||||||
protected string GetDisplayTextFromGrid(DxGridDataColumn column)
|
|
||||||
{
|
|
||||||
var dataItem = GetActiveDataItem();
|
|
||||||
if (_currentGrid == null || dataItem == null || string.IsNullOrWhiteSpace(column.FieldName))
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var value = _currentGrid.GetDataItemValue(dataItem, column.FieldName);
|
|
||||||
|
|
||||||
if (value == null)
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
// Try to resolve display text from EditSettings
|
|
||||||
var editSettings = GetEditSettings(column.FieldName);
|
|
||||||
if (editSettings is DxComboBoxSettings comboSettings)
|
|
||||||
{
|
|
||||||
var displayText = ResolveComboBoxDisplayText(comboSettings, value);
|
|
||||||
if (!string.IsNullOrEmpty(displayText))
|
|
||||||
return displayText;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply column's DisplayFormat if available
|
|
||||||
if (!string.IsNullOrEmpty(column.DisplayFormat))
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return string.Format(column.DisplayFormat, value);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// If format fails, fall through to default formatting
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return FormatValue(value);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return string.Empty;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets edit settings for the specified field (with caching)
|
|
||||||
/// </summary>
|
|
||||||
private IEditSettings? GetEditSettings(string fieldName)
|
|
||||||
{
|
|
||||||
if (_currentGrid == null || string.IsNullOrEmpty(fieldName))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (_editSettingsCache.TryGetValue(fieldName, out var cached))
|
|
||||||
return cached;
|
|
||||||
|
|
||||||
IEditSettings? settings = null;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Try each EditSettings type
|
|
||||||
settings = _currentGrid.GetColumnEditSettings<DxComboBoxSettings>(fieldName)
|
|
||||||
?? _currentGrid.GetColumnEditSettings<DxDateEditSettings>(fieldName)
|
|
||||||
?? _currentGrid.GetColumnEditSettings<DxTimeEditSettings>(fieldName)
|
|
||||||
?? _currentGrid.GetColumnEditSettings<DxSpinEditSettings>(fieldName)
|
|
||||||
?? _currentGrid.GetColumnEditSettings<DxCheckBoxSettings>(fieldName)
|
|
||||||
?? _currentGrid.GetColumnEditSettings<DxMemoSettings>(fieldName)
|
|
||||||
?? (IEditSettings?)_currentGrid.GetColumnEditSettings<DxTextBoxSettings>(fieldName);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// Ignore errors
|
|
||||||
}
|
|
||||||
|
|
||||||
_editSettingsCache[fieldName] = settings;
|
|
||||||
return settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string? ResolveComboBoxDisplayText(DxComboBoxSettings settings, object value)
|
|
||||||
{
|
|
||||||
if (settings.Data == null || string.IsNullOrEmpty(settings.ValueFieldName) || string.IsNullOrEmpty(settings.TextFieldName))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
foreach (var item in (System.Collections.IEnumerable)settings.Data)
|
|
||||||
{
|
|
||||||
if (item == null) continue;
|
|
||||||
|
|
||||||
var itemType = item.GetType();
|
|
||||||
var valueProperty = itemType.GetProperty(settings.ValueFieldName);
|
|
||||||
var textProperty = itemType.GetProperty(settings.TextFieldName);
|
|
||||||
|
|
||||||
if (valueProperty == null || textProperty == null) continue;
|
|
||||||
|
|
||||||
var itemValue = valueProperty.GetValue(item);
|
|
||||||
if (itemValue != null && itemValue.Equals(value))
|
|
||||||
{
|
|
||||||
return textProperty.GetValue(item)?.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
// If lookup fails, return null and fall back to default formatting
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string FormatValue(object? value)
|
|
||||||
{
|
|
||||||
if (value == null)
|
|
||||||
return string.Empty;
|
|
||||||
|
|
||||||
return value switch
|
|
||||||
{
|
|
||||||
DateTime dateTime => dateTime.ToString("yyyy-MM-dd HH:mm:ss"),
|
|
||||||
DateOnly dateOnly => dateOnly.ToString("yyyy-MM-dd"),
|
|
||||||
TimeOnly timeOnly => timeOnly.ToString("HH:mm:ss"),
|
|
||||||
TimeSpan timeSpan => timeSpan.ToString(@"hh\:mm\:ss"),
|
|
||||||
bool boolValue => boolValue ? "Igen" : "Nem",
|
|
||||||
decimal decValue => decValue.ToString("N2"),
|
|
||||||
double dblValue => dblValue.ToString("N2"),
|
|
||||||
float fltValue => fltValue.ToString("N2"),
|
|
||||||
int or long or short or byte => $"{value:N0}",
|
|
||||||
_ => value.ToString() ?? string.Empty
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <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>();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var allColumns = grid.GetDataColumns();
|
|
||||||
|
|
||||||
if (allColumns != null)
|
|
||||||
{
|
|
||||||
foreach (var column in allColumns)
|
|
||||||
{
|
|
||||||
if (column is DxGridDataColumn dataColumn &&
|
|
||||||
!string.IsNullOrWhiteSpace(dataColumn.FieldName))
|
|
||||||
{
|
|
||||||
columns.Add(dataColumn);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
// Ignore errors
|
|
||||||
}
|
|
||||||
|
|
||||||
return columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the EditSettings type for rendering logic
|
|
||||||
/// </summary>
|
|
||||||
private EditSettingsType GetEditSettingsType(DxGridDataColumn column)
|
|
||||||
{
|
|
||||||
var settings = GetEditSettings(column.FieldName);
|
|
||||||
|
|
||||||
return settings switch
|
|
||||||
{
|
|
||||||
DxComboBoxSettings => EditSettingsType.ComboBox,
|
|
||||||
DxDateEditSettings => EditSettingsType.DateEdit,
|
|
||||||
DxTimeEditSettings => EditSettingsType.TimeEdit,
|
|
||||||
DxSpinEditSettings => EditSettingsType.SpinEdit,
|
|
||||||
DxCheckBoxSettings => EditSettingsType.CheckBox,
|
|
||||||
DxMemoSettings => EditSettingsType.Memo,
|
|
||||||
_ => EditSettingsType.None
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum EditSettingsType
|
|
||||||
{
|
|
||||||
None,
|
|
||||||
ComboBox,
|
|
||||||
DateEdit,
|
|
||||||
TimeEdit,
|
|
||||||
SpinEdit,
|
|
||||||
CheckBox,
|
|
||||||
Memo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,148 +0,0 @@
|
||||||
/* Shared edit mode background color configuration - change only here */
|
|
||||||
/* Grid row background: #fffbeb (see MgGridBase.cs OnCustomizeElement) */
|
|
||||||
/* InfoPanel background: #fffbeb (see below .edit-mode) */
|
|
||||||
/* Border color: #f59e0b */
|
|
||||||
|
|
||||||
/* Main panel - contained within splitter pane */
|
|
||||||
.mg-grid-info-panel {
|
|
||||||
container-type: inline-size;
|
|
||||||
container-name: infopanel;
|
|
||||||
background-color: var(--dxbl-bg-secondary, #f8f9fa);
|
|
||||||
transition: background-color 0.3s ease, border-color 0.3s ease;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
/* Prevent panel from pushing out the splitter */
|
|
||||||
min-height: 0;
|
|
||||||
max-height: 100%;
|
|
||||||
/* Default breakpoints - can be overridden via style attribute */
|
|
||||||
--mg-bp-2col: 500px;
|
|
||||||
--mg-bp-3col: 800px;
|
|
||||||
--mg-bp-4col: 1200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-grid-info-panel.edit-mode {
|
|
||||||
background-color: #fffbeb !important;
|
|
||||||
border-left: 3px solid #f59e0b !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-grid-info-panel.view-mode {
|
|
||||||
background-color: #f8f9fa !important;
|
|
||||||
border-left: 3px solid transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Content area - scrollable, takes remaining space */
|
|
||||||
.mg-info-panel-content {
|
|
||||||
flex: 1 1 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
padding: 1rem;
|
|
||||||
min-height: 0; /* Critical for flex child to allow shrinking */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Grid layout with responsive column wrapping based on panel width */
|
|
||||||
.mg-info-panel-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fixed column count classes - override responsive behavior */
|
|
||||||
.mg-columns-1 .mg-info-panel-grid {
|
|
||||||
grid-template-columns: 1fr !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-columns-2 .mg-info-panel-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-columns-3 .mg-info-panel-grid {
|
|
||||||
grid-template-columns: repeat(3, 1fr) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-columns-4 .mg-info-panel-grid {
|
|
||||||
grid-template-columns: repeat(4, 1fr) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive layouts using container queries (when no fixed column count) */
|
|
||||||
/* 1 column for narrow panels (< 2col breakpoint) - default above */
|
|
||||||
|
|
||||||
/* 2 columns for medium width */
|
|
||||||
@container infopanel (min-width: 500px) {
|
|
||||||
.mg-grid-info-panel:not(.mg-columns-1):not(.mg-columns-2):not(.mg-columns-3):not(.mg-columns-4) .mg-info-panel-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 3 columns for wider panels */
|
|
||||||
@container infopanel (min-width: 800px) {
|
|
||||||
.mg-grid-info-panel:not(.mg-columns-1):not(.mg-columns-2):not(.mg-columns-3):not(.mg-columns-4) .mg-info-panel-grid {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 4 columns for very wide panels */
|
|
||||||
@container infopanel (min-width: 1200px) {
|
|
||||||
.mg-grid-info-panel:not(.mg-columns-1):not(.mg-columns-2):not(.mg-columns-3):not(.mg-columns-4) .mg-info-panel-grid {
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-item {
|
|
||||||
min-width: 0; /* Prevent grid blowout */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fallback styles */
|
|
||||||
.info-panel-form {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-panel-form .fw-semibold {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--dxbl-text-secondary, #495057);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-panel-form .fw-semibold.text-primary {
|
|
||||||
color: var(--dxbl-primary, #0d6efd);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Text overflow handling - show ellipsis and full text in tooltip */
|
|
||||||
.info-panel-text-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-panel-text-wrapper input[readonly] {
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* View mode value styling - matches DevExpress theme */
|
|
||||||
.mg-info-panel-value {
|
|
||||||
padding: 0.375rem 0.75rem;
|
|
||||||
min-height: 2rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
background-color: var(--dxbl-bg, #fff);
|
|
||||||
border: 1px solid var(--dxbl-border-color, #dee2e6);
|
|
||||||
border-radius: var(--dxbl-border-radius, 0.25rem);
|
|
||||||
font-size: var(--dxbl-font-size, 0.875rem);
|
|
||||||
color: var(--dxbl-text, #212529);
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-value-numeric {
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-value-bool {
|
|
||||||
/* Keep left aligned */
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-value-date {
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace AyCode.Blazor.Components.Components.Grids;
|
|
||||||
|
|
||||||
public class MgGridInfoPanelHelper
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
using DevExpress.Blazor;
|
|
||||||
using Microsoft.AspNetCore.Components;
|
|
||||||
|
|
||||||
namespace AyCode.Blazor.Components.Components.Grids
|
|
||||||
{
|
|
||||||
public class MgGridToolbarBase : DxToolbar
|
|
||||||
{
|
|
||||||
[Parameter] public IMgGridBase Grid { get; set; }
|
|
||||||
[Parameter] public Func<ToolbarItemClickEventArgs, Task> RefreshClick { get; set; }
|
|
||||||
[Parameter] public bool ShowOnlyIcon { get; set; } = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace AyCode.Blazor.Components.Components.Grids;
|
|
||||||
|
|
||||||
public class MgGridToolbarHelper
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
@using AyCode.Blazor.Components.Components.Grids
|
|
||||||
|
|
||||||
<MgGridToolbarBase @ref="GridToolbar" Grid="Grid" ItemRenderStyleMode="ToolbarRenderStyleMode.Plain" ShowOnlyIcon="ShowOnlyIcon">
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "New")" Click="NewItem_Click" IconCssClass="grid-new-row" Visible="@(!IsEditing)" Enabled="@(!IsSyncing)" />
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Edit")" Click="EditItem_Click" IconCssClass="grid-edit-row" Visible="@(!IsEditing)" Enabled="@(HasFocusedRow && !IsSyncing)" />
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Delete")" Click="DeleteItem_Click" IconCssClass="grid-delete-row" Visible="@(!IsEditing)" Enabled="@(false && HasFocusedRow && !IsSyncing)" />
|
|
||||||
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Save")" Click="SaveItem_Click" IconCssClass="grid-save" Visible="@IsEditing" RenderStyle="ButtonRenderStyle.Primary" />
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Cancel")" Click="CancelEdit_Click" IconCssClass="grid-cancel" Visible="@IsEditing" RenderStyle="ButtonRenderStyle.Secondary" />
|
|
||||||
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Prev Row")" BeginGroup="true" Click="PrevRow_Click" IconCssClass="grid-chevron-up" Enabled="@(HasFocusedRow && !IsSyncing && !IsEditing)" />
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Next Row")" Click="NextRow_Click" IconCssClass="grid-chevron-down" Enabled="@(HasFocusedRow && !IsSyncing && !IsEditing)" />
|
|
||||||
|
|
||||||
@if (!OnlyGridEditTools)
|
|
||||||
{
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Column Chooser")" BeginGroup="true" Click="ColumnChooserItem_Click" IconCssClass="grid-column-chooser" />
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Export")" IconCssClass="grid-export" Visible="false" Enabled="@(HasFocusedRow && !IsEditing)">
|
|
||||||
<Items>
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "To CSV")" Click="ExportCsvItem_Click" IconCssClass="grid-export-xlsx" />
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "To XLSX")" Click="ExportXlsxItem_Click" IconCssClass="grid-export-xlsx" />
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "To XLS")" Click="ExportXlsItem_Click" IconCssClass="grid-export-xlsx" />
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "To PDF")" Click="ExportPdfItem_Click" IconCssClass="grid-export-pdf" />
|
|
||||||
</Items>
|
|
||||||
</DxToolbarItem>
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : "Reload data")" BeginGroup="true" Click="ReloadData_Click" IconCssClass="grid-refresh" Enabled="@(!IsSyncing && !_isReloadInProgress && !IsEditing)" />
|
|
||||||
<DxToolbarItem Text="@(ShowOnlyIcon ? "" : FullscreenButtonText)" Click="Fullscreen_Click" IconCssClass="@FullscreenIconCssClass" Enabled="@(!IsEditing)" />
|
|
||||||
@ToolbarItemsExtended
|
|
||||||
}
|
|
||||||
</MgGridToolbarBase>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
[Parameter] public bool OnlyGridEditTools { get; set; } = false;
|
|
||||||
[Parameter] public IMgGridBase Grid { get; set; } = null!;
|
|
||||||
[Parameter] public RenderFragment? ToolbarItemsExtended { get; set; }
|
|
||||||
[Parameter] public EventCallback<ToolbarItemClickEventArgs> OnReloadDataClick { get; set; }
|
|
||||||
[Parameter] public bool ShowOnlyIcon { get; set; } = false;
|
|
||||||
|
|
||||||
public MgGridToolbarBase GridToolbar { get; set; } = null!;
|
|
||||||
const string ExportFileName = "ExportResult";
|
|
||||||
|
|
||||||
private bool _isReloadInProgress;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the grid is currently in edit mode (New or Edit)
|
|
||||||
/// </summary>
|
|
||||||
private bool IsEditing => Grid?.GridEditState != MgGridEditState.None;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the grid is currently syncing data
|
|
||||||
/// </summary>
|
|
||||||
private bool IsSyncing => Grid?.IsSyncing ?? false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether there is a focused row in the grid
|
|
||||||
/// </summary>
|
|
||||||
private bool HasFocusedRow => Grid?.GetFocusedRowIndex() >= 0;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the grid is currently in fullscreen mode
|
|
||||||
/// </summary>
|
|
||||||
private bool IsFullscreenMode => Grid?.IsFullscreen ?? false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Button text for fullscreen toggle
|
|
||||||
/// </summary>
|
|
||||||
private string FullscreenButtonText => IsFullscreenMode ? "Exit Fullscreen" : "Fullscreen";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Icon class for fullscreen toggle button
|
|
||||||
/// </summary>
|
|
||||||
private string FullscreenIconCssClass => IsFullscreenMode ? "grid-fullscreen-exit" : "grid-fullscreen";
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task ReloadData_Click(ToolbarItemClickEventArgs e)
|
|
||||||
{
|
|
||||||
_isReloadInProgress = true;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await OnReloadDataClick.InvokeAsync(e);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_isReloadInProgress = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task NewItem_Click()
|
|
||||||
{
|
|
||||||
await Grid.StartEditNewRowAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task EditItem_Click()
|
|
||||||
{
|
|
||||||
await Grid.StartEditRowAsync(Grid.GetFocusedRowIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
void DeleteItem_Click()
|
|
||||||
{
|
|
||||||
Grid.ShowRowDeleteConfirmation(Grid.GetFocusedRowIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task SaveItem_Click()
|
|
||||||
{
|
|
||||||
await Grid.SaveChangesAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task CancelEdit_Click()
|
|
||||||
{
|
|
||||||
await Grid.CancelEditAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrevRow_Click()
|
|
||||||
{
|
|
||||||
Grid.StepPrevRow();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NextRow_Click()
|
|
||||||
{
|
|
||||||
Grid.StepNextRow();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ColumnChooserItem_Click(ToolbarItemClickEventArgs e)
|
|
||||||
{
|
|
||||||
Grid.ShowColumnChooser();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Fullscreen_Click()
|
|
||||||
{
|
|
||||||
Grid.ToggleFullscreen();
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task ExportXlsxItem_Click()
|
|
||||||
{
|
|
||||||
await Grid.ExportToXlsxAsync(ExportFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task ExportXlsItem_Click()
|
|
||||||
{
|
|
||||||
await Grid.ExportToXlsAsync(ExportFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task ExportCsvItem_Click()
|
|
||||||
{
|
|
||||||
await Grid.ExportToCsvAsync(ExportFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
async Task ExportPdfItem_Click()
|
|
||||||
{
|
|
||||||
await Grid.ExportToPdfAsync(ExportFileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
@using DevExpress.Blazor
|
|
||||||
|
|
||||||
<CascadingValue Value="this">
|
|
||||||
@if (_isFullscreen)
|
|
||||||
{
|
|
||||||
<div class="mg-fullscreen-overlay">
|
|
||||||
<div class="mg-fullscreen-header">
|
|
||||||
<span class="mg-fullscreen-title">@(_currentGrid?.Caption ?? "Grid")</span>
|
|
||||||
<button type="button" class="btn-close btn-close-white" aria-label="Close" @onclick="ExitFullscreen"></button>
|
|
||||||
</div>
|
|
||||||
<div class="mg-fullscreen-body">
|
|
||||||
@RenderMainContent()
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
@RenderMainContent()
|
|
||||||
}
|
|
||||||
</CascadingValue>
|
|
||||||
|
|
||||||
@code {
|
|
||||||
private IInfoPanelBase? _infoPanelInstance;
|
|
||||||
private IMgGridBase? _currentGrid;
|
|
||||||
private bool _isFullscreen;
|
|
||||||
|
|
||||||
/// <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>
|
|
||||||
/// Whether the wrapper is currently in fullscreen mode
|
|
||||||
/// </summary>
|
|
||||||
public bool IsFullscreen => _isFullscreen;
|
|
||||||
|
|
||||||
/// <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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Registers the grid instance (called by MgGridBase)
|
|
||||||
/// </summary>
|
|
||||||
public void RegisterGrid(IMgGridBase grid)
|
|
||||||
{
|
|
||||||
_currentGrid = grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Toggles fullscreen mode
|
|
||||||
/// </summary>
|
|
||||||
public void ToggleFullscreen()
|
|
||||||
{
|
|
||||||
_isFullscreen = !_isFullscreen;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Enters fullscreen mode
|
|
||||||
/// </summary>
|
|
||||||
public void EnterFullscreen()
|
|
||||||
{
|
|
||||||
_isFullscreen = true;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Exits fullscreen mode
|
|
||||||
/// </summary>
|
|
||||||
public void ExitFullscreen()
|
|
||||||
{
|
|
||||||
_isFullscreen = false;
|
|
||||||
StateHasChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private RenderFragment RenderMainContent() => __builder =>
|
|
||||||
{
|
|
||||||
if (ShowInfoPanel)
|
|
||||||
{
|
|
||||||
<DxSplitter Width="100%" Height="@(_isFullscreen ? "100%" : null)" 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
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
namespace AyCode.Blazor.Components.Components;
|
|
||||||
|
|
||||||
public class MgComponentsHelper
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,265 +0,0 @@
|
||||||
/* MgGridInfoPanel styles - DevExpress theme compatible */
|
|
||||||
|
|
||||||
/* Main panel - uses DevExpress theme variables */
|
|
||||||
.mg-grid-info-panel {
|
|
||||||
container-type: inline-size;
|
|
||||||
container-name: infopanel;
|
|
||||||
background-color: var(--dxbl-bg-secondary);
|
|
||||||
color: var(--dxbl-text);
|
|
||||||
font-family: var(--dxbl-font-family);
|
|
||||||
font-size: var(--dxbl-font-size);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
min-height: 0;
|
|
||||||
max-height: 100%;
|
|
||||||
border-left: 1px solid var(--dxbl-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-grid-info-panel.edit-mode {
|
|
||||||
background-color: var(--dxbl-warning-bg, #fffbeb);
|
|
||||||
border-left: 3px solid var(--dxbl-warning, #f59e0b);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header styling */
|
|
||||||
.mg-grid-info-panel .mg-info-panel-header {
|
|
||||||
padding: var(--dxbl-spacer-sm) var(--dxbl-spacer);
|
|
||||||
background-color: var(--dxbl-bg);
|
|
||||||
border-bottom: 1px solid var(--dxbl-border-color);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Toolbar styling */
|
|
||||||
.mg-info-panel-toolbar {
|
|
||||||
padding: var(--dxbl-spacer-xs) var(--dxbl-spacer-sm);
|
|
||||||
background-color: var(--dxbl-bg);
|
|
||||||
border-bottom: 1px solid var(--dxbl-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Content area - scrollable */
|
|
||||||
.mg-info-panel-content {
|
|
||||||
flex: 1 1 0;
|
|
||||||
overflow-y: auto;
|
|
||||||
overflow-x: hidden;
|
|
||||||
padding: var(--dxbl-spacer);
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Grid layout for columns */
|
|
||||||
.mg-info-panel-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: var(--dxbl-spacer-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fixed column count classes */
|
|
||||||
.mg-columns-1 .mg-info-panel-grid { grid-template-columns: 1fr !important; }
|
|
||||||
.mg-columns-2 .mg-info-panel-grid { grid-template-columns: repeat(2, 1fr) !important; }
|
|
||||||
.mg-columns-3 .mg-info-panel-grid { grid-template-columns: repeat(3, 1fr) !important; }
|
|
||||||
.mg-columns-4 .mg-info-panel-grid { grid-template-columns: repeat(4, 1fr) !important; }
|
|
||||||
|
|
||||||
/* Responsive layouts using container queries */
|
|
||||||
@container infopanel (min-width: 400px) {
|
|
||||||
.mg-grid-info-panel:not(.mg-columns-1):not(.mg-columns-2):not(.mg-columns-3):not(.mg-columns-4) .mg-info-panel-grid {
|
|
||||||
grid-template-columns: repeat(2, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@container infopanel (min-width: 800px) {
|
|
||||||
.mg-grid-info-panel:not(.mg-columns-1):not(.mg-columns-2):not(.mg-columns-3):not(.mg-columns-4) .mg-info-panel-grid {
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@container infopanel (min-width: 1300px) {
|
|
||||||
.mg-grid-info-panel:not(.mg-columns-1):not(.mg-columns-2):not(.mg-columns-3):not(.mg-columns-4) .mg-info-panel-grid {
|
|
||||||
grid-template-columns: repeat(4, 1fr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Grid item */
|
|
||||||
.mg-info-panel-item {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Label styling */
|
|
||||||
.mg-info-panel-label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: var(--dxbl-spacer-xs);
|
|
||||||
font-size: calc(var(--dxbl-font-size) * 0.875);
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--dxbl-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-label.editable {
|
|
||||||
color: var(--dxbl-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* View mode value styling */
|
|
||||||
.mg-info-panel-value {
|
|
||||||
display: block;
|
|
||||||
padding: var(--dxbl-spacer-xs) 0;
|
|
||||||
color: var(--dxbl-text);
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-value-numeric {
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-value-date {
|
|
||||||
font-variant-numeric: tabular-nums;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Empty state */
|
|
||||||
.mg-info-panel-empty {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
|
||||||
color: var(--dxbl-text-muted);
|
|
||||||
padding: var(--dxbl-spacer-lg);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Tables inside info panel */
|
|
||||||
.mg-info-panel-content table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: var(--dxbl-font-size);
|
|
||||||
color: var(--dxbl-text);
|
|
||||||
margin-bottom: var(--dxbl-spacer);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-content table th,
|
|
||||||
.mg-info-panel-content table td {
|
|
||||||
padding: var(--dxbl-spacer-xs) var(--dxbl-spacer-sm);
|
|
||||||
border: 1px solid var(--dxbl-border-color);
|
|
||||||
text-align: left;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-content table th {
|
|
||||||
background-color: var(--dxbl-bg-secondary);
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--dxbl-text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-content table tbody tr:nth-child(odd) {
|
|
||||||
background-color: var(--dxbl-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-content table tbody tr:nth-child(even) {
|
|
||||||
background-color: var(--dxbl-bg-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-content table tbody tr:hover {
|
|
||||||
background-color: var(--dxbl-row-hover-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Splitter pane styling */
|
|
||||||
.mg-grid-with-info-panel {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-info-panel-pane {
|
|
||||||
background-color: var(--dxbl-bg-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fullscreen overlay styling (Bootstrap 5 based) */
|
|
||||||
.mg-fullscreen-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
z-index: 1050;
|
|
||||||
background-color: var(--dxbl-bg, #fff);
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-fullscreen-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 0.75rem 1rem;
|
|
||||||
background-color: var(--dxbl-primary, #0d6efd);
|
|
||||||
color: #fff;
|
|
||||||
border-bottom: 1px solid var(--dxbl-border-color);
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-fullscreen-title {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-fullscreen-header .btn-close-white {
|
|
||||||
filter: brightness(0) invert(1);
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-fullscreen-header .btn-close-white:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-fullscreen-body {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-fullscreen-body .mg-grid-with-info-panel,
|
|
||||||
.mg-fullscreen-body .dxbl-grid {
|
|
||||||
flex: 1;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Legacy DxWindow styling (kept for backwards compatibility) */
|
|
||||||
.mg-fullscreen-window {
|
|
||||||
position: fixed !important;
|
|
||||||
top: 0 !important;
|
|
||||||
left: 0 !important;
|
|
||||||
margin: 0 !important;
|
|
||||||
border-radius: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-fullscreen-window .dxbl-window-body {
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-fullscreen-content {
|
|
||||||
flex: 1;
|
|
||||||
height: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mg-fullscreen-content .mg-grid-with-info-panel,
|
|
||||||
.mg-fullscreen-content .dxbl-grid {
|
|
||||||
flex: 1;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Fullscreen icon classes */
|
|
||||||
.grid-fullscreen::before {
|
|
||||||
content: "\e90c";
|
|
||||||
font-family: 'devextreme-icons';
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-fullscreen-exit::before {
|
|
||||||
content: "\e90d";
|
|
||||||
font-family: 'devextreme-icons';
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
||||||
// MgGridInfoPanel - Sticky scroll handling
|
|
||||||
// Makes the InfoPanel sticky to viewport when scrolling
|
|
||||||
|
|
||||||
window.MgGridInfoPanel = {
|
|
||||||
observers: new Map(),
|
|
||||||
|
|
||||||
// Initialize sticky behavior for an InfoPanel element
|
|
||||||
initSticky: function (element, topOffset) {
|
|
||||||
if (!element) return;
|
|
||||||
|
|
||||||
const elementId = element.id || this.generateId(element);
|
|
||||||
|
|
||||||
// Clean up existing observer if any
|
|
||||||
this.disposeSticky(element);
|
|
||||||
|
|
||||||
// Store the initial position of the element (relative to document)
|
|
||||||
const rect = element.getBoundingClientRect();
|
|
||||||
const initialTop = rect.top + window.scrollY;
|
|
||||||
|
|
||||||
// Calculate and set initial state
|
|
||||||
this.updatePosition(element, initialTop);
|
|
||||||
|
|
||||||
// Handler to update position on scroll and resize
|
|
||||||
const updateHandler = () => {
|
|
||||||
this.updatePosition(element, initialTop);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add event listeners - use passive to not block scrolling
|
|
||||||
window.addEventListener('resize', updateHandler, { passive: true });
|
|
||||||
window.addEventListener('scroll', updateHandler, { passive: true });
|
|
||||||
|
|
||||||
// Store cleanup info
|
|
||||||
this.observers.set(elementId, {
|
|
||||||
element: element,
|
|
||||||
updateHandler: updateHandler,
|
|
||||||
initialTop: initialTop
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Dispose sticky behavior
|
|
||||||
disposeSticky: function (element) {
|
|
||||||
if (!element) return;
|
|
||||||
|
|
||||||
const elementId = element.id || this.findElementId(element);
|
|
||||||
const observer = this.observers.get(elementId);
|
|
||||||
|
|
||||||
if (observer) {
|
|
||||||
window.removeEventListener('resize', observer.updateHandler);
|
|
||||||
window.removeEventListener('scroll', observer.updateHandler);
|
|
||||||
|
|
||||||
// Reset styles
|
|
||||||
element.style.height = '';
|
|
||||||
element.style.maxHeight = '';
|
|
||||||
element.style.transform = '';
|
|
||||||
|
|
||||||
this.observers.delete(elementId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Update panel position and height based on scroll
|
|
||||||
updatePosition: function (element, initialTop) {
|
|
||||||
if (!element) return;
|
|
||||||
|
|
||||||
const scrollY = window.scrollY;
|
|
||||||
const viewportHeight = window.innerHeight;
|
|
||||||
const bottomPadding = 30; // 30px from bottom
|
|
||||||
|
|
||||||
// Calculate how much we've scrolled past the initial position
|
|
||||||
const scrolledPast = Math.max(0, scrollY - initialTop);
|
|
||||||
|
|
||||||
// Get the splitter pane to know our container limits
|
|
||||||
const paneContainer = element.closest('.dxbl-splitter-pane');
|
|
||||||
let maxScrollOffset = Infinity;
|
|
||||||
|
|
||||||
if (paneContainer) {
|
|
||||||
// Don't scroll past the bottom of the pane
|
|
||||||
const paneHeight = paneContainer.offsetHeight;
|
|
||||||
const elementHeight = element.offsetHeight;
|
|
||||||
maxScrollOffset = Math.max(0, paneHeight - elementHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clamp the scroll offset
|
|
||||||
const translateY = Math.min(scrolledPast, maxScrollOffset);
|
|
||||||
|
|
||||||
// Apply transform to make it "sticky"
|
|
||||||
element.style.transform = `translateY(${translateY}px)`;
|
|
||||||
|
|
||||||
// Calculate height: from current visual position to viewport bottom
|
|
||||||
const rect = element.getBoundingClientRect();
|
|
||||||
const visualTop = rect.top; // This already accounts for transform
|
|
||||||
|
|
||||||
// Height from current visual top to viewport bottom minus padding
|
|
||||||
const availableHeight = viewportHeight - visualTop - bottomPadding;
|
|
||||||
|
|
||||||
// Clamp height
|
|
||||||
const finalHeight = Math.max(200, Math.min(availableHeight, viewportHeight - bottomPadding));
|
|
||||||
|
|
||||||
element.style.height = finalHeight + 'px';
|
|
||||||
element.style.maxHeight = finalHeight + 'px';
|
|
||||||
},
|
|
||||||
|
|
||||||
// Generate a unique ID for the element
|
|
||||||
generateId: function (element) {
|
|
||||||
const id = 'mg-info-panel-' + Math.random().toString(36).substr(2, 9);
|
|
||||||
element.id = id;
|
|
||||||
return id;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Find element ID from stored observers
|
|
||||||
findElementId: function (element) {
|
|
||||||
for (const [id, observer] of this.observers.entries()) {
|
|
||||||
if (observer.element === element) {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Loading…
Reference in New Issue