أداة مساعدة في مجال الوسم الأساسي (الجزء 2، حجم الصفحة) (العربية (Arabic))

أداة مساعدة في مجال الوسم الأساسي (الجزء 2، حجم الصفحة)

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

//

7 minute read

أولاً

كجزء من قياستي الجارية مع مساعدي الخاص بعلامة الاستدعاء لقد فصلت الآن بيز سايز إلى مساعده الخاص بعلامته هذا هو لجعل مساعد الوسم أكثر مرونة والسماح لسيناريوهات أكثر تعقيداً.

ومرة أخرى، هذه كلها أعمال تقدمية. أَنا مُسْبَقُ حتى 0.9.0.0 في وقت كتابة هذا التقرير ولكن استخدام في المخاطر الخاصة بك. إنه مجاني لكن ليس مجانياً من نوعية الإطلاق (أنا لا أملك حتى الطلعة الأولى: (ببلان)

حجم الصفحة مُحززز

وكما هو الحال مع مساعدي الآخرين للعلامة هذا مصمم لـ a استخدام حالة أنا في كثير من الأحيان صادف؛ كيفية بسهولة تبديل حجم صفحة في قوائم النتائج. للوهلة الأولى هذا يبدو بسيطاً إلى حد ما ولكن يمكن أن يصبح ترايكي بمجرد أن تضيف HTMX في المزيج.

لذلك بالنسبة لحالة استخدامي فإن المتطلبات كانت التالية

  1. يسمح بسهولة الاستخدام لمطور Teh (بأكبر قدر ممكن هو مُجهّز باستخدام نموذج صفحة بسيط)
  2. دعم تحديث حجم الصفحة باستخدام HTMX - يعني أنه قادر على وضع جاري جاري. وهذا مهم لأنه يسمح للمستخدم بتغيير حجم الصفحة دون فقدان أي مرشحات أخرى طبقتها.
  3. له نطاق ديناميكي إلى حد ما من حجم الصفحات (بما في ذلك خيار 'كل').

& مُفك مُفك مُحرف

ومع وضع هذا في الاعتبار، صممت أداة التأشير لتكون على النحو التالي:

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

والتي هي عبارة عن قطعة بسيطة من جافاسكربت والتي ستحقن معاملات الإقتراح الحالية في الطلب.

إذا كان 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();
    }
})();

تغيير واحد سأقوم بعمله هو تغيير اسم المتغير 'flaf'، __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 ........... ،.............................................................................................................. هذا هو a مفصول قائمة من صفحة حجم الـ مستخدم اختيار من. أو يمكنك أن تمر في خطواتك الخاصة. يمكنك أيضاً تحديد الحد الأقصى للصفحات التي يمكن انتقائها.

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

كما هو مع بطاقة مساعدة أنا استخدام Tagfaeder <View عنصر نمط إلى عرض مكوّن. هذا يسمح لي بالإبقاء على مساعد الوسم بسيط ومركب مكوّن العرض. كما أنه يسمح لك بتغيير وجهات النظر لتجارب مختلفة، أو حتى لضخ وجهة نظرك الخاصة واستخدام ذلك (استخدام 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;
        }
    }

في الإستنتاج

حسناً، هذا كل ما في الأمر لمساعِد العلامَة. ما زلت أعمل على الوثائق والأمثلة لكن الرمز أفضل يومياً

logo

©2024 Scott Galloway