Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/ci-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ env:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
SOLUTION_FILE: SuperBlazorComponents.slnx
PACKAGE_PROJECT: src/SuperBlazorComponents/SuperBlazorComponents.csproj
MAIN_PACKAGE_PROJECT: src/SuperBlazorComponents/SuperBlazorComponents.csproj
EXPORTER_PACKAGE_PROJECT: src/SuperBlazorComponents.DataGridExporter/SuperBlazorComponents.DataGridExporter.csproj

permissions:
contents: read
Expand Down Expand Up @@ -64,7 +65,14 @@ jobs:

- name: Pack SuperBlazorComponents
run: >
dotnet pack ${{ env.PACKAGE_PROJECT }}
dotnet pack ${{ env.MAIN_PACKAGE_PROJECT }}
--no-build
--configuration Release
--output ./nupkgs

- name: Pack SuperBlazorComponents.DataGridExporter
run: >
dotnet pack ${{ env.EXPORTER_PACKAGE_PROJECT }}
--no-build
--configuration Release
--output ./nupkgs
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
| Preview | Component | Description | Docs |
|---|---|---|---|
| <img src="docs/images/components/superdatagrid.svg" width="88" alt="SuperDataGrid preview"> | **SuperDataGrid** | Virtualized data grid — frozen columns/rows, hierarchical lazy-loading rows, reordering, resizing, filtering, sorting, inline editing, row selection, settings persistence | [📖 SUPERDATAGRID.md](SUPERDATAGRID.md) |
| <img src="docs/images/components/superdatagrid.svg" width="88" alt="SuperDataGrid exporter preview"> | **SuperDataGrid Exporter** | Optional CSV and Excel export extension for complete filtered, sorted and virtualized datasets | [📖 SUPERDATAGRIDEXPORTER.md](SUPERDATAGRIDEXPORTER.md) |
| <img src="docs/images/components/superlayout.svg" width="88" alt="SuperLayout preview"> | **SuperLayout** | Responsive app layout — header, sidebar, body, footer, chat panel with collapsible sidebar | [📖 SUPERLAYOUT.md](SUPERLAYOUT.md) |
| <img src="docs/images/components/supercontext.svg" width="88" alt="SuperContext preview"> | **SuperContext** | Context-aware tabs and contextual panels — discover components by runtime type and zone, render one or many contexts, and isolate host state per instance | [📖 SUPERCONTEXT.md](SUPERCONTEXT.md) |
| <img src="docs/images/components/supertabs.svg" width="88" alt="SuperTabs preview"> | **SuperTabs** | Dynamic tabbed interface — badges, closable tabs, lazy loading, persistence (URL + localStorage), keyboard navigation, service-driven management | [📖 SUPERTABS.md](SUPERTABS.md) |
Expand Down
124 changes: 124 additions & 0 deletions SUPERDATAGRIDEXPORTER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# SuperDataGrid CSV and Excel exporter

The **SuperBlazorComponents.DataGridExporter** extension exports the complete
filtered and sorted view of a **SuperDataGrid**, including rows that are not
currently rendered by virtualization.

## Installation

Reference both packages/projects:

~~~bash
dotnet add package SuperBlazorComponents
dotnet add package SuperBlazorComponents.DataGridExporter
~~~

Register the exporter and map its download endpoint in **Program.cs**:

~~~csharp
using SuperBlazorComponents.DataGridExporter;

builder.Services.AddSuperComponents();
builder.Services.AddSuperDataGridExporter(options =>
{
options.TemporaryDirectory = Path.Combine(
builder.Environment.ContentRootPath, "_temp", "grid-exports");
options.FileLifetime = TimeSpan.FromHours(24);
options.CleanupInterval = TimeSpan.FromDays(1);
});

var app = builder.Build();
app.MapSuperDataGridExporter();
~~~

The endpoint uses an unguessable 256-bit token and is anonymous by design.
Files expire after **FileLifetime**; cleanup runs at startup and then at
**CleanupInterval**.

## Usage

Keep a component reference to the grid and pass it to either export button:

~~~razor
@using SuperBlazorComponents.DataGridExporter.Components

<SuperDataGrid @ref="_grid"
TItem="Product"
ItemsProvider="@LoadProducts">
<HeaderTemplate>
<div class="d-flex align-items-center gap-2 w-100">
<div class="ms-auto d-flex align-items-center gap-2">
<SuperDataGridExcelExportButton TItem="Product"
Grid="@_grid"
DefaultFileName="products"
IconOnly="true" />
<SuperDataGridCsvExportButton TItem="Product"
Grid="@_grid"
DefaultFileName="products"
IconOnly="true" />
</div>
</div>
</HeaderTemplate>
<DataGridColumn For="@(p => p.Name)" Title="Product" />
<DataGridColumn For="@(p => p.Price)" Title="Price" FormatString="{0:F2}" />
</SuperDataGrid>

@code {
private SuperDataGrid<Product>? _grid;
}
~~~

Set **IconOnly="true"** to hide the text while preserving it as the tooltip
and accessible label. The Bootstrap **ms-auto** wrapper aligns the export
actions to the right of the grid header. The grid keeps the custom header area
separated from its built-in actions. Omit **IconOnly** to display the icon and
text together.

Only currently visible columns are exported, in their current order. The
exporter captures the grid filters and sort order once, then reads the complete
result from **ItemsProvider** in batches. Hierarchical grids export root items
only. The default batch size is 200 rows and can be changed with
**SuperDataGridExporterOptions.BatchSize**.

## Custom columns

Headers are resolved in this order:

1. **ExportHeader**
2. plain text found directly in **HeaderTemplate**
3. **Title**
4. **Property**

Values use **ExportValue** first, then **Property** or **For**. Configure both
overrides when a column is entirely template-driven:

~~~razor
<DataGridColumn Title="Status"
ExportHeader="Current status"
ExportValue="@GetExportedStatus">
<Template>
...
</Template>
</DataGridColumn>

@code {
private static object GetExportedStatus(Product item)
=> item.IsActive ? "Active" : "Inactive";
}
~~~

Set **Exportable="false"** to exclude a visible column.

## Formats and limits

- CSV defaults to UTF-8 without BOM, comma delimiter and invariant culture.
These defaults are configurable through **SuperDataGridExporterOptions**.
- CSV strings beginning with formula control characters are prefixed safely by
default.
- Excel preserves native number, date and boolean cell types, freezes the
header, reproduces the grid's left frozen columns, enables filtering and
sizes columns to their content. Excel cannot freeze columns from the right.
- One worksheet supports at most 1,048,575 exported data rows because the
header occupies the first Excel row.
- The grid must use **ItemsProvider**; the currently rendered virtualized items
alone are intentionally never treated as the complete dataset.
1 change: 1 addition & 0 deletions SuperBlazorComponents.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<Build />
</Project>
<Project Path="src/DemoWebSite/DemoWebSite.csproj" />
<Project Path="src/SuperBlazorComponents.DataGridExporter/SuperBlazorComponents.DataGridExporter.csproj" />
<Project Path="src/SuperBlazorComponents/SuperBlazorComponents.csproj" />
<Project Path="tests/SuperBlazorComponents.Tests/SuperBlazorComponents.Tests.csproj" />
</Solution>
37 changes: 33 additions & 4 deletions src/DemoWebSite/Components/Pages/SuperGridDemo.razor
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
@using SuperBlazorComponents.Components.SuperDataGrid
@using SuperBlazorComponents.Components.SuperDataGrid.Filters
@using SuperBlazorComponents.Components.SuperDataGrid.Tools
@using SuperBlazorComponents.DataGridExporter.Components

<PageTitle>SuperGrid Demo - 5000 Items</PageTitle>

Expand Down Expand Up @@ -78,10 +79,24 @@
</SelectorMenuItemsContent>

<HeaderTemplate>
<div class="d-flex align-items-center gap-2">
<div class="d-flex align-items-center gap-2 w-100">
<span>Header</span>
<button type="button" class="btn btn-sm btn-outline-primary" @onclick="ExpandAllHierarchyAsync">Expand all</button>
<button type="button" class="btn btn-sm btn-outline-secondary" @onclick="CollapseAllHierarchyAsync">Collapse all</button>
<div class="ms-auto d-flex align-items-center gap-2">
<SuperDataGridExcelExportButton TItem="DemoItem"
Grid="@_grid"
DefaultFileName="supergrid-demo"
Size="SuperButtonSize.Small"
IconOnly="true"
Outline="true" />
<SuperDataGridCsvExportButton TItem="DemoItem"
Grid="@_grid"
DefaultFileName="supergrid-demo"
Size="SuperButtonSize.Small"
IconOnly="true"
Outline="true" />
</div>
</div>
</HeaderTemplate>

Expand Down Expand Up @@ -194,9 +209,23 @@
FreezeLeftColumns="1"
GridId="supergrid-demo">
<HeaderTemplate>
<button type="button" class="btn btn-sm btn-outline-primary" @onclick="ExpandAllHierarchyAsync">
Expand all
</button>
<div class="d-flex align-items-center gap-2 w-100">
<button type="button" class="btn btn-sm btn-outline-primary" @onclick="ExpandAllHierarchyAsync">
Expand all
</button>
<div class="ms-auto d-flex align-items-center gap-2">
<SuperDataGridExcelExportButton TItem="DemoItem"
Grid="@_grid"
DefaultFileName="supergrid-demo"
Size="SuperButtonSize.Small"
IconOnly="true" />
<SuperDataGridCsvExportButton TItem="DemoItem"
Grid="@_grid"
DefaultFileName="supergrid-demo"
Size="SuperButtonSize.Small"
IconOnly="true" />
</div>
</div>
</HeaderTemplate>
<ChildContent>
<DataGridColumn TItem="DemoItem" Property="Id" Title="ID" Width="100px" />
Expand Down
8 changes: 6 additions & 2 deletions src/DemoWebSite/DemoWebSite.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
<None Include="..\..\SUPERDATAGRID.md" Link="wwwroot\help\SUPERDATAGRID.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="..\..\SUPERDATAGRIDEXPORTER.md" Link="wwwroot\help\SUPERDATAGRIDEXPORTER.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Include="..\..\SUPERDATERANGEPICKER.md" Link="wwwroot\help\SUPERDATERANGEPICKER.md">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
Expand Down Expand Up @@ -71,12 +74,13 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Markdig" Version="1.2.0" />
<PackageReference Include="ModelContextProtocol.AspNetCore" Version="1.2.0" />
<PackageReference Include="Markdig" Version="1.3.2" />
<PackageReference Include="ModelContextProtocol.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.23.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SuperBlazorComponents.DataGridExporter\SuperBlazorComponents.DataGridExporter.csproj" />
<ProjectReference Include="..\SuperBlazorComponents\SuperBlazorComponents.csproj" />
</ItemGroup>

Expand Down
11 changes: 11 additions & 0 deletions src/DemoWebSite/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

using SuperBlazorComponents;
using SuperBlazorComponents.Components.SuperDataGrid;
using SuperBlazorComponents.DataGridExporter;

using System.Text.RegularExpressions;

Expand Down Expand Up @@ -76,6 +77,15 @@
DisplayDefaultFooterTemplate = true
});
});
builder.Services.AddSuperDataGridExporter(options =>
{
options.TemporaryDirectory = Path.Combine(
builder.Environment.ContentRootPath,
"_temp",
"data-grid-exports");
options.FileLifetime = TimeSpan.FromHours(24);
options.CleanupInterval = TimeSpan.FromDays(1);
});

System.Globalization.CultureInfo.DefaultThreadCurrentUICulture = new System.Globalization.CultureInfo("en-US");
System.Globalization.CultureInfo.DefaultThreadCurrentCulture = new System.Globalization.CultureInfo("en-US");
Expand Down Expand Up @@ -113,6 +123,7 @@
})
}));
app.MapMcp("/mcp");
app.MapSuperDataGridExporter();

app.MapGet("/demo-source", (string route, IWebHostEnvironment environment) =>
{
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@typeparam TItem
@inherits SuperDataGridExportButtonBase<TItem>

<SuperButton Text="@ResolvedText"
Icon="fa-file-csv"
IconOnly="@IconOnly"
Style="@Style"
Size="@Size"
Outline="@Outline"
Disabled="@IsDisabled"
Click="@OpenDialogAsync" />

@code {
protected override SuperDataGridExportFormat Format => SuperDataGridExportFormat.Csv;
protected override string DefaultText => "Exporter CSV";
protected override string DialogTitle => "Export CSV";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@typeparam TItem
@inherits SuperDataGridExportButtonBase<TItem>

<SuperButton Text="@ResolvedText"
Image="_content/SuperBlazorComponents.DataGridExporter/icons/microsoft-excel.svg"
IconOnly="@IconOnly"
Style="@Style"
Size="@Size"
Outline="@Outline"
Disabled="@IsDisabled"
Click="@OpenDialogAsync" />

@code {
protected override SuperDataGridExportFormat Format => SuperDataGridExportFormat.Excel;
protected override string DefaultText => "Exporter Excel";
protected override string DialogTitle => "Export Excel";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;

using SuperBlazorComponents.Components.Buttons;
using SuperBlazorComponents.Components.Dialogs;
using SuperBlazorComponents.Components.SuperDataGrid;
using SuperBlazorComponents.Services;

namespace SuperBlazorComponents.DataGridExporter.Components;

public abstract class SuperDataGridExportButtonBase<TItem> : ComponentBase
{
[Inject]
private SuperDialogService DialogService { get; set; } = default!;

[Parameter, EditorRequired]
public SuperDataGrid<TItem>? Grid { get; set; }

[CascadingParameter]
private SuperDataGrid<TItem>? CascadedGrid { get; set; }

[Parameter]
public string? DefaultFileName { get; set; }

[Parameter]
public string? Text { get; set; }

[Parameter]
public bool Disabled { get; set; }

[Parameter]
public SuperButtonStyle Style { get; set; } = SuperButtonStyle.Primary;

[Parameter]
public SuperButtonSize Size { get; set; } = SuperButtonSize.Default;

[Parameter]
public bool Outline { get; set; }

/// <summary>
/// Displays only the format icon. The resolved text remains available as
/// the button tooltip and accessible label.
/// </summary>
[Parameter]
public bool IconOnly { get; set; }

protected abstract SuperDataGridExportFormat Format { get; }
protected abstract string DefaultText { get; }
protected abstract string DialogTitle { get; }

protected string ResolvedText => string.IsNullOrWhiteSpace(Text) ? DefaultText : Text;
private SuperDataGrid<TItem>? EffectiveGrid => Grid ?? CascadedGrid;
protected bool IsDisabled => Disabled || EffectiveGrid is null;

protected async Task OpenDialogAsync(MouseEventArgs _)
{
var grid = EffectiveGrid;
if (grid is null)
return;

var parameters = new Dictionary<string, object>
{
[nameof(SuperDataGridExportDialog<TItem>.Grid)] = grid,
[nameof(SuperDataGridExportDialog<TItem>.Format)] = Format,
[nameof(SuperDataGridExportDialog<TItem>.DefaultFileName)] = DefaultFileName ?? string.Empty
};

await DialogService.OpenAsync<SuperDataGridExportDialog<TItem>>(
DialogTitle,
parameters,
new DialogOptions { Width = "560px" });
}
}
Loading
Loading