Rumah >hujung hadapan web >tutorial js >Saya menggantikan htmx dengan komponen web yang mudah

Saya menggantikan htmx dengan komponen web yang mudah

WBOY
WBOYasal
2024-09-07 06:37:16592semak imbas

I replaced htmx with a simple web component

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

Baru-baru ini saya berbual di Mastodon tentang cara saya menggunakan htmx untuk mencapai kejayaan yang besar, dan seseorang menyertakan sebutan saya yang mencabar saya mengenainya, dan bagaimana htmx sebenarnya merupakan pergantungan yang agak berat memandangkan saya menggunakannya. Mereka memaut saya ke siaran ini dan segala-galanya.

Pada mulanya, saya agak jengkel. Saya fikir saya telah melakukan kerja yang cukup baik untuk memastikan perkara yang ringan, dan htmx telah melayani saya dengan baik, tetapi kemudian saya memakai topi yang saya cuba pakai sepanjang masa ini apabila ia datang untuk mencipta semula cara saya melakukan pembangunan web : betulkah andaian saya? Bolehkah saya berbuat lebih baik?

Jadi saya meneruskan dan menggantikan keseluruhan penggunaan htmx saya dengan komponen web vanillajs yang kecil, 100 baris, yang akan saya sertakan dalam siaran ini secara keseluruhannya:

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

Anda menggunakannya seperti ini:

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

Dan itu sahaja! Sebarang elemen dengan id yang disertakan dalam respons akan diganti apabila respons itu kembali. Ia berfungsi untuk unsur juga!

Elemen berfungsi dua cara utama:

  1. Jika tindakan atau href anda termasuk cincang, elemen pada atau halaman semasa dengan padanan id cincang itu akan digantikan dengan kandungan keseluruhan respons.
  2. Jika html anda yang dikembalikan mengandungi elemen yang mempunyai ID sendiri, dan ID tersebut mempunyai padanan dalam dokumen semasa, elemen tersebut akan diganti dahulu (dan dikecualikan daripada penggantian "keseluruhan respons" di atas). Ini pada asasnya cara anda melakukan swap "di luar jalur" (aka hx-swap-oob).

Jadi, dengan beberapa html seperti ini:

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

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

dan respons pelayan seperti ini:

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

Anda akan mendapat:

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

Tetapi jika jawapan anda ialah:

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

Hello, I'm out-of-band

anda mungkin akan mendapat:

Hello, I'm out-of-band

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

...dengan id=bahan tambahan ditukar di luar jalur dan

Kenyataan:
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn