Une visite d'arrêt des extensions HTMX et l'utilisation de HTMX avec ASP.NET Core (Français (French))

Une visite d'arrêt des extensions HTMX et l'utilisation de HTMX avec 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

//

18 minute read

Présentation

HTMX est une puissante bibliothèque JavaScript qui vous permet de créer des applications web dynamiques avec un minimum de JavaScript. Il vous permet de faire des requêtes AJAX, d'échanger du contenu HTML et de gérer les événements directement dans vos attributs HTML. J'utilise HTMX depuis environ deux ans à ce stade et avec chaque projet j'en apprends de plus en plus sur ses capacités; et surtout, c'est des limites.

Cependant, je ne prétends toujours pas en avoir une connaissance experte. Je voulais juste partager certaines choses que j'ai apprises en chemin.

Événements

Afin de permettre l'extensibilité, HTMX soulève un certain nombre d'événements au cours de son cycle de vie. Ces événements peuvent être utilisés pour s'accrocher au cycle de vie HTMX et effectuer des actions personnalisées à différentes étapes. En fonction de l'étape, vous pouvez modifier les urls, les en-têtes, les paramètres de requête en modifiant le contenu avant / après l'échange HTMX, etc.

Demande de préparation

La phase de préparation de la demande est l'endroit où HTMX met en place la demande avant sa sortie. Cela comprend la personnalisation des en-têtes, la gestion de la confirmation de l'utilisateur et la collecte des entrées. Les événements suivants permettent un contrôle fin de ces comportements :

flowchart LR A[htmx:configRequest] --> B[htmx:confirm] --> C[htmx:prompt] --> D[htmx:abort]
  • htmx:configRequest – Viré avant l'envoi de la demande. Vous permet de modifier les en-têtes, les paramètres ou l'URL de la requête.
  • htmx:confirm – Viré pour autoriser les dialogues de confirmation. Annuler l'événement si l'utilisateur décline.
  • htmx:prompt – Viré quand un hx-prompt l'attribut est présent. Vous pouvez passer outre le comportement rapide ici.
  • htmx:abort – Viré avant l'envoi de la demande. En cas d'annulation, la demande est entièrement annulée.

Demande de cycle de vie

Une fois la requête configurée, HTMX l'envoie au serveur. Cette phase comprend l'envoi, le suivi et le traitement des réponses au niveau du réseau :

flowchart LR E[htmx:beforeRequest] --> F[htmx:request] --> G[htmx:afterRequest]
  • htmx:beforeRequest – Viré juste avant l'envoi de la demande. Utile pour l'enregistrement ou les ajustements finaux.
  • htmx:request – Viré immédiatement après l'envoi de la demande.
  • htmx:afterRequest – Viré à la fin de la demande (avec succès ou avec erreur), avant le traitement de la réponse.

Gestion de la réponse

Après la réponse du serveur, HTMX gère la façon dont la réponse met à jour le DOM. Ces événements vous permettent d'accéder aux étapes de manipulation 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 – Viré avant le début du traitement de la réponse.
  • htmx:onLoad – Viré une fois que la réponse a chargé et est prêt pour les mises à jour DOM.
  • htmx:beforeSwap – Viré juste avant que le contenu soit échangé dans le DOM. Vous pouvez annuler ou personnaliser l'échange.
  • htmx:swap – Viré lorsque l'échange réel de DOM se produit.
  • htmx:afterSwap – Viré après l'échange de contenu.
  • htmx:afterSettle – Viré une fois les animations et les transitions terminées.
  • htmx:afterOnLoad – Tiré à la fin du cycle de vie de la réponse après que tout soit réglé.

Gestion de l'histoire

La phase de gestion de l'historique est l'endroit où HTMX met à jour l'historique du navigateur et l'URL. Les événements suivants sont déclenchés pendant cette phase:

flowchart LR A[htmx:historyRestoreRequest] A --> B[htmx:historyPopped] B --> C[htmx:historyRestorePage]
  • htmx:historyRestoreRequest – Viré lorsque HTMX détecte une requête pour restaurer un état de page précédent.
  • htmx:historyPopped – Viré lorsque l'utilisateur navigue à l'aide des boutons arrière/avant du navigateur.
  • htmx:historyRestorePage – Viré lorsque HTMX restaure une page précédente du contenu de l'historique.

HTMX fournit un ensemble riche d'événements de cycle de vie qui vous permettent de personnaliser en profondeur comment les demandes et les réponses sont traitées, ce qui le rend remarquablement puissant pour une bibliothèque aussi légère.

Prorogations

L'un des aspects les plus puissants de HTMX est la capacité de créer des extensions d'étendre ses capacités. Dans mon cas, j'ai tendance à m'accrocher à htmx:configRequest ajouter des paramètres supplémentaires à la demande. Ceci est utile lorsque vous souhaitez transmettre des données supplémentaires au serveur sans avoir à modifier le code HTML ou JavaScript.

D'autres extensions pourraient s'accrocher htmx:beforeRequest modifier la requête avant qu'elle ne soit envoyée; mais après la plupart des autres extensions qui crochetent configRequest; comme dans beforeRequest des trucs comme HX-Vals et HX-Includes sont déjà attachés à la reuest (soit dans la charge utile \ querystring). Tu peux même t'accrocher htmx:afterSwap pour effectuer des actions après que le contenu a été échangé. Combiné avec le côté client templating bibliothèques comme Alpine.js ou Lit vous pouvez créer des applications dynamiques puissantes avec un code minimal.

HTMX fournit quelques extensions intégrées comme hx-boost et hx-swap-oob qui vous permettent d'améliorer la fonctionnalité de HTMX sans écrire de code personnalisé. Cependant, il y a des moments où vous devez créer vos propres extensions pour répondre à des exigences spécifiques.

Par exemple, vous pouvez ajouter des en-têtes personnalisés à vos demandes, modifier la charge utile de la demande ou gérer des événements spécifiques d'une manière unique.

Pour réaliser ce HTMX vous offre quelques points d'intégration pratiques:


{
  /**
   * 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 fournit un certain nombre d'extensions pré-construites que vous pouvez lire à propos de ici.

Par exemple, une pratique Extension intégrée json-encode vous permet d'envoyer des données JSON dans le corps de la requête au lieu de données de formulaire encodées par URL. Ceci est utile lorsque vous souhaitez envoyer des structures ou des tableaux de données complexes au serveur. Vous pouvez voir que cela s'accroche à 3 événements

  • init - pour configurer l'extension et stocker une référence à l'API HTMX
  • onEvent - pour régler le Content-Type en-tête vers application/json lorsque la requête est configurée
  • encodeParameters - pour surcharger le formulaire par défaut encodé par URL et sérialiser les paramètres comme JSON. Il retourne également une chaîne pour empêcher HTMX d'utiliser l'encodage par défaut du formulaire encodé par URL.
(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))
    }
  })
})()

Ou même les plus simples mais même plus maigres hx-debug extension qui ajoute une HX-Debug l'en-tête de la requête. Ceci est utile pour le débogage et l'enregistrement, car il vous permet de voir les données de requête brute et de réponse dans la 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')
      }
    }
  })
})()

Il y en a beaucoup d'autres, y compris un très puissant extension de templatation côté client qui vous permet d'utiliser les bibliothèques de templatation côté client pour transformer les données JSON retournées en HTML. Ceci est utile pour créer des interfaces dynamométriques sans devoir compter sur le rendu côté serveur.

Quelques extensions personnalisées

Identificateurs dynamiques des lignes

Par exemple, dans un projet récent, j'ai utilisé des swaps HTMX OOB pour mettre à jour un certain nombre de lignes dans une table. Pour ce faire, je voulais savoir quelles lignes étaient actuellement affichées dans la table, donc je n'ai mis à jour que les lignes qui étaient visibles.

L'extension

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
    }
}

L'utiliser

Pour l'utiliser, nous devons ajouter l'extension à notre configuration HTMX. Donc, dans votre fichier js point d'entrée (en supposant que vous utilisez des modules; yoy devrait être) vous pouvez faire quelque chose comme ceci:

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

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

Ensuite, sur quel élément vous voulez l'utiliser sur vous pouvez ajouter le hx-ext attribut avec la valeur 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>

Préserver les params

Il s'agit d'une autre simple extension HTMX, cette fois accrochée à htmx:configRequest comme nous modifions l'URL avant l'envoi de la requête. Cette extension est pratique si vous utilisez le filtrage basé sur requêtestring etc. et vous voulez que certaines requêtes préservent les filtres existants tandis que d'autres ne le font pas (par exemple, 'nom' et'startdate' mais pas 'page' ou'sort').

Ceci est SIMILAR à mais pas exactement la même que l'extension HTMX existante Params poussoirs

L'extension

Vous pouvez voir qu'on s'accroche onEvent d'écouter pour htmx:configRequest l'événement.

Ensuite, nous:

  • Obtenez l'élément qui a déclenché l'événement
  • Prends la preserve-params-exclude attribut de l'élément (s'il existe) et le diviser en un tableau de touches à exclure (pour que nous ne les ajoutions pas à la requête)
  • Obtenez les paramètres URL actuels depuis l'emplacement de la fenêtre
  • Obtenez les nouveaux paramètres à partir de l'URL de la requête
  • Loop à travers les paramètres actuels et vérifier s'ils ne sont pas dans la liste d'exclusion et pas déjà dans les nouveaux paramètres
  • Si ce n'est pas le cas, nous les ajoutons aux nouveaux paramètres.
  • Enfin, nous définissons les nouveaux paramètres à l'URL de la requête et retournons true pour continuer avec la requête.
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;
    }
};

Ici j'utilise l'essentiel HTMX.Net pour ses aides-étiquettes. Les mêmes hx-controller et hx-action sont des helpers de tags qui génèrent les attributs HTMX corrects pour vous. Aussi bien que hx-route-<x> pour les valeurs à passer dans l'itinéraire. Ceci est vraiment utile car il vous permet d'utiliser le code C# pour générer les valeurs correctes pour les attributs au lieu d'avoir à les coder dur dans votre HTML.

L'utiliser

Être une extension c'est vraiment simple à utiliser:

Nous devons d'abord ajouter l'extension à notre configuration HTMX.


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

REMARQUE : Vous remarquerez que les extensions HTMX par défaut utilisent la méthode « chargement automatique » pour charger l'extension.

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

C'est une bonne façon de le faire si vous utilisez HTMX dans un environnement non modulaire. Cependant, si vous utilisez des modules (ce que vous devriez être), il est préférable d'utiliser le import déclaration pour charger l'extension puis l'enregistrer explicitement contre votre htmx C'est le cas. Cela vous permet de profiter de l'arborescence et de charger uniquement les extensions dont vous avez besoin.

Ensuite, sur votre élément, vous pouvez ajouter le hx-ext attribut avec la valeur preserve-params et les preserve-params-exclude attribut avec une liste de paramètres séparés par des virgules à exclure de la requête.


<a class="btn-outline-icon"
   hx-controller="MyController"
   hx-action="MyAction"
   hx-route-myparam="@MyParam"
   hx-push-url="true"
   hx-ext="preserve-params"
   preserve-params-exclude="page,sort"
   hx-target="#page-content"
   hx-swap="innerHTML show:top"
   hx-indicator>
    <i class="bx bx-search"></i>
</a>

En l'espèce, en raison de event.detail.path avoir le nouveau myparam valeur définie dans il remplacera celui avec notre nouvelle valeur mais préserver tous les autres (sauf page et sort)............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................. Ainsi, nous pouvons continuer à passer tous les filtres que nous avons définis dans l'URL au serveur sans avoir à nous soucier qu'ils soient perdus lorsque nous faisons une nouvelle requête.

ASP.NET Core

L'une des choses propres à HTMX est qu'une grande partie de son interaction avec le serveur se fait par le biais d'en-têtes HTTP. Ces en-têtes fournissent au serveur un contexte riche sur ce qui a déclenché la requête, vous permettant de répondre correctement à partir de vos paramètres ASP.NET Core ou vues Razor.

Encore une fois, un élément clé de cette HTMX.NetC'est ce que j'ai dit. Parmi beaucoup d'articles qu'il fournit sont soignés Request extensions pour détecter les demandes HTMX. Ceci est utile pour déterminer si une demande a été faite par HTMX ou non, et pour la traiter en conséquence.

Il a également son propre mécanisme pour envoyer des déclencheurs


Response.Htmx(h => {
    h.WithTrigger("yes")
     .WithTrigger("cool", timing: HtmxTriggerTiming.AfterSettle)
     .WithTrigger("neat", new { valueForFrontEnd= 42, status= "Done!" }, timing: HtmxTriggerTiming.AfterSwap);
});

Push Urls etc...etc... Khalid a fait un excellent travail de création d'un ensemble d'extensions pour le rendre facile à travailler avec HTMX dans ASP.NET Core.

C'est un outil clé dans ma boîte à outils lorsque vous travaillez avec HTMX et ASP.NET Core. Regarde!

En-têtes de demande commune HTMX

Voici une ventilation des en-têtes les plus utiles que HTMX envoie avec chaque requête :

Description de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête de l'en-tête. |----------------------------|------------------------------------------------------------------------------------------------------------------| Toujours réglé à true pour toute demande d'origine HTMX. Idéal pour détecter les appels HTMX dans les intergiciels ou les contrôleurs. - Oui, c'est ça. L'identifiant de l'élément cible dans le DOM dans lequel la réponse sera échangée. - Oui, c'est ça. L'identifiant de l'élément qui a déclenché la requête (p. ex. un bouton). - Oui, c'est ça. HX-Trigger-Name Le nom de l'élément déclencheur (utile pour les formulaires). - Oui, c'est ça. HX-Prompt Contient l'entrée utilisateur de hx-prompt. - Oui, c'est ça. HX-Current-URL L'URL du navigateur lorsque la requête a été lancée. Utile pour l'enregistrement et le contexte. - Oui, c'est ça. HX-History-Restore-Request.Envoyé comme vrai si la demande fait partie d'une restauration d'historique après la navigation (p. ex., bouton arrière). - Oui, c'est ça.

Demande de prolongation

Je les utilise assez largement dans mes applications ASP.NET Core. Combiné avec les HTMX.Net comme Request.IsHtmx() , Request.IsHtmxBoosted() et Request.IsHtmxNonBoosted() vous pouvez facilement détecter les demandes HTMX et répondre en conséquence.

Par exemple, j'ai une extension vraiment simple à Request qui me permet de détecter si une requête vise mon principal #page-content Div. Si c'EST alors je sais que je devrais renvoyer une partie. REMARQUE: Beaucoup ne réalisent pas que vous pouvez spécifier une « page complète » comme une partie, il ne fait que sauter la mise en page.

        if (Request.PageContentTarget())
        {   
            Response.PushUrl(Request);
            return PartialView("List", vm);
        }
        return View("List", vm);
        
        public static class RequestExtensions
{
        
        public static bool PageContentTarget(this HttpRequest request)
    {
        bool isPageContentTarget = request.Headers.TryGetValue("hx-target", out var pageContentHeader) 
                                   && pageContentHeader == "page-content";
        
        return isPageContentTarget;
    }
    }

Prolongations des réponses

En plus des extensions Request, vous pouvez également créer des extensions de réponse pour renvoyer les événements au client. Ceci est utile pour déclencher des événements côté client.

Exemple SweetAlert

Par exemple dans mon intégration SweetAlert2 J'autorise la fermeture de la boîte de dialogue à l'aide d'un déclencheur défini à partir du serveur.

    document.body.addEventListener('sweetalert:close', closeSweetAlertLoader);

Ceci est déclenché à partir du serveur en tant qu'événement déclencheur HTMX.


    public static void CloseSweetAlert(this HttpResponse response)
    {
        response.Headers.Append("HX-Trigger" , JsonSerializer.Serialize(new
        {
            sweetalert = "close"
        }));

    }

Cela déclenchera la sweetalert:close événement du côté client, vous permettant de fermer la boîte de dialogue. Vous pouvez également transmettre les données au client en utilisant la HX-Trigger l'en-tête. Ceci est utile pour transmettre les données du serveur au client sans avoir à modifier le code HTML ou JavaScript.

Comme vous le voyez, il est facile d'écouter ces événements en ajoutant simplement un auditeur d'événement au corps. J'utilise JSON principalement car il code toujours correctement.

AfficherToast

J'ai déjà écrit sur ma méthode de toast. Ici., mais ça vaut la peine d'être mentionné ici aussi. Pour très simplement permettre au serveur de déclencher une notification toast du côté client. J'ai mis le déclencheur dans cette extension de réponse.

    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
            }
        }));

    }

Je m'accroche ensuite au côté client de l'événement et j'appelle mon showToast fonction.

import { showToast, showHTMXToast } from './toast';

window.showHTMXToast = showHTMXToast;

document.body.addEventListener("showToast", showHTMXToast);

C'est alors que j'appelle showToast fonction et bien, montre un toast; à nouveau voir plus à ce sujet dans l'article .



export function showHTMXToast(event) {
    const xhr = event?.detail?.xhr;
    let type = 'success';
    let message = xhr?.responseText || 'Done!';

    try {
        const data = xhr ? JSON.parse(xhr.responseText) : event.detail;

        if (data.toast) message = data.toast;
        if ('issuccess' in data) {
            type = data.issuccess === false ? 'error' : 'success';
        } else if (xhr?.status >= 400) {
            type = 'error';
        } else if (xhr?.status >= 300) {
            type = 'warning';
        }

    } catch {
        if (xhr?.status >= 400) type = 'error';
        else if (xhr?.status >= 300) type = 'warning';
    }

    showToast(message, 3000, type);
}

En conclusion

C'est ça, un tour tourbillonnant de HTMX et ASP.NET Core. J'espère que vous l'avez trouvé utile et instructif. Si vous avez des questions ou des commentaires, n'hésitez pas à les commenter ci-dessous.

logo

©2024 Scott Galloway