Un Componente Per la Visualizzazione di Pago ASP.NET Core Tag Helper (Part 2, PageSizeM SK3 (Italiano (Italian))

Un Componente Per la Visualizzazione di Pago ASP.NET Core Tag Helper (Part 2, PageSizeM SK3

Tuesday, 25 March 2025

//

8 minute read

Introduzione

Come parte del mio ongoing sage con il mio tag helper di paging ho ora separato PageSize in un'auto taghelper. Questo è per rendere più flessibile l'aiuto tag e permettere scenari di pagamento più complessi.

Ancora una volta; questi sono tutti lavori-in progressoM SK2 I 'm già fino a 0.9.0 al momento di scrivere ma usate a vostro rischio. E' gratis, ma non è ancora di qualità di pubblicazione IMHO ( Io non l'ho. campioni completi. ancora!)

Vedete la Il codice per il tag helper qui..

L'aiuto per i tag della dimensione della pagina

Come con i miei altri taghelper questo è progettato per un uso-questo che spesso incontro; come cambiare facilmente la dimensione della pagina nelle liste dei risultatiM SK2 A prima vista sembra abbastanza semplice ma può diventare TRICKY una volta aggiunto HTMX al mix.

Richiedi

Per il mio caso di uso i requisiti erano:

  1. Permette un uso facile per teh developer ( è configurato il più possibile usando un semplice modello di pagina)
  2. Sostene la aggiornazione della dimensione della pagina usando HTMX - significa che è in grado di rispondere. mantenendo tutti i parametri della stringa di query.. Questo è importante perché permette all'utente di cambiare la dimensione della pagina senza perdere altri filtri che hanno applicato.
  3. Ha una gamma leggermente dinamica di dimensioni delle pagine (incluendo un'opzione 'AllM SK2

L'aiuto della targhetta

Con questo in mente ho progettato il TagHelper come follows:

<page-size
    hx-target="#list"
    hx-swap="innerHTML"
    model="Model">
    
</page-size>
<div id="list">
    <partial name="_ResultsList" model="Model"/>
</div>

Quindi in questo caso, ancora una volta, serve il mio modello standard di paging:

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

Il che fornisce Page, TotalItems, PageSize, ViewType e LinkUrl per il pagamento.

Si possono anche indicare sotto forma di attributi al modello:


<page-size
    hx-target="#list"
    hx-swap="innerHTML"
    total-items="100"
    page-size="10"
    view-type="DaisyAndTailwind">
</page-size>

Dove il hx-target Qui è l'obiettivo per la richiesta HTMX e il hx-swap è l'obiettivo della risposta.

In questo caso ' userà la specificazione. PageSize e TotalItems e ViewType per rendere il selettore della dimensione della pagina.

JS Shenanigans

Sfortunatamente questa volta ho avuto un'esigenza che non riuscivo a gestire puramente da parte del server. In particolare volevo preservare i parametri della sequenza di query nella richiesta di cambio di dimensione della pagina. Per ottenere questo ho due pezzi di JS che si caricano nel controllo a seconda dell'uso del HTMX o non.

@if (Model.UseHtmx)
{
@Html.HTMXPageSizeChange()
}
else
{
    @Html.PageSizeOnchangeSnippet()
}

Se HTMX è usato lo rende sulla pagina:

(() => {
    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;
})();

Che è un semplice pezzo di JavaScript che inietterà i parametri della stringa di query attuale nella richiesta.

Oppure se HTMX non è attivato renderà questo:

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

Uno dei cambiamenti che faremo è cambiare il nome della variabile 'flagM SK2 __pageSizeListenerAttached perché' è un po' troppo generico E lo uso sia per le richieste HTMX che non.

Il CODE

L'aiuto di targhetta è abbastanza semplice, il codice principale riempie solo alcune proprietà e poi presenta la visuale Componente.

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

Una cosa complicata è gestire correttamente il PageSizeSteps attribute. Questa è una lista separata dalla comma delle dimensioni della pagina che l'utente può selezionare da. O potete passare con i vostri stessi passaggi.

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

Come con gli altri tag helper uso il TagHelper->ViewComponent pattern per rendere la componente visiva. Questo mi permette di mantenere semplice l'aiuto del tag and the view component complexM SK2 Permette anche di cambiare le prospettive per diverse esperienze ; o anche di iniettare la propria vista e usarla 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;
        }
    }

Conclusione

Beh, quello' è per l'aiuto al tag PageSize. IM SK2 sto ancora lavorando alla documentation e agli esempi ma il codice è migliorato ogni giorno.

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.