ご無沙汰しております。LDIで開発担当しているOKです。
今回のテーマですが、
普段はWebのフロントエンドとか全然やってないのですが、
訳あってHTMXを使ってみたので、アウトプットも兼ねてまずは導入部分について記事にしようと思います。
HTMXとは
公式ドキュメント には次のようにあります。
htmx gives you access to AJAX, CSS Transitions, WebSockets and Server Sent Events directly in HTML, using attributes, so you can build modern user interfaces with the simplicity and power of hypertext
(訳) htmxは、属性を使用して、AJAX、CSSトランジション、WebSocket、およびサーバー送信イベントに直接HTMLでアクセスできるようにするため、ハイパーテキストのシンプルさとパワーで最新のユーザーインターフェイスを構築できます。
ということですが、JavaScriptを書かずとも、通常のHTML記法を拡張した形で通信したり、 DOMレベルでの更新ができたりするライブラリで、 割とシンプルな記述でもSPA的なものが作れます。
とりあず今回の完成系
今回どんなことをやるかという事ですが、、大したことはしてないです。
ボタンを押すとアンカーに、アンカーを押すとボタンに変わるってだけ!!
見てわかる通り(わからないか、、、)ページ全体をリロードしているわけではなく、
コンポーネントが部分的に差し代わっています。
右側の通信結果のレスポンスでも部分的なコンテンツしか返ってきてない事がわかるかと思います。
それでは、これを作っていきます。
開発環境情報
- Mac Book Pro
- HTMX (v2.0.4)
- Go言語 (v1.23.2)
- Gin (v1.10)
それと、とりあえずレイアウトを考えるのが面倒なので、今回はclassとか指定しなくてもそれっぽいレイアウトに整えてくれる MVP.cssを利用してます。
ディレクトリ構成
- main.go - assets/ - css - mvp.css - templates/ - footer.html - header.html - sample1.html - sample2.html - index.html - go.mod
実装
まず、HTMLのテンプレートはそれぞれの実装は次の通り。
header.html
定型的になる記述になる部分を header として定義しました。
8〜10行目で HTMX
を指定しています。今回は簡単に利用するためCDNを参照していますが、本番環境では非推奨とのことです
(メニューの内容は今回触れないです。また機会があれば)
{{define "header.html"}} <html> <head> <meta charset="utf-8"> <meta http-equiv="x-ua-compatible" content="IE=edge" > <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <link rel="stylesheet" href="/assets/css/mvp.css"> <script src="https://unpkg.com/htmx.org@2.0.4" integrity="sha384-HGfztofotfshcF7+8n44JQL2oJmowVChPTg48S+jvZoztPfvwD79OC/LTtG6dMp+" crossorigin="anonymous"></script> </head> <body> <header> <nav> <ul> <li><a href="">編集画面</a></li> <li><a href="">無限スクロール</a></li> </ul> </nav> </header> {{end}}
footer.html
こちらも定型的なところで footer としています。
特に説明することは何もないですね。
{{define "footer.html"}} <footer> <p>Copyright example.com All Rights Reserved.</p> </footer> </body> </html> {{end}}
sample1.html
ボタンが表示されていて、そのボタンを押すとアンカーに切り替えるコンポーネントです。
4行目のbuttonのところにHTMXの処理を記述しています。
とりあえずコードを全部載せちゃいたいので、説明は後述します。
{{define "sample1.html"}} <main> <section> <button hx-get="/sample2" hx-target="main" hx-swap="outerHTML">ボタンからアンカー</button> </section> </main> {{end}}
sample2.html
こちらは、アンカー表示されていて、それを押すとボタンに切り替えるコンポーネントです。 こちらも詳細は後述します。
{{define "sample2.html"}} <main> <section> <a hx-get="/sample1" hx-target="main" hx-swap="outerHTML">アンカーからボタン</a> </section> </main> {{end}}
index.html
あとはトップページですが、header、footer、sample1のコンポーネントを組み合わせて構成しただけです。
{{template "header.html" .}} {{template "sample1.html" .}} {{template "footer.html" .}}
main.go
最後にこれらを配信するサーバの実装です。
Goでサクッと作成。
package main import ( "net/http" "github.com/gin-gonic/gin" ) func main() { router := gin.Default() // HTMLテンプレートなど設定 router.Static("/assets", "./assets") router.LoadHTMLGlob("./templates/*.html") setupAPI(router) // サーバ起動 router.Run(":8080") } // いろいろ追加していくつもりなので、APIのセットアップは別メソッド func setupAPI(router *gin.Engine) { router.GET("/", func(context *gin.Context) { context.HTML(http.StatusOK, "index.html", gin.H{}) }) router.GET("/sample1", func(context *gin.Context) { context.HTML(http.StatusOK, "sample1.html", gin.H{}) }) router.GET("/sample2", func(context *gin.Context) { context.HTML(http.StatusOK, "sample2.html", gin.H{}) }) }
HTMX の動作解説
それでは、 sample1.html
ベースに解説していきます。
<main> <section> <button hx-get="/sample2" hx-target="main" hx-swap="outerHTML">ボタンからアンカー</button> </section> </main>
hx-get
ですが、これは AJAXのGETリクエストを実施してくれます。今回だと /sample2
に対してGETリクエストをしてくれる形です。
他にも hx-post
や hx-put
、hx-delete
などもあり、名前でわかると思いますが、それぞれで POSTやPUT、DELETEリクエストを出す事もできます。
このリクエストを受けるサーバ側はというと、 sample2.html
を返すようになっているので、
router.GET("/sample2", func(context *gin.Context) { context.HTML(http.StatusOK, "sample2.html", gin.H{}) })
これがレスポンスとして返ってきます。
<main> <section> <a hx-get="/sample1" hx-target="main" hx-swap="outerHTML">アンカーからボタン</a> </section> </main>
この返ってきたレスポンスをどう扱うかを操作しているのが hx-target
と hx-swap
になります。
hx-target
で main
を指定しているので、 mainのタグが対象になります。
hx-swap
で指定している outerHTML
は ターゲットに指定した要素全体をレスポンスで置き換える
ものになります。
この結果、mainタグで囲んでいる全体が置き換わるという動きをしてくれることになります。
ざっくりとHTMXの導入部分に関して今回は説明させてもらいました。
次回はもう少し実用的な所に踏み込んでいきたいと思うので乞うご期待ください。
ではまた!!