AtharvaITS.QuickGrid.Extensions 1.2.0

dotnet add package AtharvaITS.QuickGrid.Extensions --version 1.2.0
                    
NuGet\Install-Package AtharvaITS.QuickGrid.Extensions -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AtharvaITS.QuickGrid.Extensions" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AtharvaITS.QuickGrid.Extensions" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="AtharvaITS.QuickGrid.Extensions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add AtharvaITS.QuickGrid.Extensions --version 1.2.0
                    
#r "nuget: AtharvaITS.QuickGrid.Extensions, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package AtharvaITS.QuickGrid.Extensions@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=AtharvaITS.QuickGrid.Extensions&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=AtharvaITS.QuickGrid.Extensions&version=1.2.0
                    
Install as a Cake Tool

AtharvaITS.QuickGrid.Extensions

A lightweight Razor Class Library that adds column resizing, drag-and-drop column reordering, a column chooser, CSV / Excel / PDF export, and state management to Microsoft's QuickGrid — without replacing it or changing how you define columns.


Features

  • Column Resizing — drag the right edge of any header cell to resize it. Default resize keeps total table width stable by balancing with a neighboring visible column. Hold Shift while dragging to resize a single column independently (table width can expand/shrink).
  • Column Reordering — drag any column header to reorder using SortableJS (bundled, no CDN required).
  • Column Chooser — a "⚙ Columns" button above the grid opens a dropdown where users can show or hide individual columns. Hidden state is preserved across sort, filter, and page changes.
  • Export — optional toolbar (Export sits right of Columns) for CSV, Excel, and PDF. Modes: Current Table view, All Data, Pages, Rows (with ExportPaginationState). Helpers GridExcelExporter / GridPdfExporter simplify server-side bytes.
  • State Management — persist column widths, order, visibility, and sort state to localStorage automatically via StorageKey, or save/restore to your own server using GetPreference / LoadPreference.
  • Zero changes to your QuickGrid — wrap with <QuickGridExtension>. Columns, sorting, pagination, and item providers work exactly as before.
  • Virtualization compatible — a MutationObserver preserves column order and hidden-column state for dynamically added rows when Virtualize="true".
  • Re-render safe — column widths, order, and visibility are restored automatically after every QuickGrid re-render (sort, filter, data refresh).

Requirements

.NET 10.0
Microsoft.AspNetCore.Components.QuickGrid 10.x
Blazor hosting model Server, WebAssembly, or Auto

Installation

.NET CLI

dotnet add package AtharvaITS.QuickGrid.Extensions

NuGet Package Manager Console (Visual Studio)

Install-Package AtharvaITS.QuickGrid.Extensions

Package Reference (.csproj)

<PackageReference Include="AtharvaITS.QuickGrid.Extensions" Version="1.2.0" />

Setup

Add to _Imports.razor:

@using AtharvaITS.QuickGrid.Extensions.Components
@using AtharvaITS.QuickGrid.Extensions.Models
@using AtharvaITS.QuickGrid.Extensions.Exporting

The Models namespace contains QuickGridPreference, export types (GridExportRequest, GridExportResult, ExportFormat, etc.). The Exporting namespace contains GridExcelExporter and GridPdfExporter for building full-data .xlsx and .pdf files from row strings.

No changes to Program.cs are needed. Static assets are served automatically via _content/AtharvaITS.QuickGrid.Extensions/.


Parameters

Parameter Type Default Description
ChildContent RenderFragment The <QuickGrid> to enhance.
EnableResize bool true Allow columns to be resized by dragging the right edge of a header.
EnableReorder bool true Allow columns to be reordered by dragging header cells.
EnableColumnChooser bool true Show a "⚙ Columns" button above the grid that lets users toggle individual column visibility. Hidden state survives sort, filter, and page changes.
ShowResetButton bool false Show a Reset button in the toolbar to restore default column order, widths, visibility, and sort preferences.
MinColumnWidth int 80 Minimum column width in pixels during resize.
StorageKey string? null Unique key identifying this grid's preferences in localStorage. Multiple grids on the same page must use distinct values. When omitted, persistence is disabled entirely.
StorageScope string? null Optional namespace prefix combined with StorageKey to isolate preferences per user, page, or tenant. Final key: eqg_pref_{StorageScope}_{StorageKey}. Examples: userId, $"{userId}/admin", $"{tenantId}/{userId}".
AutoRestoreSort bool false When true, automatically restores the last active sort on page load by programmatically clicking the stored column header. Causes 1–2 extra data loads; best suited for in-memory IQueryable grids. For GridItemsProvider grids, use OnSortChanged instead.
OnSortChanged EventCallback<QuickGridPreference> Raised whenever the active sort column or direction changes, and once on first render when a stored sort preference is found. Use SortColumnIndex and SortDirection from the snapshot to seed your GridItemsProvider.
EnableExport bool false When true, shows an Export control (to the right of Columns) with CSV, Excel (.xlsx by default), and PDF options.
DataLoadMode GridDataLoadMode InMemoryFullDom When Virtualized, Current Table view and Pages are hidden in the export panel.
EnableExportCurrentView bool true Allow Current Table view (DOM export) unless DataLoadMode is Virtualized.
EnableExportFullData bool true Allow All Data when OnExportRequested is set.
AllowDomExportWhenVirtualized bool false Obsolete — kept for backward compatibility.
ExportPaginationState PaginationState? null Recommended. Same PaginationState as the grid; page/row limits refresh when totals change.
ExportPagination GridExportPaginationContext? null Manual snapshot for Pages and Rows. Requires host StateHasChanged when pagination changes unless you use ExportPaginationState.
ExportFileNameBase string "export" Base download file name (no extension); invalid characters are stripped in JavaScript.
IncludeUtf8BomInCsv bool true Prefix CSV with a UTF-8 BOM for Excel-friendly opening.
ExportCurrentViewExcelAsSpreadsheetMl bool false When true, current-table Excel uses legacy Excel 2003 SpreadsheetML (.xml) instead of .xlsx.
OnExportRequested Func<GridExportRequest, Task<GridExportResult?>>? null Required for All Data, Pages, and Rows exports. Receives column metadata, sort info, and ExportFormat. Return null to signal failure.

Export scopes and data loading

Scope Meaning
Current Table view Serializes the live DOM (visible columns and all <tbody> rows currently shown). Hidden when DataLoadMode="Virtualized".
All Data Full dataset via OnExportRequested (ExportScope.FullData).
Pages Shown when ExportPaginationState or ExportPagination is set and DataLoadMode is not Virtualized. Enter From page / To page (required). Server export with FromPage / ToPage on GridExportRequest.
Rows Shown when ExportPaginationState or ExportPagination is set. Enter From row / To row (required, global 1-based). Server export with ExportScope.RowRange and FromRow / ToRow.

Bind pagination for page-range export (recommended):

<QuickGridExtension ExportPaginationState="@_pagination" OnExportRequested="ExportAsync" ...>
    <QuickGrid Pagination="@_pagination" ... />
</QuickGridExtension>
<Paginator State="@_pagination" />

Or build a snapshot manually (call StateHasChanged when PaginationState changes):

ExportPagination="@GridExportPaginationContext.From(_pagination)"

Excel note: Current-table Excel downloads a .xlsx file (Open XML) unless ExportCurrentViewExcelAsSpreadsheetMl is true (legacy .xml). For all rows, return bytes from OnExportRequested; you can build the workbook with GridExcelExporter.CreateXlsx (or ClosedXML, EPPlus, etc.).

PDF note: Current-view PDF opens a new window with a clean table and the browser Print dialog (user chooses “Save as PDF”). For all rows, return PDF bytes from your handler; GridPdfExporter.CreateSimpleTablePdf uses PDFsharp with an embedded Roboto font when no custom GlobalFontSettings.FontResolver is set, so it works on Blazor WebAssembly without extra setup.


Resize Behavior

  • Default drag: adjusts the target column and a deterministic neighboring visible column (right-first, then left fallback) to keep table width stable.
  • Shift + drag: resizes only the target column (independent mode). Use this when you want the table to grow or shrink horizontally.
  • Hidden columns: when a column is hidden and later shown from the chooser, its previous width is restored.

Examples

1. Default — resize, reorder, and column chooser enabled

@using Microsoft.AspNetCore.Components.QuickGrid

<QuickGridExtension>
    <QuickGrid Items="@_employees.AsQueryable()" TGridItem="Employee">
        <PropertyColumn Property="@(e => e.Name)"       Title="Name"       Sortable="true" />
        <PropertyColumn Property="@(e => e.Department)" Title="Department" Sortable="true" />
        <PropertyColumn Property="@(e => e.Salary)"     Title="Salary"     Sortable="true" Format="C0" />
    </QuickGrid>
</QuickGridExtension>

2. Column chooser only (disable resize and reorder)

<QuickGridExtension EnableResize="false" EnableReorder="false">
    <QuickGrid Items="@_employees.AsQueryable()" TGridItem="Employee">
        <PropertyColumn Property="@(e => e.Name)"       Title="Name"       Sortable="true" />
        <PropertyColumn Property="@(e => e.Department)" Title="Department" Sortable="true" />
        <PropertyColumn Property="@(e => e.Location)"   Title="Location" />
        <PropertyColumn Property="@(e => e.Salary)"     Title="Salary"     Sortable="true" Format="C0" />
    </QuickGrid>
</QuickGridExtension>

2a. Enable the reset button in toolbar

<QuickGridExtension ShowResetButton="true" StorageKey="employees-grid">
    <QuickGrid Items="@_employees.AsQueryable()" TGridItem="Employee">
        <PropertyColumn Property="@(e => e.Name)"       Title="Name"       Sortable="true" />
        <PropertyColumn Property="@(e => e.Department)" Title="Department" Sortable="true" />
        <PropertyColumn Property="@(e => e.Location)"   Title="Location" />
    </QuickGrid>
</QuickGridExtension>

3. Resize only / Reorder only

@* Resize only — hide column chooser too *@
<QuickGridExtension EnableReorder="false" EnableColumnChooser="false" MinColumnWidth="120">
    <QuickGrid Items="@_items.AsQueryable()" TGridItem="Product">
        <PropertyColumn Property="@(p => p.Name)"  Title="Product" />
        <PropertyColumn Property="@(p => p.Price)" Title="Price" Format="C2" />
    </QuickGrid>
</QuickGridExtension>

@* Reorder only — hide column chooser too *@
<QuickGridExtension EnableResize="false" EnableColumnChooser="false">
    <QuickGrid Items="@_items.AsQueryable()" TGridItem="Product">
        <PropertyColumn Property="@(p => p.Name)"     Title="Product" />
        <PropertyColumn Property="@(p => p.Category)" Title="Category" />
    </QuickGrid>
</QuickGridExtension>

4. With virtualization and server-side data

<QuickGridExtension>
    <QuickGrid TGridItem="Order" ItemsProvider="@_ordersProvider" Virtualize="true" ItemSize="40">
        <PropertyColumn Property="@(o => o.OrderId)"  Title="Order #"  Sortable="true" />
        <PropertyColumn Property="@(o => o.Customer)" Title="Customer" Sortable="true" />
        <PropertyColumn Property="@(o => o.Total)"    Title="Total"    Sortable="true" Format="C2" />
        <PropertyColumn Property="@(o => o.Status)"   Title="Status" />
    </QuickGrid>
</QuickGridExtension>

5. Blazor Server App with pagination

Note: The component requires an interactive render mode. Without it, the grid renders as a static table and resize/reorder will not work.

@page "/employees"
@rendermode InteractiveServer
@using Microsoft.AspNetCore.Components.QuickGrid
@inject IEmployeeService EmployeeService

<QuickGridExtension>
    <QuickGrid Items="@_employees" TGridItem="Employee" Pagination="@_pagination">
        <PropertyColumn Property="@(e => e.Name)"       Title="Name"       Sortable="true" />
        <PropertyColumn Property="@(e => e.Department)" Title="Department" Sortable="true" />
        <PropertyColumn Property="@(e => e.Location)"   Title="Location" />
        <PropertyColumn Property="@(e => e.Salary)"     Title="Salary"     Sortable="true" Format="C0" />
    </QuickGrid>
</QuickGridExtension>

<Paginator State="@_pagination" />

@code {
    private IQueryable<Employee> _employees = Enumerable.Empty<Employee>().AsQueryable();
    private readonly PaginationState _pagination = new() { ItemsPerPage = 20 };

    protected override async Task OnInitializedAsync()
    {
        _employees = (await EmployeeService.GetAllAsync()).AsQueryable();
    }
}

6. localStorage auto-persistence with StorageKey and StorageScope

Column widths, order, visibility, and sort are automatically saved to localStorage and restored on the next page load. Use StorageScope to isolate preferences per user.

@page "/orders"
@rendermode InteractiveServer
@inject IHttpContextAccessor HttpContext

<QuickGridExtension StorageKey="orders-grid"
                    StorageScope="@_userId"
                    AutoRestoreSort="true">
    <QuickGrid Items="@_orders.AsQueryable()" TGridItem="Order">
        <PropertyColumn Property="@(o => o.OrderId)"  Title="Order #"  Sortable="true" />
        <PropertyColumn Property="@(o => o.Customer)" Title="Customer" Sortable="true" />
        <PropertyColumn Property="@(o => o.Total)"    Title="Total"    Sortable="true" Format="C2" />
        <PropertyColumn Property="@(o => o.Status)"   Title="Status" />
    </QuickGrid>
</QuickGridExtension>

@code {
    private List<Order> _orders = new();
    private string? _userId;

    protected override async Task OnInitializedAsync()
    {
        _userId = HttpContext.HttpContext?.User.FindFirst("sub")?.Value;
        _orders = await OrderService.GetAllAsync();
    }
}

7. Server-side sort restore using OnSortChanged and GridItemsProvider

For grids backed by a GridItemsProvider, use OnSortChanged to seed the sort state before data is fetched. This avoids the extra network requests that AutoRestoreSort would cause.

@page "/products"
@rendermode InteractiveServer
@using AtharvaITS.QuickGrid.Extensions.Models

<QuickGridExtension StorageKey="products-grid"
                    OnSortChanged="@HandleSortChanged">
    <QuickGrid TGridItem="Product" ItemsProvider="@_provider">
        <PropertyColumn Property="@(p => p.Name)"     Title="Name"     Sortable="true" />
        <PropertyColumn Property="@(p => p.Category)" Title="Category" Sortable="true" />
        <PropertyColumn Property="@(p => p.Price)"    Title="Price"    Sortable="true" Format="C2" />
        <PropertyColumn Property="@(p => p.Stock)"    Title="In Stock" Sortable="true" />
    </QuickGrid>
</QuickGridExtension>

@code {
    private GridItemsProvider<Product>? _provider;
    private string _sortField = nameof(Product.Name);
    private bool _sortAscending = true;

    protected override void OnInitialized()
    {
        _provider = async req =>
        {
            var result = await ProductService.GetPagedAsync(_sortField, _sortAscending, req.StartIndex, req.Count ?? 20);
            return GridItemsProviderResult.From(result.Items, result.TotalCount);
        };
    }

    private Task HandleSortChanged(QuickGridPreference pref)
    {
        // Map the stored column index back to the sort field name.
        _sortField = pref.SortColumnIndex switch
        {
            0 => nameof(Product.Name),
            1 => nameof(Product.Category),
            2 => nameof(Product.Price),
            3 => nameof(Product.Stock),
            _ => nameof(Product.Name)
        };
        _sortAscending = pref.SortDirection != "descending";
        return Task.CompletedTask;
    }
}

8. Export toolbar (CSV, Excel, PDF)

Set EnableExport="true". For all rows, assign OnExportRequested and set DataLoadMode when the grid uses virtualization or a GridItemsProvider. The example below uses GridExcelExporter and GridPdfExporter for full-data Excel and PDF.

@rendermode InteractiveServer
@using Microsoft.AspNetCore.Components.QuickGrid
@using AtharvaITS.QuickGrid.Extensions.Models
@using AtharvaITS.QuickGrid.Extensions.Exporting
@using System.Text

<QuickGridExtension EnableExport="true"
                    StorageKey="employees-export"
                    DataLoadMode="GridDataLoadMode.InMemoryFullDom"
                    ExportFileNameBase="Employees"
                    OnExportRequested="@HandleExport">
    <QuickGrid Items="@_employees.AsQueryable()" TGridItem="Employee">
        <PropertyColumn Property="@(e => e.Name)"       Title="Name"       Sortable="true" />
        <PropertyColumn Property="@(e => e.Department)" Title="Department" Sortable="true" />
    </QuickGrid>
</QuickGridExtension>

@code {
    private List<Employee> _employees = new();

    private Task<GridExportResult?> HandleExport(GridExportRequest request)
    {
        var baseName = string.IsNullOrWhiteSpace(request.FileNameBase) ? "employees_full" : request.FileNameBase;
        var rows = _employees.Select(e =>
            (IReadOnlyList<string>)request.ColumnOriginalIndicesInDisplayOrder
                .Select(i => CellFor(e, i)).ToList()).ToList();

        switch (request.Format)
        {
            case ExportFormat.Csv:
            {
                var sb = new StringBuilder();
                sb.AppendLine(string.Join(",", request.ColumnHeaders.Select(h => $"\"{h.Replace("\"", "\"\"")}\"")));
                foreach (var line in rows)
                    sb.AppendLine(string.Join(",", line.Select(c => $"\"{c.Replace("\"", "\"\"")}\"")));

                var bytes = Encoding.UTF8.GetBytes(sb.ToString());
                return Task.FromResult<GridExportResult?>(new GridExportResult
                {
                    Content = bytes,
                    ContentType = "text/csv; charset=utf-8",
                    FileName = $"{baseName}.csv"
                });
            }
            case ExportFormat.ExcelXml:
            {
                var bytes = GridExcelExporter.CreateXlsx(request.ColumnHeaders, rows);
                return Task.FromResult<GridExportResult?>(new GridExportResult
                {
                    Content = bytes,
                    ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
                    FileName = $"{baseName}.xlsx"
                });
            }
            case ExportFormat.Pdf:
            {
                var bytes = GridPdfExporter.CreateSimpleTablePdf(request.ColumnHeaders, rows, title: "Employees");
                return Task.FromResult<GridExportResult?>(new GridExportResult
                {
                    Content = bytes,
                    ContentType = "application/pdf",
                    FileName = $"{baseName}.pdf"
                });
            }
            default:
                return Task.FromResult<GridExportResult?>(null);
        }
    }

    private static string CellFor(Employee e, int colIdx) => colIdx switch
    {
        0 => e.Name,
        1 => e.Department,
        _ => ""
    };
}

For Virtualize="true" or ItemsProvider, set DataLoadMode="GridDataLoadMode.Virtualized" or ItemsProviderOrServerPaged and implement OnExportRequested to query the database (or call an export API) without relying on the DOM.

9. Programmatic save and restore to a database

Use GetPreference to capture the current layout and LoadPreference to restore a previously saved one from your own storage.

@page "/reports"
@rendermode InteractiveServer
@using AtharvaITS.QuickGrid.Extensions.Models
@inject IUserPreferenceService PreferenceService
@inject IHttpContextAccessor HttpContext

<button @onclick="SaveLayout">Save Layout</button>
<button @onclick="ResetLayout">Reset Layout</button>

<QuickGridExtension @ref="_grid">
    <QuickGrid Items="@_reports.AsQueryable()" TGridItem="Report">
        <PropertyColumn Property="@(r => r.Title)"    Title="Title"    Sortable="true" />
        <PropertyColumn Property="@(r => r.Category)" Title="Category" Sortable="true" />
        <PropertyColumn Property="@(r => r.Date)"     Title="Date"     Sortable="true" />
        <PropertyColumn Property="@(r => r.Author)"   Title="Author" />
    </QuickGrid>
</QuickGridExtension>

@code {
    private QuickGridExtension _grid = default!;
    private List<Report> _reports = new();
    private string? _userId;

    protected override async Task OnInitializedAsync()
    {
        _userId = HttpContext.HttpContext?.User.FindFirst("sub")?.Value;
        _reports = await ReportService.GetAllAsync();
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender && _userId is not null)
        {
            var saved = await PreferenceService.LoadAsync(_userId, "reports-grid");
            if (saved is not null)
                await _grid.LoadPreference(saved);
        }
    }

    private async Task SaveLayout()
    {
        var pref = _grid.GetPreference();
        if (_userId is not null)
            await PreferenceService.SaveAsync(_userId, "reports-grid", pref);
    }

    private async Task ResetLayout()
    {
        await _grid.ClearPreference();
    }
}

State Management

localStorage Auto-Persistence

Set StorageKey to automatically save and restore column widths, order, visibility, and sort state across page loads. No additional code is required — the component reads from and writes to localStorage on every user interaction.

<QuickGridExtension StorageKey="my-grid">
    ...
</QuickGridExtension>

Use StorageScope to namespace preferences per user, page, or tenant so that multiple users sharing the same browser profile (or the same user across different tenants) do not overwrite each other's layouts:

<QuickGridExtension StorageKey="employees-grid" StorageScope="@userId">
    ...
</QuickGridExtension>

The final localStorage key is eqg_pref_{StorageScope}_{StorageKey} (or eqg_pref_{StorageKey} when StorageScope is omitted).

QuickGridPreference Model

QuickGridPreference (namespace AtharvaITS.QuickGrid.Extensions.Models) represents a complete snapshot of the grid's column layout. It is the type passed to OnSortChanged, returned by GetPreference, and accepted by LoadPreference.

Property Type Description
ColumnOrder int[]? Original column indices in their current visual positions. null means default order.
ColumnWidths Dictionary<int, int>? Resized column widths in pixels, keyed by original column index.
HiddenColumns int[]? Original indices of columns currently hidden. null or empty means all columns visible.
SortColumnIndex int? Zero-based original index of the active sort column, or null when unsorted.
SortDirection string? "ascending", "descending", or null when unsorted.

Developer API Methods

These methods are available on the QuickGridExtension component reference (via @ref). They must be called after the component's first render (i.e., in or after OnAfterRenderAsync with firstRender = true).

Method Returns Description
GetPreference() QuickGridPreference Returns a snapshot of the current column layout — order, widths, visibility, and sort state. Use this to save the layout to your server or database.
LoadPreference(QuickGridPreference) Task Applies a saved preference to the live DOM and updates internal Blazor state. If StorageKey is set, the next user interaction will also persist this preference to localStorage.
ClearPreference() Task Removes the stored localStorage entry for this grid. The current in-memory layout is unchanged; the grid will start from its default layout on the next page load. No-op when StorageKey is not set.

Sort Persistence

Two modes are available depending on your data source:

AutoRestoreSort="true" (in-memory grids)

The component programmatically clicks the stored column header to restore the sort. Zero developer code required. Note that ascending sorts cause one extra data load and descending sorts cause two, because QuickGrid requires two clicks to reach descending order. Best suited for in-memory IQueryable data sources.

<QuickGridExtension StorageKey="my-grid" AutoRestoreSort="true">
    ...
</QuickGridExtension>

OnSortChanged callback (GridItemsProvider grids)

For server-side grids, subscribe to OnSortChanged to receive the stored sort on first render and every subsequent user sort. Use SortColumnIndex and SortDirection from the QuickGridPreference snapshot to update your GridItemsProvider query without triggering extra network requests.

<QuickGridExtension StorageKey="my-grid" OnSortChanged="@HandleSortChanged">
    ...
</QuickGridExtension>

@code {
    private Task HandleSortChanged(QuickGridPreference pref)
    {
        _sortField = MapIndexToField(pref.SortColumnIndex);
        _sortAscending = pref.SortDirection != "descending";
        return Task.CompletedTask;
    }
}

How It Works

Column Resize: After QuickGrid renders, extendedQuickGrid.js injects a <colgroup> and appends a 5 px drag handle to each header cell. Default drag keeps overall table width stable by balancing with a visible neighboring column; holding Shift switches to independent single-column resize. Final widths are reported back to Blazor and restored after every re-render.

Column Reorder: SortableJS is initialised on thead > tr. A drag-handle icon (⠿) is prepended to each header so accidental drags on column text are avoided. On drop, <td> cells in every <tbody> row are reordered to match the new header order (single DOM write per row via DocumentFragment). The order is stored in Blazor state and re-applied after every re-render.

Column Chooser: A "⚙ Columns" toolbar is injected above the scrollable table area. Clicking it opens a dropdown listing every column with a checkbox. Toggling a checkbox collapses the corresponding <col> (via visibility: collapse and width management) and updates matching <th>/<td> visibility. Each visibility change is reported back to Blazor via OnColumnVisibilityChanged so the hidden-column set is restored on every re-render (sort, filter, page change). The MutationObserver that already handles virtualization also applies visibility to newly added rows for hidden columns. Hidden state is tracked by the original column index (data-col-idx), so it survives column reorders.

State Management: On first render, Blazor loads any stored QuickGridPreference from localStorage (when StorageKey is set) and passes it as the initial preference to initGrid, ensuring the correct layout is applied before the first paint. Subsequent user interactions (resize, reorder, visibility toggle, sort) update both the Blazor-side state and localStorage atomically. On every QuickGrid re-render, reinitGrid re-applies the current Blazor state to the DOM — making column layout fully transparent to QuickGrid's sort, filter, and pagination cycles.

Export: When EnableExport is true, Export appears to the right of Columns. Choose Current Table view, All Data, Pages, or Rows; page and row inputs appear only for the selected mode. Bind ExportPaginationState (recommended) or ExportPagination for Pages and Rows server exports.

SortableJS: The Sortable.min.js file (v1.15.7, SortableJS) is bundled inside the package and served from _content/AtharvaITS.QuickGrid.Extensions/js/. No CDN access or manual download is required.


Limitations

Limitation Detail
Persistence requires explicit opt-in Set StorageKey for automatic localStorage persistence, or use GetPreference / LoadPreference to integrate with server-side storage. Without a StorageKey, column layout is in-memory only and is lost on page reload.
Reorder and visibility are DOM-level only Blazor component state for column positions is not updated; only the rendered DOM changes.
Fixed-width colgroup required The wrapper always injects a <colgroup>, switching the table to table-layout: fixed. Adjust MinColumnWidth or CSS if needed.
Column names in chooser come from header text If a column header uses complex markup, the chooser reads the text content after stripping resize handles. Provide clear text in Title for best results.
Current-table export scope Exports only rows present in the DOM. With Virtualize="true", ItemsProvider, or server-side pagination, that is usually a partial dataset unless every row is rendered. Use DataLoadMode and OnExportRequested for full exports.

🤝 Support

For support, feature requests, or bug reports:

📄 License

Copyright © 2026 Atharva IT Services Pvt. Ltd.

🏢 About Atharva IT Services

Atharva IT Services Pvt. Ltd. a global software development company in Ahmedabad, excels in e-commerce solutions, web development, and software testing.

Made with ❤️ by Atharva IT Services

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 91 5/26/2026
1.1.0 97 5/11/2026
1.0.3 92 5/5/2026
1.0.2 94 5/1/2026
1.0.1 89 4/30/2026
1.0.0 91 4/30/2026

# Release Notes

## v1.2.0
- Added **Export** toolbar for QuickGrid.

## v1.1.0
- Added Column Chooser: toggle individual column visibility via a "⚙ Columns" dropdown above the grid
- Added State Management: automatic localStorage persistence via StorageKey and StorageScope parameters
- Added QuickGridPreference model with GetPreference / LoadPreference / ClearPreference developer API
- Added sort persistence: AutoRestoreSort parameter and OnSortChanged event callback
- Added Shift-resize mode: hold Shift while dragging a resize handle to resize a single column independently
- Fixed column resize issue causing unexpected behavior

## v1.0.2
- Added README.md to NuGet package

## v1.0.1
- Fixed: column re-ordering did not preserve cell values in reordered columns

## v1.0.0
- Initial release with Column Resize and Column Re-Order functionality