diff --git a/SUPERDATAGRIDEXPORTER.md b/SUPERDATAGRIDEXPORTER.md index b74ed76..e9f3fa2 100644 --- a/SUPERDATAGRIDEXPORTER.md +++ b/SUPERDATAGRIDEXPORTER.md @@ -101,8 +101,26 @@ Headers are resolved in this order: 3. **Title** 4. **Property** -Values use **ExportValue** first, then **Property** or **For**. Configure both -overrides when a column is entirely template-driven: +Values use **ExportValue** first, then the rendered text of **Template**, and +finally **Property** or **For**. A template-only column can therefore be +exported without declaring a binding; its title is taken from **Title** and +its textual cell content is taken from the template: + +~~~razor + + + +~~~ + +HTML elements are removed from the exported value and the remaining text is +normalized. For a column whose output is produced only by a child component, +configure **ExportValue** explicitly. Configure both overrides when a column +is entirely template-driven and needs a custom header or value: ~~~razor > Resolve(SuperDataGrid ExtractCellText(cellTemplate, item); + } else if (!string.IsNullOrWhiteSpace(column.Property)) { var untypedAccessor = Accessors.GetOrAdd( @@ -52,14 +57,16 @@ public static IReadOnlyList> Resolve(SuperDataGrid(header, column.FormatString, accessor)); } if (result.Count == 0) + { throw new InvalidOperationException("The grid has no visible exportable columns."); + } return result; } @@ -75,7 +82,9 @@ public static IReadOnlyList> Resolve(SuperDataGrid> Resolve(SuperDataGrid property.GetValue(value), @@ -105,23 +116,51 @@ public static IReadOnlyList> Resolve(SuperDataGrid(RenderFragment template, TItem item) + { + var builder = new RenderTreeBuilder(); + template(item)(builder); + return ExtractRenderedText(builder.GetFrames()); + } + private static string? ExtractHeaderText(RenderFragment? template) { if (template is null) + { return null; + } var builder = new RenderTreeBuilder(); template(builder); - var frames = builder.GetFrames(); + return ExtractRenderedText(builder.GetFrames()); + } + + private static string? ExtractRenderedText(ArrayRange frames) + { var parts = new List(); for (var i = 0; i < frames.Count; i++) { var frame = frames.Array[i]; if (frame.FrameType == RenderTreeFrameType.Text) + { parts.Add(frame.TextContent); + } else if (frame.FrameType == RenderTreeFrameType.Markup) + { parts.Add(HtmlTagRegex().Replace(frame.MarkupContent, " ")); + } + else if (frame.FrameType == RenderTreeFrameType.Attribute + && frame.AttributeValue is RenderFragment childContent) + { + var childBuilder = new RenderTreeBuilder(); + childContent(childBuilder); + var childText = ExtractRenderedText(childBuilder.GetFrames()); + if (!string.IsNullOrWhiteSpace(childText)) + { + parts.Add(childText); + } + } } var text = WhitespaceRegex().Replace(WebUtility.HtmlDecode(string.Join(" ", parts)), " ").Trim(); diff --git a/src/SuperBlazorComponents.DataGridExporter/SuperBlazorComponents.DataGridExporter.csproj b/src/SuperBlazorComponents.DataGridExporter/SuperBlazorComponents.DataGridExporter.csproj index 47aaf72..5975cbf 100644 --- a/src/SuperBlazorComponents.DataGridExporter/SuperBlazorComponents.DataGridExporter.csproj +++ b/src/SuperBlazorComponents.DataGridExporter/SuperBlazorComponents.DataGridExporter.csproj @@ -5,7 +5,7 @@ enable SuperBlazorComponents.DataGridExporter SuperBlazorComponents.DataGridExporter - 2.0.10 + 2.0.12 SuperBlazorComponents.DataGridExporter SuperBlazorComponents DataGrid Exporter Appliman diff --git a/tests/SuperBlazorComponents.Tests/SuperDataGridExporterTests.cs b/tests/SuperBlazorComponents.Tests/SuperDataGridExporterTests.cs index 731dff5..de639c9 100644 --- a/tests/SuperBlazorComponents.Tests/SuperDataGridExporterTests.cs +++ b/tests/SuperBlazorComponents.Tests/SuperDataGridExporterTests.cs @@ -40,12 +40,16 @@ public void Setup() public void Cleanup() { foreach (var context in _contexts) + { context.Dispose(); + } _contexts.Clear(); _renderedGrids.Clear(); if (Directory.Exists(_temporaryDirectory)) + { Directory.Delete(_temporaryDirectory, recursive: true); + } } [TestMethod] @@ -76,6 +80,31 @@ public void ColumnResolver_UsesConfiguredPriorityAndExportValue() Assert.IsTrue(column.IsCurrentlyVisible); } + [TestMethod] + public void ColumnResolver_UsesTitleAndRenderedCellTemplateWithoutBinding() + { + var grid = CreateGrid((request) => + ValueTask.FromResult(GridItemsProviderResult.Empty())); + var column = AddColumn(grid, new TestColumn + { + Title = "Fournisseurs", + Template = item => builder => + { + builder.OpenElement(0, "div"); + builder.AddContent(1, item.Name); + builder.CloseElement(); + } + }); + + var resolved = ExportColumnResolver.Resolve(grid); + + Assert.HasCount(1, resolved); + Assert.AreEqual("Fournisseurs", resolved[0].Header); + Assert.AreEqual(string.Empty, column.Property); + Assert.AreEqual("Alpha", resolved[0].ValueAccessor( + new TestRow(1, "Alpha", 12.5m, true))); + } + [TestMethod] public void ColumnResolver_ExtractsSimpleHeaderTemplateAndRejectsMissingValue() {