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.
Monday, 21 April 2025
//Less than a minute
Sur un projet de travail, j'ai utilisé et abusé de HTMX pour construire une interface utilisateur admin. Dans le cadre de ce que j'utilise la belle SweetAlert2 Bibliothèque Javascript pour mes dialogues de confirmationC'est ce que j'ai dit. Il fonctionne bien, mais je voulais aussi les utiliser pour remplacer mes indicateurs de chargement HTMX.
Cela s'est avéré être un défi donc je pensais le documenter ici pour vous sauver la même douleur.
Warning I'm a C# coder my Javascript is likely horrible.
[TOC]Donc HTMX est très intelligent, c'est hx-indicator
normalement vous permet de définir un indicateur de chargement pour vos demandes HTMX. Normalement, c'est un élément HTML dans votre page comme
<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>
Ensuite, lorsque vous voulez l'utiliser, vous décoreriez votre demande HTMX avec hx-indicator="#loading-modal"
et il montrera le modal lorsque la demande est en cours.
Maintenant HTMX fait une magie intelligente à l'aide d'un request
objet il suit en interne
function addRequestIndicatorClasses(elt) {
let indicators = /** @type Element[] */ (findAttributeTargets(elt, 'hx-indicator'))
if (indicators == null) {
indicators = [elt]
}
forEach(indicators, function(ic) {
const internalData = getInternalData(ic)
internalData.requestCount = (internalData.requestCount || 0) + 1
ic.classList.add.call(ic.classList, htmx.config.requestClass)
})
return indicators
}
Il est donc difficile de les remplacer. Comment suivre les demandes et ensuite afficher le mode SweetAlert2 lorsque la demande est en cours et le cacher quand elle est terminée.
J'ai donc mis sur le point (pas parce que je devais, parce que j'avais besoin de :)) de remplacer l'indicateur de chargement HTMX par un modal SweetAlert2. Quoi qu'il en soit, voici le code que j'ai trouvé.
You'd commencer par importer SweetAlert2 dans votre HTML / l'importer pour webpack ou similaire (voir leurs documents pour cela).
import Swal from 'sweetalert2';
export function registerSweetAlertHxIndicator() {
document.body.addEventListener('htmx:configRequest', function (evt) {
const trigger = evt.detail.elt;
const indicatorAttrSource = getIndicatorSource(trigger);
if (!indicatorAttrSource) return;
const path = getRequestPath(evt.detail);
if (!path) return;
evt.detail.indicator = null; // Disable HTMX's default class logic
sessionStorage.setItem(SWEETALERT_PATH_KEY, path);
Swal.fire({
title: 'Loading...',
allowOutsideClick: false,
allowEscapeKey: false,
showConfirmButton: false,
theme: 'dark',
didOpen: () => Swal.showLoading()
});
});
function maybeClose(evt) {
const activePath = sessionStorage.getItem(SWEETALERT_PATH_KEY);
const path = getRequestPath(evt.detail);
if (activePath && path && activePath === path) {
Swal.close();
sessionStorage.removeItem(SWEETALERT_PATH_KEY);
}
}
document.body.addEventListener('htmx:afterRequest', maybeClose);
document.body.addEventListener('htmx:responseError', maybeClose);
}
const SWEETALERT_PATH_KEY = 'swal-active-path';
function getIndicatorSource(el) {
return el.closest('[hx-indicator], [data-hx-indicator]');
}
function getRequestPath(detail) {
const basePath = detail?.pathInfo?.path ?? detail?.path ?? '';
const responsePath = detail?.pathInfo?.responsePath ?? basePath;
const elt = detail.elt;
const isGet = (detail.verb ?? '').toUpperCase() === 'GET';
const form = elt.closest('form');
// ✅ If it's a GET form, append query params
if (isGet && form) {
const params = new URLSearchParams();
for (const el of form.elements) {
if (!el.name || el.disabled) continue;
const type = el.type;
if ((type === 'checkbox' || type === 'radio') && !el.checked) continue;
if (type === 'submit') continue;
params.append(el.name, el.value);
}
const queryString = params.toString();
return queryString ? `${responsePath}?${queryString}` : responsePath;
}
// ✅ For all others, just return the response path
return responsePath;
}
document.body.addEventListener('htmx:afterRequest', maybeClose);
document.body.addEventListener('htmx:responseError', maybeClose);
Vous configurez ceci (si vous utilisez ESM) dans votre main.js
fichier comme ceci
import { registerSweetAlertHxIndicator } from './hx-sweetalert-indicator.js';
registerSweetAlertHxIndicator();
Tu verras que j'utilise le getIndicatorSource
fonction pour trouver l'élément qui a déclenché la requête HTMX. C'est important car nous avons besoin de savoir quel élément a déclenché la demande afin que nous puissions fermer le modal quand il est terminé. C'est important car HTMX a 'héritage' pour que vous ayez besoin d'escalader l'arbre pour trouver l'élément qui a déclenché la requête.
function getIndicatorSource(el) {
return el.closest('[hx-indicator], [data-hx-indicator]');
}
Puis sur n'importe quelle demande HTMX (donc hx-get
ou hx-post
) vous pouvez utiliser le hx-indicator
attribut pour spécifier le mode SweetAlert2. Vous n'avez même pas besoin de spécifier la classe comme avant, juste le paramètre existant fonctionne.
Examinons comment tout cela fonctionne :
registerSweetAlertHxIndicator()
Cela sert de point d'entrée. Vous pouvez voir qu'il s'accroche à la htmx:configRequest
l'événement. Ceci est renvoyé lorsque HTMX est sur le point de faire une demande.
Il obtient alors l'élément qui a déclenché l'événement dans evt.detail.elt
et vérifie si elle a un hx-indicator
attribut.
Enfin, il montre le mode SweetAlert2 en utilisant Swal.fire()
.
rt function registerSweetAlertHxIndicator() {
document.body.addEventListener('htmx:configRequest', function (evt) {
const trigger = evt.detail.elt;
const indicatorAttrSource = getIndicatorSource(trigger);
if (!indicatorAttrSource) return;
Si c'est le cas, il obtient le chemin de requête en utilisant getRequestPath(evt.detail)
et le stocke dans le stockage de session.
Niw HTMX est un bugger délicat, il stocke le chemin dans différents endroits en fonction de l'endroit où vous êtes dans le cycle de vie. Donc dans mon code, je fais tout le monde. avec detail?.pathInfo?.path ?? detail?.path ?? '';
Il s'avère que HTMX a stocké les demande chemin dans detail.path
et le chemin de réponse (pour document.body.addEventListener('htmx:afterRequest', maybeClose); document.body.addEventListener('htmx:responseError', maybeClose);
) dans detail.PathInfo.responsePath
Donc nous avons besoin de gérer les deux.
Nous devons aussi gérer GET
les formulaires; comme leur réponse comprendra probablement les éléments d'URL transmis comme <input >
valeurs afin que l'url de réponse puisse finir par être différent.
function getRequestPath(detail) {
const basePath = detail?.pathInfo?.path ?? detail?.path ?? '';
const responsePath = detail?.pathInfo?.responsePath ?? basePath;
const elt = detail.elt;
const isGet = (detail.verb ?? '').toUpperCase() === 'GET';
const form = elt.closest('form');
// ✅ If it's a GET form, append query params
if (isGet && form) {
const params = new URLSearchParams();
for (const el of form.elements) {
if (!el.name || el.disabled) continue;
const type = el.type;
if ((type === 'checkbox' || type === 'radio') && !el.checked) continue;
if (type === 'submit') continue;
params.append(el.name, el.value);
}
const queryString = params.toString();
return queryString ? `${responsePath}?${queryString}` : responsePath;
}
// ✅ For all others, just return the response path
return responsePath;
}
REMARQUE: Ceci est particulièrement le cas si vous utilisez le HX-Push-Url
header pour modifier l'URL de la requête que HTMX stocke pour History.
J'utilise ce petit Response
méthode d'extension pour définir la HX-Push-Url
header dans mon application ASP.NET Core.
public static class ResponseExtensions
{
public static void PushUrl(this HttpResponse response, HttpRequest request)
{
response.Headers["HX-Push-Url"] = request.GetEncodedUrl();
}
}
Ok donc maintenant nous avons le chemin, qu'est-ce qu'on en fait? Eh bien, pour garder la trace de la requête qui a déclenché le modal SweetAlert2 nous le stockons dans sessionStorage
utilisant sessionStorage.setItem(SWEETALERT_PATH_KEY, path);
.
(De plus, vous pouvez rendre cela plus complexe et vous assurer que vous n'en avez qu'un si vous en avez besoin.)
Nous montrons ensuite simplement le mode SweetAlert2 en utilisant Swal.fire()
.
Swal.fire({
title: 'Loading...',
allowOutsideClick: false,
allowEscapeKey: false,
showConfirmButton: false,
theme: 'dark',
didOpen: () => Swal.showLoading()
});
Donc maintenant nous avons le mode ouvert, nous devons le fermer quand la demande est terminée. Pour ce faire, nous appelons le maybeClose
fonction. Ceci est appelé lorsque la requête est terminée (avec succès ou avec une erreur).
Utilisation htmx:afterRequest
et htmx:responseError
les événements. Ces événements font feu une fois que HTMX a terminé une demande (note, ceux-ci sont importants, HX-Boost
Ce qui peut être un peu drôle à propos des événements qu'il allume.)
document.body.addEventListener('htmx:afterRequest', maybeClose);
document.body.addEventListener('htmx:responseError', maybeClose);
function maybeClose(evt) {
const activePath = sessionStorage.getItem(SWEETALERT_PATH_KEY);
const path = getRequestPath(evt.detail);
if (activePath && path && activePath === path) {
Swal.close();
sessionStorage.removeItem(SWEETALERT_PATH_KEY);
}
}
Vous verrez cette fonction vérifier si le chemin dans le stockage de session est le même que le chemin de la requête. S'il l'est, il ferme le modal et supprime le chemin du stockage de session.
Il s'agit d'une mise en œuvre naieve mais ça marche. surtout; à l'avenir, je pourrais ajouter quelques choses:
C'est ainsi que vous pouvez utiliser SweetAlert2 comme indicateur de chargement HTMX. C'est un peu un hack mais il fonctionne et c'est une bonne façon d'utiliser la même bibliothèque pour les indicateurs de chargement et les dialogues de confirmation.9