# PaggingTagHelper v1.0: Enterprise-Ready Pagination for Modern ASP.NET Core

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

<!--category-- Nuget, ASP.NET Core, HTMX, Alpine.js, Javascript, TagHelper, PagingTagHelper -->
> HUOMAUTUS: TULOSSA MYÖHEMMIN, viimeistelen vain sen. [Seuraa GitHubia! ](https://github.com/scottgal/mostlylucid.pagingtaghelper).

**Tämä on vain näyttääkseni teille kaikille, että olen edistymässä tämän hallinnan kanssa!**

## Johdanto

Kuukausien evoluution ja yhteisöltä saadun arvokkaan palautteen (5.7k+-lataukset!) jälkeen olen innoissani voidessani ilmoittaa, että PaggingTagHelper-kirjasto on saavuttanut version 1.0.0. Tämä ei ole vain versionumerokuoppa – se edustaa kirjaston täydellistä kypsymistä, jossa on ominaisuuksia, jotka tekevät siitä sopivan todellisiin tuotantosovelluksiin.

Jos olet seurannut tätä sarjaa, muistat, että aloitimme [paljaat luulliset hakulaitteet](https://www.mostlylucid.net/blog/pagingtaghelper), lisätty [kiltimpiä otsikoita](https://www.mostlylucid.net/blog/pagingtaghelperpt11), ja uutettu [Sivukokosäätimet](https://www.mostlylucid.net/blog/pagingtaghelperpt2). Versio 1.0.0 vie kaiken oppimamme ja lisää kriittisiä yritteliäisyysominaisuuksia:

- **Jatka Token Pagination** NoSQL-tietokannat (Cosmos DB, DynamoDB, Azure Table Storage)
- **Monikielinen lokalisointi** 8 kielen tuki ulos laatikosta
- **Joustavat javascript-tilat** HTMX:stä nollaksi JavaScriptiin
- **Puhtaat myötätuulinäkymät** ilman DaisyUI:n riippuvuuksia
- **Älykäs URL-parametrin säilyttäminen** Kaikki navigaatiot
- **HTMX 2.0,4** päivitys takautuvasti yhteensopivaksi

Sukellataan jokaiseen näistä piirteistä ja katsotaan, miten ne toimivat yhdessä luodakseen todella joustavan paginaatioratkaisun.

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

[TOC]

## Jatkoa Token Paginationille

Perinteinen paginointi toimii kauniisti SQL-tietokannoilla, joissa voit helposti `SKIP` sekä `TAKE` Mutta mitä tapahtuu, kun on mukana NoSQL-tietokantoja, kuten Cosmos DB, DynamoDB tai Azure Table Storage? Nämä tietokannat eivät tue offset-pohjaista paginointia – sen sijaan ne käyttävät **jatkotolpat**.

### Token-based Pagination-ymmärrän-token-based-pagination-ymmärtäminen

Näin jatkokeittopaginointi eroaa perinteisestä hakutaidosta:

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

**Perinteinen haku:**

- Määrität tarkalleen, mitkä tietueet on haettava (OFFSET/LIMIT)
- Voit hypätä mille tahansa sivulle suoraan
- Tietokannan täytyy käydä läpi kaikki aiemmat tietueet

**Token-pohjainen haku:**

- Tietokanta palauttaa epäselvän poletin, joka edustaa "missä jatkaa"
- Token-muoto on tietokantakohtainen ja asiakkaan kannalta vaikeaselkoinen
- Eteenpäin navigointi on luonnollista, takautuva navigointi vaatii symbolista historiaa

### Jatkosivun toteutus Jatkosivun toteutus}

Uusi `<continuation-pager>` Tag-auttaja tekee token-based paginationin toteuttamisesta mutkatonta. Luo ensin malli, joka toteuttaa `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();
}
```

Liitäntä on minimaalinen mutta tehokas. Katsotaanpa, mitä jokainen ominaisuus tekee:

- `NextPageToken`: Seuraavan sivun hakutunnus (tietokannan tarjoama)
- `HasMoreResults`: Boolean ilmoittaa, onko sivuja enemmän
- `PageSize`: Tuotteita sivulla
- `CurrentPage`: Vain näytön sivunumero UI:lle
- `PageTokenHistory`: Dictionary kartoitussivujen numerot takautuvan navigoinnin kuponkeihin
- `ViewType`: Mitä CSS-kehystä käytetään renderointiin

Nyt toteutetaan ohjaintoiminto, joka simuloi Cosmos DB -tyylistä paginaatiota:

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

Tämä toteutus osoittaa, kuinka symbolinen historia mahdollistaa takautuvan navigoinnin. Ilman sitä jatkokylttipaginointi tukisi vain "Next"-painikkeita. Pitämällä yllä sanakirjaa sivusta toiseen -kartoituksista voimme tukea sekä "Ennen" että "Seuraavan" navigointia.

Tässä on kuponkikertymän virtaama visualisoituna:

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

Razor-näkymässäsi jatkohakulaitteen käyttö on yksinkertaista:

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

Tagiavustin automaattisesti:

- Serialisoi merkkihistorian kyselymuuttujiksi
- Rakentaa navigoinnin URL-osoitteita oikeilla tunnuksilla
- Poistaa "edeltävän" käytöstä sivulla 1
- Poistaa "Seuraavan" käytöstä, kun `HasMoreResults` on väärä
- Säilyttää kaikki muut kyselyparametrit (haku, suodattimet jne.)

### Token History for Backward Navigation token-historiikki

Nimellishistorian nerokkuus on se, että se on täysin valinnainen. Jos tarvitset vain "Seuraavan" navigoinnin (esim. finiittikäärön), voit jättää symbolihistorian kokonaan huomiotta:

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

Tämä tekee vain "Seuraavan" painikkeen ilman sivuindikaattoreita tai historianhallintaa.

Täyden navigoinnin osalta symbolihistoria sarjataan automaattisesti JSON:ksi kyselyn merkkijonossa. URL näyttää tältä symbolihistoriallaan:

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

Erytropoietiini `tokenHistory` Parametri sisältää koodatun sanakirjan, mikä tekee takautuvasta navigoinnista saumatonta.

### Numbered Page Navigation Numbered Page Navigation}

Yksi tärkeimmistä UX-parannuksista jatkohakulaitteessa on **Numeroidut sivupainikkeet**. Kun navigoit eteenpäin, pager näyttää napsautettavia sivunumeroita kaikille vierailluille sivuille:

```
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)
```

Tämä tarjoaa perinteisen pagination UX:n säilyttäen samalla tokend-pohjaisen backend-arkkitehtuurin. Toteutus tallentaa kuponkeja jokaiselle vieraillulle sivulle, mikä mahdollistaa suoran navigoinnin kaikille aiemmin käytetyille sivuille.

**Historian kasvun rajoittaminen:**

Rajattoman muistin käytön estämiseksi aseta `max-history-pages` (oletus: 20):

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

Kun raja saavutetaan, vanhimmat sivumerkit leikataan automaattisesti.

### Kriittinen: Query Parametrin säilyttäminen

**Tämä on hakulaitteen jatkototeutuksen tärkein ominaisuus.**

Jatkomerkit ovat voimassa vain samassa kyselykontekstissa (suodattimet, lajit, haut), joka loi ne. Tokentin käyttö eri kyselyparametrien kanssa palauttaa virheelliset tiedot tai epäonnistuu kokonaan.

Jatkohakulaite säilyttää ALL-kyselyparametrit automaattisesti omia lukuun ottamatta:

```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. -->
```

Voit tarvittaessa poistaa tämän käytöksen käytöstä:

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

Mutta tämä on **lannistui vahvasti** Ellet ole täysin varma, että viestisi eivät ole riippuvaisia kyselykontekstista.

**Miksi tällä on merkitystä:**

Cosmos DB -esimerkki:

```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!
);
```

Jatkohakulaitteen automaattinen parametrien säilyttäminen takaa, että poletteja käytetään aina alkuperäisessä kyselykontekstissaan.

---


## Lokalisointituki _ lokalisointi-tuki}

Modernit sovellukset palvelevat maailmanlaajuista yleisöä, ja paginointiohjainten on puhuttava käyttäjien kieltä. Versio 1.0.0 sisältää laajan lokalisointituen, joka on rakennettu suoraan kirjastoon.

### Sisäänrakennetut kielet

Kirjaston laivat, joissa on kahdeksan kielen käännökset:

Koodi "Kieli"
|------|----------|
| `en` Englanti (oletus)
| `de` Saksa (Deutsch)
| `es` Espanja (Español)
| `fr` Ranska (Français)
| `it` Italia (Italia)
| `pt` Portugali (Português)
| `ja` Japanilainen (anteeksi)
| `zh-Hans` Kiinalainen yksinkertaistettu (anteeksi)

Kaikki teksti on lokalisoitu, mukaan lukien:

- Edellinen/Seuraava/Ensimmäinen/Viimeinen painiketarrat
- Sivun yhteenvetoteksti ("Showing X to Y of Z Items")
- ARIA-merkit saavutettavuutta varten
- Sivun kokomerkintä ("Esimerkit sivulla")

Lokalisointijärjestelmän voimanlähteenä on `.resx` Resource-tiedostot, jolloin on helppo lisätä omia kieliä. Kaikki resource-tiedostot ovat `mostlylucid.pagingtaghelper/Resources/`.

### Paikallistamisen käyttö localization-usage}

Lokalisointi on yksinkertaista. Lisää vain `language` Ominaisuus:

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

Tämä tarkoittaa, että kaikki teksti on saksaksi:

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

Dynaamista kielenvaihtoa varten, joka perustuu käyttäjän asetuksiin, aseta kieli ohjaimeen:

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

Luo sitten näkemyksesi mukaan kielivalitsija:

```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" />
```

Voit myös ohittaa yksittäiset tekstijonot, mutta voit silti hyödyntää lokalisointia muille elementeille:

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

Erytropoietiini `PagingLocalizer` Palvelu käsittelee kulttuurikohtaista muotoilua automaattisesti. Jos ohitat väärän kielikoodin, se palaa sulavasti englannille.

HTMX-integraatiota varten haluat säilyttää kielen kaikissa pyynnöissä:

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

Näin varmistetaan, että HTMX:n osakatselupäivitykset ylläpitävät valittua kieltä.

---


## Javascript-moodit

Yksi merkittävimmistä v1.0.0:n parannuksista on joustavien JavaScript-tilojen käyttöönotto. Aiemmin sinulla oli boolean valinta: `use-htmx="true"` tai `use-htmx="false"`. Nyt sinulla on viisi eri tilaa, joista jokainen on optimoitu erilaisiin skenaarioihin.

### Käytössä olevat tilat

Tässä on Javascript-tilojen täydellinen jakautuminen:

```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]

```

Katsotaan jokainen toimintatila:

**1. HTMX-tila (Default)**

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

Renderöijät:

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

Täydellinen dynaamisille sivupäivityksille ilman koko sivun uudelleenlatauksia. Tämä on suosituin tila nykyisille ASP.NET Core -sovelluksille.

**2. HTMXAlpine Mode**

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

Renderöijät:

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

Yhdistää HTMX:n navigaatioon Alppien.js:n kanssa ja lisää asiakaspuolen vuorovaikutusta. Käytä tätä, kun tarvitset reaktiivisia UI-elementtejä paginoinnin rinnalla (latausmittarit, animaatiot, asiakaspuolen validointi).

**3. Alppitila**

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

Renderöijät:

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

Puhtaat alppi.js ilman HTMX:ää. Hyödyllistä, kun käytät alppi.j:itä, mutta et halua HTMX:n riippuvuuksia.

**4. PlainJS-tila**

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

Renderöijät:

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

Ei kehysriitoja, vain vanilja JavaScript. Tähän tilaan kuuluu myös sivunkokomuutosten auttaja:

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

Tämä ruiskuttaa tarvittavat JavaScript-ohjeet sivun koon laskuun ilman HTMX:ää.

**5. NoJS-tila**

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

Renderöijät:

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

Zero JavaScript vaaditaan. Täydellinen:

- Saavutettavuusvaatimukset
- Progressiivista tehostamista koskevat skenaariot
- Ympäristöt, joissa JavaScript on pois päältä
- SEO-kriittiset sivut, joihin haluat ryömijäystävällisen navigoinnin

Tämän järjestelmän kauneus on se, että **Kaikki tilat säilyttävät nykyiset kyselyparametrisi**. Suodatatpa sitten kategorioittain, etsimällä tai lajittelemalla, paginaatio ylläpitää tilaasi automaattisesti.

### Muutto käytöstä-htmx Maahanmuutto käytöstä-htmx

Taaksepäin yhteensopivuus, vanha `use-htmx` Ominaisuus toimii yhä:

```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" -->
```

Suosittelen kuitenkin muuttoa uuteen `js-mode` Selkeyden ominaisuus:

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

---


## ViewType Enhancements viewtype-enhancements}

Versiossa 1.0.0 esitellään kaksi tärkeää ViewType-lisäystä, joissa käsitellään yhteisiä reaalimaailman skenaarioita.

### Puhdasta myötätuulta

Aiemmin, jos haluat TailwindCSS-tyylin, sinulla on `TailwindAndDaisy` Näkymä, jossa käytetään DaisyUI:n komponentteja. Tämä on hienoa, jos käytät jo DaisyUI:ta, mutta entä jos haluat puhtaan Tailwindin ilman DaisyUI:n riippuvuutta?

Syötä `ViewType.Tailwind`:

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

Tämä tarkoittaa, että käytetään vain normaaleja Tailwind-apuohjelmia:

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

Ei tarvitse. `btn`, `badge`, tai `join` Luokat – vain puhdasta myötätuulta. Näin voit hallita täydellisesti tyylittelyä ilman komponenttikirjaston riippuvuuksia.

**Vertailu:**

ViewType CSS Framework Komponenttikirjasto Use Case
|----------|---------------|-------------------|----------|
| `TailwindAndDaisy` Kääntötuulessa DaisyUI-projektit, joissa käytetään jo DaisyUI-hanketta
| `Tailwind` Kääntötuulta ei ole olemassa Puhtaan myötätuulen projekteja
| `Bootstrap` Bootstrap Bootstrap Komponentit Bootstrap-hankkeet
| `Plain` Yhteyttä CSS:ään Ei mitään kehystä riippuvuussuhteita
| `NoJS` Uppoutunut CSS None Zero JavaScript requirements

### NoJS-tila nojs-mode

Erytropoietiini `NoJS` ViewType yhdistää nolla JavaScriptin tavalliseen CSS-tyyliin:

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

Tärkeimmät erot muihin katsojatyyppeihin:

1. **Navigointi käyttää ankkurilinkkejä**, ei painikkeita:

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

2. **Sivukokovalitsin on lomake**:

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

Erytropoietiini `onchange="this.form.submit()"` tarjoaa mukavuutta, kun JavaScript on saatavilla, mutta `<noscript>` Nappi takaa täyden toimivuuden silloin, kun se ei ole.

---


## URL-parametrin säilyttäminen

Yksi turhauttavimmista puolista paginaation toteutuksessa on suodattimien, hakutermien tai järjestyksen menettäminen sivujen välillä navigoidessa. Versio 1.0.0 ratkaisee tämän tyylikkäästi **Säilytetään automaattisesti kaikki kyselyparametrit paitsi paginaatio-ohjauksen omat parametrit**.

Tämä ominaisuus toimii samalla tavalla eri puolilla **Sekä säännölliset hakulaitteet että jatkohakulaitteet**, ja yli **Kaikki Javascript-tilat ja ViewTypes**.

Näin se toimii sisäisesti:

```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)}"));
}
```

Tämä lähestymistapa tarkoittaa seuraavaa:

**Skenaario 1: Etsi + Paginaatio**

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

**Skenaario 2: Lajittelu + Paginaatio**

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

**Skenaario 3: Jatka suodatinten kanssa**

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

Sama säilytys toimii muodossa (NoJS-tilassa). Kun teet sivun kokoista muotoa, näkymä sisältää automaattisesti piilotetut syötteet kaikille ei-paginointiparametreille:

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

Tämä toimii saumattomasti **Kaikki Javascript-tilat ja kaikki ViewTypes**. Sinun ei koskaan tarvitse hallita kyselyjonon lisäystä käsin.

---


## Maahanmuutto-opas

Esi-1.0-versioiden päivittäminen on yksinkertaista, mutta tiedossa on muutamia murto-osia.

### Muutosten murtaminen

**1. `use-htmx` on poissuljettu (mutta toimii kuitenkin)**

Vanha:

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

Uusi (suositeltu):

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

**2. ViewType.TailwindAndDaisy käyttää nyt täysiä DaisyUI-komponentteja**

Jos käytät `ViewType.TailwindAndDaisy` ja haluavat puhdasta myötätuulta ilman DaisyUI:ta:

Vanha käytös (puhdas myötätuuli):

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

Uusi (vanhaa käytöstä varten):

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

Jatka käyttöä `TailwindAndDaisy` jos käytät DaisyUI-komponentteja:

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

**3. HTMX päivitetty 2.0.4:ksi**

Jos käytät HTMX:ää muualla sovelluksessasi, varmista yhteensopivuus HTMX 2.0.4.:n kanssa. Useimmat HTMX 1.x-koodit toimivat muuttumattomina, mutta tarkista [HTMX 2.0 -siirtolaisopas](https://htmx.org/migration-guide-htmx-1/) Edge-tapausten osalta.

### Askel kerrallaan tapahtuva muuttoliike

**Vaihe 1: Päivitä NuGet-paketti**

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

**Vaihe 2: Tarkista olemassa olevaa koodiasi**

Etsi koodipohjasta `use-htmx` Ominaisuudet:

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

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

**Vaihe 3: Päivitys js-tilaan (suositeltu)**

Korvaa `use-htmx` yy) kanssa, kun `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" />
```

**Vaihe 4: Katsaus TailwindAndDaisyn käyttöön**

Jos et ole asentanut DaisyUI:ta, mutta käytit `TailwindAndDaisy`:

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

**Vaihe 5: Testaa perusteellisesti**

Suorita hakemuksesi ja testaa:

- Sivun navigointi
- Sivun koon muutokset
- HTMX:n osapäivitykset (jos käytetään HTMX:ää)
- Suodatin/etsintäsäilytys
- Liikkuva reagointikyky

### Uusia ominaisuuksia otettava käyttöön

Kun olet muuttanut, harkitse näiden uusien piirteiden omaksumista:

**Lokalisointi:**

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

**Jatkosivu (jos käytetään NoSQL:ää):**

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

**NoJS-tila (saatavuus):**

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

---


## Demosovellus demo-sovellus}

Kirjastossa on kattava demosovellus, jossa esitellään kaikki ominaisuudet. Voit tehdä sen paikallisesti tai katsoa sen [demosivusto](https://paging-demo.mostlylucid.net) (tulee pian).

**Demon pyörittäminen paikallisesti:**

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

Navigoi `https://localhost:5001` Tutkittavaksi:

1. **Peruspaginointi mallilla** - Perinteinen haku SQL-tyylisellä paginaatiolla
2. **HTMX-integraatio** - Dynaamiset sivupäivitykset ilman koko sivun uudelleenlatauksia
3. **Etsi HTMX:llä** - Yhdistetty etsintä ja paginaatio
4. **Tavallinen CSS** - Ei riippuvuuksia kehyksestä
5. **Puhdas myötätuuli** - PerätuuliCSS ilman DaisyUI:ta
6. **Ei JavaScriptia** - Täysin toimiva nolla-JS-paginaatio
7. **Javascript-tilat** - Kaikki viisi JS-tilaa osoittivat vierekkäin
8. **Sivun lajittelu** - Lajiteltavia otsikoita HTMX:llä
9. **Sivun lajittelu ei ole HTMX** - Lajiteltavissa otsikoissa koko sivun kuormat
10. **Sivun koko HTMX:llä** - Dynaaminen sivun koko muuttuu
11. **Sivun koko: HTMX** - Sivun koko ja lomake
12. **Jatkosivu** - NoSQL-tyylinen token-pohjainen paginaatio
13. **Paikallistaminen** - Kielenvalitsija 8 kielellä

Jokaiseen demoon kuuluu:

- Työskentelyn lähdekoodi
- Tekniikan selitys
- Linkki GitHubin täytäntöönpanoon
- Interaktiiviset kontrollit kokeiluun

---


## Päätelmät

Versio 1.0.0 on merkittävä virstanpylväs PaggingTagHelper-kirjastolle. Yksinkertaisena työvaatimuksena alkanut on kehittynyt kattavaksi, tuotantovalmiiksi paginointiratkaisuksi, joka käsittelee:

- **Perinteinen SQL-paginointi** offset/limit
- **NoSQL-jatkujapaginointi** Cosmos DB:lle, DynamoDB:lle jne.
- **Monikielinen lokalisointi** globaalille yleisölle
- **Joustavat Javascript-tilat** HTMX:stä nollaksi JavaScriptiin
- **Useita CSS-kehyksiä** DaisyUI:sta pelkkään myötätuuleen ei yhtään
- **Älykäs parametrien säilyttäminen** Kaikki navigaatiot
- **Täysi esteettömyystuki** ARIA-tarroilla ja näppäimistön navigoinnilla

Kirjastoa on testattu 1,7k+-latauksilla, ja se on valmis tuotantokäyttöön. Kaikki 106 yksikköä läpäisevät kokeet, ja kattava demosovellus esittelee tosimaailman käyttömalleja.

### Mitä seuraavaksi?

Tulevia parannuksia harkitsen:

- CSS:n lisäkehystuki (Material UI, Bulma)
- Lisää lokalisointikieliä (yhteisön panos on tervetullut!)
- Palvelinpuolen Blazor-komponentit
- Suurten tietoaineistojen parempi saavutettavuus

### Aloita

Asenna NuGetin kautta:

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

Katso dokumentit:

- [GitHub-varasto](https://github.com/scottgal/mostlylucid.pagingtaghelper)
- [NuGet-paketti](https://www.nuget.org/packages/mostlylucid.pagingtaghelper)
- [Dokumentaatio kokonaisuudessaan](https://github.com/scottgal/mostlylucid.pagingtaghelper/tree/main/docs)

Kysymyksiä, palautetta tai kannanottoja? Avaa kysymys GitHubissa tai ota yhteyttä Twitterissä [@scottgal](https://twitter.com/scottgal).

Happy paginating!