NOTE: Apart from
(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
//Less than a minute
HTMX är ett bra bibliotek för att göra dina webbapplikationer mer dynamiska och lyhörda. I det här inlägget ska jag visa dig hur du använder HTMX för att visa en rostat bröd anmälan och byta innehåll på sidan.
En av "begränsningarna" i standard HTMX är att du vanligtvis bara har en enda del av innehållet bytt från baksidan. Detta kan dock övervinnas med hjälp av HX-Trigger
Rubriker och lite javascript.
Jag har använt det här systemet ett tag nu. Det är en enkel funktion som tar ett meddelande, varaktighet, och typ (framgång, fel, varning) och visar en rostat meddelande på sidan.
// 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 });
}
};
Detta använder lite HTML-slippet jag definierar i min _Layout.cshtml fil (med min föredragna Tailwind CSS & DaisyUI). Lägg märke till "klassbevarande blocket" i slutet. Detta är ett litet trick för att se till att klasserna bevaras i den slutliga HTML-utmatningen. Detta är verkligen för min medvind installation som jag bara tittar på 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>
Här definierar jag vilka filer att "träd-skaka" från samt definiera några animation klasser toast använder.
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")],
};
Hemligheten med att få allt detta att fungera är att använda HTMX Trigger-huvudfunktionalitet.
Nu "normalt" skulle du definiera detta i din faktiska HTML / rakhyvelkod:
<div hx-get="/clicked" hx-trigger="click[ctrlKey]">Control Click Me</div>
Eller du kan definiera det i en efter förfrågan händelse. Så du gör något, sen utlöser det en ny händelse.
<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>
Detta är praktiskt om du bara vill "göra något sedan ange att det är gjort" men i mitt fall vill jag byta lite innehåll och visa en skål.
Response.Headers.Append("HX-Trigger", JsonSerializer.Serialize(new
{
showToast = new
{
toast = result.Message,
issuccess = result.Success
}
}));
I mitt fall är min utlösare namngiven showToast
Jag skickar in ett meddelande och en framgångsflagga. Så jag min JS Jag har definierat en händelse lyssnare för denna händelse. Detta kräver sedan in i showToast
fungerar och passerar i meddelandet och framgång flaggan.
// 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);
});
Så varför använder jag den här? I ett nytt arbetsprojekt ville jag vidta några åtgärder mot en användare som visas i en tabell. Jag ville visa en rostat bröd anmälan och byta innehållet i användarraden med det nya innehållet.
Som ni kan se har jag en BUNCH av knappar som "gör saker" till användaren. Jag ville visa en rostat bröd anmälan och byta innehållet i användarraden med det nya innehållet.
Så i min kontrollerade jag har en enkel "witch" som tar handlingens namn, gör saker sedan returnerar den nya begäran resultatet.
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
}
}));
}
}
Du kan se Jag bifogar också HX-Trigger
Huvudet på svaret. Detta är ett JSON objekt med showToast
nyckel och ett värde på ett objekt med toast
och issuccess
Nycklarna. I detta sammanhang är det viktigt att se till att toast
nyckeln är meddelandet att visa i rostat bröd anmälan och issuccess
Nyckeln är en boolean som visar om åtgärden var framgångsrik eller inte.
Sedan i _Row
partiell Jag har HX (med hjälp av HTMX.Net) attribut för att utlösa åtgärden.
<!-- 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>
Du kan se att jag använder målet closest tr
Att byta hela raden med det nya innehållet. Detta är ett enkelt sätt att uppdatera innehållet i raden utan att behöva göra en uppdatering på hela sidan.
Detta är verkligen mycket enkel och en bra teknik för ASP.NET Core med HTMX.
Du kan välja att använda HTMX.Nets
Begär.IsHtmx" här men i det här fallet använder jag bara detta från en HTMX callback.
[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 detta fall den partiella åsikten _Row
är en enkel tabellrad med användarinformationen och knapparna för att utföra åtgärderna.
Jag använder också ett par fler HTMX-funktioner för att göra användarupplevelsen bättre.
Jag använder också en enkel loading modal
ange att begäran pågår. Detta är ett enkelt sätt att visa användaren att något händer i bakgrunden.
<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>
Jag använder också hx-confirm
attribut för att visa en bekräftelsedialogruta innan åtgärden utförs. Detta är ett enkelt sätt att se till att användaren verkligen vill utföra åtgärden. Detta använder SötaAlert2 för att visa en bekräftelsedialogruta.
Nu om du inte gör detta, HTMX fungerar fortfarande men det använder den vanliga webbläsaren "bekräfta" dialogrutan som kan vara lite jarring för användaren.
// 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);
});
});
Detta är ett enkelt sätt att använda HTMX för att visa en rostat meddelande och byta innehåll på sidan. Detta är ett bra sätt att göra dina webbapplikationer mer dynamiska och lyhörda.