Heim >Web-Frontend >js-Tutorial >Ich habe htmx durch eine einfache Webkomponente ersetzt

Ich habe htmx durch eine einfache Webkomponente ersetzt

WBOY
WBOYOriginal
2024-09-07 06:37:16556Durchsuche

I replaced htmx with a simple web component

(Bildnachweis: https://www.maicar.com/GML/Ajax1.html)

Vor kurzem habe ich auf Mastodon ein Gespräch darüber geführt, wie ich Htmx mit großem Erfolg verwendet habe, und jemand hat mich in meinen Erwähnungen dazu herausgefordert, und dass Htmx tatsächlich eine ziemlich starke Abhängigkeit darstellt, wenn man bedenkt, wofür ich es verwende. Sie haben mich mit diesem Beitrag und allem verlinkt.

Zuerst war ich etwas genervt. Ich dachte, dass es mir ziemlich gut gelingt, die Dinge leichtgewichtig zu halten, und Htmx hat mir gute Dienste geleistet, aber dann habe ich den Hut aufgesetzt, den ich die ganze Zeit über zu tragen versucht habe, wenn es darum geht, die Art und Weise, wie ich Webentwicklung mache, neu zu erfinden : Sind meine Annahmen richtig? Kann ich es besser machen?

Also habe ich meine gesamte Nutzung von htmx durch eine winzige, 100 Zeilen lange Vanillajs-Webkomponente ersetzt, die ich vollständig in diesen Beitrag einbeziehen werde:

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

Sie verwenden es so:

<ajax-it>
  <form action="/some/url">
    <input name=name>
  </form>
</ajax-it>

Und das ist es! Alle in der Antwort enthaltenen Elemente mit einer ID werden ersetzt, wenn die Antwort zurückkommt. Es funktioniert für Elemente auch!

Das Element funktioniert im Wesentlichen auf zwei Arten:

  1. Wenn Ihre Aktion oder Href einen Hash enthält, wird das Element auf oder der aktuellen Seite mit einer ID, die mit diesem Hash übereinstimmt, durch den Inhalt der gesamten Antwort ersetzt.
  2. Wenn Ihr zurückgegebener HTML-Code Elemente enthält, die selbst IDs haben, und diese IDs im aktuellen Dokument übereinstimmen, werden diese Elemente zuerst ersetzt (und von der oben genannten Ersetzung der „gesamten Antwort“ ausgeschlossen). Dies ist im Wesentlichen die Art und Weise, wie Sie „Out-of-Band“-Swaps (auch bekannt als hx-swap-oob) durchführen.

Also, mit etwas HTML wie diesem:

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

<ajax-it>
  <a href="/users/list#put-it-here">
    Get users
  </a>
</ajax-it>

und eine Serverantwort wie diese:

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

Das Ergebnis ist:

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

Aber wenn Ihre Antwort gewesen wäre:

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

Hello, I'm out-of-band

Sie hätten am Ende Folgendes erhalten:

Hello, I'm out-of-band

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

...mit der id=extra-stuff außerhalb des Bandes ausgetauscht und dem

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn