NOTE: Apart from
(and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.
Monday, 17 March 2025
//Less than a minute
Olen nyt erottanut PageSizen omaksi tunnisteavustajakseen osana jatkuvaa salviaani hakutunnisteen avulla. Näin tunnisteavustin on joustavampi ja mahdollistaa monimutkaisemmat hakuskenaariot.
Nämä ovat kaikki käynnissä olevia toimia. Olen jo enintään 0,9,0 Kun kirjoitat, mutta käytät omaa riskiäsi. Se on ilmaista, mutta EI vielä julkaisulaatuista Imhoa (minulla ei edes ole täydelliset näytteet vielä!)
Katso tunnisteen auttajan koodi täällä.
Kuten muidenkin taghelpeideni kohdalla, tämäkin on suunniteltu usein kohtaamaani käyttökertaa varten, kuinka voit helposti vaihtaa sivukokoa tulosluetteloissa. Ensi silmäyksellä tämä vaikuttaa melko suoraviivaiselta, mutta siitä voi tulla TRICKY, kun siihen lisätään HTMX.
Joten minun käyttötapauksessani vaatimukset olivat seuraavat:
Tätä silmällä pitäen suunnittelin TagHelper-ohjelman seuraavasti:
<page-size
hx-target="#list"
hx-swap="innerHTML"
model="Model">
</page-size>
<div id="list">
<partial name="_ResultsList" model="Model"/>
</div>
Tässäkin tapauksessa se vaatii vakiohakumallini:
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; }
}
Tästä seuraa, että Page
, TotalItems
, PageSize
, ViewType
sekä LinkUrl
Hakemista varten.
Voit myös määritellä ne mallin attribuuttien muodossa:
<page-size
hx-target="#list"
hx-swap="innerHTML"
total-items="100"
page-size="10"
view-type="DaisyAndTailwind">
</page-size>
Jossa hx-target
Tässä on kohde HTMX-pyynnölle ja hx-swap
on vastauskohteena.
Tässä tapauksessa se käyttää määritettyä PageSize
sekä TotalItems
sekä ViewType
sivun kokovalitsinta varten.
Valitettavasti tällä kertaa minulla oli vaatimus, jota en pystynyt käsittelemään pelkästään palvelinpuolella. Halusin erityisesti säilyttää kyselyn merkkijonon parameteraattoreiden sivuille muutospyynnön. Tämän saavuttamiseksi minulla on kaksi kappaletta JS:ää, jotka lataavat ohjaimeen riippuen siitä, käytetäänkö HTMX:ää vai ei
@if (Model.UseHtmx)
{
@Html.HTMXPageSizeChange()
}
else
{
@Html.PageSizeOnchangeSnippet()
}
Jos HTMX:ää käytetään, se kääntää tämän sivulle:
(() => {
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;
})();
Mikä on yksinkertainen Javascript-kappale, joka ruiskuttaa nykyiset kyselyparametrit pyyntöön.
Vaihtoehtoisesti, jos HTMX ei ole käytössä, se tekee tämän:
(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();
}
})();
Yksi muutos, jonka teen lilely, on "lippumuuttujan" nimen muuttaminen. __pageSizeListenerAttached
Koska se on hieman liian yleinen JA käytän sitä sekä HTMX- että ei-HTMX-pyyntöihin.
Itse tunnisteavustin on melko yksinkertainen, pääkoodi vain kansoittaa muutaman ominaisuuden ja kääntää sitten näkymän Componentiksi.
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>"
);
}
}
Yksi hankala osa on käsitellä oikein PageSizeSteps
Ominaisuus. Tämä on pilkkueroteltu lista sivukokoista, joista käyttäjä voi valita. OR voit kulkea omia askeleitasi. Voit myös määrittää MaxPageSize-koon, joka on suurin mahdollinen sivun koko.
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;
}
Kuten muidenkin tag-auttajieni kohdalla, käytän TagHelper->ViewComponent-mallia näkymän komponentin tekemiseen. Näin pystyn pitämään tunnisteavustimen yksinkertaisena ja näkymän komponenttikompleksina. Sen avulla voit myös muuttaa näkemyksiäsi erilaisiin kokemuksiin tai jopa ruiskuttaa omaa näkemystäsi ja käyttää sitä (käyttäen 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;
}
}
Siinä kaikki PageSize-tagin auttajalle. Työstän edelleen dokumentteja ja esimerkkejä, mutta koodi on päivä päivältä parempi.