REMARQUE: Venir, juste mettre les touches de finition à elle. Suivez GitHub ! .
C'est juste pour vous montrer tout ce que je suis en train de faire avec ce contrôle !
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, ajouté en-têtes triables, et extrait contrôle de la taille des pages. La version 1.0.0 prend tout ce que nous avons appris et ajoute des fonctionnalités d'entreprise critiques:
Plongons dans chacune de ces caractéristiques et voyons comment ils travaillent ensemble pour créer une solution de pagination vraiment flexible.
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.
Voici comment la pagination continue de jeton diffère de la pagination traditionnelle:
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 :
Pagination à base de jetons :
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:
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 pagesPageSize: Articles par pageCurrentPage: Numéro de page affiché uniquement pour l'interface utilisateurPageTokenHistory: Dictionary mapping pages numbers to jens for backer navigationViewType: Quel cadre CSS utiliser pour le renduImplémentons maintenant une action de contrôleur qui simule la pagination Cosmos DB :
[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é:
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:
@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>[email protected]("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 :
HasMoreResults est fauxLe 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:
<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.
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):
<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.
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 :
<!-- URL with filters -->
/Products?category=electronics&brand=acme&minPrice=100
<!-- After clicking Next -->
/Products?category=electronics&brand=acme&minPrice=100¤tPage=2&pageToken=xyz123&tokenHistory={...}
<!-- All filters preserved! Token is valid because query context matches. -->
Vous pouvez désactiver ce comportement si nécessaire :
<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 :
// 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.
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.
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 :
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/.
L'utilisation de la localisation est simple. Il suffit d'ajouter le language attribut & #160;:
<paging
model="Model"
language="de"
show-summary="true"
first-last-navigation="true" />
Cela rend tout le texte en allemand:
<!-- 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 :
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:
@{
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 :
<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 :
<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.
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.
Voici la ventilation complète des modes JavaScript :
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)
<paging
model="Model"
js-mode="HTMX"
htmx-target="#results-container" />
Reveurs :
<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
<paging
model="Model"
js-mode="HTMXWithAlpine"
htmx-target="#results-container" />
Reveurs :
<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
<paging
model="Model"
js-mode="Alpine" />
Reveurs :
<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
<paging
model="Model"
js-mode="PlainJS" />
Reveurs :
<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:
@Html.PageSizeOnchangeSnippet()
Ceci injecte le JavaScript nécessaire pour gérer les changements de taille de page déroulante sans HTMX.
5. Mode noJS
<paging
model="Model"
js-mode="NoJS" />
Reveurs :
<!-- 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 :
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.
Pour la compatibilité arrière, l'ancien use-htmx l'attribut fonctionne toujours :
<!-- 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é:
<!-- New syntax (recommended) -->
<paging model="Model" js-mode="HTMX" />
<paging model="Model" js-mode="PlainJS" />
La version 1.0.0 introduit deux ajouts importants de ViewType qui traitent des scénarios réels communs.
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:
<paging
model="Model"
view-type="Tailwind" />
Cela rend uniquement en utilisant les classes d'utilitaire Tailwind standard:
<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
Les NoJS ViewType combine zéro JavaScript avec le style CSS Plain:
<paging
model="Model"
view-type="NoJS"
show-pagesize="true" />
Principales différences par rapport aux autres types de vues :
<a href="/Products?page=2" class="pager-button">Next ›</a>
<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.
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 :
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¤tPage=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 :
<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.
La mise à niveau à partir des versions pré-1.0 est simple, mais il y a quelques changements de rupture à prendre en compte.
1. use-htmx est déprécié (mais fonctionne toujours)
Vieux :
<paging model="Model" use-htmx="true" />
<paging model="Model" use-htmx="false" />
Nouveau (recommandé) :
<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):
<paging model="Model" view-type="TailwindAndDaisy" />
Nouveau (pour obtenir un vieux comportement):
<paging model="Model" view-type="Tailwind" />
Continuer d'utiliser TailwindAndDaisy si vous utilisez des composants DaisyUI:
<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 pour les caisses de bord.
Étape 1: Mettre à jour le paquet NuGet
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;:
# 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:
- <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:
- <paging model="Model" view-type="TailwindAndDaisy" />
+ <paging model="Model" view-type="Tailwind" />
Étape 5: Tester soigneusement
Exécutez votre application et testez :
Une fois migrés, envisagez d'adopter ces nouvelles caractéristiques :
Localisation :
<paging
model="Model"
language="@CultureInfo.CurrentUICulture.TwoLetterISOLanguageName" />
Pager de suite (si vous utilisez NoSQL):
<continuation-pager
model="Model"
htmx-target="#results-container"
show-page-number="true" />
Mode NoJS (pour l'accessibilité):
<paging model="Model" js-mode="NoJS" />
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 (à venir bientôt).
Lancer la démo localement :
git clone https://github.com/scottgal/mostlylucid.pagingtaghelper.git
cd mostlylucid.pagingtaghelper/mostlylucid.pagingtaghelper.sample
dotnet run
Naviguez vers https://localhost:5001 d'explorer:
Chaque démo comprend :
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:
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.
Améliorations futures que j'envisage :
Installer via NuGet :
dotnet add package mostlylucid.pagingtaghelper --version 1.0.0
Consultez la documentation:
Questions, commentaires ou contributions? Ouvrez un numéro sur GitHub ou contactez-nous sur Twitter @scottgal.
Bonne pagination !
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.