# Компонент перегляду розкладки, ASP. NET Core Tag Помічник (Part 1. 1, щось типу... A Flippy Помічник міток)

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

<!--category-- ASP.NET Core, TagHelper, PagingTagHelper -->
# Вступ

Тож, коли я будував проект, я спочатку будував [Помічник створення міток для ](https://www.mostlylucid.net/blog/category/PagingTagHelper) А ещё я сообщаю по поводу нужды в АНГ. Спосіб спрощення збирання функціональних можливостей впорядкування для таблиці результатів з підтримкою HtMX.

Так что... я представляю тебе табличку "Заголовок Флиппі Таблицы."
Це просто швидка стаття з кодом. Завжди можна бачити [зразки](https://taghelpersample.mostlylucid.net/). І початковий код, як завжди [є тут](https://github.com/scottgal/mostlylucid.pagingtaghelper).

Я досить жахливі приклади будівництва (це логова інсталяція), але я спробую додати все більше і більше.
Щоб встановити:

```bash
dotnet add package mostlylucid.pagingtaghelper
```

![Допомога для роботи з мітками Flippy](flippyheaders.png?width=1000&format=webp)

[TOC]

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

Що це за штука? Коротко кажучи, це спосіб створити заголовок таблиці (або будь-де інше), який дозволить вам впорядкувати таблицю результатів.

Це найпростіше (і без інтеграції з HTMX) це дозволяє вам зробити це.

```html
    <sortable-header column="@nameof(FakeDataModel.CompanyCity)"
                             current-order-by="@Model.OrderBy"
                             descending="@Model.Descending"
                             controller="Home"
                             use-htmx="false"
                             action="PageSortTagHelperNoHtmx"
                       >@Html.DisplayNameFor(x => x.Data.First().CompanyCity)
</sortable-header>
```

Тут ви можете бачити, що ви вказали назву стовпчика, тест для заголовка і місце для відсилання назад (дякую) [JetBrains. Annotations](https://www.nuget.org/packages/Jetbrains.Annotations) які серед багатьох інших речей, які я ледь розчулив поверхню, даючи інтелігенції для Контролерів і Дій).

За допомогою цього пункту можна створити посилання, яке буде надіслано назад до `PageSortTagHelperNoHtmx` дія над `Home` контролер з назвою стовпчика, поточним впорядкуванням та всіма іншими параметрами у адресі URL (налаштовано за допомогою параметра `auto-append-querystring` атрибут. За допомогою цього пункту ви можете легко отримати корисне посилання зі зворотним зв' язком, ви побачите нижче, що воно зробило його досить гнучким, за допомогою якого ви зможете вказати href / action & control і отримати посилання з доданим параметром querystring.

```csharp
    private void AddQueryStringParameters(TagHelperOutput output, bool newDescending)
    {
        string? href = "";

        // If Controller and Action are provided, generate URL dynamically
        if (!string.IsNullOrEmpty(Controller) && !string.IsNullOrEmpty(Action))
        {
            href = Url.ActionLink(Action, Controller);
          
        }
        else if (output.Attributes.ContainsName("href")) // If href is manually set, use it
        {
            href = output.Attributes["href"].Value?.ToString() ?? "";
        }
        if(string.IsNullOrEmpty(href)) throw new ArgumentException("No href was provided or could be generated");
        
        // If AutoAppend is false or href is still empty, don't modify anything
        if (!AutoAppend && !string.IsNullOrWhiteSpace(href))
        {
            output.Attributes.RemoveAll("href");
            output.Attributes.SetAttribute("href", href);
            return;
        }

        // Parse the existing URL to append query parameters
        var queryStringBuilder = QueryString.Empty
            .Add("orderBy", Column)
            .Add("descending", newDescending.ToString().ToLowerInvariant());

        // Preserve existing query parameters from the current request
        foreach (var key in ViewContext.HttpContext.Request.Query.Keys)
        {
            var keyLower = key.ToLowerInvariant();
            if (keyLower != "orderby" && keyLower != "descending") // Avoid duplicating orderBy params
            {
             queryStringBuilder=   queryStringBuilder.Add(key, ViewContext.HttpContext.Request.Query[key]!);
            }
        }
href+= queryStringBuilder.ToString();
        
        // Remove old href and set the new one with the appended parameters
        output.Attributes.RemoveAll("href");
        output.Attributes.SetAttribute("href", href);
    }

```

Це метод, який додає параметри підрядка діалогу до адреси URL. Це досить просто, він створює URL на основі `Controller` і `Action` Атрибути або те, що ви встановили `href` атрибут він використає його. Якщо ви встановили `AutoAppend` щоб знешкодити, він просто використовуватиме `href` attribute as is (перекинути можна для окремих випадків використання).

## Безглузде налаштування

Щоб зробити це якомога кориснішим з / без HTMX. з / без Thailwind & DaisUI тощо Я надав вам властивості, які слід використовувати для налаштування цього відносно простого керування

```csharp
    [HtmlAttributeName("hx-controller")]
    [AspMvcController] // Enables IntelliSense for controller names
    public string? HXController { get; set; }

    [HtmlAttributeName("hx-action")]
    [AspMvcAction] // Enables IntelliSense for action names
    public string? HXAction { get; set; }
    
    
    [HtmlAttributeName("action")]
    [AspMvcAction]
    public string? Action { get; set; }
    
    [HtmlAttributeName("controller")]
    [AspMvcController]
    public string? Controller { get; set; }
    
    [ViewContext]
    [HtmlAttributeNotBound]
    public ViewContext ViewContext { get; set; }
    /// <summary>
    /// The column to sort by
    /// </summary>

    [HtmlAttributeName("column")] public string Column { get; set; } = string.Empty;

    /// <summary>
    /// Whether to auto-append any query string parameters
    /// </summary>

    [HtmlAttributeName("auto-append-querystring")] public bool AutoAppend { get; set; } = true;
    
    // <summary>
    /// Whether to use htmx ; specifcally used to set hx-vals
    /// </summary>

    [HtmlAttributeName("use-htmx")] public bool UseHtmx { get; set; } = true;

    /// <summary>
    /// The currently set order by column
    /// </summary>

    [HtmlAttributeName("current-order-by")]
    public string? CurrentOrderBy { get; set; }

    /// <summary>
    /// Sort direction, true for descending, false for ascending
    /// </summary>

    [HtmlAttributeName("descending")] public bool Descending { get; set; }

    
    /// <summary>
    ///  CSS class for the chevron up icon
    /// </summary>

    [HtmlAttributeName("chevron-up-class")]
    public string? ChevronUpClass { get; set; }
    
    /// <summary>
    ///  CSS class for the chevron down icon
    /// </summary>


    [HtmlAttributeName("chevron-down-class")]
    public string? ChevronDownClass { get; set; }
    
    /// <summary>
    /// The CSS class for the chevron when unsorted
    /// </summary>

    
    [HtmlAttributeName("chevron-unsorted-class")]
    public string? ChevronUnsortedClass { get; set; }

    /// <summary>
    /// The CSS class to use for the tag.
    /// </summary>

    [HtmlAttributeName("tag-class")] public string? TagClass { get; set; }
```

Ви можете бачити тут, що у мене є властивості для ПРЕТЕНЬ множества всього, що знаходиться в контролі (Я не буду проходити через них всі тут вони повинні бути досить зрозумілим для себе).

# З HTMX

Тепер, як ви, можливо, дізналися, я HTMX NUT, так як зазвичай, це підтримує HTMX досить безперешкодно:
ОСОБЛИВІСТЬ ДОConstellation name (optional) `use-htmx` true, що заповнює `hx-vals` атрибут. Разом з цим я використовую[ Помічники міток HTMX](https://github.com/khalidabuhakmeh/Htmx.Net/tree/bbc9de911a723bff7cd0884aa566ed68502fa94b) зробити код якомога простішим.

```html
            <sortable-header column="Name"
                             current-order-by="@Model.OrderBy"
                             descending="@Model.Descending"
                             hx-get
                             hx-route-pagesize="@Model.PageSize"
                             hx-route-page="@Model.Page"
                             hx-route-search="@Model.SearchTerm"
                             hx-controller="Home"
                             hx-action="PageSortTagHelper"
                             hx-indicator="#loading-modal"
                             hx-target="#list"
                             hx-push-url="true">@Html.DisplayNameFor(x => x.Data.First().Name)
            </sortable-header>

```

І це дуже просто. *працює* С HTMX безупречно.

# Контролер MVC

Потім я відношу це назад до простого контролера MVC, який створює деякі дані вибірки:

```csharp
    [Route("PageSortTagHelper")]
    public async Task<IActionResult> PageSortTagHelper(string? search, int pageSize = 10, int page = 1, string? orderBy = "", bool descending = false)
    {
        var pagingModel = await SortResults(search, pageSize, page, orderBy, descending);

        if (Request.IsHtmxBoosted() || Request.IsHtmx())
        {
            return PartialView("_PageSortTagHelper", pagingModel);
        }
        return View("PageSortTagHelper", pagingModel);
    }
    
     private async Task<OrderedPagingViewModel> SortResults(string? search, int pageSize, int page, string? orderBy, bool descending)
    {
        search = search?.Trim().ToLowerInvariant();
        var fakeModel = await dataFakerService.GenerateData(1000);
        var results = new List<FakeDataModel>();

        if (!string.IsNullOrEmpty(search))
            results = fakeModel.Where(x => x.Name.ToLowerInvariant().Contains(search)
                                           || x.Description.ToLowerInvariant().Contains(search) ||
                                           x.CompanyAddress.ToLowerInvariant().Contains(search)
                                           || x.CompanyEmail.ToLowerInvariant().Contains(search)
                                           || x.CompanyCity.ToLowerInvariant().Contains(search)
                                           || x.CompanyCountry.ToLowerInvariant().Contains(search)
                                           || x.CompanyPhone.ToLowerInvariant().Contains(search)).ToList();
        else
        {
            results = fakeModel.ToList();
        }

        if (!string.IsNullOrWhiteSpace(orderBy))
        {
            results = results.OrderByField(orderBy, descending).ToList();
        }

        var pagingModel = new OrderedPagingViewModel();
        pagingModel.TotalItems = results.Count();
        pagingModel.Page = page;
        pagingModel.SearchTerm = search;
        pagingModel.PageSize = pageSize;
        pagingModel.Data = results.Skip((page - 1) * pageSize).Take(pageSize).ToList();
        pagingModel.OrderBy = orderBy;
        pagingModel.Descending = descending;
        return pagingModel;
    }

```

## The `OrderByField` Метод розширення

О і у мене є невеличкий запакований метод розширення, у якому застосовується поле з назвою порядку до даних (або `IQueryable` тощо). Унизу лежить шлях розширення тінґе. Ви можете бачити, що це має 3 способи, один для потужних введених назв стовпчиків, один для назв рядків Iqueryable і один для IEnumerables.

```csharp
using System.Linq.Expressions;
using System.Reflection;

namespace mostlylucid.pagingtaghelper.Extensions;

public static class QueryableExtensions
{
    public static IQueryable<T> OrderByField<T, TKey>(
        this IQueryable<T> source,
        Expression<Func<T, TKey>> keySelector,
        bool descending = false)
    {
        return descending ? source.OrderByDescending(keySelector) : source.OrderBy(keySelector);
    }


    public static IQueryable<T> OrderByField<T>(
        this IQueryable<T> source,
        string sortBy,
        bool descending = false)
    {
        if (string.IsNullOrWhiteSpace(sortBy))
            return source; // No sorting applied

        var property =
            typeof(T).GetProperty(sortBy, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
        if (property == null)
            throw new ArgumentException($"Property '{sortBy}' not found on type '{typeof(T)}'.");

        var param = Expression.Parameter(typeof(T), "x");
        var propertyAccess = Expression.MakeMemberAccess(param, property);
        var lambda = Expression.Lambda(propertyAccess, param);

        var methodName = descending ? "OrderByDescending" : "OrderBy";

        var resultExpression = Expression.Call(
            typeof(Queryable),
            methodName,
            new[] { typeof(T), property.PropertyType },
            source.Expression,
            Expression.Quote(lambda)
        );

        return source.Provider.CreateQuery<T>(resultExpression);
    }
    
    public static IEnumerable<T> OrderByField<T>(
        this IEnumerable<T> source,
        string sortBy,
        bool descending = false)
    {
        var property = typeof(T).GetProperty(sortPropertyName(sortBy), BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
        if (property == null)
            throw new ArgumentException($"Property '{sortBy}' not found on type '{typeof(T)}'.");

        return descending
            ? source.OrderByDescending(x => property.GetValue(x, null))
            : source.OrderBy(x => property.GetValue(x, null));
    }

    // Helper methods for readability (optional)
    private static string sortPropertyName(string sortBy) => sortBy.Trim();
    private static string methodName(bool descending) => descending ? "OrderByDescending" : "OrderBy";
}
```

# Включення

Ось і все. Це просто те, що мені потрібно, так що я зробив його застрягти в пакеті нугета.