# PagingTagHelper v1.0.0: Pagination Enterprise-Ready pour le noyau ASP.NET moderne

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

<!--category-- Nuget, ASP.NET Core, HTMX, Alpine.js, Javascript, TagHelper, PagingTagHelper -->
> REMARQUE: Venir, juste mettre les touches de finition à elle. [Suivez GitHub ! ](https://github.com/scottgal/mostlylucid.pagingtaghelper).

**C'est juste pour vous montrer tout ce que je suis en train de faire avec ce contrôle !**

## Présentation

Après des mois d'évolution et des retours précieux de la communauté (5.7k+ téléchargements!), je suis heureux d'annoncer que la bibliothèque PagingTagHelper a atteint la version 1.0.0. Ce n'est pas seulement une bosse de numéro de version – il représente une maturation complète de la bibliothèque avec des fonctionnalités qui le rendent adapté pour le monde réel, les applications de production.

Si vous avez suivi cette série, vous vous souviendrez que nous avons commencé avec [lance-roquettes nues](https://www.mostlylucid.net/blog/pagingtaghelper), ajouté [en-têtes triables](https://www.mostlylucid.net/blog/pagingtaghelperpt11), et extrait [contrôle de la taille des pages](https://www.mostlylucid.net/blog/pagingtaghelperpt2). La version 1.0.0 prend tout ce que nous avons appris et ajoute des fonctionnalités d'entreprise critiques:

- **Pagination des jetons de continuation** pour les bases de données NoSQL (Cosmos DB, DynamoDB, Azure Table Storage)
- **Localisation multilingue** soutenir 8 langues hors de la boîte
- **Modes JavaScript flexibles** de HTMX à zéro-JavaScript
- **Pure Tailwind Views** Sans dépendances DaisyUI
- **Préservation des paramètres de l'URL intelligente** sur toute la navigation
- **HTMX 2.0.4** mise à jour avec compatibilité en arrière

Plongons dans chacune de ces caractéristiques et voyons comment ils travaillent ensemble pour créer une solution de pagination vraiment flexible.

[![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]

## Pagination continue du jeton {#pagination continue du jeton}

Pagination traditionnelle fonctionne magnifiquement avec les bases de données SQL où vous pouvez facilement `SKIP` et `TAKE` Mais que se passe-t-il lorsque vous travaillez avec des bases de données NoSQL comme Cosmos DB, DynamoDB ou Azure Table Storage ? Ces bases de données ne prennent pas en charge la pagination offset – à la place, elles utilisent **jetons de suite**.

### Comprendre la pagination basée sur les jetons {#comprendre la pagination basée sur les jetons}

Voici comment la pagination continue de jeton diffère de la pagination traditionnelle:

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

**Pâques traditionnelles :**

- Vous spécifiez exactement les enregistrements à récupérer (OFFSET/LIMIT)
- Vous pouvez sauter sur n'importe quelle page directement
- La base de données doit analyser tous les enregistrements antérieurs

**Pagination à base de jetons :**

- Base de données retourne un jeton opaque représentant "où continuer"
- Le format de jeton est spécifique à la base de données et opaque pour le client
- La navigation vers l'avant est naturelle, la navigation vers l'arrière nécessite une histoire symbolique

### Poursuite de la mise en œuvre de Pager {#suite-pager-mise en œuvre}

La nouvelle `<continuation-pager>` tag helper rend la mise en œuvre de pagination basée sur jeton simple. Tout d'abord, créer un modèle qui implémente `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();
}
```

L'interface est minimale mais puissante. Voyons ce que chaque propriété fait :

- `NextPageToken`: Le jeton pour récupérer la page suivante (fournie par votre base de données)
- `HasMoreResults`: Booléen indiquant s'il y a plus de pages
- `PageSize`: Articles par page
- `CurrentPage`: Numéro de page affiché uniquement pour l'interface utilisateur
- `PageTokenHistory`: Dictionary mapping pages numbers to jens for backer navigation
- `ViewType`: Quel cadre CSS utiliser pour le rendu

Implémentons maintenant une action de contrôleur qui simule la pagination Cosmos DB :

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

Cette implémentation montre comment l'historique des jetons permet une navigation en arrière. Sans elle, la pagination continue des jetons ne prendrait en charge que les boutons "Next". En maintenant un dictionnaire de cartes page à jeton, nous pouvons prendre en charge à la fois la navigation "Précédente" et "Next".

Voici le flux d'accumulation de jetons visualisé:

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

Dans votre vue Razor, l'utilisation du pager de suite est simple:

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

L'aide de tag automatiquement :

- Sérialise l'historique des jetons en paramètres de requête
- Construit des URLs de navigation avec des jetons appropriés
- Désactive "Précédent" quand à la page 1
- Désactive "Next" lorsque `HasMoreResults` est faux
- Préserve tous les autres paramètres de requête (recherche, filtres, etc.)

### Historique des jetons pour la navigation arrière {#token-history}

Le génie de l'approche historique des jetons est qu'elle est entièrement optionnelle. Si vous avez seulement besoin de navigation "Next" (par exemple, défilement infini), vous pouvez ignorer entièrement l'histoire des jetons:

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

Cela rend juste un bouton "Next" sans indicateurs de page ou gestion de l'historique.

Pour la navigation complète, l'historique des jetons est automatiquement sérialisé comme JSON dans la chaîne de requête. Voici à quoi ressemble une URL avec l'historique des jetons :

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

Les `tokenHistory` paramètre contient le dictionnaire encodé, rendant la navigation en arrière transparente.

### Navigation de page numérotée {#numbered-page-navigation}

L'une des plus importantes améliorations de UX dans le pager de suite est **boutons de page numérotés**. Lorsque vous naviguez vers l'avant, le pager affiche les numéros de page cliquables pour toutes les pages visitées :

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

Cela fournit une pagination traditionnelle UX tout en maintenant l'architecture de backend basée sur des jetons. L'implémentation stocke des jetons pour chaque page visitée, permettant la navigation directe sur toute page précédemment consultée.

**Limiter la croissance historique :**

Pour empêcher l'utilisation de la mémoire non limitée, définissez `max-history-pages` (par défaut: 20):

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

Lorsque la limite est atteinte, les jetons de page les plus anciens sont automatiquement parés.

### Critique : Préservation des paramètres de requête {#query-parameter-conservation}

**C'est la caractéristique la plus importante de l'implémentation de la pager de suite.**

Les jetons de continuation ne sont valides qu'avec le même contexte de requête (filtres, tris, recherches) qui les a générés. L'utilisation d'un jeton avec différents paramètres de requête retournera des données incorrectes ou échouera entièrement.

Le pager de suite conserve automatiquement TOUS les paramètres de requête à l'exception de ses propres paramètres :

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

Vous pouvez désactiver ce comportement si nécessaire :

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

Mais c'est **fortement découragé** Sauf si vous êtes absolument certain que vos jetons ne dépendent pas du contexte de requête.

**Pourquoi cela importe-t-il?**

Exemple de DB Cosmos :

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

La préservation automatique des paramètres du pager assure que les jetons sont toujours utilisés avec leur contexte de requête d'origine.

---


## Soutien à la localisation {#soutien à la localisation}

Les applications modernes servent le public mondial, et les contrôles de pagination doivent parler la langue de vos utilisateurs. La version 1.0.0 inclut un support de localisation complet intégré directement dans la bibliothèque.

### Langues intégrées {#langues intégrées}

La bibliothèque est livrée avec des traductions pour 8 langues:

Code (en anglais seulement) Langue (en anglais seulement)
|------|----------|
| `en` Anglais (par défaut)
| `de` Allemand (Deutsch)
| `es` Espagnol (Español)
| `fr` Français (français)
| `it` Italien (Italiano)
| `pt` Portugais (Português)
| `ja` Japonais (日本語)
| `zh-Hans` Chinois simplifié (

Tout le texte est localisé, y compris :

- Les étiquettes des boutons précédents/suivant/premier/dernier
- Texte sommaire de la page ("Afficher X à Y des éléments Z")
- Étiquettes ARIA pour l'accessibilité
- Étiquette de taille de page ("Items par page")

Le système de localisation est alimenté par `.resx` fichiers ressources, ce qui facilite l'ajout de vos propres langues. Tous les fichiers ressources sont dans `mostlylucid.pagingtaghelper/Resources/`.

### Utilisation de la localisation {#localisation-usage}

L'utilisation de la localisation est simple. Il suffit d'ajouter le `language` attribut & #160;:

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

Cela rend tout le texte en allemand:

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

Pour un changement de langage dynamique basé sur les préférences de l'utilisateur, définissez la langue dans votre contrôleur :

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

Puis, dans votre vue, créez un sélecteur de langue:

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

Vous pouvez également outrepasser les chaînes de texte individuelles tout en bénéficiant de la localisation pour d'autres éléments :

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

Les `PagingLocalizer` Le service gère automatiquement le formatage spécifique à la culture. Si vous passez un code de langue invalide, il revient gracieusement à l'anglais.

Pour l'intégration HTMX, vous souhaitez préserver la langue à travers les requêtes :

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

Cela garantit que les mises à jour de la vue partielle HTMX maintiennent la langue sélectionnée.

---


## Modes JavaScript {#javascript-modes}

L'une des améliorations les plus significatives de v1.0.0 est l'introduction de modes JavaScript flexibles. Auparavant, vous aviez un choix booléen: `use-htmx="true"` ou `use-htmx="false"`. Maintenant, vous avez cinq modes distincts, chacun optimisé pour différents scénarios.

### Modes disponibles {#modes disponibles}

Voici la ventilation complète des modes JavaScript :

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

```

Voyons chaque mode en action :

**1. Mode HTMX (par défaut)**

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

Reveurs :

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

Parfait pour les mises à jour de pages dynamiques sans rechargement complet de la page. C'est le mode recommandé pour les applications ASP.NET Core modernes.

**2. HTMXWithAlpine Mode**

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

Reveurs :

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

Combine HTMX pour la navigation avec Alpine.js pour une interactivité supplémentaire côté client. Utilisez ceci lorsque vous avez besoin d'éléments d'interface utilisateur réactifs à côté de la pagination (indicateurs de chargement, animations, validation côté client).

**3. Mode alpin**

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

Reveurs :

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

Pure Alpine.js sans HTMX. Utile lorsque vous utilisez déjà Alpine.js mais ne voulez pas de dépendances HTMX.

**4. Mode PlainJS**

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

Reveurs :

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

Pas de dépendances framework, il suffit de vanille JavaScript. Ce mode inclut également un helper pour les changements de taille de page:

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

Ceci injecte le JavaScript nécessaire pour gérer les changements de taille de page déroulante sans HTMX.

**5. Mode noJS**

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

Reveurs :

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

Zéro JavaScript requis. Parfait pour :

- Exigences en matière d'accessibilité
- Scénarios d'amélioration progressive
- Environnements où JavaScript est désactivé
- Pages critiques de référencement où vous voulez une navigation conviviale

La beauté de ce système est que **tous les modes préservent vos paramètres de requête existants**. Que vous filtriez par catégorie, recherche ou tri, la pagination maintient automatiquement votre état.

### Migration à partir de use-htmx {#migration-from-use-htmx}

Pour la compatibilité arrière, l'ancien `use-htmx` l'attribut fonctionne toujours :

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

Cependant, je recommande de migrer vers la nouvelle `js-mode` attribut pour plus de clarté:

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

---


## Améliorations du type de vue {#viewtype-enhancements}

La version 1.0.0 introduit deux ajouts importants de ViewType qui traitent des scénarios réels communs.

### Pure Tailwind {#pure-tailwind}

Auparavant, si vous vouliez du style TailwindCSS, vous avez `TailwindAndDaisy` vue qui utilise les composants DaisyUI. C'est génial si vous utilisez déjà DaisyUI, mais que faire si vous voulez pur Tailwind sans la dépendance DaisyUI?

Entrez `ViewType.Tailwind`:

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

Cela rend uniquement en utilisant les classes d'utilitaire Tailwind standard:

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

Numéro `btn`, `badge`, ou `join` classes – juste pur Tailwind. Cela vous donne un contrôle complet sur le style sans dépendances de bibliothèque de composants.

**Comparaison :**

VoirTypeSérie CSS Librairie des composantsUtiliser le cas d'utilisation
|----------|---------------|-------------------|----------|
| `TailwindAndDaisy` TailwindCSS de DaisyUIS Projets utilisant déjà DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUIS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyUS de DaisyU.
| `Tailwind` TailwindCSS Aucun projet Pure Tailwind
| `Bootstrap` Constituants de bootstrap de bootstrap de bootstrap de projets de bootstrap de bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap de type bootstrap.
| `Plain` Pas de dépendances du cadre
| `NoJS` CSS embedded Aucun besoin de JavaScript Zéro

### Mode NoJS {#nojs-mode}

Les `NoJS` ViewType combine zéro JavaScript avec le style CSS Plain:

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

Principales différences par rapport aux autres types de vues :

1. **La navigation utilise des liens d'ancrage**, pas les boutons:

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

2. **Le sélecteur de taille de page est un formulaire**:

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

Les `onchange="this.form.submit()"` fournit la commodité lorsque JavaScript est disponible, mais le `<noscript>` bouton assure la pleine fonctionnalité quand ce n'est pas.

---


## Préservation des paramètres d'URL {#url-parameter-conservation}

L'un des aspects les plus frustrants des implémentations de pagination est de perdre vos filtres, termes de recherche, ou trier l'ordre lors de la navigation entre les pages. **préserver automatiquement tous les paramètres de requête sauf les propres paramètres du contrôle de pagination**.

Cette fonctionnalité fonctionne de la même façon **à la fois les bipers réguliers et les bipers continus**, et de l'autre côté **tous les modes JavaScript et ViewTypes**.

Voici comment ça marche en interne :

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

Cette approche signifie:

**Scénario 1: Recherche + 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
```

**Scénario 2: Tri + Pagination**

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

**Scénario 3 : Pager continu avec filtres**

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

La même conservation fonctionne sous forme (mode NoJS). Lors du rendu de la taille de la page, la vue inclut automatiquement des entrées cachées pour tous les paramètres de non-pagination :

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

Cela fonctionne de façon transparente à travers **tous les modes JavaScript et tous les ViewTypes**. Vous n'avez jamais à gérer manuellement la propagation de la chaîne de requête.

---


## Guide de migration {#Guide de migration}

La mise à niveau à partir des versions pré-1.0 est simple, mais il y a quelques changements de rupture à prendre en compte.

### Briser les changements

**1. `use-htmx` est déprécié (mais fonctionne toujours)**

Vieux :

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

Nouveau (recommandé) :

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

**2. ViewType.TailwindEtDaisy utilise désormais des composants DaisyUI complets**

Si vous utilisiez `ViewType.TailwindAndDaisy` et veulent pur Tailwind sans DaisyUI:

Ancien comportement (pur Tailwind):

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

Nouveau (pour obtenir un vieux comportement):

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

Continuer d'utiliser `TailwindAndDaisy` si vous utilisez des composants DaisyUI:

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

**3. HTMX mis à jour à 2.0.4**

Si vous utilisez HTMX ailleurs dans votre application, assurez-vous de la compatibilité avec HTMX 2.0.4. [Guide de migration HTMX 2.0](https://htmx.org/migration-guide-htmx-1/) pour les caisses de bord.

### Migration étape par étape

**Étape 1: Mettre à jour le paquet NuGet**

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

**Étape 2 : Passez en revue votre code existant**

Rechercher votre base de code pour `use-htmx` attributs & #160;:

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

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

**Étape 3 : Mettre à jour le mode js (recommandé)**

Remplacer `use-htmx` avec `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" />
```

**Étape 4 : Examiner l'utilisation de TailwindEtDaisy**

Si vous n'avez pas DaisyUI installé mais utilisé `TailwindAndDaisy`:

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

**Étape 5: Tester soigneusement**

Exécutez votre application et testez :

- Navigation des pages
- Changements de taille de la page
- Mises à jour partielles HTMX (si vous utilisez HTMX)
- Préservation du filtre/de la recherche
- Réactivité mobile

### Nouvelles fonctionnalités à adopter

Une fois migrés, envisagez d'adopter ces nouvelles caractéristiques :

**Localisation :**

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

**Pager de suite (si vous utilisez NoSQL):**

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

**Mode NoJS (pour l'accessibilité):**

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

---


## Démo Application {#demo-application}

La bibliothèque comprend une application de démonstration complète montrant toutes les fonctionnalités. Vous pouvez l'exécuter localement ou la voir sur le [site de démonstration](https://paging-demo.mostlylucid.net) (à venir bientôt).

**Lancer la démo localement :**

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

Naviguez vers `https://localhost:5001` d'explorer:

1. **Pagination de base avec modèle** - Pagination traditionnelle avec pagination de style SQL
2. **Intégration HTMX** - Mises à jour dynamiques de la page sans rechargement complet de la page
3. **Rechercher avec HTMX** - Recherche combinée et pagination
4. **SCS simple** - Pas de dépendances-cadres
5. **Un vent de queue pur** - TailwindCSS sans DaisyUI
6. **Pas de JavaScript** - Pagination zéro JS entièrement fonctionnelle
7. **Modes JavaScript** - Les cinq modes JS ont été démontrés côte à côte
8. **Tri de page** - En-têtes triables avec HTMX
9. **Tri de page Non HTMX** - En-têtes triables avec charges pleine page
10. **Taille de la page avec HTMX** - Changements de taille dynamique de la page
11. **Taille de la page Non HTMX** - Taille de la page avec présentation du formulaire
12. **Pager de suite** - Pagination à base de jetons de type NoSQL
13. **Localisation** - Sélecteur de langue avec 8 langues

Chaque démo comprend :

- Code source de travail
- Explication de la technique
- Lien vers la mise en œuvre de GitHub
- Contrôles interactifs à expérimenter

---


## Le présent règlement entre en vigueur le vingtième jour suivant celui de sa publication au Journal officiel de l'Union européenne.

La version 1.0.0 représente un jalon important pour la bibliothèque PagingTagHelper. Ce qui a commencé par une simple exigence de travail est devenu une solution de pagination complète et prête à la production qui gère:

- **Pagination traditionnelle SQL** avec décalage/limite
- **Pagination du jeton de poursuite de la LSQN** pour Cosmos DB, DynamoDB, etc.
- **Localisation multilingue** pour le public mondial
- **Modes JavaScript flexibles** de HTMX à zéro-JavaScript
- **Cadres CSS multiples** de DaisyUI à pur Tailwind à aucun
- **Préservation intelligente des paramètres** sur toute la navigation
- **Assistance complète en matière d'accessibilité** avec étiquettes ARIA et navigation clavier

La bibliothèque a été testée avec 1.7k+ téléchargements et est prête pour l'utilisation de la production. Tous les 106 tests unitaires passent, et l'application de démonstration complète met en évidence les modèles d'utilisation du monde réel.

### Qu'est-ce qu'il y a ?

Améliorations futures que j'envisage :

- Support supplémentaire du cadre CSS (interface utilisateur matériel, Bulma)
- Plus de langues de localisation (contributions communautaires bienvenues!)
- Composants Blazor côté serveur
- Amélioration de l'accessibilité des grands ensembles de données

### Commencer

Installer via NuGet :

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

Consultez la documentation:

- [Dépôt GitHub](https://github.com/scottgal/mostlylucid.pagingtaghelper)
- [Paquet NuGet](https://www.nuget.org/packages/mostlylucid.pagingtaghelper)
- [Documentation complète](https://github.com/scottgal/mostlylucid.pagingtaghelper/tree/main/docs)

Questions, commentaires ou contributions? Ouvrez un numéro sur GitHub ou contactez-nous sur Twitter [@scottgal](https://twitter.com/scottgal).

Bonne pagination !