(画像クレジット: https://www.maicar.com/GML/Ajax1.html)
私は最近、htmx を使用して大きな成功を収めていることについてマストドンで会話しました。そして、誰かが私のメンションに転がり込んできて、それについて私に挑戦し、htmx が実際に私が使用していた目的を考えるとかなり重い依存関係であることについて言及しました。彼らは私をこの投稿やあらゆるものにリンクしてくれました。
最初はちょっとイライラしました。軽量化に関してはかなりうまくやっていると思っていましたし、htmx も役に立ちました。しかし、Web 開発のやり方を再発明するということになると、これまでずっとかぶろうとしてきた帽子をかぶることになりました。 :私の推測は正しいでしょうか?もっと上手くできるでしょうか?
そこで、htmx の使用全体を、100 行の小さな vanillajs Web コンポーネントに置き換えることにしました。この投稿には、その全体を含めることにします。
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);
次のように使用します:
<ajax-it> <form action="/some/url"> <input name=name> </form> </ajax-it>
それで終わりです!応答に含まれる ID を持つ要素は、応答が返されたときに置き換えられます。それは に対して機能します。要素も!
この要素は主に 2 つの方法で機能します:
それでは、次のような HTML を使用します。
<div id=extra-stuff></div> <div id=user-list></div> <ajax-it> <a href="/users/list#put-it-here"> Get users </a> </ajax-it>
そして次のようなサーバー応答:
<ul> <li>user 1 <li>user 2 </ul>
最終的には次のようになります:
<ul> <li>user 1 <li>user 2 </ul>Get users
しかし、あなたの反応が次のようなものだったとします。
<ul> <li>user 1 <li>user 2 </ul>Hello, I'm out-of-band
最終的には次のようになります:
Hello, I'm out-of-band
<ul> <li>user 1 <li>user 2 </ul>Get users
...id=extra-stuff が帯域外で交換され、
ただし、冪等性を維持するために、私はハッシュ バージョンを使用する傾向はなく、すべての応答要素に ID が添付されていることを確認するようにしています。
<ul id=user-list> <li>user 1 <li>user 2 </ul> <p id=extra-stuff>Hello, I'm out-of-band</p>
これは