(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:
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
Namun, untuk mengekalkan ketidakupayaan, saya tidak cenderung menggunakan versi cincang sesuatu dan cuma pastikan semua elemen respons saya telah melampirkan ID:
<ul id=user-list> <li>user 1 <li>user 2 </ul> <p id=extra-stuff>Hello, I'm out-of-band</p>
Yang akan mengekalkan