Compare commits
2 Commits
3d377c542d
...
4eb0d1566c
| Author | SHA1 | Date |
|---|---|---|
|
|
4eb0d1566c | |
|
|
27d5e03955 |
|
|
@ -6,6 +6,7 @@ using AyCode.Core.Loggers;
|
|||
using AyCode.Services.SignalRs;
|
||||
using AyCode.Utils.Extensions;
|
||||
using DevExpress.Blazor;
|
||||
using DevExpress.Data.Filtering;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
using Microsoft.JSInterop;
|
||||
|
|
@ -93,6 +94,13 @@ public interface IMgGridBase : IGrid
|
|||
/// returns true optimistically when server layouts are enabled (no server round-trip)
|
||||
/// </summary>
|
||||
Task<bool> HasUserLayoutAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Applies a per-field quick filter to the grid — delegates to DevExpress <c>SetFieldFilterCriteria</c>,
|
||||
/// which AND-combines it with the user's filter row (and other fields' quick filters). Passing
|
||||
/// <paramref name="criteria"/> = null clears this field's filter. See ADR 0004.
|
||||
/// </summary>
|
||||
void SetFieldQuickFilter(string fieldName, CriteriaOperator? criteria);
|
||||
}
|
||||
|
||||
public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClient> : DxGrid, IMgGridBase, IAsyncDisposable
|
||||
|
|
@ -243,6 +251,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
// Body
|
||||
contentBuilder.OpenElement(12, "div");
|
||||
contentBuilder.AddAttribute(13, "class", "mg-fullscreen-body");
|
||||
if (FilterPanel != null) contentBuilder.AddContent(14, FilterPanel);
|
||||
base.BuildRenderTree(contentBuilder);
|
||||
contentBuilder.CloseElement(); // body div
|
||||
|
||||
|
|
@ -254,6 +263,7 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
contentBuilder.OpenElement(0, "div");
|
||||
contentBuilder.SetKey(_gridRenderKey);
|
||||
contentBuilder.AddAttribute(1, "style", "display: contents;");
|
||||
if (FilterPanel != null) contentBuilder.AddContent(2, FilterPanel);
|
||||
base.BuildRenderTree(contentBuilder);
|
||||
contentBuilder.CloseElement();
|
||||
}
|
||||
|
|
@ -283,6 +293,13 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
|
||||
[Parameter] public string Caption { get; set; } = typeof(TDataItem).Name;
|
||||
|
||||
/// <summary>
|
||||
/// Optional quick-filter panel rendered above the grid (typically a <c>GridFilterPanel</c> hosting
|
||||
/// <see cref="MgTagBox{TData,TValue}"/> controls). Rendered within this grid's <see cref="IMgGridBase"/>
|
||||
/// cascade, so the controls can reach this grid and apply per-field filters. See ADR 0004.
|
||||
/// </summary>
|
||||
[Parameter] public RenderFragment? FilterPanel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Name for auto-saving/loading grid layout. If not set, defaults to "Grid{TDataItem.Name}"
|
||||
/// </summary>
|
||||
|
|
@ -1004,6 +1021,10 @@ public abstract class MgGridBase<TSignalRDataSource, TDataItem, TId, TLoggerClie
|
|||
return _dataSource.LoadDataSourceAsync(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void SetFieldQuickFilter(string fieldName, CriteriaOperator? criteria)
|
||||
=> SetFieldFilterCriteria(fieldName, criteria);
|
||||
|
||||
/// <summary>
|
||||
/// Navigates to the previous row in the grid
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Rendering;
|
||||
|
||||
namespace AyCode.Blazor.Components.Components.Grids;
|
||||
|
||||
/// <summary>
|
||||
/// Base for a grid quick-filter panel — a styled container placed in a grid's <c>FilterPanel</c> slot,
|
||||
/// hosting <see cref="MgTagBox{TData,TValue}"/> (or other) per-field quick-filter controls above the grid.
|
||||
/// <para>
|
||||
/// Framework base. Projects derive a (typically empty) <c>GridFilterPanel</c> per the Mg* seam policy
|
||||
/// (cf. <c>MgGridBase</c> → project grid). The grid reference reaches the child controls via the grid's own
|
||||
/// <see cref="IMgGridBase"/> cascade (<c>MgGridBase</c> provides it) — this panel is layout only, no wiring.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class MgGridFilterPanelBase : ComponentBase
|
||||
{
|
||||
/// <summary>The quick-filter controls (e.g. <see cref="MgTagBox{TData,TValue}"/>) to render in the panel.</summary>
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
/// <summary>Extra CSS classes appended to the panel container.</summary>
|
||||
[Parameter] public string? CssClass { get; set; }
|
||||
|
||||
protected override void BuildRenderTree(RenderTreeBuilder builder)
|
||||
{
|
||||
builder.OpenElement(0, "div");
|
||||
builder.AddAttribute(1, "class", string.IsNullOrWhiteSpace(CssClass)
|
||||
? "mg-grid-filter-panel"
|
||||
: $"mg-grid-filter-panel {CssClass}");
|
||||
builder.AddContent(2, ChildContent);
|
||||
builder.CloseElement();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
using AyCode.Blazor.Components.Components.Grids;
|
||||
using DevExpress.Blazor;
|
||||
using DevExpress.Data.Filtering;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AyCode.Blazor.Components.Components;
|
||||
|
||||
/// <summary>
|
||||
/// <c>DxTagBox</c> seam (per the Mg* "derive every DevExpress component" policy) with an added grid
|
||||
/// quick-filter capability.
|
||||
/// <para>
|
||||
/// When placed inside a grid's <c>FilterPanel</c>, changing the selection applies a per-field
|
||||
/// <c>IN(FieldName, selected...)</c> filter to the grid via <see cref="IMgGridBase.SetFieldQuickFilter"/>.
|
||||
/// DevExpress AND-combines it with the user's filter row (it uses <c>SetFieldFilterCriteria</c>, NOT
|
||||
/// <c>SetFilterCriteria</c>) — see ADR 0004.
|
||||
/// </para>
|
||||
/// The selection is managed internally, so the consumer writes just
|
||||
/// <c><MgTagBox FieldName="..." Data="..." /></c> — no <c>@bind-Values</c> / backing field needed.
|
||||
/// Outside a grid filter panel (no <see cref="Grid"/> cascade) it behaves as a plain tag box.
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">Data item type (as for <c>DxTagBox</c>).</typeparam>
|
||||
/// <typeparam name="TValue">Selected value type (as for <c>DxTagBox</c>).</typeparam>
|
||||
public class MgTagBox<TData, TValue> : DxTagBox<TData, TValue>
|
||||
{
|
||||
/// <summary>
|
||||
/// Grid column field this quick-filter targets. Consistent with <c>DxGridDataColumn.FieldName</c> and the
|
||||
/// <c>SetFieldFilterCriteria(fieldName, ...)</c> parameter. When null/empty, no grid filter is applied.
|
||||
/// </summary>
|
||||
[Parameter] public string? FieldName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The grid to filter — supplied by the grid's <c>FilterPanel</c> cascade (<c>MgGridBase</c> provides the
|
||||
/// <see cref="IMgGridBase"/> cascade). Null when the tag box is not inside a grid filter panel.
|
||||
/// </summary>
|
||||
[CascadingParameter] public IMgGridBase? Grid { get; set; }
|
||||
|
||||
private bool _initialized;
|
||||
private IEnumerable<TValue>? _selected;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
base.OnParametersSet();
|
||||
|
||||
// Own the selection internally so the consumer needs no @bind-Values.
|
||||
// COMPILE-VERIFY (DevExpress version specific): Blazor re-applies base.Values / base.ValuesChanged from the
|
||||
// consumer markup on every parameter set, so we re-assert ours here each time. First set adopts any initial Values.
|
||||
if (!_initialized)
|
||||
{
|
||||
_selected = Values;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
base.Values = _selected;
|
||||
base.ValuesChanged = EventCallback.Factory.Create<IEnumerable<TValue>>(this, OnSelectionChanged);
|
||||
}
|
||||
|
||||
private void OnSelectionChanged(IEnumerable<TValue>? values)
|
||||
{
|
||||
_selected = values;
|
||||
base.Values = values; // reflect the new selection in the editor
|
||||
ApplyQuickFilter(values);
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private void ApplyQuickFilter(IEnumerable<TValue>? values)
|
||||
{
|
||||
if (Grid is null || string.IsNullOrWhiteSpace(FieldName)) return;
|
||||
|
||||
var selected = values?.Where(v => v is not null).Cast<object>().ToArray() ?? [];
|
||||
|
||||
// COMPILE-VERIFY: confirm the InOperator(string, IEnumerable / params object[]) overload on your
|
||||
// DevExpress.Data version. Empty selection => null => clears this field's filter.
|
||||
CriteriaOperator? criteria = selected.Length > 0 ? new InOperator(FieldName, selected) : null;
|
||||
|
||||
Grid.SetFieldQuickFilter(FieldName, criteria);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
For known issues / bugs see `MGGRID_ISSUES.md`.
|
||||
|
||||
> **Server-side grid filtering — design in ADR 0004.** MgGrid `FilterMode` (Local/Server), the criteria-string forwarding, and the `DxTagBox` quick-filter template extension are captured in `AyCode.Core/docs/adr/0004-signalr-datasource-server-side-filtering.md` (in AyCode.Core repo, Status: Proposed). Not yet broken into per-step entries — reference the ADR when implementation begins.
|
||||
|
||||
## Priority legend
|
||||
- **P0** blocker · **P1** important · **P2** nice-to-have · **P3** idea
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue