# Att visa bröd och byta innehåll med HTMX ( Och ASP.NET CoreM SK2

<datetime class="hidden">2025-04-12T13:33</datetime>

<!--category-- ASP.NET Core, HTMX -->
# Inleiding

HTMX är en fantastisk bibliotek för att göra din webbapplikation mer dynamisk och responsiv.

En av 'gränsningarna' i standard HTMX (iM SK3eMSC4 [inte OOB-bytes](https://htmx.org/attributes/hx-swap-oob/)) innebär att du vanligtvis bara har en enda bit innehåll som bytes från baksidan. `HX-Trigger` opskrifte och lite javascript.

**NOTA: Du kan använda [`hx-swap-oob`](https://htmx.org/attributes/hx-swap-oob/) att byta ut två olika *innehåll* element, men detta är lite mer komplext och inte lika lätt att använda för att göra några JavaScripty saker.**

[TOC]

# Toast

I' har använt detta som en variant av det här enkla notificationssystemet [i ett tag nu](https://www.mostlylucid.net/blog/acopybuttonforhightlightjs#showtoast-function). Det är en enkel funktion som tar ett meddelande , loppet M SK3 och skriver ( framgång Mska5 fel Mske6 varning M Ska7 och visar en toast-avsikt på sidan

Den här versionen ' den senaste versionen ' har några fler blinkar runt ikoner

## Javascript

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

```

Det här använder en liten HTML snippet som jag definierar i min _Layout.cshtml-lêer ( med min prefererade Tailwind CSS ♫ & ♫ DaisyUI ♫). ♫ Lägg märke till ♫ class preserving block ♫ ' ♫ i slutet ♫ МSK6 ♫ Det här är ett litet trick för att säkerställa att klasserna är bevarade i den slutgiltiga HTML-outputen ♫ `cshtml`.

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

## Tailwind

Här definierar jag vilka plier som 'träd-shakeM SK2 kommer ifrån och definierar några animationsklassar som toast använder.

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

# Triggerad

Hemligheten för att göra allt detta bra är att använda [HTMX Trigger-Headerfunktion](https://htmx.org/headers/hx-trigger/).

Nu 'normalt ' skulle du [definiera detta i din riktiga html / rasor kod](https://htmx.org/attributes/hx-trigger/):

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

Eller så kan du definiera det i en efterfrågan händelse. Så när du gör något så triggar den en ny händelse

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

Det här är praktiskt om du bara vill "'", göra något, och sedan indikera det.

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

I mitt fall är min aktivator `showToast` och jag skickar in ett meddelande och en framgångsflagga. Så i mitt JS har jag definierat en händelse lyssnare för den här händelsen `showToast` funktion och passerar i meddelandet och framgångsflaggen.

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

Så varför använder jag det här?

![userrow.png](userrow.png)

Som ni kan se har jag en BUCH av knappar som ' gör sakerM SK1 till användaren . Jag ville visa ett toast-avsikte och byta innehållet i användarens rad med det nya innehållet

Så i min kontrollerade har jag en enkel 'switch' som tar actionnamen , gör saker och sedan återverkar den nya begäranresultatet

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

        }
    }
```

Ni kan se att jag också lägger till `HX-Trigger` header till svaret. Detta är ett JSON objekt med `showToast` nyckeln och en värde på ett objekt med `toast` och `issuccess` nyckeln. Den `toast` nyckeln är meddelandet som ska visas i toast-avsikten och `issuccess` nyckeln är en booleer som visar om handlingen var lyckad eller inte.

Sedan i `_Row` partiell Jag har HX (som använder HTMX.NetM SK2 egenskaper för att utlösa aktionen

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

Ni kan se att jag använder målet `closest tr` att byta hela raden med den nya innehållet. Det här är ett enkelt sätt att uppdatera innehållet i raden utan att behöva göra en komplett sidauppgradering.

## Partiell syn

Det här är verkligen en väldigt enkel och bra teknik för ASP.NET Core med HTMX.
Du kan optionellt använda HTMX.Net`s `begäran.IsHtmx` här men i det här fallet använder jag bara detta från en HTMX återkoppling

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

I det här fallet `_Row` är en enkel tabell rad med användarinformation och knapparna för att utföra aktionerna.

# Aditionella HTMX-feature

Jag använder också några fler HTMX-features för att göra användarupplevelsen bättre.

## Loading

Jag använder också en enkel `loading modal` för att indikera att begäran är på gång. Det här är ett enkelt sätt att visa till användaren att något händer i bakgrunden

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

## Bekräftelse

Jag använder också `hx-confirm` attribut för att visa en bekräftande dialog innan handlingen är utförd. Det här är ett enkelt sätt att säkerställa att användaren verkligen vill utföra handlingen [SweetAlert2](https://sweetalert2.github.io/) för att visa ett bekräftande dialog.

Nu om du DON'T gör detta, HTMX fungerar fortfarande men den använder standardbrowsern i dialogen | ' | confirm |' , som kan vara lite stökig för användaren

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

# I slutsats

Det här är ett enkelt sätt att använda HTMX för att visa ett toast-avsikte och byta innehåll på sidan.