using DevExpress.Blazor; using Microsoft.AspNetCore.Components; using System.Collections.Concurrent; using System.Linq.Expressions; using System.Text; using System.Text.RegularExpressions; namespace AyCode.Blazor.Components.Components.Grids; /// /// Extended DxGridDataColumn with additional parameters for InfoPanel support. /// public partial class MgGridDataColumn : DxGridDataColumn { private static readonly ConcurrentDictionary<(Type Type, string Property), Func?> SAccessorCache = new(); private string? _urlLink; private bool _isInitialized; private TemplatePart[]? _templateParts; /// /// Whether this column should be visible in the InfoPanel. Default is true. /// [Parameter] public bool ShowInInfoPanel { get; set; } = true; /// /// Custom display format for InfoPanel (overrides DisplayFormat if set). /// [Parameter] public string? InfoPanelDisplayFormat { get; set; } /// /// Column order in InfoPanel (lower = earlier). Default is int.MaxValue. /// [Parameter] public int InfoPanelOrder { get; set; } = int.MaxValue; /// /// URL template with {property} placeholders that will be replaced with row values. /// Example: https://shop.fruitbank.hu/Admin/Order/Edit/{Id}/{OrderId} /// [Parameter] public string? UrlLink { get => _urlLink; set { if (_urlLink == value) return; _urlLink = value; if (_isInitialized) UpdateCellDisplayTemplate(); } } protected override void OnParametersSet() { base.OnParametersSet(); _isInitialized = true; UpdateCellDisplayTemplate(); } private void UpdateCellDisplayTemplate() { if (string.IsNullOrWhiteSpace(_urlLink)) return; _templateParts = ParseTemplate(_urlLink); var parts = _templateParts; CellDisplayTemplate = context => builder => { var url = BuildUrl(parts, context.DataItem); builder.OpenElement(0, "a"); builder.AddAttribute(1, "href", url); builder.AddAttribute(2, "target", "_blank"); builder.AddAttribute(3, "style", "text-decoration: underline; color: inherit;"); builder.AddContent(4, context.DisplayText); builder.CloseElement(); }; } /// /// Represents a parsed segment of a URL template: either a literal string or a property placeholder. /// internal readonly record struct TemplatePart(string Value, bool IsProperty); [GeneratedRegex(@"\{([^}]+)\}")] private static partial Regex TemplateRegex(); /// /// Parses a URL template into literal and property placeholder segments. /// internal static TemplatePart[] ParseTemplate(string template) { var parts = new List(); var lastIndex = 0; foreach (Match match in TemplateRegex().Matches(template)) { if (match.Index > lastIndex) parts.Add(new TemplatePart(template[lastIndex..match.Index], IsProperty: false)); parts.Add(new TemplatePart(match.Groups[1].Value, IsProperty: true)); lastIndex = match.Index + match.Length; } if (lastIndex < template.Length) parts.Add(new TemplatePart(template[lastIndex..], IsProperty: false)); return [.. parts]; } /// /// Builds a URL from pre-parsed template parts using cached compiled property accessors. /// internal static string BuildUrl(TemplatePart[] parts, object? dataItem) { if (dataItem is null || parts.Length == 0) return string.Empty; var type = dataItem.GetType(); var sb = new StringBuilder(parts.Length * 16); foreach (var part in parts) { if (!part.IsProperty) { sb.Append(part.Value); continue; } var accessor = SAccessorCache.GetOrAdd((type, part.Value), static key => CompileAccessor(key.Type, key.Property)); if (accessor is not null) sb.Append(accessor(dataItem)?.ToString() ?? string.Empty); else sb.Append('{').Append(part.Value).Append('}'); } return sb.ToString(); } /// /// Replaces {property} placeholders in the template with values from the data item. /// Convenience overload that parses the template on each call — prefer pre-parsed for hot paths. /// internal static string BuildUrlFromTemplate(string template, object? dataItem) { return dataItem is null ? template : BuildUrl(ParseTemplate(template), dataItem); } private static Func? CompileAccessor(Type type, string propertyName) { var prop = type.GetProperty(propertyName); if (prop is null) return null; var param = Expression.Parameter(typeof(object), "obj"); var body = Expression.Convert( Expression.Property(Expression.Convert(param, type), prop), typeof(object)); return Expression.Lambda>(body, param).Compile(); } }