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
En un proyecto de trabajo he estado usando y abusando de HTMX para construir una interfaz de usuario de administración. Como parte de esto estoy usando la encantadora SweetAlert2 Biblioteca Javascript para mis diálogos de confirmación. Funciona muy bien, pero también quería usarlos para reemplazar mis indicadores de carga HTMX.
Esto resultó ser un reto así que pensé en documentarlo aquí para salvarte el mismo dolor.
Warning I'm a C# coder my Javascript is likely horrible.
[TOC]Así que HTMX es muy inteligente, es hx-indicator
normalmente le permite establecer un indicador de carga para sus solicitudes HTMX. Normalmente esto es un elemento HTML en su página como
<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>
Luego, cuando quieras usarlo, decorarías tu petición HTMX con hx-indicator="#loading-modal"
y mostrará el modal cuando la solicitud esté en curso.
Ahora HTMX hace magia inteligente usando un request
objeto se rastrea internamente
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
}
Por lo tanto, la sustitución de estos es un poco un reto. ¿Cómo hacer un seguimiento de las solicitudes y luego mostrar el modal SweetAlert2 cuando la solicitud está en curso y ocultarlo cuando se ha terminado.
Así que me puse a trabajar (no porque tenía que hacerlo, porque NECESITÉ :)) para reemplazar el indicador de carga HTMX con un modal SweetAlert2. De todos modos, aquí está el código que se me ocurrió.
Empezaría importando SweetAlert2 en su HTML / importarlo para webpack o similar (ver sus documentos para esto).
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);
Configure esto (si está usando ESM) en su main.js
archivo como este
import { registerSweetAlertHxIndicator } from './hx-sweetalert-indicator.js';
registerSweetAlertHxIndicator();
Ya verás que uso el getIndicatorSource
función para encontrar el elemento que desencadenó la petición HTMX. Esto es importante, ya que necesitamos saber qué elemento desencadenó la petición para que podamos cerrar el modal cuando esté terminado. Esto es importante ya que HTMX tiene 'herencia' por lo que es necesario escalar el árbol para encontrar el elemento que desencadenó la solicitud.
function getIndicatorSource(el) {
return el.closest('[hx-indicator], [data-hx-indicator]');
}
A continuación, en cualquier solicitud HTMX (así que hx-get
o hx-post
) se puede utilizar el hx-indicator
atributo para especificar el modal de SweetAlert2. Ni siquiera necesitas especificar la clase como antes, solo el parámetro existente funciona.
Repasemos cómo funciona todo esto:
registerSweetAlertHxIndicator()
Esto actúa como punto de entrada. Puedes ver que se engancha en el htmx:configRequest
evento. Esto se dispara cuando HTMX está a punto de hacer una solicitud.
Luego obtiene el elemento que desencadenó el evento en evt.detail.elt
y comprueba si tiene un hx-indicator
atributo.
Por último, muestra el modal SweetAlert2 utilizando 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 lo hace, obtiene la ruta de solicitud usando getRequestPath(evt.detail)
y lo almacena en el almacenamiento de sesión.
Niw HTMX es un enculado complicado, almacena el camino en diferentes lugares dependiendo de dónde estés en el ciclo de vida. Así que en mi código hago todo lo demás. con detail?.pathInfo?.path ?? detail?.path ?? '';
Resulta que HTMX almacenaba el solicitud entrada detail.path
y la ruta de respuesta (para document.body.addEventListener('htmx:afterRequest', maybeClose); document.body.addEventListener('htmx:responseError', maybeClose);
) en detail.PathInfo.responsePath
Así que necesitamos manejar ambas cosas.
También tenemos que manejar GET
formularios; ya que su respuesta probablemente incluirá los elementos URL pasados como <input >
valores para que la url de respuesta pueda terminar siendo diferente.
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;
}
NOTA: Este es especialmente el caso si se utiliza la HX-Push-Url
encabezado para cambiar la URL de la solicitud que HTMX almacena para History.
Yo uso este pequeño Response
método de extensión para establecer el HX-Push-Url
encabezado en mi aplicación ASP.NET Core.
public static class ResponseExtensions
{
public static void PushUrl(this HttpResponse response, HttpRequest request)
{
response.Headers["HX-Push-Url"] = request.GetEncodedUrl();
}
}
Bien, ahora que tenemos el camino, ¿qué hacemos con él? Bueno, para hacer un seguimiento de qué petición se disparó el modal SweetAlert2 lo almacenamos en sessionStorage
utilizando sessionStorage.setItem(SWEETALERT_PATH_KEY, path);
.
(De nuevo puede hacer esto más complejo y asegurarse de que sólo tiene uno si lo necesita.)
A continuación, simplemente mostrar el modal SweetAlert2 utilizando Swal.fire()
.
Swal.fire({
title: 'Loading...',
allowOutsideClick: false,
allowEscapeKey: false,
showConfirmButton: false,
theme: 'dark',
didOpen: () => Swal.showLoading()
});
Así que ahora tenemos el modal abierto, tenemos que cerrarlo cuando la solicitud esté terminada. Para hacer esto llamamos a la maybeClose
función. Esto se llama cuando la petición está terminada (con éxito o con un error).
Uso htmx:afterRequest
y htmx:responseError
Acontecimientos. Estos eventos se disparan una vez que HTMX ha terminado una solicitud (nota, estos son importantes, especial para HX-Boost
que puede ser un poco divertido acerca de los eventos que dispara.)
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);
}
}
Verá que esta función comprueba si la ruta en el almacenamiento de sesión es la misma que la ruta de la solicitud. Si lo es, cierra el modal y elimina la ruta del almacenamiento de sesión.
Esta es una implementación ingenua, pero Funciona. mayormente; en el futuro podría añadir algunas cosas:
Así es como puedes usar SweetAlert2 como un indicador de carga HTMX. Es un poco de un truco, pero funciona y es una buena manera de utilizar la misma biblioteca para cargar indicadores y diálogos de confirmación.9