Présentation du toast et échange de contenu avec HTMX (And ASP.NET CoreM SK2 (Français (French))

Présentation du toast et échange de contenu avec HTMX (And ASP.NET CoreM SK2

Saturday, 12 April 2025

//

9 minute read

Introduction

HTMX est une excellente bibliothèque pour rendre vos applications Web plus dynamiques et réactives. Dans ce post, IM SK2 je vous montrerai comment utiliser HT MX pour montrer un message de toast et échanger du contenu sur la page .

Une des 'limitations' dans la norme HTMX (iM SK3eMSC4 non swaps OOB) est que vous n’avez habituellement qu’un seul morceau de contenu remplacé à partir de l’extrémité arrière. HX-Trigger headers et un peu de javascript.

NOTE: Vous pouvez utiliser hx-swap-oob pour échanger deux Table des matières mais cela est un peu plus complexe et pas aussi facile à utiliser pour faire certaines choses JavaScripty.

TOAST (en anglais seulement)

I' j’ai utilisé cette variante de ce système simple de notification du toast pour un certain temps maintenant. C'est une fonction simple qui prend un message, la durée du message, le type de message, l'exactitude du message et le type d'erreur.

Cette 'version la plus récente ' a un peu plus de bouillage autour d'icônesM SK2 animations etc...

Le 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 });
    }
};

Cela utilise un petit fragment HTML que je définis dans mon _Layout.cshtml file (usant le CSS de Tailwind que j'ai préféré & DaisyUIM SK3 Notez le bloc de préservation de la classe ' à l'extrémité de ' à . C'est un petit truc pour s'assurer que les classes sont conservées dans la sortie HTML finale. 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>

Le vent de Tail

Ici, je définis les fichiers à partir desquels 'treeM SK1shake', ainsi que certaines classes d'animation que le toast utilise.

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")],
};

Triggered

Le secret de tout cela est d’utiliser HTMX Functionnalité de l'Header Trigger.

Maintenant 'normalement ' vous auriez définir ce dans votre html réel / code de rasoir:

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

Vous pouvez aussi le définir dans un événement après demande. Si vous faites quelque chose, il déclenche alors un nouvel événement.

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

C'est pratique si vous voulez simplement ' faire quelque chose puis l'indiquer ' s'est faite ' mais dans mon cas, je veux échanger du contenu et montrer un toast

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

Dans mon cas, le déclencheur est nommé showToast et je passe dans un message et une flage de succès. Alors j’ai défini mon JS un auditeur d’événement pour cet événement. Ce qui appele ensuite à la showToast fonction et passes dans le message et flage de succès.

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

Pourquoi puis-je utiliser ce système?? Dans un récent projet de travail, j’ai voulu prendre des mesures sur un utilisateur affiché dans une table.

userrow.png

Comme vous pouvez le voir, j'ai une BUNCH de boutons qui 'do stuff' à l'utilisateurM SK2 J'ai voulu montrer un avis de toast et échanger le contenu de la ligne d'utilisateur avec le nouveau contenu .

Donc dans mon contrôle j'ai un simple 'switch' qui prend le nom d'actionM SK2 fait des choses puis retourne le nouveau résultat de la demande .

    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
                }
            }));

        }
    }

Vous pouvez voir que j’ajoute également le HX-Trigger header à la réponse. Il s'agit d'un objet JSON avec le showToast clé et une valeur d'un objet avec le toast et issuccess clés. Le toast clé est le message à montrer dans la notification de toast et issuccess la clé est un boolean indiquant si l'action a été réussie ou non.

Ensuite, dans le _Row partiel J'ai les attributs HX (usant HTMXM SK1Net) pour déclencher l'actionMSC3

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

Vous pouvez voir que j'utilise la cible closest tr pour échanger l'ensemble de la ligne avec le nouveau contenu. Il s'agit d'une façon simple de mettre à jour le contenu de la rangée sans avoir à renouveler la page entière.

Vue partielle

C'est vraiment très simple et une bonne technique pour ASP.NET Core avec HTMX. Vous pouvez optionnellement utiliser HTMX.Nets Demande.IsHtmx` ici mais dans ce cas je ne l'utilise qu'à partir d'un appel 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);
    }

Dans ce cas, le point de vue partiel _Row est une ligne de tableau simple avec l'information de l'utilisateur et les boutons pour effectuer les actions.

Caractéristiques HTMX additionnelles

J’utilise également quelques autres fonctionnalités HTMX pour améliorer l’expérience de l’utilisateur.

Déchargement

J'utilise également un loading modal pour indiquer que la demande est en cours. Il s'agit d'une façon simple de montrer à l'utilisateur qu'il se passe quelque chose dans le contexte

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

Confirmation

J'utilise également le hx-confirm attribut pour afficher un dialogue de confirmation avant que l'action ne soit exécutée. Il s'agit d'une façon simple de s'assurer que les utilisateurs veulent réellement exécuter l'opération. SweetAlert2 pour afficher un dialogue de confirmation.

Maintenant, si vous DONT'T faire ceciM SK1 HTMX fonctionne toujours mais il utilise le dialogue standard de navigateur 'confirmMNK3 qui peut être un peu choquant pour l'utilisateurMSC4

// 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);
    });
});

En conclusion

Il s’agit d’une façon simple d’utiliser HTMX pour montrer une notification de toast et échanger des contenus sur la page. C’est un excellent moyen de rendre vos applications Web plus dynamiques et réactives.

Finding related posts...
logo

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