Back to "Näytetään paahtopaisti ja Swapping Content with HTMX (ja ASP.NET Core)"

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

Näytetään paahtopaisti ja Swapping Content with HTMX (ja ASP.NET Core)

Saturday, 12 April 2025

Johdanto

HTMX on loistava kirjasto, joka tekee web-sovelluksistasi entistä dynaamisempia ja reagoivampia. Tässä viestissä näytän, miten HTMX:llä näytetään paahtoleipämainos ja vaihdetaan sisältöä sivulla.

Yksi normaalin HTMX:n "rajoituksista" on se, että yleensä vain yksi sisältö vaihdetaan takapäästä. Tämä voidaan kuitenkin ratkaista käyttämällä HX-Trigger Headers ja pieni javascript.

TOAST

Olen käyttänyt tätä yksinkertaista paahtoleipäilmoitusjärjestelmää jo jonkin aikaa. Se on yksinkertainen toiminto, joka vie viestin, keston ja tyypin (menestys, virhe, varoitus) ja näyttää paahtoleipäilmoituksen sivulla.

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

Tämä käyttää pientä HTML-kimppua, jonka määrittelen omassani _Layout.cshtml-tiedosto (käyttäen suosikkiani Tailwind CSS & DaisyUI). Huomaa lopussa "luokan säilytyspalikka". Tämä on pieni temppu, jolla varmistetaan, että luokat säilyvät lopullisessa HTML-tuotoksessa. Tämä on todella myötätuuleen, kun katson vain 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>

Perätuuli

Määritän tässä, mitä tiedostot "puiden shake" ja mitä animaatioluokkia paahtoleipä käyttää.

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

Kytketty

Salaisuus saada tämä kaikki toimimaan on käyttää HTMX-laukaisimen toiminnallisuus.

Nyt "normaalisti" tekisit Määrittele tämä varsinaisessa html / partakoneen koodissa:

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

Tai voit määritellä sen tilausten jälkeisessä tapahtumassa. Joten jos teet jotain, se laukaisee uuden tapahtuman.

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

Tämä on kätevää, jos haluat vain "tehdä jotain, mikä osoittaa, että se on tehty", mutta minun tapauksessani haluan vaihtaa sisältöä ja nostaa maljan.

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

Minun tapauksessani liipaisin on nimetty showToast ja välitän viestin ja menestyslipun. Joten minun JS Olen määritellyt tapahtuman kuuntelijaksi tämän tapahtuman. "Tämän jälkeen showToast toimi ja kulkee viestissä ja menestyslipussa.

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

Miksi käytän tätä? No, tuoreessa työprojektissa halusin ryhtyä toimenpiteisiin pöydässä näkyvän käyttäjän suhteen. Halusin näyttää paahtoleipäilmoituksen ja vaihtaa käyttäjärivin sisällön uuteen sisältöön.

userrow.png

Kuten näette, minulla on BUNCH nappeja, jotka "tekevät juttuja" käyttäjälle. Halusin näyttää paahtoleipäilmoituksen ja vaihtaa käyttäjärivin sisällön uuteen sisältöön.

Joten minun hallinnassani on yksinkertainen "kytkin", joka ottaa toiminnan nimen, tekee juttuja sitten palauttaa uuden pyynnön tulos.

    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
logo

©2024 Scott Galloway