# Показ тоста і поміняння вмісту за допомогою ядра HTMX (І ядра ASP. NET)

<datetime class="hidden">2025-04-12T13:33</datetime>

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

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

Одна з " меж " у стандартному HTMX (тобто, [не міняються місцями OOB](https://htmx.org/attributes/hx-swap-oob/)) у тому, що зазвичай у вас зворотній бік поміняється лише один вміст. Однако это можно покончить с помощью `HX-Trigger` заголовки і маленький javascript.

**ЗАУВАЖЕННЯ: ВИ МОЖЕТЕ використовувати [`hx-swap-oob`](https://htmx.org/attributes/hx-swap-oob/) щоб поміняти місцями дві різні *вміст* елементи, але це трохи складніше і не так просто використовувати для створення JavaScript.**

[TOC]

# ТОБАConstellation name (optional)

Я використовую цей варіант цієї простої системи сповіщень тостів [на деякий час](https://www.mostlylucid.net/blog/acopybuttonforhightlightjs#showtoast-function). Це проста функція, яка потребує повідомлення, тривалості і типу (успішна, помилка, попередження) і показує сповіщення про тост на сторінці.

У цій " найкращій версії " є ще декілька олівця навколо піктограм, анімації і т.д....

## Javascript

```javascript
// HTMX toast notification
// Simple HTMX toast handler for use with hx-on::after-request
window.showToast = (message, duration = 3000, type = 'info') => {
    const toast = document.getElementById('toast');
    const toastMessage = document.getElementById('toast-message');
    const toastText = document.getElementById('toast-text');
    const toastIcon = document.getElementById('toast-icon');

    // Reset classes
    toastMessage.className = 'alert shadow-lg gap-2 transition-all duration-300 ease-in-out cursor-pointer';
    toastIcon.className = 'bx text-2xl';

    // Add DaisyUI alert type
    const alertClass = `alert-${type}`;
    toastMessage.classList.add(alertClass);

    // Add icon class
    const iconMap = {
        success: 'bx-check-circle',
        error: 'bx-error-circle',
        warning: 'bx-error',
        info: 'bx-info-circle'
    };
    const iconClass = iconMap[type] || 'bx-bell';
    toastIcon.classList.add(iconClass);

    // Set the message
    toastText.textContent = message;

    // Add slide-in animation
    toastMessage.classList.add('animate-slide-in');
    toast.classList.remove('hidden');

    // Allow click to dismiss
    toastMessage.onclick = () => hideToast();

    // Auto-dismiss
    clearTimeout(window.toastTimeout);
    window.toastTimeout = setTimeout(() => hideToast(), duration);

    function hideToast() {
        toastMessage.classList.remove('animate-slide-in');
        toastMessage.classList.add('animate-fade-out');
        toastMessage.onclick = null;

        toastMessage.addEventListener('animationend', () => {
            toast.classList.add('hidden');
            toastMessage.classList.remove('animate-fade-out');
        }, { once: true });
    }
};

```

Тут використовується маленький фрагмент HTML, який я визначаю у моєму _Компонування файла. cshtml (за допомогою мого улюбленого CSS і DaisUI). Зверніть увагу на блок збереження класу в кінці. Це невеличка хитрість, щоб переконатися, що класи зберігаються у кінцевому виводі HTML. Це дійсно для моєї конструкції з хвостовим вітром, як я тільки дивлюся на `cshtml`.

```html
<div
        id="toast"
        class="toast toast-bottom fixed z-50 hidden w-full md:w-auto max-w-sm right-4 bottom-4"
>
    <div
            id="toast-message"
            class="alert shadow-lg gap-2 transition-all duration-300 ease-in-out cursor-pointer"
    >
        <i id="toast-icon" class="bx text-2xl"></i>
        <span id="toast-text">Notification message</span>
    </div>
</div>

<!-- class-preserving dummy block -->
<div class="hidden">
    <div class="alert alert-success alert-error alert-warning alert-info"></div>
    <i class="bx bx-check-circle bx-error-circle bx-error bx-info-circle bx-bell"></i>
    <div class="animate-slide-in animate-fade-out"></div>
</div>
```

## Хвіршовий вітерweather condition

Тут я визначаю файли для "тривоги," а також визначаю деякі класи анімації, які використовує тост.

```javascript
const defaultTheme = require("tailwindcss/defaultTheme");

module.exports = {
  content: ["./Views/**/*.cshtml", "./Areas/**/*.cshtml"],
  safelist: ["dark"],
  darkMode: "class",
  theme: {
    extend: {
      keyframes: {
        'slide-in': {
          '0%': { opacity: 0, transform: 'translateY(20px)' },
          '100%': { opacity: 1, transform: 'translateY(0)' },
        },
        'fade-out': {
          '0%': { opacity: 1 },
          '100%': { opacity: 0 },
        },
      },
      animation: {
        'slide-in': 'slide-in 0.3s ease-out',
        'fade-out': 'fade-out 0.5s ease-in forwards',
      },
  },
  plugins: [require("daisyui")],
};
```

# Вимкнено

Секрет в тому, щоб зробити все це, це використання [Функціонування заголовка HTMX](https://htmx.org/headers/hx-trigger/).

Тепер "нормально" ви могли б [визначити цей код у коді html / arroel](https://htmx.org/attributes/hx-trigger/):

```html
<div hx-get="/clicked" hx-trigger="click[ctrlKey]">Control Click Me</div>
```

Або ви можете визначити його після події з просьбою. Отже, ви робите щось, а потім це призводить до нової події.

```html
<button 
    hx-get="/api/do-something"
    hx-swap="none"
    hx-on::afterRequest="window.showToast('API call complete!', 3000, 'success')"
    class="btn btn-primary"
>
    Do Something
</button>
```

Це зручно, якщо ви просто хочете "робити щось, потім вказати, що це зроблено," але у моєму випадку я хочу поміняти деякі зміст і показати тост.

```csharp
            Response.Headers.Append("HX-Trigger", JsonSerializer.Serialize(new
            {
                showToast = new
                {
                    toast = result.Message,
                    issuccess = result.Success
                }
            }));
```

У моєму випадку - мій курок. `showToast` і я передаю повідомлення і прапор успіху. Тож я визначила слухача подій для цієї події.  Це викликає в `showToast` function і передається повідомлення та прапор успіху.

```javascript
// Handles HX-Trigger: { "showToast": { "toast": "...", "issuccess": true } }
document.body.addEventListener("showToast", (event) => {
    const { toast, issuccess } = event.detail || {};
    const type = issuccess === false ? 'error' : 'success';
    showToast(toast || 'Done!', 3000, type);
});
```

# ASP. NET

Так почему я использую это? В недавньому проекті я хотів дещо зробити з користувачем, який був показаний у таблиці. Я хотів показати сповіщення про тост і поміняти зміст рядка користувача новим вмістом.

![userrow.png](userrow.png)

Як бачите, у мене є BUNCH кнопок, які "робити речі" користувачеві. Я хотів показати сповіщення про тост і поміняти зміст рядка користувача новим вмістом.

Отже, у моєму контрольованому я маю просте "перетворення," яке бере назву дії, робить речі, а потім повертає новий результат запиту.

```csharp
    private async Task ApplyAction(string email, string useraction)
    {
        if (!string.IsNullOrWhiteSpace(useraction) &&
            Enum.TryParse<UserActionType>(useraction, true, out var parsedAction))
        {
            RequestResult result;

            switch (parsedAction)
            {
                case UserActionType.FlipRoles:
                    result = await userActionService.FlipRestaurantPermissions(email);
                    break;
                case UserActionType.UnflipRoles:
                    result = await userActionService.UnFlipRestaurantPermissions(email);
                    break;
                case UserActionType.Enable2FA:
                    result = await userActionService.ToggleMFA(email, true);
                    break;
                case UserActionType.Disable2FA:
                    result = await userActionService.ToggleMFA(email, false);
                    break;~~~~
                case UserActionType.RevokeTokens:
                    result = await userActionService.RevokeTokens(email);
                    break;
                case UserActionType.Lock:
                    result = await userActionService.Lock(email);
                    break;
                case UserActionType.Unlock:
                    result = await userActionService.Unlock(email);
                    break;
                case UserActionType.Nuke:
                    result = await userActionService.Nuke(email);
                    break;
                case UserActionType.Disable:
                    result = await userActionService.DisableUser(email);
                    break;
                case UserActionType.Enable:
                    result = await userActionService.EnableUser(email);
                    break;
                case UserActionType.ResetPassword:
                    result = await userActionService.ChangePassword(email);
                    break;
                case UserActionType.SendResetEmail:
                    result = await userActionService.SendResetEmail(email);
                    break;
                default:
                    result = new RequestResult(false, "Unknown action");
                    break;
                  
            }

            Response.Headers.Append("HX-Trigger", JsonSerializer.Serialize(new
            {
                showToast = new
                {
                    toast = result.Message,
                    issuccess = result.Success
                }
            }));

        }
    }
```

Ви бачите, що я також доповнюю `HX-Trigger` заголовок відповіді. Це об'єкт JSON з `showToast` key і значення об' єкта з `toast` і `issuccess` Ключі. The `toast` key - це повідомлення, яке слід показувати у сповіщеннях про тост і `issuccess` key - це булівське значення, що вказує на те, чи була ця дія успішною, чи ні.

Потім в `_Row` part I has HX (за допомогою HTMX.Net) атрибутів, які вмикають дію.

```html
                     <!-- Revoke Login Tokens -->
                            <button class="btn btn-xs btn-error border whitespace-normal text-wrap tooltip tooltip-left" data-tip="Revoke login tokens"
                                    hx-get hx-indicator="#loading-modal" hx-target="closest tr" hx-swap="outerHTML"
                                    hx-action="Row" hx-controller="Users"
                                    hx-route-email="@user.Email" hx-route-useraction="@UserActionType.RevokeTokens"
                                    hx-confirm="Are you sure you want to revoke the login tokens for this user?">
                                <i class="bx bx-power-off"></i> Revoke
                            </button>
```

Ви можете бачити, що я використовую ціль `closest tr` щоб змінити весь рядок на новий. Це простий спосіб оновлення вмісту рядка без повного оновлення сторінки.

## Частковий вигляд

Це дійсно дуже проста і чудова техніка для ядра ASPNET з HTMX.
За бажання, ви можете використовувати HTMX.Net`s `Запит. IsHtmx тут, але в цьому випадку я тільки коли-небудь використовував це з зворотного виклику HTMX.

```csharp
    [Route("row")]
 
    public async Task<IActionResult> Row(string email, string? useraction = null)
    {

        if(!string.IsNullOrEmpty(useraction))
          await ApplyAction(email, useraction);

        var userRow = await userViewService.GetSingleUserViewModel(email);
        return PartialView("_Row", userRow);
    }
```

У цьому випадку частковий перегляд `_Row` є простим рядком таблиці з інформацією про користувача і кнопками для виконання дій.

# Додаткові можливості HTMX

Я також використовую ще декілька функцій HTMX, щоб покращити досвід користувача.

## Завантаження

Я також використовую прості `loading modal` щоб показати, що запит триває. Це простий спосіб показати користувачеві, що щось відбувається у фоновому режимі.

```html
<div id="loading-modal" class="modal htmx-indicator">
    <div
        class="modal-box flex flex-col items-center justify-center bg-base-200 border border-base-300 shadow-xl rounded-xl text-base-content dark text-center ">
        <div class="flex flex-col items-center space-y-4">
            <h2 class="text-lg font-semibold tracking-wide">Loading...</h2>
            <span class="loading loading-dots loading-xl text-4xl text-stone-200"></span>
        </div>
    </div>
</div>
```

## Підтвердити

Я також використовую `hx-confirm` атрибут для показу діалогового вікна підтвердження перед виконанням дії. Це простий спосіб переконатися, що користувач справді хоче виконати цю дію. Застосовувати [SweetAlert2](https://sweetalert2.github.io/) щоб відкрити діалогове вікно підтвердження.

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

```javascript
// HTMX confirm with SweetAlert2
window.addEventListener('htmx:confirm', (e) => {
    const message = e.detail.question;
    if (!message) return;

    e.preventDefault();

    Swal.fire({
        title: 'Please confirm',
        text: message,
        icon: 'warning',
        showCancelButton: true,
        confirmButtonText: 'Yes',
        cancelButtonText: 'Cancel',
        theme: 'dark',
    }).then(({ isConfirmed }) => {
        if (isConfirmed) e.detail.issueRequest(true);
    });
});
```

# Включення

Це простий спосіб використання HTMX для показу сповіщення про тост і зміни вмісту на сторінці. Це чудовий спосіб зробити ваші веб-застосунки більш динамічними та чуйними.