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
+
+
+ @foreach (var supplier in product.AvailableSupplierProductList)
+ {
+ @supplier.SupplierName
+ }
+
+
+~~~
+
+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()
{