Компонент керування переглядом, ASP. NET Core Tag Помічник (Part 2, PageSize) (Українська (Ukrainian))

Компонент керування переглядом, ASP. NET Core Tag Помічник (Part 2, PageSize)

Comments

NOTE: Apart from English (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

Вступ

Як частина мого продовжуючого мудреця з помічником шилінгової книжечки, я тепер відокремила PageSize на свого помічника. Це зробить tag помічником більш гнучким і дозволить створити складніші сценарії.

Знову ж таки, все працює. Вже йду. до 0. 9. 0 коли писав, але не зрозумів, що це таке. Він безкоштовний, але не YET якості випуску IMHO (У мене навіть немає повні зразки ще!)

Видите код помічника теґу.

Допоміжна довідка з розміром сторінки

Так само, як і з моїми іншими інструментами для роботи з мітками, це розроблено для використання, я часто зустрічаюся з ними; як легко перемикати розмір сторінки у списках результатів. На перший погляд це здається досить простою справою, але вона може стати ТРИКІ, коли ви додаєте HTMX до цієї суміші.

Вимоги

Таким чином, для мого використання, вимоги були наступні

  1. Надає вам змогу легко використовувати для розробника тег (хоча можна налаштувати програму за допомогою простої моделі сторінок)
  2. Підтримує оновлення розміру сторінки за допомогою HTMX - тобто він може відсилати назад під час збереження всіх параметрів рядка запиту. Це важливо, оскільки за його допомогою користувач може змінювати розмір сторінки без втрати всіх інших фільтрів, які він застосовував.
  3. Має дещо динамічний діапазон розмірів сторінки (зокрема параметр " Всі ").

Помічник міток

Пам'ятаючи про це, я сконструював TagHelper таким чином:

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

Отже, в цьому випадку це знову займає мою стандартну модель розтягування:

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

Що забезпечує Page, TotalItems, PageSize, ViewType і LinkUrl за флирт.

Крім того, ви можете вказати ці атрибути у формі атрибутів моделі:


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

Де hx-target тут є призначення для запиту HTMX і hx-swap є мішенню відповіді.

У цьому випадку буде використано вказаний PageSize і TotalItems і ViewType для вибору розміру сторінки.

JS Shananians

К сожалению, в этот раз у меня была умова, что я не смог справиться с чистой стороной сервера. Ім' я Я хотів зберегти текстові параметри запитів на сторінці Змінити запит. Щоб досягти цього, я маю дві частини JS, які завантажуються у керування залежно від використання Oor, HTMX

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

Якщо використано HTMX, він переведе це на сторінку:

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

Що є простим фрагментом JavaScript, за допомогою якого можна ввести поточні параметри підрядка діалогу у запит.

Крім того, якщо HTMX НЕ увімкнено, програма покаже це:

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

Одна зміна, яку я лілічно зроблю це змінити назву змінної flag; __pageSizeListenerAttached тому що це трохи занадто загальним і я використовую це як для запитів HTMX, так і не-HTMX.

ЗАГРОЗА

Помічник міток сам по собі досить простий, головний код просто заповнив декілька властивостей, а потім переніс компонент перегляду.

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

Однією складною частиною є коректна обробка PageSizeSteps атрибут. Це відокремлений комами список розмірів сторінок, з якого користувач може вибрати. АБО ти можеш пройти своїми кроками. Крім того, ви можете вказати MaxPageSize, який є максимальним розміром сторінки, який можна обрати.

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

Так само, як і з моєю іншою допоміжною командою міток, я використовую шаблон TagHelper- >ViewComponent для показу компонента перегляду. Це дозволяє мені тримати помічника-помічника простим, а також комплексом компонентів зору. Вона також дозволяє змінювати погляди на різні досвіди; або навіть вкладати ваш власний погляд і використовувати його (за допомогою 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;
        }
    }

Включення

Що ж, це все для помічника з тегами PageSize. Я все ще працюю над документацією та прикладами, але код отримується краще кожного дня.

logo

©2024 Scott Galloway