Maison > interface Web > js tutoriel > le corps du texte

J'ai remplacé htmx par un simple composant Web

WBOY
Libérer: 2024-09-07 06:37:16
original
534 Les gens l'ont consulté

I replaced htmx with a simple web component

(Crédit image : https://www.maicar.com/GML/Ajax1.html)

J'ai récemment eu une conversation sur Mastodon sur la façon dont j'utilisais htmx avec beaucoup de succès, et quelqu'un est entré dans mes mentions en me mettant au défi à ce sujet, et sur le fait que htmx est en fait une dépendance assez lourde compte tenu de l'usage que je l'utilisais. Ils m'ont lié à ce post et tout.

Au début, j'étais plutôt ennuyé. Je pensais que je faisais du bon travail en gardant les choses légères, et htmx m'avait bien servi, mais j'ai ensuite mis le chapeau que j'essayais de porter tout ce temps lorsqu'il s'agissait de réinventer ma façon de développer du Web. : mes hypothèses sont-elles correctes ? Puis-je faire mieux ?

J'ai donc décidé de remplacer toute mon utilisation de htmx par un petit composant Web vanillajs de 100 lignes, que je vais inclure dans cet article dans son intégralité :

export class AjaxIt extends HTMLElement {
  constructor() {
    super();
    this.addEventListener("submit", this.#handleSubmit);
    this.addEventListener("click", this.#handleClick);
  }

  #handleSubmit(e: SubmitEvent) {
    const form = e.target as HTMLFormElement;
    if (form.parentElement !== this) return;
    e.preventDefault();
    const beforeEv = new CustomEvent("ajax-it:beforeRequest", {
      bubbles: true,
      composed: true,
      cancelable: true,
    });
    form.dispatchEvent(beforeEv);
    if (beforeEv.defaultPrevented) {
      return;
    }
    const data = new FormData(form);
    form.dispatchEvent(new CustomEvent("ajax-it:beforeSend", { bubbles: true, composed: true }));
    const action = (e.submitter as HTMLButtonElement | null)?.formAction || form.action;
    (async () => {
      try {
        const res = await fetch(action, {
          method: form.method || "POST",
          headers: {
            "Content-Type": "application/x-www-form-urlencoded",
            "Ajax-It": "true",
          },
          body: new URLSearchParams(data as unknown as Record<string, string>),
        });
        if (!res.ok) {
          throw new Error("request failed");
        }
        form.dispatchEvent(new CustomEvent("ajax-it:afterRequest", { bubbles: true, composed: true }));
        const text = await res.text();
        this.#injectReplacements(text, new URL(res.url).hash);
      } catch {
        form.dispatchEvent(new CustomEvent("ajax-it:requestFailed", { bubbles: true, composed: true }));
      }
    })();
  }

  #handleClick(e: MouseEvent) {
    const anchor = e.target as HTMLAnchorElement;
    if (anchor.tagName !== "A" || anchor.parentElement !== this) return;
    e.preventDefault();
    anchor.dispatchEvent(new CustomEvent("ajax-it:beforeRequest", { bubbles: true, composed: true }));
    anchor.dispatchEvent(new CustomEvent("ajax-it:beforeSend", { bubbles: true, composed: true }));
    (async () => {
      try {
        const res = await fetch(anchor.href, {
          method: "GET",
          headers: {
            "Ajax-It": "true",
          },
        });
        if (!res.ok) {
          throw new Error("request failed");
        }
        anchor.dispatchEvent(new CustomEvent("ajax-it:afterRequest", { bubbles: true, composed: true }));
        const text = await res.text();
        this.#injectReplacements(text, new URL(res.url).hash);
      } catch {
        anchor.dispatchEvent(new CustomEvent("ajax-it:requestFailed", { bubbles: true, composed: true }));
      }
    })();
  }

  #injectReplacements(html: string, hash: string) {
    setTimeout(() => {
      const div = document.createElement("div");
      div.innerHTML = html;
      const mainTargetConsumed = !!hash && !!div.querySelector(
        hash,
      );
      const elements = [...div.querySelectorAll("[id]") ?? []];
      for (const element of elements.reverse()) {
        // If we have a parent that's already going to replace us, don't bother,
        // it will be dragged in when we replace the ancestor.
        const parentWithID = element.parentElement?.closest("[id]");
        if (parentWithID && document.getElementById(parentWithID.id)) {
          continue;
        }
        document.getElementById(element.id)?.replaceWith(element);
      }
      if (mainTargetConsumed) return;
      if (hash) {
        document
          .querySelector(hash)
          ?.replaceWith(...div.childNodes || []);
      }
    });
  }
}
customElements.define("ajax-it", AjaxIt);
Copier après la connexion

Vous l'utilisez comme ceci :

<ajax-it>
  <form action="/some/url">
    <input name=name>
  </form>
</ajax-it>
Copier après la connexion

Et c'est tout ! Tous les éléments avec un identifiant inclus dans la réponse seront remplacés lorsque la réponse reviendra. Cela fonctionne pour des éléments aussi !

L'élément fonctionne de deux manières principales :

  1. Si votre action ou href inclut un hachage, l'élément sur ou la page actuelle avec un identifiant correspondant à ce hachage sera remplacé par le contenu de la réponse entière.
  2. Si votre code HTML renvoyé contient des éléments qui ont eux-mêmes des identifiants et que ces identifiants ont des correspondances dans le document actuel, ces éléments seront remplacés en premier (et exclus du remplacement de « réponse complète » ci-dessus). C'est essentiellement ainsi que vous effectuez des échanges « hors bande » (alias hx-swap-oob).

Donc, avec du HTML comme celui-ci :

<div id=extra-stuff></div>
<div id=user-list></div>

<ajax-it>
  <a href="/users/list#put-it-here">
    Get users
  </a>
</ajax-it>
Copier après la connexion

et une réponse du serveur comme celle-ci :

<ul>
  <li>user 1
  <li>user 2
</ul>
Copier après la connexion

Vous obtiendrez :

Mais si votre réponse avait été :

<ul>
  <li>user 1
  <li>user 2
</ul>

Hello, I'm out-of-band

Copier après la connexion

vous auriez fini avec :

Hello, I'm out-of-band

<ul> <li>user 1 <li>user 2 </ul> Get users
Copier après la connexion

...avec l'id=extra-stuff échangé hors bande et le

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!