Back to "Un ayudante de etiquetas habilitado para Alpine.js y HTMX para ASP.NET Core"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

Alpine.js ASP.NET Core HTMX Javascript

Un ayudante de etiquetas habilitado para Alpine.js y HTMX para ASP.NET Core

Friday, 25 April 2025

Introducción

Sólo una rápida, tenía una necesidad en un proyecto de trabajo para la capacidad de 'limpiar' los parámetros de URL de una URL. Esto es útil cuando tienes una URL con múltiples parámetros, y quieres eliminar uno o más de ellos (por ejemplo, para un filtro de búsqueda).

El problema

Mi proyecto actual utiliza cadenas de consulta de la vieja escuela (es un sitio de administración por lo que no necesita la fantasía de 'agradable' urls). Así que termino con una URL como esta:

/products?category=electronics&search=wireless+headphones&sort=price_desc&inStock=true&page=3

Ahora estos pueden variar con cada página, así que puedo terminar con un BUNCH en la URL de la página y necesito ser capaz de borrarlos sin escribir un montón de placa de caldera para hacerlo.

Puede hacer esto como parte de cualquier control de entrada que utilice, por ejemplo, junto a cada casilla de verificación (o un icono claro de estilo de marcador de posición de lujo) pero puede utilizar esta técnica para ellos también. Sin embargo, en este caso quería hacer dos cosas principales:

  1. Ser capaz de borrar un parámetro nombrado
  2. Ser capaz de borrar una lista de parámetros.
  3. Ser capaz de borrar todos los parámetros
  4. Tenerlo de vuelta con HTMX
  5. Haz que use mi indicador de carga. mi indicador de carga.

La solución

En mi proyecto ya utilizo

  • HTMX
  • Alpine.js
  • ASP.NET Core
  • CoilwindCSS
  • DaisyUI

Así que mi solución se centró en el uso de estos para obtener una solución bonita y funcional con un código mínimo.

El ayudante de la etiqueta

Mi TagHelper es bastante simple, todo lo que hago es crear un <a> más tarde pasaré al módulo alpino y habremos terminado.

[HtmlTargetElement("clear-param")]
public class ClearParamTagHelper : TagHelper
{
    [HtmlAttributeName("name")]
    public string Name { get; set; }
    
    [HtmlAttributeName("all")]
    public bool All { get; set; }= false;
    
    [HtmlAttributeName("target")]
    public string Target { get; set; } = "#page-content";

    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        output.TagName = "a";
        output.Attributes.SetAttribute("x-data", "window.queryParamClearer({})");

        if (All)
        {
        output.Attributes.SetAttribute("x-all", All);
        }
        else
        {
            output.Attributes.SetAttribute("x-param", Name);
        }
        output.Attributes.SetAttribute("data-target", Target);
        output.Attributes.SetAttribute("x-on:click.prevent", "clearParam($event)");
        output.Content.SetHtmlContent(@"
            <div class='w-6 h-6 flex items-center justify-center bg-red-600 text-white rounded-full'>
                <i class='bx bx-x text-lg'></i>
            </div>");
    }
}

Parámetros

En uso esto se ve así, primero para 'limpiar todos los parámetros'. Así que sólo miro el Context.Request.Query si hay algún parámetro allí renderizo el pequeño x para que el usuario limpie todos los parámetros.


@if(Context.Request.Query.Any())
{
<label class="param-label">
    <clear-param all="true"></clear-param>
    clear all
</label>
}
</div>

Alternativamente para parámetros nombrados puedo hacer esto


<div class="param-label">
    <clear-param name="myParam"></clear-param>
    <p>My Param: @Model.MyParam</p>
</div>

Lo que, por supuesto, aclararía ese parámetro único.

O incluso


<div class="param-label">
    <clear-param name="myParam1,myParam2,myParam3"></clear-param>
    <p>My Param: @Model.MyParam1</p>
    <p>My Param: @Model.MyParam2</p>
    <p>My Param: @Model.MyParam3</p>
</div>

Esto borra todos los parámetros nombrados de la cadena.

Los target atributo

You también tiene la opción de pasar en un target atributo que se utilizará como la hx-target atributo. Esto es útil si desea actualizar una parte específica de la página con el nuevo contenido.


<div class="param-label">
    <clear-param name="myParam" target="#my-thing"></clear-param>
    <p>My Param: @Model.MyParam</p>
</div>

En mi caso (porque lo escribí) por defecto el objetivo a mi #page-content div.

    [HtmlAttributeName("target")]
    public string Target { get; set; } = "#page-content";

El resultado

Estos resultados en la representación del siguiente HTML:

  • Todos: Así que obtenemos HTML con el x-all atributo y no x-param atributo.
<a x-data="window.queryParamClearer({})" x-all="True" data-target="#page-content" x-on:click.prevent="clearParam($event)">
    <div class="w-6 h-6 flex items-center justify-center bg-red-600 text-white rounded-full">
        <i class="bx bx-x text-lg"></i>
    </div>
</a>

  • Soltero Obtenemos HTML con el x-param atributo y no x-all atributo.
<a x-data="window.queryParamClearer({})" x-param="myParam" data-target="#page-content" x-on:click.prevent="clearParam($event)">
    <div class="w-6 h-6 flex items-center justify-center bg-red-600 text-white rounded-full">
        <i class="bx bx-x text-lg"></i>
    </div>
</a>
  • Múltiples Obtenemos HTML con el x-param atributo con una cadena separada por coma y no x-all atributo.
<a x-data="window.queryParamClearer({})" x-param="myParam1,myParam2,myParam3" data-target="#page-content" x-on:click.prevent="clearParam($event)">
    <div class="w-6 h-6 flex items-center justify-center bg-red-600 text-white rounded-full">
        <i class="bx bx-x text-lg"></i>
    </div>
</a>

Cada uno de ellos también tiene los dos atributos alpinos x-data y x-on:click.prevent que se utilizan para configurar el módulo alpino y llamar a la función para limpiar los parámetros.

Veremos cómo funciona a continuación...

El módulo alpino

Esto es, por supuesto, posible a través del uso de Alpine.js para configurar nuestra solicitud y HTMX para realizarla.

Como se puede ver en el código de abajo, tengo un módulo simple que toma la path de la página actual y luego utiliza el URL API para analizar la cadena de consulta (también puede pasar en una diferente por cualquier razón :)).

A continuación, obtenemos el elemento que se hizo clic y comprobar si tiene el x-all atributo; si lo hace eliminamos todos los parámetros de la URL, de lo contrario dividimos la x-param Atribuir por comas y eliminar cada uno de esos parámetros.

Luego creamos una nueva URL con la cadena de consulta actualizada y usamos HTMX para hacer una solicitud a esa URL.

export function queryParamClearer({ path = window.location.pathname }) {
    return {
        clearParam(e) {
            const el = e.target.closest('[x-param],[x-all]');
            if (!el) return;

            const url = new URL(window.location.href);

            if (el.hasAttribute('x-all')) {
                // → delete every single param
                // we copy the keys first because deleting while iterating modifies the collection
                Array.from(url.searchParams.keys())
                    .forEach(key => url.searchParams.delete(key));
            } else {
                // → delete only the named params
                (el.getAttribute('x-param') || '')
                    .split(',')
                    .map(p => p.trim())
                    .filter(Boolean)
                    .forEach(key => url.searchParams.delete(key));
            }

            const qs = url.searchParams.toString();
            const newUrl = path + (qs ? `?${qs}` : '');

            showAlert(newUrl);
            htmx.ajax('GET', newUrl, {
                target: el.dataset.target || el.getAttribute('hx-target') || 'body',
                swap: 'innerHTML',
                pushUrl: true
            });
        }
    };
}

//In your entry point / anywhere you want to register the module
import { queryParamClearer } from './param-clearer.js'; // webpackInclude: true

window.queryParamClearer = queryParamClearer;

Los showAlert función usando SweetAlert2

También notarás que llamo a un showAlert función. Esto es sólo un envoltorio simple alrededor del indicador de carga SweetAlert2 que utilizo en mi proyecto. Por supuesto, puedes reemplazar esto con lo que quieras hacer.

Esto está ligeramente ajustado de la La última vez que lo vimos. Para poder extraer el showAlert funcionar y ponerlo a disposición de los módulos externos. Que vamos a utilizar en ambos la param-clearer módulo y el hx-indicator módulo.

export function registerSweetAlertHxIndicator() {
    document.body.addEventListener('htmx:configRequest', function (evt) {
        const trigger = evt.detail.elt;

        const indicatorAttrSource = getIndicatorSource(trigger);
        if (!indicatorAttrSource) return;

        // ✅ If this is a pageSize-triggered request, use our custom path
        let path;
        if (evt.detail.headers?.['HX-Trigger-Name'] === 'pageSize') {
            path = getPathWithPageSize(evt.detail);
            console.debug('[SweetAlert] Using custom path with updated pageSize:', path);
        } else {
            path = getRequestPath(evt.detail);
        }

        if (!path) return;
        evt.detail.indicator = null;
        showAlert(path);
    });
}

export function showAlert(path)
{
    const currentPath = sessionStorage.getItem(SWEETALERT_PATH_KEY);

    // Show SweetAlert only if the current request path differs from the previous one
    if (currentPath !== path) {
        closeSweetAlertLoader();
        sessionStorage.setItem(SWEETALERT_PATH_KEY, path);


        Swal.fire({
            title: 'Loading...',
            allowOutsideClick: false,
            allowEscapeKey: false,
            showConfirmButton: false,
            theme: 'dark',
            didOpen: () => {
                // Cancel immediately if restored from browser history
                if (sessionStorage.getItem(SWEETALERT_HISTORY_RESTORED_KEY) === 'true') {
                    sessionStorage.removeItem(SWEETALERT_HISTORY_RESTORED_KEY);
                    Swal.close();
                    return;
                }

                Swal.showLoading();
                document.dispatchEvent(new CustomEvent('sweetalert:opened'));

                // Set timeout to auto-close if something hangs
                clearTimeout(swalTimeoutHandle);
                swalTimeoutHandle = setTimeout(() => {
                    if (Swal.isVisible()) {
                        console.warn('SweetAlert loading modal closed after timeout.');
                        closeSweetAlertLoader();
                    }
                }, SWEETALERT_TIMEOUT_MS);
            },
            didClose: () => {
                document.dispatchEvent(new CustomEvent('sweetalert:closed'));
                sessionStorage.removeItem(SWEETALERT_PATH_KEY);
                clearTimeout(swalTimeoutHandle);
                swalTimeoutHandle = null;
            }
        });
    }
}

//Register it
import { registerSweetAlertHxIndicator, showAlert } from './hx-sweetalert-indicator.js';
registerSweetAlertHxIndicator();
window.showAlert = showAlert;

Como recordatorio, se utiliza el path como la clave para saber cuándo ocultar la alerta.

HTMX

Por último, utilizamos htmx.ajax para hacer la petición. Se trata de una simple petición GET a la nueva URL que creamos con la cadena de consulta actualizada.

   htmx.ajax('GET', newUrl, {
                target: el.dataset.target || el.getAttribute('hx-target') || 'body',
                swap: 'innerHTML',
                pushUrl: true
            });

Conclusión

Esta es una forma sencilla de borrar los parámetros de URL usando un ayudante de etiquetas y Alpine.js. Le permite borrar todos los parámetros, o sólo los específicos, con un código mínimo.

logo

©2024 Scott Galloway