AtharvaITS.QuickGrid.Extensions 1.4.1

dotnet add package AtharvaITS.QuickGrid.Extensions --version 1.4.1
                    
NuGet\Install-Package AtharvaITS.QuickGrid.Extensions -Version 1.4.1
                    
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.4.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="AtharvaITS.QuickGrid.Extensions" Version="1.4.1" />
                    
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.4.1
                    
#r "nuget: AtharvaITS.QuickGrid.Extensions, 1.4.1"
                    
#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.4.1
                    
#: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.4.1
                    
Install as a Cake Addin
#tool nuget:?package=AtharvaITS.QuickGrid.Extensions&version=1.4.1
                    
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.
  • Conditional Formatting — optional Format toolbar (left of Columns) lets end users create, edit, and delete highlight rules in a modal. Also includes Header Style and Text Alignment for grid-wide formatting. Rules and formatting persist with StorageKey and survive sort, filter, page changes, and QuickGrid re-renders.
  • State Management — persist column widths, order, visibility, sort state, user conditional formatting rules, header style, and text alignment 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.
EnableConditionalFormatEditor bool false When true, shows a Format control on the right toolbar so users can manage conditional formatting rules, header style, and text alignment.
OnConditionalFormatsChanged EventCallback<IReadOnlyList<GridConditionalFormatRule>> Raised when user-defined formatting rules change (add, edit, delete, or reset). Use to sync rules to your server.
OnHeaderStyleChanged EventCallback<GridHeaderStyle?> Raised when the user-defined grid header style changes (save, clear, or reset). Use to sync header appearance to your server.
OnGridAlignmentChanged EventCallback<GridAlignment?> Raised when the user-defined grid text alignment changes (save, clear, or reset). Use to sync alignment to your server.

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();
    }
}

10. Conditional formatting (user-defined highlight rules)

Opt-in feature for end-user-defined cell and row highlighting. Enable with EnableConditionalFormatEditor="true". Rules are created in the browser — no hardcoded rule list is required in your page.

  1. Format toolbar — a Format button appears on the right toolbar (before Columns) when the editor is enabled. The modal includes Add rule, Header Style, and Text Alignment.
  2. Rule modal — users open a centered modal to view all rules, add new ones, or return to the list after editing.
  3. Column picker — each rule targets one column by its original index (data-col-idx: first PropertyColumn / TemplateColumn is 0).
  4. Operators — Equal, Not equal, Greater/Less than (and or equal), Between, Not between, Contains, Does not contain, Starts with, Ends with, Is empty, Is not empty.
  5. ScopeCell styles only matching cells in that column; Row styles every visible cell in the row when the trigger column matches.
  6. Appearance — background color, text color, font weight, and font style applied as inline styles on matching cells.
  7. Displayed values — rules compare against rendered cell text (after QuickGrid Format, e.g. currency), not raw model values.
  8. List order — when multiple rules could apply, the first matching rule in the list wins for each cell or row.
  9. Persistence — with StorageKey, rules are saved in localStorage as UserConditionalFormatRules on QuickGridPreference and restored on the next visit.
  10. Re-render safe — formatting is re-applied after sort, filter, pagination, and data refresh; virtualized rows receive formatting via MutationObserver.

Optional: bind OnConditionalFormatsChanged to receive rule updates, or use GetPreference() / LoadPreference() to save and restore rules from your database alongside column layout.

@page "/employees"
@rendermode InteractiveServer
@using Microsoft.AspNetCore.Components.QuickGrid
@using AtharvaITS.QuickGrid.Extensions.Models

<QuickGridExtension StorageKey="employees-grid"
                    EnableConditionalFormatEditor="true"
                    OnConditionalFormatsChanged="@HandleConditionalFormatsChanged">
    <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" />
        <PropertyColumn Property="@(e => e.Status)"     Title="Status"     Sortable="true" />
    </QuickGrid>
</QuickGridExtension>

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

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

    private Task HandleConditionalFormatsChanged(IReadOnlyList<GridConditionalFormatRule> rules)
    {
        // Optional: persist rules to your API, e.g. await PreferenceService.SaveCfRulesAsync(rules);
        return Task.CompletedTask;
    }
}

Typical user workflow: click FormatAdd rule → choose column (e.g. Salary), operator (e.g. Less than), value (e.g. 50000), scope (Cell or Row), and colors → Save. Matching rows or cells highlight immediately. Reset (when ShowResetButton="true") clears user formatting rules, header style, and text alignment along with other saved preferences.

Header style (inside the Format modal)

When EnableConditionalFormatEditor="true", the Format modal also includes a Header Style button. Users can customize the appearance of all column headers at once:

Setting Options
Background color Color picker
Text color Color picker
Font size 12px–20px or default
Font weight Normal, Bold, or default
Text transform Uppercase, Lowercase, Capitalize, or default

Changes preview live on the grid header while editing. Save commits the style; Clear removes it. The saved style is stored on QuickGridPreference.HeaderStyle when StorageKey is set, restored on page load, and reported to Blazor via OnHeaderStyleChanged. Header style is re-applied after every QuickGrid re-render (sort, filter, data refresh).

<QuickGridExtension StorageKey="employees-grid"
                    EnableConditionalFormatEditor="true"
                    OnHeaderStyleChanged="@HandleHeaderStyleChanged">
    ...
</QuickGridExtension>

@code {
    private Task HandleHeaderStyleChanged(GridHeaderStyle? style)
    {
        // Optional: persist style to your API
        return Task.CompletedTask;
    }
}
Text alignment (inside the Format modal)

When EnableConditionalFormatEditor="true", the Format modal also includes a Text Alignment button. Users can align all column headers and body cells at once:

Setting Options
Text alignment Left, Center, Right

Changes preview live on the grid while editing. Save commits the alignment to every <th> and <td>; Clear removes it. The saved value is stored on QuickGridPreference.GridAlignment (TextAlign: "left", "center", or "right") when StorageKey is set, restored on page load, and reported to Blazor via OnGridAlignmentChanged. Text alignment is re-applied after every QuickGrid re-render (sort, filter, data refresh); virtualized rows receive alignment via MutationObserver.

<QuickGridExtension StorageKey="employees-grid"
                    EnableConditionalFormatEditor="true"
                    OnGridAlignmentChanged="@HandleGridAlignmentChanged">
    ...
</QuickGridExtension>

@code {
    private Task HandleGridAlignmentChanged(GridAlignment? alignment)
    {
        // alignment?.TextAlign is "left", "center", or "right"
        // Optional: persist via GetPreference() to your API
        return Task.CompletedTask;
    }
}

Typical user workflow: click FormatText Alignment → choose Left, Center, or Right → Save.


State Management

localStorage Auto-Persistence

Set StorageKey to automatically save and restore column widths, order, visibility, sort state, user conditional formatting rules, header style, and text alignment 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.
UserConditionalFormatRules GridConditionalFormatRule[]? User-defined conditional formatting rules from the Format toolbar. null or empty when no rules exist.
HeaderStyle GridHeaderStyle? User-defined header appearance (background, text color, font size, weight, text transform) from the Header Style editor. null when not set.
GridAlignment GridAlignment? User-defined text alignment for the entire grid from the Text Alignment editor. TextAlign is "left", "center", or "right". null when not set.

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, sort state, conditional formatting rules, header style, and text alignment. 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.

Conditional Formatting: When EnableConditionalFormatEditor is true, Format appears on the right toolbar before Columns. Users manage rules in a modal (add, edit, delete), open Header Style to customize all column headers (background, text color, font size, weight, text transform), and open Text Alignment to set Left / Center / Right on all headers and body cells. Each rule specifies a column, comparison operator, optional values, Cell or Row scope, and inline style (colors, font). The engine reads displayed cell text, applies styles to matching rows or cells, and re-applies after every QuickGrid re-render. Rules are stored in state.userConditionalFormats, synced to QuickGridPreference.UserConditionalFormatRules in localStorage when StorageKey is set, and reported to Blazor via NotifyUserConditionalFormatsChanged. Header style is stored in QuickGridPreference.HeaderStyle and applied to every <th> on init, reinit, and save. Text alignment is stored in QuickGridPreference.GridAlignment and applied to every <th> and <td> on init, reinit, and save; QuickGrid flex headers use justify-content so header text aligns with body cells.

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.
Conditional formatting is user-driven Rules are created in the Format modal, not from a ConditionalFormats parameter. Enable EnableConditionalFormatEditor and optionally StorageKey for persistence. Header style and text alignment are also user-driven from the same modal.

🤝 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.4.1 103 7/30/2026
1.4.0 101 7/30/2026
1.3.0 115 7/2/2026
1.2.1 112 6/24/2026
1.2.0 113 5/26/2026
1.1.0 110 5/11/2026
1.0.3 103 5/5/2026
1.0.2 106 5/1/2026
1.0.1 101 4/30/2026
1.0.0 104 4/30/2026

# Release Notes

## v1.4.1
-Updated Release Note to NuGet package.

## v1.4.0
- Added **Text Alignment** editor inside the Format modal (`GridAlignment`, `OnGridAlignmentChanged`) for aligning all column headers and body cells (Left, Center, Right) with persistence via `QuickGridPreference.GridAlignment`.

## V1.3.0
- Added **Header Style** editor inside the Format modal (`GridHeaderStyle`, `OnHeaderStyleChanged`) for customizing grid header background, text color, font size, weight, and text transform with persistence via `QuickGridPreference.HeaderStyle`.

## v1.2.1
- Added **Conditional formatting** toolbar editor (`EnableConditionalFormatEditor`) for user-defined rules with save/load via `StorageKey`.
- Added rule models (`GridConditionalFormatRule`, `ConditionalFormatOperator`, `ConditionalFormatScope`, `ConditionalFormatStyle`).

## 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