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 ist eine großartige Bibliothek, um Ihre Web-Anwendungen dynamischer und responsiver zu machen. In diesem Beitrag zeige ich Ihnen, wie Sie HTMX verwenden, um eine Toast-Benachrichtigung anzuzeigen und Inhalte auf der Seite auszutauschen.
Eine der 'Begrenzungen' in Standard HTMX ist, dass Sie in der Regel nur ein einziges Stück Inhalt vom hinteren Ende getauscht haben. Allerdings kann dies mit der Verwendung von HX-Trigger
Kopfzeilen und ein wenig Javascript.
Ich benutze dieses einfache Toast-Benachrichtigungssystem seit einer Weile. Es ist eine einfache Funktion, die eine Nachricht, Dauer und Typ (Erfolg, Fehler, Warnung) und zeigt eine Toast-Benachrichtigung auf der Seite.
// 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 });
}
};
Dies benutzt ein kleines HTML-Snippet, das ich in meinem _Layout.cshtml Datei (mit meinem bevorzugten Tailwind CSS & DaisyUI). Beachten Sie am Ende den 'class conserving block'. Dies ist ein kleiner Trick, um sicherzustellen, dass die Klassen in der endgültigen HTML-Ausgabe erhalten bleiben. Das ist wirklich für mein Rückenwind-Setup, wie ich nur sehe 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>
Hier definiere ich, welche Dateien von 'tree-shake' zu 'tree-shake' werden, sowie definiere einige Animationsklassen, die der Toast verwendet.
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")],
};
Das Geheimnis, dass dies alles funktioniert, ist die Verwendung der HTMX Trigger-Header-Funktionalität.
Nun, 'normalerweise' würdest du Definieren Sie dies in Ihrem aktuellen html / Rasierer-Code:
<div hx-get="/clicked" hx-trigger="click[ctrlKey]">Control Click Me</div>
Oder Sie können es in einem Event nach Anfrage definieren. Man tut also etwas, dann löst es ein neues Ereignis aus.
<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>
Dies ist praktisch, wenn Sie nur'etwas tun wollen dann zeigen, dass es getan ist', aber in meinem Fall möchte ich einige Inhalte austauschen UND einen Toast zeigen.
Response.Headers.Append("HX-Trigger", JsonSerializer.Serialize(new
{
showToast = new
{
toast = result.Message,
issuccess = result.Success
}
}));
In meinem Fall ist mein Auslöser benannt. showToast
und ich übergebe eine Botschaft und eine Erfolgsflagge. Also i my JS Ich habe einen Event-Hörer für dieses Event definiert. Dies führt dann in die showToast
Funktion und übergeben in der Botschaft und Erfolg Flagge.
// 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);
});
Warum benutze ich das also? Nun, in einem aktuellen Arbeitsprojekt wollte ich etwas auf einen Benutzer, der in einer Tabelle angezeigt wird, einwirken. Ich wollte eine Toast-Benachrichtigung anzeigen und den Inhalt der Benutzerzeile mit dem neuen Inhalt austauschen.
Wie Sie sehen können, habe ich ein BUNCH von Tasten, die'tun Zeug', um den Benutzer. Ich wollte eine Toast-Benachrichtigung anzeigen und den Inhalt der Benutzerzeile mit dem neuen Inhalt austauschen.
Also in meinem kontrollierten habe ich einen einfachen 'Schalter', der den Aktionsnamen nimmt, gibt dann Sachen das neue Anfrageergebnis zurück.
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
}
}));
}
}
Sie können sehen, dass ich auch die HX-Trigger
header zur Antwort. Dies ist ein JSON-Objekt mit dem showToast
Schlüssel und ein Wert eines Objekts mit dem toast
und issuccess
Schlüssel. Das toast
Schlüssel ist die Nachricht, die in der Toast-Benachrichtigung und der issuccess
Schlüssel ist ein Boolean, der anzeigt, ob die Aktion erfolgreich war oder nicht.
Dann in der _Row
teilweise habe ich die HX (mit HTMX.Net) Attribute, um die Aktion auszulösen.
<!-- 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 siehst, ich benutze das Ziel. closest tr
um die gesamte Zeile mit dem neuen Inhalt zu tauschen. Dies ist ein einfacher Weg, um den Inhalt der Zeile zu aktualisieren, ohne eine ganze Seite aktualisieren zu müssen.
Das ist wirklich sehr einfach und eine tolle Technik für ASP.NET Core mit HTMX.
Sie können optional HTMX.Net verwendens
Anfrage.IsHtmx` hier, aber in diesem Fall benutze ich das nur von einem 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);
}
In diesem Fall die Teilansicht _Row
ist eine einfache Tabellenzeile mit den Benutzerinformationen und den Schaltflächen, um die Aktionen durchzuführen.
Ich benutze auch ein paar weitere HTMX-Funktionen, um das Benutzererlebnis zu verbessern.
Ich benutze auch eine einfache loading modal
die Angabe, dass der Antrag im Gange ist. Dies ist ein einfacher Weg, um dem Benutzer zu zeigen, dass etwas im Hintergrund passiert.
<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>
Ich benutze auch die hx-confirm
Attribut, um einen Bestätigungsdialog anzuzeigen, bevor die Aktion ausgeführt wird. Dies ist ein einfacher Weg, um sicherzustellen, dass der Benutzer wirklich die Aktion durchführen will. Dabei wird SüßerAlert2 um einen Bestätigungsdialog anzuzeigen.
Wenn Sie dies nun nicht tun, arbeitet HTMX noch, aber es verwendet den Standard-Browser-Dialog 'bestätigen', der für den Benutzer ein wenig störend sein kann.
// 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);
});
});
Dies ist eine einfache Möglichkeit, HTMX zu verwenden, um eine Toast-Benachrichtigung anzuzeigen und Inhalte auf der Seite auszutauschen. Dies ist ein guter Weg, um Ihre Web-Anwendungen dynamischer und responsiver zu machen.