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 è un'ottima libreria per rendere le vostre applicazioni web più dinamiche e reattive. In questo post, vi mostrerò come utilizzare HTMX per mostrare una notifica di brindisi e scambiare contenuti sulla pagina.
Una delle 'limitazioni' dello standard HTMX è che di solito si ha solo un singolo pezzo di contenuto scambiato dal retro. Tuttavia questo può essere superato con l'uso di HX-Trigger
Intestazioni e un po' di javascript.
E' da un po' che uso questo semplice sistema di notifica dei toast. Si tratta di una funzione semplice che richiede un messaggio, durata e tipo (successo, errore, avviso) e mostra una notifica di brindisi sulla pagina.
// 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 });
}
};
Questo usa un piccolo frammento HTML che definisco nel mio _File Layout.cshtml (utilizzando il mio CSS & DaisyUI Tailwind preferito). Notare il 'blocco di conservazione della classe' alla fine. Questo è un piccolo trucco per garantire che le classi siano conservate nell'output HTML finale. Questo è davvero per la mia configurazione vento di coda mentre guardo solo 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>
Qui definisco quali file da 'tree-shake' oltre a definire alcune classi di animazione che il brindisi usa.
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")],
};
Il segreto di far funzionare tutto questo è usare il Funzionalità dell'intestazione HTMX Trigger.
Ora 'normalmente' tu vorresti definire questo nel vostro codice effettivo html / rasoio:
<div hx-get="/clicked" hx-trigger="click[ctrlKey]">Control Click Me</div>
Oppure puoi definirlo in un evento post-richiesta. Quindi si fa qualcosa e si innesca un nuovo evento.
<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>
Questo è utile se si vuole solo 'fare qualcosa poi indicare che è fatto', ma nel mio caso voglio scambiare alcuni contenuti e mostrare un brindisi.
Response.Headers.Append("HX-Trigger", JsonSerializer.Serialize(new
{
showToast = new
{
toast = result.Message,
issuccess = result.Success
}
}));
Nel mio caso il mio grilletto si chiama showToast
E passo un messaggio e una bandiera di successo. Così ho il mio JS ho definito un ascoltatore di eventi per questo evento. In questo caso, si tratta di una questione di competenza del Parlamento europeo. showToast
funzione e passa nel messaggio e la bandiera di successo.
// 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);
});
Allora perche' lo uso? Bene in un recente progetto di lavoro ho voluto intraprendere qualche azione su un utente visualizzato in una tabella. Volevo mostrare una notifica di brindisi e scambiare il contenuto della riga utente con il nuovo contenuto.
Come potete vedere ho un BUNCH di pulsanti che 'fare cose' per l'utente. Volevo mostrare una notifica di brindisi e scambiare il contenuto della riga utente con il nuovo contenuto.
Così nel mio controllo ho un semplice 'interruttore' che prende il nome di azione, fa roba poi restituisce il nuovo risultato della richiesta.
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
}
}));
}
}
Potete vedere che ho anche appendere il HX-Trigger
Intestazione alla risposta. Questo è un oggetto JSON con il showToast
chiave e un valore di un oggetto con il toast
e issuccess
Le chiavi. La toast
chiave è il messaggio da mostrare nella notifica toast e la issuccess
chiave è un booleano che indica se l'azione ha avuto successo o meno.
Poi nel _Row
parziale Ho gli attributi HX (utilizzando HTMX.Net) per attivare l'azione.
<!-- 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>
Potete vedere che uso l'obiettivo. closest tr
per scambiare l'intera riga con il nuovo contenuto. Questo è un modo semplice per aggiornare il contenuto della riga senza dover fare un aggiornamento a pagina intera.
Questo è davvero molto semplice e una grande tecnica per ASP.NET Core con HTMX.
È possibile utilizzare opzionalmente HTMX.Nets
Request.IsHtmx è qui ma in questo caso lo uso solo da un richiamo HTMX.
[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 questo caso la vista parziale _Row
è una semplice riga della tabella con le informazioni dell'utente e i pulsanti per eseguire le azioni.
Utilizzo anche un paio di altre funzionalità HTMX per migliorare l'esperienza dell'utente.
Uso anche un semplice loading modal
indicare che la richiesta è in corso. Questo è un modo semplice per mostrare all'utente che qualcosa sta accadendo in background.
<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>
Io uso anche il hx-confirm
attributo per mostrare una finestra di conferma prima che l'azione sia eseguita. Questo è un modo semplice per garantire che l'utente voglia davvero eseguire l'azione. Questo usa SweetAlert2City name (optional, probably does not need a translation) per mostrare una finestra di conferma.
Ora, se non si fa questo, HTMX funziona ancora, ma utilizza la finestra di dialogo standard del browser 'conferma' che può essere un po' disturbante per l'utente.
// 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);
});
});
Questo è un modo semplice per utilizzare HTMX per mostrare una notifica di brindisi e scambiare contenuti sulla pagina. Questo è un ottimo modo per rendere le applicazioni web più dinamiche e reattive.