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

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET Core HTMX

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

Saturday, 12 April 2025

Вступ

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

Одна з "екскрементів" у стандартному HTMX полягає в тому, що зазвичай ви маєте лише один фрагмент вмісту, поміняного з заднього кінця. Однако это можно покончить с помощью HX-Trigger заголовки і маленький javascript.

ТОБАConstellation name (optional)

Я вже давно використовую цю просту систему тостів. Це проста функція, яка потребує повідомлення, тривалості і типу (успішна, помилка, попередження) і показує сповіщення про тост на сторінці.

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.

<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

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

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.

Тепер "нормально" ви могли б визначити цей код у коді html / arroel:

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

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

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

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

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

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

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

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

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

    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) атрибутів, які вмикають дію.

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

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

logo

©2024 Scott Galloway