# Показ Toast і Swapping контенту з HTMX (And ASPM SK1NET Core)

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

<!--category-- ASP.NET Core, HTMX -->
# Введение

HTMX - чудова бібліотека, яка робить ваші веб-приложения більш динамічними та чутливими.

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

**NOTA: Ви можете використовувати [`hx-swap-oob`](https://htmx.org/attributes/hx-swap-oob/) замінити дві різні *зміст* але це трохи складніше і не так просто використовувати, щоб робити JavaScripty речі.**

[TOC]

# СТОС

I' використовувала цю версію цієї простої системи повідомлень про тост [деякий час](https://www.mostlylucid.net/blog/acopybuttonforhightlightjs#showtoast-function). Це МSK1 проста функція, яка бере повідомлення МSK2 тривалість , і наділяє мSK4успіх \ , помилка \ МSK6 попередження \) і показує тостове повідомлення на сторінці

Ця ' остання версія ' має трохи більше кліпів навколо ікон M SK2 анімації і т.д.

## 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 файл M SK1за допомогою мого улюбленого CSS Tailwind & DaisyUI). Зауважте, що блок збереження класи МSK4 в кінці МSK5 Це маленький трюк для того, щоб переконатися, що класи зберігаються в кінцевому 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>
```

## Тайлвінд

Тут я визначаю, з яких файлів 'treeM SK1shake' походить, а також деякі класи анімації, які використовує тост

```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 Trigger](https://htmx.org/headers/hx-trigger/).

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

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

Або ви можете визначити його в подіях після запиту. Якщо ви щось робите, то він запускає нову подіюM SK1

```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>
```

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

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

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

```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)

Як ви бачите, у мене є БОНКА кнопок, які ' роблять речі ' для користувача МSK2 Я хотів показати повідомлення про тост і поміняти зміст пользоватевого ряду з новим змістом

Таким чином, у моєму контролі я маю простий 'switch', який бере ім 'я дії , робить речі, а потім повертає новий результат запиту

```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` і значення об 'єкта з `toast` і `issuccess` ключі. `toast` ключ - це повідомлення, яке треба показати в повідомленні про тост і `issuccess` ключ - bool, що вказує на успіх чи ні дії.

А потім `_Row` частково Я маю атрибути HX (за допомогою HTMXM SK1Net), щоб активувати дію .

```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` поміняти весь ряд з новим змістом. Це простий спосіб оновити зміст ряду, не мусив переглядати сторінки повністю

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

Це справді дуже проста та чудова техніка для ASP.NET Core з 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 все ще працює, але він використовує стандартний браузер 'confirm', що може бути трохи незручним для користувача

```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, щоб показувати тосту та обмінювати змістом на сторінці. Це чудовий спосіб зробити ваші веб-приложения більш динамічними і чутливими