Back to "Actualizador parcial de actualización automática con Alpine.js y HTMX"

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

Actualizador parcial de actualización automática con Alpine.js y HTMX

Wednesday, 23 April 2025

Introducción

Así que en lo que se está convirtiendo en una serie, en un proyecto de trabajo que quería añadir la capacidad de una parcial a la actualización automática en una escala de tiempo dada.

Así es como lo hice usando Alpine.js y HTMX.

Necesidades

Así que quería que esto fuera

  1. reutilizable; por lo que debe ser simple y lo suficientemente autónomo como para auto-actualizar cualquier elemento.
  2. Debe respetar los parámetros Url existentes
  3. Debe ser detectable Server Side (en ASP.NET Core en este caso)
  4. Si está activada, debe estar activada. para ese punto final sólo y esto debe ser recordado entre peticiones.
  5. Debe hacer instantáneamente la actualización cuando está habilitado (así que el usuario sabe lo que parece)
  6. Debe ser capaz de ser apagado por el usuario
  7. Debería ser sencillo incluirlo en una página.

Con esto en mente me propuse construir un pequeño módulo JS usando Alpine.js y HTMX.

NOTA

Usted puede hacer actualizaciones automáticas sin las características 'on and off' y'remember' bastante simplemente con HTMX solo. Por ejemplo;usando Activadores HTMX Realmente puedes hacer un montón de cosas.


<div id="campaignemail-request-list" hx-get="@Url.Action("List", "CampaignEmailRequest")" hx-trigger="every 30s" hx-swap="innerHTML">
    <partial name="_List" model="@Model"/>
</div>

Gracias a @KhalidAbuhakmeh por señalar esto.

Este código de hecho utiliza hx-trigger para configurar la actualización automática. Simplemente usando Alpine.js para configurar los atributos HTMX. Es por eso que HTMX con Alpine.js es una combinación tan poderosa; HTMX maneja toda la interacción del servidor y las peticiones AJAX, mientras que Alpine.js maneja el estado e interacción del lado del cliente. ¿Podrías también hacer esto en Vanilla JS? Claro, pero terminas escribiendo un montón de código para hacer lo mismo que estas dos bibliotecas TINY ya hacen.

El Código

El código para esto es bastante compacto, se divide en dos partes principales: un módulo JS, los manejadores de eventos y el HTML.

El módulo

El módulo es un simple módulo JS que utiliza Alpine.js para gestionar el estado de la actualización automática. Utiliza almacenamiento local para recordar el estado de la actualización automática entre solicitudes.

Acepta los parámetros:

  • endpointId - el id del elemento a actualizar
  • actionUrl - la url a llamar para actualizar el elemento
  • initialInterval - el intervalo inicial que se utilizará para la actualización automática (por defecto es de 30 segundos)

También podemos ver que utiliza un par de teclas; estas se utilizan para el almacenamiento local para recordar el estado de la actualización automática. Usted puede ver que yo uso el actionurl como parte de la clave para hacer este punto final específico.

export function autoUpdateController(endpointId, actionUrl, initialInterval = 30) {
const keyPrefix = `autoUpdate:${actionUrl}`;
const enabledKey = `${keyPrefix}:enabled`;

    return {
        autoUpdate: false,
        interval: initialInterval,

        toggleAutoUpdate() {
            const el = document.getElementById(endpointId);
            if (!el) return;

            const url = new URL(window.location.href);
            const query = url.searchParams.toString();
            const fullUrl = query ? `${actionUrl}?${query}` : actionUrl;

            const wasPreviouslyEnabled = localStorage.getItem(enabledKey) === 'true';

            if (this.autoUpdate) {
                el.setAttribute('hx-trigger', `every ${this.interval}s`);
                el.setAttribute('hx-swap', 'innerHTML');
                el.setAttribute('hx-get', fullUrl);
                el.setAttribute('hx-headers', JSON.stringify({ AutoPoll: 'auto' }));

                localStorage.setItem(enabledKey, 'true');

                htmx.process(el); // rebind with updated attributes
                
                if (!wasPreviouslyEnabled) {
                    htmx.ajax('GET', fullUrl, {
                        target: el,
                        swap: 'innerHTML',
                        headers: {AutoPoll: 'auto'}
                    });
                }
            } else {
                el.removeAttribute('hx-trigger');
                el.removeAttribute('hx-get');
                el.removeAttribute('hx-swap');
                el.removeAttribute('hx-headers');

                localStorage.removeItem(enabledKey);
                htmx.process(el);
            }
        },

        init() {
            this.autoUpdate = localStorage.getItem(enabledKey) === 'true';
            this.toggleAutoUpdate();
        }
    };
}

toggleAutoUpdate() Método

Este método permite o deshabilita la votación automática de un elemento HTML de destino usando HTMX.

Comportamiento

  • Selecciona un elemento por su endpointId.
  • Construye el URL de la solicitud (fullUrl) mediante la combinación de las actionUrl con la cadena de consulta de la página actual.
  • Controles si la votación fue previamente habilitada por la lectura de localStorage (bueno como se recuerda entre las sesiones del navegador).

Cuándo this.autoUpdate es true (es decir, la votación está habilitada):

  • Establece atributos HTMX en el elemento:
    • hx-trigger para encuestar cada interval segundos
    • hx-swap="innerHTML" para sustituir el contenido del elemento
    • hx-get para apuntar a la URL de votación
    • hx-headers para añadir una costumbre "AutoPoll": "auto" encabezado
  • Guarda el estado habilitado en localStorage
  • Llamadas htmx.process(el) para que HTMX reconozca los nuevos atributos
  • Si estaba desactivada previamente, activa inmediatamente una solicitud HTMX a través de htmx.ajax() (no depende del cableado de eventos HTMX)

Cuándo this.autoUpdate es false (es decir, la votación está inhabilitada):

  • Elimina los atributos HTMX anteriores
  • Limpia la configuración guardada de localStorage
  • Llamadas htmx.process(el) de nuevo para actualizar el comportamiento de HTMX

Encuesta automática cuando se ha activado por primera vez

También tenemos una rama aquí para realizar el auto-poll cuando se habilitó por primera vez.

const wasPreviouslyEnabled = localStorage.getItem(enabledKey) === 'true';
      if (!wasPreviouslyEnabled) {
                    htmx.ajax('GET', fullUrl, {
                        target: el,
                        swap: 'innerHTML',
                        headers: {AutoPoll: 'auto'}
                    });
                }

Esto realiza una solicitud HTMX a la fullUrl y actualiza el elemento objetivo con la respuesta. Esto es útil para mostrar al usuario cómo será la actualización automática cuando lo habilitan.

Cabeceras

Notará que también enviamos un encabezado HTMX con la solicitud. Esto es importante, ya que nos permite detectar el lado del servidor de peticiones.

   el.setAttribute('hx-headers', JSON.stringify({ AutoPoll: 'auto' }));
headers: {AutoPoll: 'auto'}

En el lado de mi servidor detecto que este encabezado está siendo configurado usando

 if (Request.Headers.TryGetValue("AutoPoll", out _))
        {
            
            
            var utcDate = DateTime.UtcNow;
            var parisTz = TimeZoneInfo.FindSystemTimeZoneById("Europe/Paris");
            var parisTime = TimeZoneInfo.ConvertTimeFromUtc(utcDate, parisTz);

            var timeStr = parisTime.ToString("yyyy-MM-dd HH:mm:ss");
              Response.ShowToast($"Auto Update Last updated: {timeStr} (paris)",true); 
         
            return PartialView("_List", requests);
        }

Ya verás que sólo busco la cabecera con Request.Headers.TryGetValue("AutoPoll", out _) Y si está ahí sé que es una petición de auto-poll.

Luego tomo el yime actual (es para un cliente francés, así que me convierto a la hora de París) y muestro un brindis con el tiempo.

MostrarTostada

Los ShowToast método es un método de extensión simple que establece un disparador para decirle a HTMX que muestre un mensaje de brindis.

    public static void ShowToast(this HttpResponse response, string message, bool success = true)
    {
        response.Headers.Append("HX-Trigger", JsonSerializer.Serialize(new
        {
            showToast = new
            {
                toast = message,
                issuccess =success
            }
        }));

    }

Esto es detectado por mi componente de tostada HTMX que muestra el mensaje.

document.body.addEventListener("showToast", (event) => {
    const { toast, issuccess } = event.detail || {};
    const type = issuccess === false ? 'error' : 'success';
    showToast(toast || 'Done!', 3000, type);
});

Esto entonces llama a mi componente Tostada I escribió sobre aquí .

Enganchándolo.

Es bastante simple conectar este módulo, en su main.js \ index.js lo que sea que sólo importarlo y conectarlo a la ventana

import './auto-actions';

window.autoUpdateController = autoUpdateController; //note this isn't strictly necessary but it makes it easier to use in the HTML


//Where we actually hook it up to Alpine.js
document.addEventListener('alpine:init', () => {
    Alpine.data('autoUpdate', function () {
        const endpointId = this.$el.dataset.endpointId;
        const actionUrl = this.$el.dataset.actionUrl;
        const interval = parseInt(this.$el.dataset.interval || '30', 10); // default to 30s

        return autoUpdateController(endpointId, actionUrl, interval);
    });
});

Luego llamamos al método init en el código ASP.NET Razor:

ASP.NET Razor Code

Para hacer esto tan pequeño y reutilizable como sea posible el código Razor es bastante simple.

Aquí puede ver que especifico los atributos de datos Alpine.js para configurar la actualización automática.

  • x-data: Aquí es donde configuramos el objeto de datos Alpine.js.
  • x-init: Aquí es donde llamamos al método init en el controlador de actualización automática.
  • x-on:change: Aquí es donde llamamos al método toggleAutoUpdate en el controlador de actualización automática.
  • data-endpoint-id: Este es el id del elemento a actualizar.
  • data-action-url: Esta es la url a llamar para actualizar el elemento.
  • Intervalo de datos: Este es el intervalo a utilizar para la actualización automática (por defecto es de 30 segundos).

Verá que establecemos el objetivo a utilizar para la solicitud a la campaignemail-request-list elemento. Este es el elemento que se actualizará con el nuevo contenido. Eso se incluye en alguna parte de la página.

Ahora, cuando se marca una casilla de verificación, se actualizará automáticamente la lista cada 30 segundos.

            <div class=" px-4 py-2 bg-base-100 border border-base-300 rounded-box"
                x-data="autoUpdate()" 
                x-init="init"
                x-on:change="toggleAutoUpdate"
                data-endpoint-id="campaignemail-request-list"
                data-action-url="@Url.Action("List", "CampaignEmailRequest")"
                data-interval="30"
                >
                <label class="flex items-center gap-2">
                    <input type="checkbox" x-model="autoUpdate" class="toggle toggle-sm" />
                    <span class="label-text">
                        Auto update every <span x-text="$data.interval"></span>s
                    </span>
                </label>
            </div>

        <!-- Voucher List -->
        <div id="campaignemail-request-container">
            <div
                id="campaignemail-request-list">
                <partial name="_List" model="@Model"/>
            </div>
        </div>

Conclusión

Y eso es todo, bastante simple. Aprovechamiento de HTMX y Alpine.js para crear un simple componente de actualización automática que podemos utilizar fácilmente desde ASP.NET Core.

logo

©2024 Scott Galloway