# HTMX (і невеличкі альпійські.js) для SPA-подібного досвіду в ядрах ASP. NET

# Вступ

На цьому сайті я використовую [HTMX](https://htmx.org/) Широко, це дуже простий спосіб змусити ваш сайт почуватися більш чуйним і плавнішим, без необхідності писати багато JavaScript.

**ЗАУВАЖЕННЯ: я ще не зовсім задоволений цим, у HTMX є деякі елементи, які роблять цю роботу заплутаною**

<datetime class="hidden">2024-09-15T06:45</datetime>

<!--category-- ASP.NET, HTMX, Alpine.js -->
[TOC]

# Ядро HTMX і ASP.NET

Використання HTMX з ядром ASP.NET є трохи мрією, я написав про це раніше в [цей допис](/blog/htmxwithaspnetcore). Це дуже легко для Вашого сайту, і це чудовий спосіб, щоб Ваш сайт почувався більш реагуючим.

## Звичайні посилання

Як частина моєї останньої поїздки, використовуючи HTMX, я вирішив зробити якомога більше посилань на цьому сайті, використовуючи HTMX якомога більше. Наприклад, якщо ви натиснете кнопку " Домівка," розташовану над нею, тепер використовуєте HTMX для завантаження вмісту сторінки.

Я досягну цього, просто додавши деякі властивості до теґу посилання, ось так:

```razor
    <a asp-action="IndexPartial" asp-controller="Home" hx-swap="show:window:top"  hx-target="#contentcontainer" hx-boost="true" class="mr-auto sm:mr-6">
     <div class="svg-container">
        <img src="/img/logo.svg" asp-append-version="true"  width="180px" height="30px"  alt="logo"  :class="{ 'img-filter-dark': isDarkMode }"/>
     </div>
    </a>
```

Тут ви бачите, що я додаю `hx-swap` attribute, за допомогою цього параметра можна наказати HTMX поміняти вміст елемента призначення на вміст, повернений з сервера.

**ЗАУВАЖЕННЯ: НЕ використовуйте hx- pushurl, оскільки це змусить навігатора двічі перезавантажити сторінку на задній кнопці.**

The `hx-target` attribute повідомляє HTMX, куди пересунути вміст повернений з сервера. В цьому випадку це `#contentcontainer` element (де завантажено весь вміст сторінки).

Я також додаю `hx-swap="show:window:top` За допомогою цього пункту можна перегорнути вікно на вершину сторінки, якщо вміст буде змінено.

Частина C# є досить простою, вона просто дублює мій звичайний метод індексування, але натомість завантажує частковий перегляд. Нижче показано код. Знову це використовується [HTMX. NET](https://github.com/khalidabuhakmeh/Htmx.Net) для виявлення `hx-request` заголовок і повернути частковий перегляд. Є також скорочення, щоб повернути звичайний вигляд, якщо запит не є запитом на HTMX.

```csharp
    [Route("/IndexPartial")]
    [OutputCache(Duration = 3600, VaryByHeaderNames = new[] { "hx-request" })]
    [ResponseCache(Duration = 300, VaryByHeader = "hx-request",
        Location = ResponseCacheLocation.Any)]
    [HttpGet]
    public async Task<IActionResult> IndexPartial()
    {
        ViewBag.Title = "mostlylucid";
        if(!Request.IsHtmx()) return RedirectToAction("Index");
        var authenticateResult = await GetUserInfo();
        var posts = await BlogService.GetPagedPosts(1, 5);
        posts.LinkUrl = Url.Action("Index", "Home");
     
        var indexPageViewModel = new IndexPageViewModel
        {
            Posts = posts, Authenticated = authenticateResult.LoggedIn, Name = authenticateResult.Name,
            AvatarUrl = authenticateResult.AvatarUrl
        };
        return PartialView("_HomePartial", indexPageViewModel);
    }
```

Результатом цього є те, що це посилання (та інші використовують ту саму методику) навантаження на запити " AJAX " і вміст змінено без перезавантаження сторінки на повну. Це уникає набридливого спалаху світла, який ви отримаєте, коли сторінка перезавантажується через те, як працює зміна теми.

## Посилання блогу (простий спосіб)

Таким чином, метод, поданий нижче, може бути приведений до роботи, але це дуже складно і трохи незграбно.
HTMX дуже любить працювати за допомогою методу, заснованого на атрибутах. Користування `htmx.ajax` є свого роду "якщо все інше зазнає невдачі" метод і означає, що вам потрібно переплутати історію Борсера.
На жаль, журнал переглядача використовує `pushState` `replaceState` і `popstate` це дійсно трохи біль в шиї (але може бути дуже потужним для випадків ребра).

Замість цього, я просто додаю `hx-boost` до мітки, що містить вміст блогу.

```html
   <div class="prose prose max-w-none border-b py-2 text-black dark:prose-dark sm:py-2" hx-boost="true"  hx-target="#contentcontainer">
        @Html.Raw(Model.HtmlContent)
    </div>
```

Це, здається, добре працює і `hx-boost` можна використовувати батьківські методи і застосовувати до всіх зв'язків між дітьми. Це також більш стандартний HTMX, не втручаючись в історію і т.д.

У рідкісних випадках, коли ви НЕ хочете використовувати HTMX для окремих посилань, ви можете просто додати `hx-boost="false"` [з посиланням](https://htmx.org/attributes/hx-boost/).

## Посилання блогу (NOTE: я відмовився від цього підходу, але зберіг його тут для посилання)

Але проблема з цим підходом була в тому, що мій блог посилається на такі посилання: [HTMX для SPA-подібного досвіду](/blog/htmxtomakeyoursitemorespalike) які не є " нормальними " посиланнями. Ці посилання створюються інструментом обробки markdown. Під час написання додатка MarkDig для додавання атрибутів HTMX до посилань, автор вирішив скористатися іншим методом (оскільки у мене вже є тонна вмісту, я не хочу перепаровувати).

Замість цього я додав функцію JavaScript, яка шукає всі ці типи посилань, а потім використовує `htmx.ajax` зробити заміну. Це, по суті, те, що HTMX робить в будь-якому випадку просто "вручну."

Як ви бачите, це просто функція, яка шукає всі посилання `div.prose` елемент, який починається з a `/` а тоді додає слухачеві події. Якщо посилання було натиснуто, події буде заборонено, буде видобуто адресу URL, а потім посилання `htmx.ajax` function викликається за допомогою адреси URL і елемента призначення для оновлення.

```javascript
(function(window) {
   
    window.blogswitcher =() =>  {
        const blogLinks = document.querySelectorAll('div.prose  a[href^="/"]');

        // Iterate through all the selected blog links
        blogLinks.forEach(link => {
            link.addEventListener('click', function(event) {
               event.preventDefault();
                let link = event.currentTarget;
                let url = link.href;
                htmx.ajax('get', url, {
                    target: '#contentcontainer',  // The container to update
                    swap: 'innerHTML',            // Replace the content inside the target


                }).then(function() {
                    history.pushState(null, '', url);
                  
                        window.scrollTo({
                            top: 0,
                            behavior: 'smooth' // For a smooth scrolling effect
                        });
                    
                });
            });
        });
        
    };
})(window);
```

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

## Кнопка зворотного зв' язку (Це важко працювати)

Зворотна кнопка у цьому типі програми може бути проблематичною, багато з них " властиві " SCAS або вимкнути кнопку зворотного зв' язку, або виконати таку неправильну поведінку. Проте я хотів переконатися, що задня кнопка працює так, як очікувалося.
Примітка: Щоб зробити так, як очікувалося, нам також потрібно послухати `popState` Замечательно. Цю подію буде запущено під час переходу користувача назад або вперед у історії переглядача. @ info: whatsthis Після запуску цієї події ми можемо перезавантажити вміст поточної адреси URL.

```javascript
window.addEventListener("popstate",   (event) => {
 // When the user navigates back, reload the content for the current URL
 event.preventDefault();
 let url = window.location.href;
 // Perform the HTMX AJAX request to load the content for the current state
 htmx.ajax('get', url, {
  target: '#contentcontainer',
  swap: 'innerHTML'
 }).then(function () {
  // Scroll to the top of the page
  window.scrollTo({
   top: 0,
   behavior: 'smooth'
  });
 });
});
```

У цьому коді ми запобігаємо типовій поведінці навігатора за допомогою `event.preventDefault()` Дзвінок. Далі ми витягуємо поточний URL з `window.location.href` властивість і виконання запиту HTMX AJAX на завантаження вмісту поточного стану. Як тільки зміст буде завантажено, ми гортатимемо його на вершину сторінки.

Як вже згадувалося раніше, ви не можете користуватися ТАКОЖ `hx-pushurl` оскільки за допомогою цього пункту можна наказати переглядачу повторно перезавантажити сторінку на бічній кнопці.

# Загалом, HTMX (і альпійські.js)

Як ви, можливо, здогадалися, я дещо фанат HTMX, разом з ядром ASP.NET і розбиття альпійських.js це дає змогу devs створити дійсно чудовий досвід користувача з мінімальними зусиллями.
Основна річ, яку я віддаю перевагу таким, як "докладніше сформовані" клієнтські оболонки на кшталт React / Кутовий тощо... це те, що вона все ще надає мені повний канал відображення на сервері з ядром ASP. NET, але "відчуття" SPA.
Альпійський.js надає можливість простої взаємодії з клієнтами, дуже проста функція, яку я нещодавно додав, робить мої категорії "прихованими," але розгорненими після клацання.

Використання альпійських.js для цього означає, що додаткового JavaScript не потрібно.

```razor
        <div class="mb-8 mt-6 border-neutral-400 dark:border-neutral-600 border rounded-lg" x-data="{ showCategories: false }">
            <h4 
                class="px-5 py-1 bg-neutral-500  bg-opacity-10 rounded-lg  font-body text-primary dark:text-white w-full flex justify-between items-center cursor-pointer"
                x-on:click="showCategories = !showCategories"
            >
                Categories
                <span>
                    <i
                        class="bx text-2xl"
                        :class="showCategories ? 'bx-chevron-up' : 'bx-chevron-down'"
                    ></i>
                </span>
            </h4>
            <div 
                class="flex flex-wrap gap-2 pt-2 pl-5 pr-5 pb-2"
                x-show="showCategories" 
                x-cloak
                x-transition:enter="max-h-0 opacity-0"
                x-transition:enter-end="max-h-screen opacity-100"
                x-transition:leave="max-h-screen opacity-100"
                x-transition:leave-end="max-h-0 opacity-0"
            >
                @foreach (var category in ViewBag.Categories)
                {
                    <partial name="_Category" model="category"/>
                }
           
            </div>
        </div>

```

Тут ви бачите, що я використовую `x-data` атрибут створення нового об' єкта даних альпійської. js, у нашому випадку `showCategories`. Це булівське значення, яке буде перемикано, якщо `h4` clicked element. The `x-on:click` атрибут використовується для прив' язки події клацання до функції- перемикача.

В межах списку категорій Я потім використовую `x-show` атрибут, який слід показати або сховати список категорій на основі значення `showCategories`. Я також використовую `x-transition` attribute, щоб додати ефект спадання під час показу або приховування категорій.
The `x-cloak` [attribute](https://alpinejs.dev/directives/cloak) використовується для запобігання запуску JavaScript для показу категорій.
У мене є маленький клас CSS визначений для цього:

```css
[x-cloak] { display: none !important; }
```

# Включення

Ось і все, коротка стаття про те, як використовувати HTMX і місце альпійського.js, щоб ваш сайт почувався більш чуйним і "SPA-подібним." Я сподіваюсь, що вам це стане у пригоді, якщо у вас є запитання або коментарі, будь ласка, залиште їх внизу.