# Component de la page d'affichage ASP.NET Core Tag Helper M SK1Part 2, PageSize)

<datetime class="hidden">2025-03-25T22:12</datetime>

<!--category-- ASP.NET Core, TagHelper, PagingTagHelper -->
# 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](https://www.nuget.org/packages/mostlylucid.pagingtaghelper#readme-body-tab) 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](https://taghelpersample.mostlylucid.net/PageSizeWithHTMX) encore!)**

Voir le [code pour l'aide à la balise ici](https://github.com/scottgal/mostlylucid.pagingtaghelper/blob/main/mostlylucid.pagingtaghelper/Components/PageSizeTagHelper.cs).

[TOC]

# 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 :

1. Permet une utilisation facile pour le développeur technique (le plus possible est configuré à l'aide d'un modèle de page simple)
2. 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.
3. 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:

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

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

```html

<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

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

Si HTMX est utilisé, il le rend dans la page:

```javascript
(() => {
    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:

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

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

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

```csharp

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.