Component de la page d'affichage ASP.NET Core Tag Helper M SK1Part 2, PageSize)
Introduction
Dans le cadre de mon sage continu avec mon outil d’aide à l’affichage, j’ai maintenant séparé PageSize en son propre outil.
Encore une fois ; ces sont tous des travaux - en cours jusqu’à 0.9.0 au moment de la rédaction mais UTILISEZ AT YOUR OWN RISK. It's free but NOT YET of release quality IMHO (I donM SK3t even have Échantillons complets encore!)
Voir le code pour l'aide à la balise ici.
Aideur à l'étiquette sur la taille de la page
Comme avec mes autres aides-étiquettes, il est conçu pour une utilisation- cas que je rencontre fréquemment ; comment changer facilement la taille de page dans les listes des résultats. À première vue, cela semble assez simple mais il peut devenir TRICKY une fois que vous ajoutez HTMX dans le mélange.
Exigences
Pour mon cas d'utilisation, les exigences étaient les suivantes :
- Permet une utilisation facile pour le développeur technique (le plus possible est configuré à l'aide d'un modèle de page simple)
- Appuie la mise à jour de la taille de la page en utilisant HTMX - ce qui signifie qu’elle' est capable d’afficher de nouveau tout en conservant tous les paramètres de la chaîne de demande. Cela est important car il permet à l'utilisateur de changer la taille de la page sans perdre les autres filtres qu'il a appliqués.
- A un éventail assez dynamique de tailles de pages (y compris une option
L'aide à l'étiquette
À cet égard, j’ai conçu le TagHelper comme suit:
<page-size
hx-target="#list"
hx-swap="innerHTML"
model="Model">
</page-size>
<div id="list">
<partial name="_ResultsList" model="Model"/>
</div>
Dans ce cas encore une fois il faut mon modèle standard de messagerie:
public interface IPagingModel
{
public int Page { get; set; }
public int TotalItems { get; set; }
public int PageSize { get; set; }
public ViewType ViewType { get; set; }
public string LinkUrl { get; set; }
}
qui fournit la Page, TotalItems, PageSize, ViewType et LinkUrl pour le messagerie.
Vous pouvez également les spécifier sous la forme d’attributes au modèle:
<page-size
hx-target="#list"
hx-swap="innerHTML"
total-items="100"
page-size="10"
view-type="DaisyAndTailwind">
</page-size>
Dans le cas des hx-target Voici le cible de la demande HTMX et hx-swap est l’objectif de la réponse.
Dans ce cas, il ' utilisera le spécifié PageSize et TotalItems et ViewType pour rendre le sélecteur de taille de la page.
JS Shenanigans
Malheureusement cette fois-ci, j’ai eu une exigence que je ne pouvais appliquer. Pour ce faire, j'ai deux pièces de JS qui seront chargées dans le contrôle en fonction du HTMX utilisé ou non
@if (Model.UseHtmx)
{
@Html.HTMXPageSizeChange()
}
else
{
@Html.PageSizeOnchangeSnippet()
}
Si HTMX est utilisé, il le rend dans la page:
(() => {
if (window.__pageSizeListenerAdded) return;
document.addEventListener('htmx:configRequest', event => {
const { elt } = event.detail;
if (elt?.matches('[name="pageSize"]')) {
const params = new URLSearchParams(window.location.search);
params.set('pageSize', elt.value); // This will update or add the pageSize param
event.detail.parameters = Object.fromEntries(params.entries());
}
});
window.__pageSizeListenerAdded = true;
})();
Il s'agit d'un élément simple de JavaScript qui injectera les paramètres de la chaîne de demande actuelle dans la requête.
En outre, si HTMX n'est pas activé il produira ce:
(function attachPageSizeListener() {
// Ensure we only attach once if this script is included multiple times
if (window.__pageSizeListenerAttached) return;
window.__pageSizeListenerAttached = true;
// Wait for the DOM to be fully loaded
document.addEventListener("DOMContentLoaded", () => {
document.body.addEventListener("change", handlePageSizeChange);
});
function handlePageSizeChange(event) {
// Check if the changed element is a page-size <select> inside .page-size-container
const select = event.target.closest(".page-size-container select[name='pageSize']");
if (!select) return;
const container = select.closest(".page-size-container");
if (!container) return;
// Default to "true" if there's no .useHtmx input
const useHtmx = container.querySelector("input.useHtmx")?.value === "true";
if (useHtmx) {
// If using HTMX, we do nothing—HTMX will handle the request
return;
}
// Either use a linkUrl from the container or the current page URL
const linkUrl = container.querySelector("input.linkUrl")?.value || window.location.href;
const url = new URL(linkUrl, window.location.origin);
// Copy existing query params from current location
const existingParams = new URLSearchParams(window.location.search);
for (const [key, value] of existingParams.entries()) {
url.searchParams.set(key, value);
}
// If user picked the same pageSize as what's already in the URL, do nothing
if (url.searchParams.get("pageSize") === select.value) {
return;
}
// Update the pageSize param
url.searchParams.set("pageSize", select.value);
// Redirect
window.location.href = url.toString();
}
})();
L’un des changements que je ferai est de changer le nom de la variable 'flageM SK2Variable; __pageSizeListenerAttached car il' est un peu trop générique ET je l'utilise à la fois pour les demandes HTMX et non
Le CODE
L’aideur de balises lui-même est assez simple, le code principal ne remplit que quelques propriétés et il rend ensuite la vue Component
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
// We want to render a <div> by default
output.TagName = "div";
// Remove any leftover attributes that we handle ourselves
RemoveUnwantedAttributes(output);
// Determine final PagerId
var pagerId = PagerId ?? PageSizeModel?.PagerId ?? $"pager-{Guid.NewGuid():N}";
// Assign ID to the outer div
output.Attributes.SetAttribute("id", pagerId);
// Build or fallback to a link URL
var finalLinkUrl = BuildLinkUrl();
if (string.IsNullOrEmpty(finalLinkUrl))
{
// If we can't build a URL, show a fallback message or short-circuit
output.Content.SetHtmlContent(
"<p style=\"color:red\">No valid link URL found for PageSize control.</p>");
return;
}
var finalPageSize = PageSize ?? PageSizeModel?.PageSize ?? 10;
// Clamp the page size to MaxPageSize
var finalTotalItems = TotalItems ?? Model?.TotalItems ?? PageSizeModel?.TotalItems ?? 0;
if (finalTotalItems == 0) throw new ArgumentNullException(nameof(finalTotalItems), "TotalItems is required");
var maxPageSize = Math.Min(finalTotalItems, MaxPageSize);
// Fallback to model's properties if not set
var finalViewType = PageSizeModel?.ViewType ?? Model?.ViewType ?? ViewType;
var pageSizeSteps = new int[] { 10, 25, 50, 75, 100, 125, 150, 200, 250, 500, 1000 };
if (!string.IsNullOrEmpty(PageSizeSteps))
pageSizeSteps = PageSizeSteps.Split(',').Select(s =>
{
if (int.TryParse(s, out var result))
return result;
else
{
throw new ArgumentException("Invalid page size step", nameof(PageSizeSteps));
}
}).ToArray();
var pageSizes = CalculatePageSizes(finalTotalItems, maxPageSize, pageSizeSteps);
var useHtmx = PageSizeModel?.UseHtmx ?? UseHtmx;
var useLocalView = PageSizeModel?.UseLocalView ?? UseLocalView;
// Acquire the IViewComponentHelper
var viewComponentHelper = ViewContext.HttpContext.RequestServices
.GetRequiredService<IViewComponentHelper>();
((IViewContextAware)viewComponentHelper).Contextualize(ViewContext);
// Construct the PageSizeModel (if not provided) with final settings
var pagerModel = new PageSizeModel
{
ViewType = finalViewType,
UseLocalView = useLocalView,
UseHtmx = useHtmx,
PageSizes = pageSizes,
PagerId = pagerId,
Model = Model,
LinkUrl = finalLinkUrl,
MaxPageSize = maxPageSize,
PageSize = finalPageSize,
TotalItems = finalTotalItems
};
// Safely invoke the "PageSize" view component
try
{
var result = await viewComponentHelper.InvokeAsync("PageSize", pagerModel);
output.Content.SetHtmlContent(result);
}
catch (Exception ex)
{
// Optional: Log or display an error
output.Content.SetHtmlContent(
$"<p class=\"text-red-500\">Failed to render PageSize component: {ex.Message}</p>"
);
}
}
Une pièce compliquée consiste à manipuler correctement PageSizeSteps attribute. Il s'agit d'une liste séparée par commas des tailles de pages que l'utilisateur peut sélectionner à partir de cette liste.
private List<int> CalculatePageSizes(int totalItems, int maxxPageSize, int[] pageSizeSteps)
{
var pageSizes = new List<int>();
// 1. Include all fixed steps up to both TotalItems and MaxPageSize
foreach (var step in pageSizeSteps)
{
if (step <= totalItems && step <= maxxPageSize)
pageSizes.Add(step);
}
// 2. If TotalItems exceeds the largest fixed step, keep doubling
int lastFixedStep = pageSizeSteps.Last();
if (totalItems > lastFixedStep)
{
int next = lastFixedStep;
while (next < totalItems && next < maxxPageSize)
{
next *= 2; // double the step
if (next <= totalItems && next <= maxxPageSize)
{
pageSizes.Add(next);
}
}
}
// 3. Include TotalItems if it's not already in the list
if (totalItems <= maxxPageSize && !pageSizes.Contains(totalItems))
{
pageSizes.Add(totalItems);
}
pageSizes.Sort();
return pageSizes;
}
Comme avec mon autre aides-tag, j'utilise le modèle TagHelper->ViewComponent pour rendre la composante de visualisation. Cela m'autorise à garder simple l'aide-tag et complexe celle des composantes de visualization . Il vous permet également de changer les vues pour différentes expériencesM SK3 ou même d'injecter votre propre vue et d'utiliser ce MSC4using use-local-view).
public class PageSizeViewComponent : ViewComponent
{
public IViewComponentResult Invoke(PageSizeModel model)
{
if(model.Model != null)
{
model.LinkUrl ??= model.Model.LinkUrl;
if(model.Model is IPagingSearchModel searchModel)
{
model.SearchTerm ??= searchModel.SearchTerm;
}
}
var viewName = "Components/Pager/Default";
var useLocalView = model.UseLocalView;
return (useLocalView, model.ViewType) switch
{
(true, ViewType.Custom) when ViewExists(viewName) => View(viewName, model),
(true, ViewType.Custom) when !ViewExists(viewName) => throw new ArgumentException("View not found: " + viewName),
(false, ViewType.Bootstrap) => View("/Areas/Components/Views/PageSize/BootstrapView.cshtml", model),
(false, ViewType.Plain) => View("/Areas/Components/Views/PageSize/PlainView.cshtml", model),
(false, ViewType.TailwindAndDaisy) => View("/Areas/Components/Views/PageSize/Default.cshtml", model),
_ => View("/Areas/Components/Views/PageSize/Default.cshtml", model)
};
// If the view exists in the app, use it; otherwise, use the fallback RCL view
}
/// <summary>
/// Checks if a view exists in the consuming application.
/// </summary>
private bool ViewExists(string viewName)
{
var result = ViewEngine.FindView(ViewContext, viewName, false);
return result.Success;
}
}
En conclusion
Bien que ce soit pour le PageSize tag helper, je travaille encore sur la documentation et les exemples, mais le code est mieux guidé chaque jour.