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

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

Comments

NOTE: Apart from English (and even then it's questionable, I'm Scottish). These are machine translated in languages I don't read. If they're terrible please contact me.
You can see how this translation was done in this article.

Saturday, 12 April 2025

//

7 minute read

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 HTMX-standardin "rajoituksista" (eli ei OMB-swapeja) on se, että sinulla on yleensä vain yksi sisältö vaihdettuna takapäästä. Tämä voidaan kuitenkin ratkaista käyttämällä HX-Trigger Headers ja pieni javascript.

HUOMAUTUS: Voit käyttää hx-swap-oob vaihtaa kaksi eri sisältöä elementtejä, mutta tämä on hieman monimutkaisempaa eikä yhtä helppoa käyttää joidenkin JavaScripty-juttujen tekemiseen.

TOAST

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

Tässä "viimeisimmässä versiossa" on lisää blingiä kuvakkeiden, animaatioiden jne. ympärillä...

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

        }
    }

Huomaat, että liitän myös HX-Trigger Header to the reaction. Tämä on JSON-objekti, jossa on showToast Avain ja arvo esineen kanssa toast sekä issuccess Avaimet. Erytropoietiini toast Avain on viesti, joka näkyy paahtoleipäilmoituksessa ja issuccess Avain on boolean, joka kertoo, onnistuiko toiminta vai ei.

Ja sitten... _Row Partial I have the HX (using HTMX.Net) attribuutit käynnistävät toiminnan.

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

Huomaat, että käytän kohdetta closest tr vaihtaa koko rivi uuteen sisältöön. Tämä on yksinkertainen tapa päivittää rivin sisältöä ilman koko sivun päivitystä.

Osittainen näkemys

Tämä on todella yksinkertainen ja erinomainen tekniikka ASP.NET Corelle HTMX:llä. Voit käyttää HTMX.Net-verkkoa valinnaisestis Pyyntö.Onko Htmx täällä, mutta tässä tapauksessa käytän tätä vain HTMX-puhelusta.

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

Tässä tapauksessa osittainen näkemys _Row On yksinkertainen pöytärivi, jossa on käyttäjätiedot ja painikkeet, joilla toiminnot voidaan suorittaa.

HTMX:n lisäominaisuudet

Käytän myös paria HTMX-ominaisuutta parantaakseni käyttökokemusta.

Ladataan

Käytän myös yksinkertaista loading modal ilmoittaa, että pyyntö on käynnissä. Tämä on yksinkertainen tapa näyttää käyttäjälle, että taustalla tapahtuu jotain.

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

Vahvista

Käytän myös hx-confirm Attribuutti, joka näyttää varmistusikkunan ennen kuin toiminto suoritetaan. Tämä on yksinkertainen tapa varmistaa, että käyttäjä todella haluaa suorittaa toiminnon. Tämä käyttää SweetAlert2 Näyttääksesi varmistusikkunan.

Jos et tee tätä, HTMX toimii yhä, mutta se käyttää vakiona Browser "vahvista" -valintaikkunaa, joka voi olla käyttäjän kannalta hieman sekava.

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

Johtopäätöksenä

Tämä on yksinkertainen tapa käyttää HTMX:ää paahtoleipäilmoituksen näyttämiseen ja sisällön vaihtamiseen sivulla. Tämä on loistava tapa tehdä web-sovelluksistasi dynaamisempia ja reagoivampia.

logo

©2024 Scott Galloway