htmx を初めて使う人がよく抱く疑問は
「画面上の他のコンテンツを更新する必要があります。どうすればいいですか?」です。
いくつかの方法がありますが、この例ではそのいくつかをご紹介します。
この概念を説明するために、次の基本的な UI を使用します。シンプルな連絡先テーブルと、その下に hx-post を使用してテーブルに新しい連絡先を追加するためのフォームがあります。
<h2>Contacts</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table">
...
</tbody>
</table>
<h2>Add A Contact</h2>
<form hx-post="/contacts">
<label>
Name
<input name="name" type="text">
</label>
<label>
Email
<input name="email" type="email">
</label>
</form>
ここで問題となるのは、フォームで新しい連絡先を送信すると、上記の連絡先テーブルを更新して、フォームで追加されたばかりの連絡先を含める必要があることです。
どのような解決策があるでしょうか?
ここで最も簡単な解決策は、フォームの「ターゲットを拡張」して、テーブル*と*フォームの両方を囲むことです。たとえば、全体を div
で囲み、フォームでその div
をターゲットにすることができます。
<div id="table-and-form">
<h2>Contacts</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table">
...
</tbody>
</table>
<h2>Add A Contact</h2>
<form hx-post="/contacts" hx-target="#table-and-form">
<label>
Name
<input name="name" type="text">
</label>
<label>
Email
<input name="email" type="email">
</label>
</form>
</div>
hx-target 属性を使用して、囲んでいる div をターゲットにしていることに注意してください。/contacts
への POST
への応答で、テーブルとフォームの両方をレンダリングする必要があります。
これはシンプルで信頼性の高いアプローチですが、特に洗練されているとは感じられないかもしれません。
この問題に対するより洗練されたアプローチは、帯域外スワップ を使用して、更新されたコンテンツを DOM にスワップインすることです。
このアプローチを使用すると、HTML は元のセットアップからまったく変更する必要がありません。
<h2>Contacts</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table">
...
</tbody>
</table>
<h2>Add A Contact</h2>
<form hx-post="/contacts">
<label>
Name
<input name="name" type="text">
</label>
<label>
Email
<input name="email" type="email">
</label>
</form>
フロントエンドで何かを変更する代わりに、/contacts
への POST
への応答に、追加のコンテンツを含めます。
<tbody hx-swap-oob="beforeend:#contacts-table">
<tr>
<td>Joe Smith</td>
<td>joe@smith.com</td>
</tr>
</tbody>
<label>
Name
<input name="name" type="text">
</label>
<label>
Email
<input name="email" type="email">
</label>
このコンテンツは、hx-swap-oob 属性を使用して、自身を #contacts-table
に追加し、連絡先が正常に追加された後にテーブルを更新します。
さらに洗練されたアプローチは、連絡先が正常に作成されたときにクライアント側のイベントをトリガーし、テーブルでそのイベントをリッスンして、テーブルを更新させることです。
<h2>Contacts</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table" hx-get="/contacts/table" hx-trigger="newContact from:body">
...
</tbody>
</table>
<h2>Add A Contact</h2>
<form hx-post="/contacts">
<label>
Name
<input name="name" type="text">
</label>
<label>
Email
<input name="email" type="email">
</label>
</form>
連絡先テーブルを再レンダリングする新しいエンドポイント /contacts/table
を追加しました。このリクエストのトリガーは、カスタムイベント newContact
です。フォームへの応答によってトリガーされると、イベントバブリングのために body に到達するため、body
でこのイベントをリッスンします。
/contacts
への POST 中に連絡先が正常に作成されると、レスポンスには次のような HX-Trigger レスポンスヘッダーが含まれます。
HX-Trigger:newContact
これにより、テーブルは /contacts/table
への GET
を発行し、新しく追加された連絡先の行がレンダリングされます。
(テーブルの残りの部分に加えて)
非常にクリーンな、イベント駆動型プログラミングです!
最後のアプローチは、REST-ful パス依存関係を使用してテーブルを更新することです。htmx の前身である Intercooler.js には、パスベースの依存関係 がライブラリに統合されていました。
htmx はこれをコア機能として削除しましたが、同様の機能を提供する拡張機能である path deps をサポートしています。
拡張機能を使用するように例を更新するには、拡張機能の javascript をロードし、HTML に次のように注釈を付ける必要があります。
<h2>Contacts</h2>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th></th>
</tr>
</thead>
<tbody id="contacts-table" hx-get="/contacts/table" hx-ext="path-deps" hx-trigger="path-deps" path-deps="/contacts">
...
</tbody>
</table>
<h2>Add A Contact</h2>
<form hx-post="/contacts">
<label>
Name
<input name="name" type="text">
</label>
<label>
Email
<input name="email" type="email">
</label>
</form>
これで、フォームが /contacts
URL に投稿されると、path-deps
拡張機能はそれを検出し、連絡先テーブルで path-deps
イベントをトリガーし、リクエストをトリガーします。
ここでの利点は、レスポンスヘッダーで特別なことをする必要がないことです。欠点は、連絡先が正常に作成されなかった場合でも、すべての POST
でリクエストが発行されることです。
一般的に、特に更新する必要がある要素が DOM 内で互いに reasonably 近接している場合は、最初のアプローチであるターゲットの拡張をお勧めします。シンプルで信頼性が高いです。
その後は、カスタムイベントと OOB スワップアプローチのどちらかを選ぶことになると思います。私はイベント指向システムが好きなので、カスタムイベントアプローチに傾いていますが、それは個人的な好みです。どちらを選択するかは、独自のソフトウェアエンジニアリングの好みと、どちらが選択したサーバー側のテクノロジーとより一致するかにより決定する必要があります。
最後に、パス依存関係アプローチは興味深いものであり、メンタルモデルとシステムアーキテクチャ全体にうまく適合する場合は、明示的な更新を回避する楽しい方法になります。ただし、その概念に本当に魅力を感じない限り、最後に検討する必要があります。