htmx

最近htmxというものをAIに教えてもらい、使い方を勉強しています。

after-swapイベントの捕捉

これをどこに設定するとイベントを捕捉できるのかを調べました。

index1.html

<html>
  <head>
    <script src="https://unpkg.com/htmx.org@2.0.8"></script>
  </head>

  <body>
    <button
      hx-get="content1.html" hx-target="#div1" 
      hx-swap="innerHTML" 
      hx-on::after-swap="console.log('after swap at button1');"
    >
      innerHTML
    </button>

    <div id="div1" style="border: 4px ridge blue; padding: 0.5rem;"
      hx-on::after-swap="console.log('after swap at original #div1');"
    >
      original content
    </div>
      
    <button
      hx-get="content2.html" hx-target="#div2" 
      hx-swap="outerHTML" 
      hx-on::after-swap="console.log('after swap at button2');"
    >
      outerHTML
    </button>

    <div id="div2" style="border: 4px ridge blue; padding: 0.5rem;"
      hx-on::after-swap="console.log('after swap at original #div2');"
    >
      original content
    </div>
      
  </body>
</html>

content1.html

<div id="div1-1" style="border: 4px ridge red;"
  hx-on::after-swap="console.log('after swap at #div1 in content1.html');"
>
  content
</div>

content2.html

<div id="div2" style="border: 4px ridge red;"
  hx-on::after-swap="console.log('after swap at #div2 in content2.html');"
>
  content
</div>

ブラウザーの開発者ツールのコンソールを開いて確認します。

結果は、swapをinnerHTMLにして、targetに設定しないとだめということです。outerHTMLだと、イベントリスナーが設定されているtargetが消えるので、after-swapイベントが捉えられません。また、新しいtargetの要素はまだhtmxによってイベントリスナーが設定されていないので、こちらもだめということです。

どうしてもouterHTMLでやりたいときにどうしたらいいのかというと、targetの親要素(今回はbody)にイベントリスナーを設定する(hx-on::after-swapをつける)のがよいようです。

index2.html

<html>
  <head>
    <script src="https://unpkg.com/htmx.org@2.0.8"></script>
    <script><!--
        function myLog(evt){
            console.log(evt.target);
        }
    --></script>
  </head>

  <body
    hx-on::after-swap="myLog(event);"
  >
    <button
      hx-get="content1.html" hx-target="#div1" 
      hx-swap="innerHTML" 
    >
      innerHTML
    </button>

    <div id="div1" style="border: 4px ridge blue; padding: 0.5rem;">
      original content
    </div>
      
    <button
      hx-get="content2.html" hx-target="#div2" 
      hx-swap="outerHTML" 
    >
      outerHTML
    </button>

    <div id="div2" style="border: 4px ridge blue; padding: 0.5rem;">
      original content
    </div>
      
  </body>
</html>

innerHTMLをクリックするとdiv#div1が、outerHTMLをクリックするとdiv#div2が示されました。

hx-push-urlを使ったURLの履歴変更

htmxを使ってタブ切り替えをつくりました。タブをクリックするとそのページの内容をhx-getで取得するようにしました。
そのとき、ページのリロード(F5)をすると、最初に読み込むページに戻ってしまい、開いたタブを再現できなくなるのが不都合でした。
そこで、クリックしたところ(ハッシュ)をURLに表示するようにして、ページリロードでそれをクリックすることを思いつきました。

ページがロード済みの場合はhx-getを無効にしつつ、URLの変遷だけはできないかといろいろと試してみました。

index3.html

<html>
  <head>
    <script src="https://unpkg.com/htmx.org@2.0.8"></script>
  </head>

  <body>
  	<table>
    <caption>hx-get, href, hx-push-urlの動作確認</caption>
    <thead>
    <tr>
        <th></th><th colspan=3>値</th><th colspan=3>結果</th>
    </tr>
    <tr>
        <th></th>
        <th>href</th><th>hx-get</th><th>hx-push-url</th>
        <th>Jump</th><th>hx-get</th><th>hx-push-url</th>
    </tr>
    </thead>
    <tbody>
    <tr>
        <th><a
            href='#jump1'
            hx-get="content.php?jump_to_valid_href"
            hx-target="#div1"
            hx-push-url="#link1"
        >Link1</a></th>
        <td>#jump1</td>
        <td>content.php</td>
        <td>#link1</td>
        <td>&#x25cb;</td>
        <td>&#x25cb;</td>
        <td>&#x25cb;</td>
    </tr>

    <tr>
        <th><a
            href=''
            hx-get="content.php?not_jump"
            hx-target="#div1"
            hx-push-url="#link2"
        >Link2</a></th>
        <td>空</td>
        <td>content.php</td>
        <td>#link2</td>
        <td>&times;</td>
        <td>&#x25cb;</td>
        <td>&#x25cb;</td>
    </tr>

    <tr>
        <th><a
            href=''
            hx-get=''
            hx-target="#div1"
            hx-push-url="#link3"
        >Link3</a></th>
        <td>空</td>
        <td>空</td>
        <td>#link3</td>
        <td>&times;</td>
        <td>index3.html</td>
        <td>&#x25cb;</td>
    </tr>

    <tr>
        <th><a
            href=''
            hx-target="#div1"
            hx-push-url="#link4"
        >Link4</a></th>
        <td>空</td>
        <td>なし</td>
        <td>#link4</td>
        <td>&times;</td>
        <td>&#x25cb;</td>
        <td>&times;</td>
        <td>same to reload</td>
    </tr>

    <tr>
        <th><a
            hx-get='content.php?link5_was_clicked'
            hx-target="#div1"
            hx-push-url="#link5"
        >Link5</a></th>
        <td>なし</td>
        <td>content.php</td>
        <td>#link5</td>
        <td>&times;</td>
        <td>&#x25cb;</td>
        <td>&#x25cb;</td>
        <td>not link</td>
    </tr>

    <tr>
        <th><a
            hx-target="#div1"
            hx-push-url="#link6"
        >Link6</a></th>
        <td>なし</td>
        <td>なし</td>
        <td>#link6</td>
        <td>&times;</td>
        <td>&times;</td>
        <td>&times;</td>
        <td>何も起きない</td>
    </tr>

    </tbody>
    </table>

    <div id="div1" style="border: 4px ridge blue; padding: 0.5rem;">
      original content
    </div>

    <div style="height: 300px">
        <span id='jump1'>Jump1</span>
    </div>
  </body>
</html>

結論はhx-getが成功しないとhx-push-urlは動かない(by ChatGPT)。

私の用途にはhx-push-urlは使えないということで、hx-on:click="history.pushState({}, ", 'link’);とするより他なさそうです。