# PagingTagHelper v1.0.0: Enterprise-Ready Pagination för modern ASP.NET Core

<datetime class="hidden">2025-11-07T19:12</datetime>

<!--category-- Nuget, ASP.NET Core, HTMX, Alpine.js, Javascript, TagHelper, PagingTagHelper -->
> ANMÄRKNING: KOMMUNKTION, bara sätta de avslutande touchs till det. [Följ med på GitHub! ](https://github.com/scottgal/mostlylucid.pagingtaghelper).

**Detta är bara för att visa er alla JAG gör framsteg med denna kontroll! Det kommer att vara värt väntan.**

## Inledning

Efter månader av utveckling och värdefull feedback från samhället (5.7k+ nedladdningar!), Jag är glad att meddela att PagingTagHelper biblioteket har nått version 1.0.0. Detta är inte bara en version nummer bulle – det representerar en fullständig mognad av biblioteket med funktioner som gör det lämpligt för verkliga, produktionsapplikationer.

Om du har följt den här serien, kommer du ihåg att vi började med [Skalning av barben](https://www.mostlylucid.net/blog/pagingtaghelper), tillagd [sorterabara rubriker](https://www.mostlylucid.net/blog/pagingtaghelperpt11), och extraherad [kontroller av sidstorleken](https://www.mostlylucid.net/blog/pagingtaghelperpt2). Version 1.0.0 tar allt vi har lärt oss och lägger till kritiska företag funktioner:

- **Fortsättning med Token Pagination** för NoSQL-databaser (Cosmos DB, DynamoDB, Azure Table Storage)
- **Lokalisering på flera språk** stödja 8 språk ur rutan
- **Flexibla JavaScript- lägen** från HTMX till noll-JavaScript
- **Ren tailwind vyer** utan DaisyUI beroenden
- **Smart URL Parameterbevarande** över hela navigeringen
- **HTMX 2,0.4** uppgradering med bakåtkompatibilitet

Låt oss dyka in i var och en av dessa funktioner och se hur de arbetar tillsammans för att skapa en verkligt flexibel paginering lösning.

[![Hämta](https://img.shields.io/nuget/v/mostlylucid.pagingtaghelper.svg)](https://www.nuget.org/packages/mostlylucid.pagingtaghelper)
[![Hämta](https://img.shields.io/nuget/dt/mostlylucid.pagingtaghelper.svg)](https://www.nuget.org/packages/mostlylucid.pagingtaghelper)

[TOC]

## Fortsättning Token Pagination {#fortsättning-token-pagination}

Traditionell paginering fungerar vackert med SQL-databaser där du enkelt kan `SKIP` och `TAKE` Men vad händer när du arbetar med NoSQL-databaser som Cosmos DB, DynamoDB eller Azure Table Storage? Dessa databaser stöder inte offsetbaserad pagination – istället använder de sig av **fortsättningspolletter**.

### Understanding Token-based Pagination {#understanding-token-based-pagination}

Här är hur fortsättning token pagination skiljer sig från traditionell personsökning:

```mermaid
graph TD
    A[Traditional Paging] --> B[Page 1: OFFSET 0 LIMIT 10]
    A --> C[Page 2: OFFSET 10 LIMIT 10]
    A --> D[Page 3: OFFSET 20 LIMIT 10]

    E[Token-Based Paging] --> F[Page 1: No token]
    F --> G[Returns: Data + Token_A]
    G --> H[Page 2: Token_A]
    H --> I[Returns: Data + Token_B]
    I --> J[Page 3: Token_B]

    style A stroke:#0ea5e9,stroke-width:3px
    style E stroke:#ec4899,stroke-width:3px
```

**Traditionell packning:**

- Du anger exakt vilka poster som ska hämtas (OFFSET/LIMIT)
- Du kan hoppa till vilken sida som helst direkt
- Databasen måste söka igenom alla tidigare poster

**Token-baserad packning:**

- Databasen returnerar en ogenomskinlig token som representerar "var man ska fortsätta"
- Token-formatet är databasspecifikt och ogenomskinligt för kunden
- Framåt navigering är naturlig, bakåt navigering kräver symbolisk historik

### Genomförande av fortsättningssidan {#fortsättningssidans genomförande}

Det nya `<continuation-pager>` tag helper gör genomförandet token-baserad pagination enkelt. Först, skapa en modell som implementerar `IContinuationPagingModel`:

```csharp
public class ProductPagingViewModel : IContinuationPagingModel
{
    public string? NextPageToken { get; set; }
    public bool HasMoreResults { get; set; }
    public int PageSize { get; set; } = 25;
    public int CurrentPage { get; set; } = 1;
    public Dictionary<int, string>? PageTokenHistory { get; set; }
    public ViewType ViewType { get; set; } = ViewType.TailwindAndDaisy;

    // Your actual data
    public List<Product> Products { get; set; } = new();
}
```

Gränssnittet är minimalt men kraftfullt. Låt oss titta på vad varje egenskap gör:

- `NextPageToken`: Den token att hämta nästa sida (som tillhandahålls av din databas)
- `HasMoreResults`: Booleska indikerar om det finns fler sidor
- `PageSize`: Föremål per sida
- `CurrentPage`: Visa endast sidnummer för UI
- `PageTokenHistory`: Dictionary mappa sidnummer till polletter för bakåt navigering
- `ViewType`: Vilket CSS-ramverk att använda för rendering

Låt oss nu genomföra en controller åtgärd som simulerar Cosmos DB-stil pagination:

```csharp
[Route("Products")]
public async Task<IActionResult> Products(
    int currentPage = 1,
    int pageSize = 25,
    string? pageToken = null,
    string? tokenHistory = null)
{
    // Simulate fetching from Cosmos DB
    var cosmosResults = await _cosmosService.GetProductsAsync(
        pageSize: pageSize,
        continuationToken: pageToken
    );

    // Deserialize token history for backward navigation
    var history = string.IsNullOrEmpty(tokenHistory)
        ? new Dictionary<int, string>()
        : JsonSerializer.Deserialize<Dictionary<int, string>>(tokenHistory)
          ?? new Dictionary<int, string>();

    // Store current token in history
    if (!string.IsNullOrEmpty(pageToken))
    {
        history[currentPage] = pageToken;
    }

    var viewModel = new ProductPagingViewModel
    {
        CurrentPage = currentPage,
        PageSize = pageSize,
        NextPageToken = cosmosResults.ContinuationToken,
        HasMoreResults = cosmosResults.HasMoreResults,
        PageTokenHistory = history,
        Products = cosmosResults.Items
    };

    if (Request.IsHtmx())
    {
        return PartialView("_ProductList", viewModel);
    }

    return View(viewModel);
}
```

Detta genomförande visar hur token historia möjliggör bakåt navigering. Utan det, fortsättning token pagination skulle bara stödja "Next" knappar. Genom att underhålla en ordbok av sida till token kartläggningar, kan vi stödja både "Föregående" och "Next" navigering.

Här är flödet av symbolisk ackumulation visualiserade:

```mermaid
sequenceDiagram
    participant User
    participant Controller
    participant Database
    participant TokenHistory

    User->>Controller: Request Page 1 (no token)
    Controller->>Database: Query with no token
    Database-->>Controller: Data + Token_A
    Controller->>TokenHistory: Store Token_A for page 1
    Controller-->>User: Display Page 1

    User->>Controller: Request Page 2 (Token_A)
    Controller->>Database: Query with Token_A
    Database-->>Controller: Data + Token_B
    Controller->>TokenHistory: Add Token_B for page 2
    Controller-->>User: Display Page 2

    User->>Controller: Request Page 1 (retrieve from history)
    Controller->>TokenHistory: Get Token for Page 1
    Controller->>Database: Query with Token_A
    Database-->>Controller: Data + Token_A
    Controller-->>User: Display Page 1
```

I din Razor-vy är det enkelt att använda fortsättningssökaren:

```razor
@model ProductPagingViewModel

<div id="product-container">
    <table class="table">
        <thead>
            <tr>
                <th>Product</th>
                <th>Company</th>
                <th>Price</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in Model.Products)
            {
                <tr>
                    <td>@product.Name</td>
                    <td>@product.CompanyName</td>
                    <td>$@product.Price.ToString("N2")</td>
                </tr>
            }
        </tbody>
    </table>

    <continuation-pager
        model="Model"
        htmx-target="#product-container"
        show-page-number="true"
        show-pagesize="true" />
</div>
```

Tagghjälparen automatiskt:

- Serializes token historia till frågeparametrar
- Bygger navigationswebbadresser med riktiga polletter
- Inaktiverar "Föregående" när på sidan 1
- Inaktiverar "Nästa" när `HasMoreResults` är falskt
- Bevarar alla andra frågeparametrar (sök, filter, etc.)

### Tokens historia för bakåtgående navigering {#token-historia}

Genialet med den symboliska historien tillvägagångssätt är att det är helt frivilligt. Om du bara behöver "Next" navigering (oändlig skriftrulle, till exempel), kan du ignorera symboliska historia helt:

```razor
<continuation-pager
    model="Model"
    enable-token-accumulation="false"
    show-page-number="false" />
```

Detta innebär bara en "Nästa" knapp utan sidindikatorer eller historikhantering.

För full navigering serialiseras tokenhistoriken automatiskt som JSON i frågesträngen. Så här ser en URL ut med symbolisk historik:

```
/Products?currentPage=3&pageSize=25&pageToken=abc123&tokenHistory=%7B%221%22%3A%22xyz789%22%2C%222%22%3A%22abc123%22%7D
```

och `tokenHistory` parametern innehåller den kodade ordboken, vilket gör bakåt navigation sömlös.

### Numrerad sidnavigering {#numrerad sida-navigation}

En av de viktigaste UX förbättringarna i fortsättningssökaren är **numrerade sidknappar**När du navigerar framåt visar sökaren klickbara sidnummer för alla besökta sidor:

```
Initial page 1:    [Next →]
After next click:  [← Prev] [1] [2 active] [3 disabled] [Next →]
After next click:  [← Prev] [1] [2] [3 active] [4 disabled] [Next →]
Click page 2:      [← Prev] [1] [2 active] [3] [4 disabled] (no next - not visited yet)
```

Detta ger traditionell paginering UX samtidigt som du behåller token-baserad bakgrundsarkitektur. Implementeringen lagrar polletter för varje besökt sida, vilket tillåter direkt navigering till någon tidigare åtkomlig sida.

**Begränsad tillväxt i historien:**

För att förhindra användning av obegränsat minne, set `max-history-pages` (standard: 20):

```razor
<continuation-pager
    model="Model"
    max-history-pages="50"
    show-page-number="true" />
```

När gränsen är nådd klipps de äldsta sidpolletterna automatiskt.

### Kritiska: Förfrågan Parameterbevarande {#query-parameter-bevarande}

**Detta är det viktigaste inslaget i fortsatt personsökarimplementering.**

Fortsättning tokens är bara giltiga med samma frågekontext (filter, typer, sökningar) som genererade dem. Att använda en token med olika frågeparametrar returnerar felaktig data eller misslyckas helt.

Fortsättningssökaren bevarar automatiskt ALLA frågeparametrar utom sina egna:

```html
<!-- URL with filters -->
/Products?category=electronics&brand=acme&minPrice=100

<!-- After clicking Next -->
/Products?category=electronics&brand=acme&minPrice=100&currentPage=2&pageToken=xyz123&tokenHistory={...}

<!-- All filters preserved! Token is valid because query context matches. -->
```

Du kan inaktivera detta beteende om det behövs:

```razor
<continuation-pager
    model="Model"
    preserve-query-parameters="false" />
```

Men det här är **kraftigt modfälld** Om du inte är helt säker på att dina polletter inte beror på frågesammanhang.

**Varför detta betyder något:**

Exempel på Cosmos DB:

```csharp
// Page 1 with filter
var query = container.GetItemQueryIterator<Product>(
    "SELECT * FROM c WHERE c.category = 'electronics'",
    continuationToken: null
);
var response = await query.ReadNextAsync();
// Returns: Products + Token_A

// Page 2 with SAME filter - Token_A is valid
var query2 = container.GetItemQueryIterator<Product>(
    "SELECT * FROM c WHERE c.category = 'electronics'",
    continuationToken: Token_A  // ✅ Works!
);

// Page 2 with DIFFERENT filter - Token_A is invalid
var query3 = container.GetItemQueryIterator<Product>(
    "SELECT * FROM c WHERE c.category = 'computers'",
    continuationToken: Token_A  // ❌ Wrong results or error!
);
```

Fortsättningssökarens automatiska parameterbevarande säkerställer att polletter alltid används i sitt ursprungliga frågesammanhang.

---


## Lokaliseringsstöd {# localization- support}

Moderna applikationer tjänar global publik, och paginering kontroller måste tala dina användares språk. Version 1.0.0 innehåller omfattande localization stöd inbyggt rätt i biblioteket.

### Inbyggda språk {#inbyggda språk}

Biblioteket skickar med översättningar för 8 språk:

Obligatoriskt ("Kod")
|------|----------|
| `en` på engelska (standard)  på engelska
| `de` på tyska (Deutsch)  på engelska
| `es` på spanska (Español)
| `fr` på franska (Français)
| `it` på italienska (Italien)
| `pt` på portugisiska (Português)
| `ja` Japanska (engelska) och japanska (engelska)
| `zh-Hans` på kinesiska förenklas ()

All text är lokaliserad, inklusive:

- Föregående/Nästa/Första/Sista knappetiketter
- Sammanfattning av sidan ("Visa X till Y av Z objekt")
- ARIA-etiketter för tillgänglighet
- Sidstorleksetikett ("Items per sida")

Lokaliseringssystemet drivs av `.resx` resursfiler, vilket gör det enkelt att lägga till dina egna språk. Alla resursfiler är i `mostlylucid.pagingtaghelper/Resources/`.

### Lokalisering Användning {# localization- användning}

Att använda lokalisering är enkelt. Lägg bara till `language` egenskap:

```razor
<paging
    model="Model"
    language="de"
    show-summary="true"
    first-last-navigation="true" />
```

Detta gör all text på tyska:

```html
<!-- Previous button -->
<button>‹ Vorherige</button>

<!-- Summary -->
<div class="text-sm text-gray-600">
    Zeige 1 bis 10 von 256 Einträgen
</div>

<!-- Next button -->
<button>Nächste ›</button>
```

För dynamisk språkbyte baserat på användarinställningar, ange språket i din controller:

```csharp
public async Task<IActionResult> Products(
    int page = 1,
    int pageSize = 10,
    string language = "en")
{
    var pagingModel = await GenerateModel(page, pageSize);
    ViewBag.SelectedLanguage = language;
    return View(pagingModel);
}
```

Sedan, i din vy, skapa en språkväljare:

```razor
@{
    var selectedLanguage = ViewBag.SelectedLanguage as string ?? "en";
    var languages = new Dictionary<string, string>
    {
        { "en", "English" },
        { "de", "German" },
        { "es", "Spanish" },
        { "fr", "French" },
        { "it", "Italian" },
        { "pt", "Portuguese" },
        { "ja", "Japanese" },
        { "zh-Hans", "Chinese" }
    };
}

<select onchange="window.location.href='/Products?language=' + this.value">
    @foreach (var lang in languages)
    {
        <option value="@lang.Key" selected="@(lang.Key == selectedLanguage)">
            @lang.Value
        </option>
    }
</select>

<paging
    model="Model"
    language="@selectedLanguage"
    link-url="/Products" />
```

Du kan också åsidosätta enskilda textsträngar medan du fortfarande drar nytta av lokalisering för andra element:

```razor
<paging
    model="Model"
    language="ja"
    previous-page-text="戻る"
    next-page-text="次へ"
    summary-template="全{TotalItems}件中 {StartItem}～{EndItem}件を表示" />
```

och `PagingLocalizer` service hanterar kulturspecifik formatering automatiskt. Om du passerar en ogiltig språkkod, det graciöst faller tillbaka till engelska.

För HTMX integration, vill du bevara språket över förfrågningar:

```html
<script>
    htmx.on('htmx:configRequest', function(event) {
        if (event.detail.path.includes('/Products')) {
            event.detail.parameters.language = '@selectedLanguage';
        }
    });
</script>
```

Detta säkerställer att HTMX partiella vyuppdateringar upprätthåller det valda språket.

---


## JavaScript- lägen {# javascript- modes}

En av de mest betydande förbättringarna i v1.0.0 är införandet av flexibla JavaScript-lägen. Tidigare hade du ett booleskt val: `use-htmx="true"` eller `use-htmx="false"`. Nu har du fem olika lägen, var och en optimerad för olika scenarier.

### Tillgängliga lägen {# tillgängliga- lägen}

Här är den fullständiga uppdelningen av JavaScript-lägen:

```mermaid
graph TD
    A[JavaScript Modes] --> B[HTMX]
    A --> C[HTMXWithAlpine]
    A --> D[Alpine]
    A --> E[PlainJS]
    A --> F[NoJS]

    B --> B1[Uses HTMX for partial updates]
    B --> B2[hx-get, hx-target, hx-swap]

    C --> C1[HTMX + Alpine.js directives]
    C --> C2[Enhanced interactivity]

    D --> D1[Pure Alpine.js]
    D --> D2[x-data, @click handlers]

    E --> E1[Vanilla JavaScript]
    E --> E2[onclick handlers]

    F --> F1[Zero JavaScript]
    F --> F2[Standard anchor links & forms]

```

Låt oss se varje läge i funktion:

**1. HTMX-läge (förvalt)**

```razor
<paging
    model="Model"
    js-mode="HTMX"
    htmx-target="#results-container" />
```

Rendrare:

```html
<button hx-get="/Products?page=2" hx-target="#results-container" hx-swap="outerHTML">
    Next ›
</button>
```

Perfekt för dynamiska siduppdateringar utan helsida omladdningar. Detta är det rekommenderade läget för moderna ASP.NET Core-applikationer.

**2. HTMXMed alpinläge**

```razor
<paging
    model="Model"
    js-mode="HTMXWithAlpine"
    htmx-target="#results-container" />
```

Rendrare:

```html
<button
    x-data
    hx-get="/Products?page=2"
    hx-target="#results-container"
    hx-swap="outerHTML">
    Next ›
</button>
```

Kombinerar HTMX för navigering med Alpine.js för ytterligare interaktivitet på klientsidan. Använd detta när du behöver reaktiva UI-element vid sidan av paginering (ladda in indikatorer, animationer, validering på klientsidan).

**3. Alpina läge**

```razor
<paging
    model="Model"
    js-mode="Alpine" />
```

Rendrare:

```html
<button
    x-data
    @click="window.location.href = '/Products?page=2'">
    Next ›
</button>
```

Pure Alpine.js utan HTMX. Användbar när du redan använder Alpine.js men inte vill HTMX beroenden.

**4. Enkelt läge för JS**

```razor
<paging
    model="Model"
    js-mode="PlainJS" />
```

Rendrare:

```html
<button onclick="window.location.href = '/Products?page=2'">
    Next ›
</button>
```

Inga ramberoenden, bara vanilj JavaScript. Detta läge innehåller också en hjälpare för sidstorleksändringar:

```razor
@Html.PageSizeOnchangeSnippet()
```

Detta injicerar det nödvändiga JavaScriptet för att hantera ändringar i sidstorlek utan HTMX.

**5. NoJS- läge**

```razor
<paging
    model="Model"
    js-mode="NoJS" />
```

Rendrare:

```html
<!-- Navigation uses standard anchor links -->
<a href="/Products?page=2">Next ›</a>

<!-- Page size uses a form with submit button -->
<form method="get" action="/Products">
    <input type="hidden" name="page" value="1" />
    <select name="pageSize" onchange="this.form.submit()">
        <option value="10">10</option>
        <option value="25" selected>25</option>
        <option value="50">50</option>
    </select>
    <noscript>
        <button type="submit">Update</button>
    </noscript>
</form>
```

Noll JavaScript krävs. Perfekt för:

- Tillgänglighetskrav
- Scenarier för progressiv förbättring
- Miljöer där JavaScript är inaktiverat
- SEO-kritiska sidor där du vill ha krypande-vänlig navigering

Skönheten i detta system är att **alla lägen bevara dina befintliga sökparametrar**. Oavsett om du filtrerar efter kategori, sökning, eller sortering, håller pagineringen ditt tillstånd automatiskt.

### Migrering från use-htmx {#migrering-from-use-htmx}

För bakåtkompatibilitet, den gamla `use-htmx` Egenskapen fortfarande fungerar:

```razor
<!-- Old syntax (still works) -->
<paging model="Model" use-htmx="true" />
<!-- Equivalent to js-mode="HTMX" -->

<paging model="Model" use-htmx="false" />
<!-- Equivalent to js-mode="PlainJS" -->
```

Jag rekommenderar dock att jag flyttar till det nya `js-mode` attribut för tydlighet:

```razor
<!-- New syntax (recommended) -->
<paging model="Model" js-mode="HTMX" />
<paging model="Model" js-mode="PlainJS" />
```

---


## VisaType förbättringar {#viewtype-enhancements}

Version 1.0.0 introducerar två viktiga ViewType tillägg som behandlar vanliga verkliga scenarier.

### Ren tailwind {#ren-tailwind}

Tidigare, om du ville ha TailwindCSS styling, fick du `TailwindAndDaisy` Visa som använder DaisyUI komponenter. Detta är bra om du redan använder DaisyUI, men tänk om du vill ha ren Tailwind utan DaisyUI beroende?

Ange `ViewType.Tailwind`:

```razor
<paging
    model="Model"
    view-type="Tailwind" />
```

Detta innebär att endast använda standard Tailwind verktygsklasser:

```html
<div class="flex gap-2 items-center">
    <button class="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700">
        ‹ Previous
    </button>

    <div class="px-3 py-1 text-sm font-medium bg-gray-100 dark:bg-gray-700 dark:text-white rounded-md">
        Page 1
    </div>

    <button class="px-4 py-2 text-sm font-medium rounded-md bg-blue-600 text-white hover:bg-blue-700">
        Next ›
    </button>
</div>
```

Ej tillämpligt `btn`, `badge`, eller `join` klasser – bara ren Tailwind. Detta ger dig fullständig kontroll över styling utan komponentbibliotek beroenden.

**Jämförelse:**

på visningstyp  på CSS-ramverket  på komponentbiblioteket  på användningsfall  på plats
|----------|---------------|-------------------|----------|
| `TailwindAndDaisy` på TailwindCSS  på DaisyUI  på projekt som redan använder DaisyUI
| `Tailwind` på TailwindCSS  på inget sätt  på ren Tailwind projekt
| `Bootstrap` Komponenter till bootstrap-bootstrap-bootstrap-projekt
| `Plain` Inbäddade CSS  och ingen  på inga ramar beroenden  på
| `NoJS` Inbäddade CSS  med noll JavaScript-krav

### NoJS- läge {# nojs- läge}

och `NoJS` ViewType kombinerar noll JavaScript med vanlig CSS-styling:

```razor
<paging
    model="Model"
    view-type="NoJS"
    show-pagesize="true" />
```

Viktiga skillnader från andra vytyper:

1. **Navigation använder ankarlänkar**, inte knappar:

```html
<a href="/Products?page=2" class="pager-button">Next ›</a>
```

2. **Sidstorleksväljaren är ett formulär**:

```html
<form method="get" action="/Products" class="page-size-form">
    <!-- Preserves all current query parameters as hidden inputs -->
    <input type="hidden" name="search" value="laptop" />
    <input type="hidden" name="category" value="electronics" />

    <!-- Reset to page 1 when changing page size -->
    <input type="hidden" name="page" value="1" />

    <label for="pageSize">Items per page:</label>
    <select name="pageSize" onchange="this.form.submit()">
        <option value="10">10</option>
        <option value="25" selected>25</option>
        <option value="50">50</option>
    </select>

    <!-- Button visible when JavaScript is disabled -->
    <noscript>
        <button type="submit" class="page-size-button">Update</button>
    </noscript>
</form>
```

och `onchange="this.form.submit()"` ger bekvämlighet när JavaScript är tillgängligt, men `<noscript>` Knappen säkerställer full funktionalitet när den inte är det.

---


## Webbadress Parameterbevarande {#url-parameter-bevarande}

En av de mest frustrerande aspekterna av paginering implementationer är att förlora dina filter, sökord, eller sortera ordning när du navigerar mellan sidor. Version 1.0.0 löser detta elegant genom **automatiskt bevara alla frågeparametrar utom pagineringskontrollens egna parametrar**.

Denna funktion fungerar identiskt över **både vanliga personsökare och fortsättningssökare**, och över **alla JavaScript-lägen och visningstyper**.

Så här fungerar det internt:

```csharp
string BuildQueryString(string? token, int page)
{
    var query = new Dictionary<string, string>();

    // Define continuation pager's own parameters that should be excluded from preservation
    var pagerParams = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
    {
        "pageSize", "currentPage", "pageToken", "tokenHistory"
    };

    // Add parameter prefix variants if using prefixed parameters
    if (!string.IsNullOrEmpty(Model.ParameterPrefix))
    {
        pagerParams.Add($"{Model.ParameterPrefix}_pageSize");
        pagerParams.Add($"{Model.ParameterPrefix}_currentPage");
        pagerParams.Add($"{Model.ParameterPrefix}_pageToken");
        pagerParams.Add($"{Model.ParameterPrefix}_tokenHistory");
    }

    // Preserve all existing query parameters (except pager's own) if enabled
    if (Model.PreserveQueryParameters)
    {
        foreach (var param in ViewContext.HttpContext.Request.Query)
        {
            if (!pagerParams.Contains(param.Key))
            {
                query[param.Key] = param.Value.ToString();
            }
        }
    }

    // Add continuation pager parameters (with prefix if specified)
    var pageSizeParam = Model.GetParameterName("pageSize");
    var currentPageParam = Model.GetParameterName("currentPage");
    var pageTokenParam = Model.GetParameterName("pageToken");
    var tokenHistoryParam = Model.GetParameterName("tokenHistory");

    query[pageSizeParam] = pageSize.ToString();
    query[currentPageParam] = page.ToString();

    if (!string.IsNullOrEmpty(token))
        query[pageTokenParam] = token;

    if (Model.EnableTokenAccumulation)
        query[tokenHistoryParam] = tokenHistoryJson;

    return string.Join("&", query.Select(kvp =>
        $"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}"));
}
```

Detta tillvägagångssätt innebär följande:

**Scenario 1: Sök + Pagination**

```
Initial URL: /Products?search=laptop&category=electronics&page=1
Click Next: /Products?search=laptop&category=electronics&page=2
Change Page Size: /Products?search=laptop&category=electronics&page=1&pageSize=50
```

**Scenario 2: Sortera + Pagination**

```
Initial URL: /Products?orderBy=price&descending=true&page=1
Click Page 3: /Products?orderBy=price&descending=true&page=3
```

**Scenario 3: Fortsättningssökare med filter**

```
Initial URL: /Products?category=electronics&brand=acme
Click Next: /Products?category=electronics&brand=acme&currentPage=2&pageToken=abc123&tokenHistory={...}
```

Samma bevarande fungerar i formar (NoJS-läge). När sidstorleksformuläret återges innehåller vyn automatiskt dolda indata för alla parametrar som inte är paginerade:

```razor
<form method="get" action="@linkUrl" class="page-size-form">
    @* Preserve all existing query parameters except pageSize and page-related ones *@
    @foreach (var param in ViewContext.HttpContext.Request.Query)
    {
        if (!new[] { "pageSize", "currentPage", "pageToken", "tokenHistory" }
            .Contains(param.Key, StringComparer.OrdinalIgnoreCase))
        {
            <input type="hidden" name="@param.Key" value="@param.Value" />
        }
    }

    @* Reset to page 1 when changing page size *@
    <input type="hidden" name="currentPage" value="1" />

    <select name="pageSize" onchange="this.form.submit()">
        <!-- options -->
    </select>
</form>
```

Detta fungerar sömlöst över **alla JavaScript-lägen och alla ViewTypes**. Du behöver aldrig hantera manuellt frågesträng utbredning.

---


## Migrationsguide {#migrationsguide}

Uppgradering från pre-1.0 versioner är enkelt, men det finns några bryta förändringar att vara medveten om.

### Bryta förändringar

**1. `use-htmx` är deprecerad (men fortfarande fungerar)**

Gamla:

```razor
<paging model="Model" use-htmx="true" />
<paging model="Model" use-htmx="false" />
```

Nytt (rekommenderas):

```razor
<paging model="Model" js-mode="HTMX" />
<paging model="Model" js-mode="PlainJS" />
```

**2. ViewType.TailwindAndDaisy använder nu hela DaisyUI komponenter**

Om du använde `ViewType.TailwindAndDaisy` Och vill ha ren Tailwind utan DaisyUI:

Gammalt beteende (rent Tailwind):

```razor
<paging model="Model" view-type="TailwindAndDaisy" />
```

Nytt (för att få gammalt beteende):

```razor
<paging model="Model" view-type="Tailwind" />
```

Fortsätt använda `TailwindAndDaisy` Om du använder DaisyUI-komponenter:

```razor
<paging model="Model" view-type="TailwindAndDaisy" />
<!-- Uses btn, join, badge, select, etc. -->
```

**3. HTMX uppgraderas till 2.0.4**

Om du använder HTMX någon annanstans i din applikation, säkerställa kompatibilitet med HTMX 2.0.4. De flesta HTMX 1.x kod fungerar oförändrad, men granska [HTMX 2.0 migrationsguide](https://htmx.org/migration-guide-htmx-1/) för kantfodral.

### Steg-för-steg-migrering

**Steg 1: Uppdatera NuGet-paketet**

```bash
dotnet add package mostlylucid.pagingtaghelper --version 1.0.0
```

**Steg 2: Granska din befintliga kod**

Sök i din kodbas efter `use-htmx` egenskaper:

```bash
# PowerShell
Get-ChildItem -Recurse -Include *.cshtml | Select-String "use-htmx"

# Bash/Git Bash
grep -r "use-htmx" --include="*.cshtml" .
```

**Steg 3: Uppdatera till js-mode (rekommenderas)**

Ersätt `use-htmx` där `js-mode`:

```diff
- <paging model="Model" use-htmx="true" htmx-target="#results" />
+ <paging model="Model" js-mode="HTMX" htmx-target="#results" />

- <paging model="Model" use-htmx="false" />
+ <paging model="Model" js-mode="PlainJS" />
```

**Steg 4: Granska TailwindAndDaisy användning**

Om du inte har DaisyUI installerat men använt `TailwindAndDaisy`:

```diff
- <paging model="Model" view-type="TailwindAndDaisy" />
+ <paging model="Model" view-type="Tailwind" />
```

**Steg 5: Testa noggrant**

Kör din ansökan och testa:

- Sidnavigering
- Ändrad sidstorlek
- HTMX partiella uppdateringar (om HTMX används)
- Filter/sökbevarande
- Mobil lyhördhet

### Nya egenskaper att anta

När jag har flyttat, överväga att anta dessa nya funktioner:

**Lokalisering:**

```razor
<paging
    model="Model"
    language="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName" />
```

**Fortsättningssida (om NoSQL används):**

```razor
<continuation-pager
    model="Model"
    htmx-target="#results-container"
    show-page-number="true" />
```

**NoJS-läge (för tillgänglighet):**

```razor
<paging model="Model" js-mode="NoJS" />
```

---


## Demoprogram {#demo- ansökan}

Biblioteket innehåller ett omfattande demoprogram som visar alla funktioner. Du kan köra det lokalt eller visa det på [demo webbplats](https://paging-demo.mostlylucid.net) (kommer snart).

**Köra Demo lokalt:**

```bash
git clone https://github.com/scottgal/mostlylucid.pagingtaghelper.git
cd mostlylucid.pagingtaghelper/mostlylucid.pagingtaghelper.sample
dotnet run
```

Navigera till `https://localhost:5001` för att undersöka:

1. **Grundläggande paginering med modell** - Traditionell personsökning med SQL- stil paginering
2. **Integrering av HTMX** - Dynamiska siduppdateringar utan helsida laddar om
3. **Sök med HTMX** - Kombinerad sökning och paginering
4. **Enkelt CSS** - Inga ramberoenden
5. **Ren tailwind** - TailwindCSS utan DaisyUI
6. **Inget JavaScript** - Fullt fungerande noll JS pagination
7. **JavaScript- lägen** - Alla fem JS-lägen demonstrerade sida vid sida
8. **Sidsortering** - Sorterade rubriker med HTMX
9. **Sidsortera ingen HTMX** - Sorterade rubriker med helsidasbelastningar
10. **Sidstorlek med HTMX** - Dynamiska sidstorleksändringar
11. **Sidstorlek nr HTMX** - Sidstorlek med ansökningsblankett
12. **Fortsättningssökare** - NoSQL-stil token-baserad paginering
13. **Lokalisering** - Språkväljare med 8 språk

Varje demo innehåller:

- Kod för arbetskälla
- Förklaring av tekniken
- Länk till genomförandet av GitHub
- Interaktiva kontroller för att experimentera

---


## Slutsatser

Version 1.0.0 är en viktig milstolpe för biblioteket PagingTagHelper. Det som började som ett enkelt arbetskrav har utvecklats till en omfattande, produktionsklar pagineringslösning som hanterar:

- **Traditionell SQL-paginering** med motvikt/begränsning
- **NoSQL- fortsättning token pagination** för Cosmos DB, DynamoDB, etc.
- **Lokalisering på flera språk** för global publik
- **Flexibla JavaScript-lägen** från HTMX till noll-JavaScript
- **Flera CSS-ramverk** från DaisyUI till ren Tailwind till ingen
- **Smart parameterbevarande** över hela navigeringen
- **Stöd för fullständig tillgänglighet** med ARIA-etiketter och tangentbordsnavigering

Biblioteket har testats i strid med 1,7k+ nedladdningar och är redo för produktionsanvändning. Alla 106 enhetstester godkänns och den omfattande demoapplikationen visar upp verkliga användningsmönster.

### Vad är nästa?

Framtida förbättringar Jag överväger:

- Ytterligare CSS-ramstöd (Material UI, Bulma)
- Fler lokalanpassningsspråk (gemenskapens bidrag välkomnas!)
- Komponenter till serversidan Blazor
- Förbättrad tillgänglighet för stora datauppsättningar

### Kom igång

Installera via NuGet:

```bash
dotnet add package mostlylucid.pagingtaghelper --version 1.0.0
```

Kolla in dokumentationen:

- [GitHub-arkivet](https://github.com/scottgal/mostlylucid.pagingtaghelper)
- [Nugga paket](https://www.nuget.org/packages/mostlylucid.pagingtaghelper)
- [Fullständig dokumentation](https://github.com/scottgal/mostlylucid.pagingtaghelper/tree/main/docs)

Frågor, feedback eller bidrag? Öppna ett nummer på GitHub eller nå ut på Twitter [@ scottgal](https://twitter.com/scottgal).

Glad paginering!