Показ Toast і Swapping контенту з HTMX (And ASPM SK1NET Core) (Українська (Ukrainian))

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

Saturday, 12 April 2025

//

8 minute read

Введение

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

Один із 'limitations' в стандартному HTMX (iM SK3eMSC4 не OOB swaps) - це те, що ви зазвичай маєте лише один шматок контенту, поміняний з заднього кінця . Однак це можна подолати за допомогою HX-Trigger заголовки і трохи javascript.

NOTA: Ви можете використовувати hx-swap-oob замінити дві різні зміст але це трохи складніше і не так просто використовувати, щоб робити JavaScripty речі.

СТОС

I' використовувала цю версію цієї простої системи повідомлень про тост деякий час. Це МSK1 проста функція, яка бере повідомлення МSK2 тривалість , і наділяє мSK4успіх \ , помилка \ МSK6 попередження ) і показує тостове повідомлення на сторінці

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

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.

<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' походить, а також деякі класи анімації, які використовує тост

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.

Тепер 'звичайно ' ви б визначити це у вашому html коді /:

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

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

<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 але в моєму випадку я хочу поміняти зміст і показати тост

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

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

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

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

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

    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), щоб активувати дію .

                     <!-- 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.Nets Запросити.IsHtmx` тут, але в цьому випадку я користуюся цим лише від зворотнього зв 'язку HTMX

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

<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 щоб показати діалог підтвердження.

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

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

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.