A Whistle-stop Tour di estensioni HTMX e l'utilizzo di HTMX con ASP.NET Core (Italiano (Italian))

A Whistle-stop Tour di estensioni HTMX e l'utilizzo di HTMX con ASP.NET Core

Comments

NOTE: Apart from English (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.

Friday, 02 May 2025

//

10 minute read

Introduzione

HTMX è una potente libreria JavaScript che consente di creare applicazioni web dinamiche con un minimo di JavaScript. Consente di effettuare richieste AJAX, scambiare contenuti HTML e gestire gli eventi direttamente negli attributi HTML. Sto usando HTMX per circa due anni a questo punto e con ogni progetto imparo sempre di più sulle sue capacità; e soprattutto sono limitazioni.

Tuttavia ancora non pretendo di avere una conoscenza esperta di esso. Volevo solo condividere alcune delle cose che ho imparato lungo il percorso.

Manifestazioni

Richiesta di preparazione

La fase di preparazione della richiesta è quella in cui HTMX imposta la richiesta prima dell'uscita. Ciò include la personalizzazione delle intestazioni, la gestione della conferma dell'utente e la raccolta degli input. I seguenti eventi consentono un controllo a grana fine su questi comportamenti:

flowchart LR A[htmx:configRequest] --> B[htmx:confirm] --> C[htmx:prompt] --> D[htmx:abort]
  • htmx:configRequest Incendio prima dell'invio della richiesta. Consente di modificare le intestazioni, i parametri o l'URL di richiesta.
  • htmx:confirm Incendio per consentire le finestre di conferma. Annulla l'evento se l'utente declina.
  • htmx:prompt Licenziato quando un hx-prompt l'attributo è presente. Puoi scavalcare il comportamento immediato qui.
  • htmx:abort Incendio prima dell'invio della richiesta. In caso di annullamento, la richiesta viene interrotta del tutto.

Richiedi ciclo di vita

Una volta configurata la richiesta, HTMX procede con l'invio al server. Questa fase comprende l'invio, il monitoraggio e la gestione delle risposte a livello di rete:

flowchart LR E[htmx:beforeRequest] --> F[htmx:request] --> G[htmx:afterRequest]
  • htmx:beforeRequest Licenziato poco prima che la richiesta sia inviata. Utile per la registrazione o le regolazioni finali.
  • htmx:request Licenziato immediatamente dopo l'invio della richiesta.
  • htmx:afterRequest Licenziato quando la richiesta viene completata (con successo o con errore), prima della gestione della risposta.

Gestione della risposta

Dopo che il server risponde, HTMX gestisce come la risposta aggiorna il DOM. Questi eventi consentono di agganciarsi a fasi di manipolazione DOM:

flowchart LR H[htmx:beforeOnLoad] --> I[htmx:onLoad] I --> J[htmx:beforeSwap] --> K[htmx:swap] --> L[htmx:afterSwap] L --> M[htmx:afterSettle] --> N[htmx:afterOnLoad]
  • htmx:beforeOnLoad Incendio prima dell'inizio dell'elaborazione della risposta.
  • htmx:onLoad Incendio una volta che la risposta ha caricato ed è pronto per gli aggiornamenti DOM.
  • htmx:beforeSwap Licenziato appena prima che il contenuto venga scambiato nel DOM. Puoi annullare o personalizzare lo swap.
  • htmx:swap Licenziato quando si verifica l'effettivo scambio DOM.
  • htmx:afterSwap Licenziato dopo che il contenuto è stato scambiato.
  • htmx:afterSettle Licenziato una volta che animazioni e transizioni hanno completato.
  • htmx:afterOnLoad Licenziato alla fine del ciclo di vita di risposta dopo che tutto si è risolto.

Gestione cronologia

La fase di gestione della cronologia è dove HTMX aggiorna la cronologia del browser e l'URL. Durante questa fase si attivano i seguenti eventi:

flowchart LR A[htmx:historyRestoreRequest] A --> B[htmx:historyPopped] B --> C[htmx:historyRestorePage]
  • htmx:historyRestoreRequest Incendio quando HTMX rileva una richiesta per ripristinare uno stato di pagina precedente.
  • htmx:historyPopped Incendio quando l'utente naviga utilizzando i pulsanti back/forward del browser.
  • htmx:historyRestorePage Incendio quando HTMX ripristina un contenuto della pagina precedente dalla cronologia.

HTMX fornisce una ricca serie di eventi del ciclo di vita che consentono di personalizzare profondamente come vengono gestite le richieste e le risposte, rendendolo notevolmente potente per una libreria così leggera.

Estensioni

Uno degli aspetti più potenti di HTMX è la capacità di crea estensioni estendere le sue capacità. Nel mio caso, di solito mi aggrappai htmx:configRequest aggiungere parametri aggiuntivi alla richiesta. Questo è utile quando si desidera passare dati aggiuntivi al server senza dover modificare il codice HTML o JavaScript.

Altre estensioni potrebbero agganciare htmx:beforeRequest modificare la richiesta prima dell'invio; ma dopo la maggior parte delle altre estensioni che agganciano configRequest; come in beforeRequest roba come... HX-Vals e HX-Includes sono già attaccati al réuest (sia nel payload \ querystring). Puoi anche agganciare htmx:afterSwap per eseguire azioni dopo che il contenuto è stato scambiato. Combinato con librerie di templating lato client come Alpine.js oppure LitCity name (optional, probably does not need a translation) è possibile creare potenti applicazioni dinamiche con codice minimo.

HTMX fornisce alcune estensioni integrate come hx-boost e hx-swap-oob che consentono di migliorare la funzionalità di HTMX senza scrivere alcun codice personalizzato. Tuttavia, ci sono momenti in cui è necessario creare le proprie estensioni per soddisfare requisiti specifici.

Ad esempio, potresti voler aggiungere intestazioni personalizzate alle tue richieste, modificare il payload della richiesta o gestire eventi specifici in modo unico.

Per ottenere questo HTMX offre alcuni comodi punti di integrazione:


{
  /**
   * init(api)
   * Called once when the extension is initialized.
   * Use it to set up internal state, store references, or access HTMX utility functions via the api parameter.
   */
  init: function(api) {
    return null;
  },

  /**
   * getSelectors()
   * Returns additional CSS selectors that HTMX should monitor.
   * Useful if your extension needs to handle custom elements or dynamic behavior.
   */
  getSelectors: function() {
    return null;
  },

  /**
   * onEvent(name, evt)
   * Called on every HTMX event (e.g., htmx:beforeRequest, htmx:afterSwap).
   * Return false to cancel the event or stop propagation.
   */
  onEvent: function(name, evt) {
    return true;
  },

  /**
   * transformResponse(text, xhr, elt)
   * Modify the raw response text before it is parsed and swapped into the DOM.
   * Use this to sanitize or preprocess HTML.
   */
  transformResponse: function(text, xhr, elt) {
    return text;
  },

  /**
   * isInlineSwap(swapStyle)
   * Return true if your extension will handle this swap style manually.
   * This tells HTMX to skip default behavior.
   */
  isInlineSwap: function(swapStyle) {
    return false;
  },

  /**
   * handleSwap(swapStyle, target, fragment, settleInfo)
   * Perform custom DOM manipulation if you implement a custom swap style.
   * Return true to prevent HTMX's default swap.
   */
  handleSwap: function(swapStyle, target, fragment, settleInfo) {
    return false;
  },

  /**
   * encodeParameters(xhr, parameters, elt)
   * Modify or serialize request parameters before sending.
   * Return null to use default URL/form encoding.
   * Return a string to override with a custom payload (e.g., JSON).
   */
  encodeParameters: function(xhr, parameters, elt) {
    return null;
  }
}

HTMX fornisce una serie di estensioni precostruite è possibile leggi qui.

Per esempio un pratico estensione incorporata json-encode consente di inviare dati JSON nell'organismo di richiesta invece di dati URL-encoded form. Questo è utile quando si desidera inviare strutture dati complesse o array al server. Potete vedere che questo aggancia in 3 eventi

  • init - impostare l'estensione e memorizzare un riferimento all'API HTMX
  • onEvent - di impostare il Content-Type intestazione a application/json quando la richiesta è configurata
  • encodeParameters - per sovrascrivere la codifica predefinita URL-encoded form e serializzare i parametri come JSON. Restituisce anche una stringa per impedire ad HTMX di utilizzare la codifica predefinita URL-encoded form.
(function() {
  let api
  htmx.defineExtension('json-enc', {
    init: function(apiRef) {
      api = apiRef
    },

    onEvent: function(name, evt) {
      if (name === 'htmx:configRequest') {
        evt.detail.headers['Content-Type'] = 'application/json'
      }
    },

    encodeParameters: function(xhr, parameters, elt) {
      xhr.overrideMimeType('text/json')

      const object = {}
      parameters.forEach(function(value, key) {
        if (Object.hasOwn(object, key)) {
          if (!Array.isArray(object[key])) {
            object[key] = [object[key]]
          }
          object[key].push(value)
        } else {
          object[key] = value
        }
      })

      const vals = api.getExpressionVars(elt)
      Object.keys(object).forEach(function(key) {
        // FormData encodes values as strings, restore hx-vals/hx-vars with their initial types
        object[key] = Object.hasOwn(vals, key) ? vals[key] : object[key]
      })

      return (JSON.stringify(object))
    }
  })
})()

O anche il più semplice ma anche più utile hx-debug estensione che aggiunge a HX-Debug Intestazione della richiesta. Questo è utile per scopi di debug e registrazione, in quanto consente di visualizzare i dati di richiesta e risposta grezzi nella console dev.

(function() {
  htmx.defineExtension('debug', {
    onEvent: function(name, evt) {
      if (console.debug) {
        console.debug(name, evt)
      } else if (console) {
        console.log('DEBUG:', name, evt)
      } else {
        throw new Error('NO CONSOLE SUPPORTED')
      }
    }
  })
})()

Ce ne sono molti di più; compreso un molto potente estensione templatura lato client che consente di utilizzare le librerie di templating lato client per trasformare i dati JSON restituiti in HTML. Questo è utile per creare UI dinamici senza dover fare affidamento sul rendering lato server.

Alcune estensioni personalizzate

ID della riga dinamica

Ad esempio in un recente progetto ho usato HTMX OOB swaps per aggiornare un certo numero di righe in una tabella. Per fare questo volevo sapere quali righe erano attualmente visualizzate nella tabella, quindi ho aggiornato solo le righe che erano visibili.

L'estensione

export default {
    encodeParameters: function (xhr, parameters, elt) {
        const ext = elt.getAttribute('hx-ext') || '';
        if (!ext.split(',').map(e => e.trim()).includes('dynamic-rowids')) {
            return null; // Use default behavior
        }

        const id = elt.dataset.id;
        const approve = elt.dataset.approve === 'true';
        const minimal = elt.dataset.minimal === 'true';
        const single = elt.dataset.single === 'true';

        const target = elt.dataset.target;
        const payload = { id, approve, minimal, single };

        if (approve && target) {
            const table = document.querySelector(target);
            if (table) {
                const rowIds = Array.from(table.querySelectorAll('tr[id^="row-"]'))
                    .map(row => row.id.replace('row-', ''));
                payload.rowIds = rowIds;
            }
        }

        // Merge payload into the parameters object
        Object.assign(parameters, payload);
        return null; // Return null to continue with default URL-encoded form encoding
    }
}

Usarlo

Per utilizzarlo dobbiamo aggiungere l'estensione alla nostra configurazione HTMX. Quindi nel tuo file js del punto di ingresso (supponendo che stai usando moduli; yoy dovrebbe essere) puoi fare una cosa del genere:

import dynamicRowIds from "./dynamicRowIds"; // Load the file

htmx.defineExtension("dynamic-rowids", dynamicRowIds); // Register the extension

Poi su qualsiasi elemento si desidera utilizzare su si può aggiungere il hx-ext attributo con il valore dynamic-rowids.

                <button
                    hx-ext="dynamic-rowids"
                    data-target="#my-table"
                    data-id="@Model.Id"
                    data-param1="true"
                    data-param2="false"
                    data-param3="@Model.Whatever"
                    hx-post
                    hx-controller="Contoller"
                    hx-action="Action"
                >
                    <i class='bx bx-check text-xl text-white'></i>
                </button>

Preserva parami

Questa è un'altra semplice estensione HTMX, questa volta agganciata htmx:configRequest mentre stiamo modificando l'URL prima dell'invio della richiesta. Questa estensione è utile se si utilizza il filtro basato su querystring etc. e vuoi che alcune richieste conservino i filtri esistenti mentre altre no (ad esempio 'name' e'startdate' ma non 'page' o'sort').

Questo è SIMILE a ma non esattamente lo stesso come l'estensione esistente HTMX push-params

L'estensione

Puoi vedere che ci agganciamo onEvent per ascoltare per il htmx:configRequest evento.

Allora noi:

  • Ottieni l'elemento che ha attivato l'evento
  • Prendi il... preserve-params-exclude attributo dall'elemento (se esiste) e dividerlo in un array di tasti da escludere (così non li aggiungiamo alla richiesta)
  • Ottieni i parametri URL attuali dalla posizione della finestra
  • Ottieni i nuovi parametri dall'URL della richiesta
  • Loop attraverso i parametri attuali e controllare se non sono nella lista di esclusione e non già nei nuovi parametri
  • Se non lo sono, li aggiungiamo ai nuovi parametri
  • Infine, abbiamo impostato i nuovi parametri per l'URL richiesta e tornare true per continuare con la richiesta.
export default {
    onEvent: function (name, evt) {
        if (name !== 'htmx:configRequest') return;
        const el = evt.detail.elt;
        const excludeStr = el.getAttribute('preserve-params-exclude') || '';
        const exclude = excludeStr.split(',').map(s => s.trim());

        const currentParams = new URLSearchParams(window.location.search);
        const newParams = new URLSearchParams(evt.detail.path.split('?')[1] || '');

        currentParams.forEach((value, key) => {
            if (!exclude.includes(key) && !newParams.has(key)) {
                newParams.set(key, value);
            }
        });

        evt.detail.path = evt.detail.path.split('?')[0] + '?' + newParams.toString();

        return true;
    }
};

Qui uso l'essenziale HTMX.Net per i suoi aiutanti di tag. I tipi di... hx-controller e hx-action sono aiutanti di tag che generano i corretti attributi HTMX per voi. Cosi' come... hx-route-<x> per i valori da passare nel percorso. Questo è davvero utile in quanto consente di utilizzare il codice C# per generare i valori corretti per gli attributi invece di doverli codificare in modo rigido nel tuo HTML.

Usarlo

Essendo un'estensione è molto semplice da usare:

Per prima cosa dobbiamo aggiungere l'estensione alla nostra configurazione HTMX.


import preserveParams from './preserveParams.js';
htmx.defineExtension('preserve-params', preserveParams);

NOTA: noterete che le estensioni HTMX predefinite usano il metodo 'autoload' per caricare l'estensione.

// Autoloading the extension and registering it
(function() {
  htmx.defineExtension('debug', {
}

Questo è un buon modo per farlo se si utilizza HTMX in un ambiente non-module. Tuttavia, se si utilizzano moduli (che si dovrebbe essere) è meglio utilizzare il import dichiarazione per caricare l'estensione quindi registrarlo esplicitamente contro il vostro htmx esempio. Questo ti permette di approfittare di tree-shaking e caricare solo le esten

logo

©2024 Scott Galloway