مركبة لرؤية التصفح ASP.NET مساعدة في كتابة العلامات الأساسية (جزء 2, حجم الصفحةM SK3 (العربية (Arabic))

مركبة لرؤية التصفح ASP.NET مساعدة في كتابة العلامات الأساسية (جزء 2, حجم الصفحةM SK3

Tuesday, 25 March 2025

//

7 minute read

المقدمة

كجزء من مشواري المستمر مع مُساعدتي في البحث عن البطاقات الآن قمت بتقسيم PageSize إلى مَساعدتي الخاصة في البحث عنه.

مرة أخرى ; هذه كلها أعمال - تسير في تقدم إلى 0.9.0 في وقت الكتابة لكن إستخدموا في خطركم الخاص . إنه free but NOT YET of release quality العينات الكاملة حتى الآن!)

أنظر إلى الرمز لمساعد البطاقة هنا.

مساعدة ملصق حجم الصفحة

كما هو الحال مع مساعدي الأدوية الأخرى هذا مصمم لاستعمال على المدى الأول يبدو هذا بسيطاً إلى حد ما لكنه يمكن أن يصبح تريككي عندما تضيف 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 التي ستشحن في التحكم اعتماداً على 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;
})();

الذي هو قطعة بسيطة من جاواscript التي ستقوم بوضع параметрات سطر الاستفسار الحالي في الطلب.

وبدلاً من ذلك، إذا لم يتم تشغيل 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();
    }
})();

أحد التغييرات التي سأقوم بها بكل جهد هو تغيير اسم المتغير __pageSizeListenerAttached لأنّه ' هو نوعاً ما أكثر تعقيداً و أنا أستخدمه لكلّ من 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 attribute. هذا هو قائمة منفصلة عن البستة من حجم الصفحات التي يمكن للمستخدم أن يختارها من. أو يمكنك المرور بخطواتك الخاصةM SK2 يمكنك أيضاً تحديد 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;
    }

كما هو الحال مع مساعدي الأدوية الأخرى، أستخدم نموذج مساعد الأدوة ->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;
        }
    }

في الختام

حسناً ذلك ' إنه لمساعدة مدوّنة مقياس الصفحة . I' أنا مازلت أعمل على الوثائق و الأمثلة لكن الشفرة تتحسن كل يوم

Finding related posts...
logo

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