HTMXでリクエスト実行後にJavaScriptコードを実行する方法

こんにちは、エキサイト株式会社の平石です。

今回は、HTMXでhx-gethx-postを使ってリクエストを実行した後に任意のJavaScriptコードを実行する方法をご紹介します。

はじめに

HTMXでは、hx-gethx-postを利用して、リクエストを実行することができます。
そして、レスポンスとして返ってきたHTMLテンプレートで既存のテンプレートの全体または一部を置き換えることができます。

しかし、このリクエストの実行後に任意のJavaScriptコードを実行したい場合もあると思います。

今回は、その方法をブログ記事として残しておきたいと思います。

環境

以下の環境で動作確認をしています。

  • Java 21
  • Spring Boot v3.2.2
  • HTMX v1.9.10

ただし、基本的にはHTMX + (生の)JavaScriptであるため、サーバーサイドの言語がJava以外であっても動作するかと思います。

以下のようなControllerを考えます。

@Controller
@RequestMapping("sample")
public class SamplePageController {
    private final List<String> stringList = new ArrayList<>();

    @GetMapping("/index")
    public String sampleIndex(
            Model model
    ) {
        model.addAttribute("stringList", stringList);

        return "sample/index";
    }

    @PostMapping("/add")
    @ResponseStatus(HttpStatus.CREATED)
    public String sampleIndex(
            Model model,
            String string
    ) {
        if (string.isBlank() || string.length() > 10) {
            throw new ResponseStatusException(
                    HttpStatus.BAD_REQUEST, "bad request"
            );
        }

        stringList.add(string);

        model.addAttribute("stringList", stringList);

        return "elements/sample/strings";
    }
}

渡された文字列のパラメータをリストに追加し、その結果のリストをテンプレートに渡しています。
ただし、空文字列または文字数が11文字以上であった場合には、ステータスコード400でレスポンスを返します。

次に、テンプレートを記述します。
今回は、Thymeleafを利用しています。

resources/templates/sample/index.htmlを以下のように記述します。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
    <title>サンプルページ</title>
    <script src="/webjars/htmx.org/1.9.10/dist/htmx.min.js"></script>
</head>
<body hx-boost="true">

<div id="stringList" th:insert="~{elements/sample/strings}"></div>

<form id="numberForm"
      hx-post="/sample/add"
      hx-target="#stringList"
      hx-swap="innerHtml">
    <input type="text" name="string">
    <input type="submit" value="追加">
</form>

</body>
</html>

また、resources/templates/elements/sample/strings.htmlを以下のように記述します。

<ul>
    <th:block th:each="string : ${stringList}">
        <li th:text="${string}"></li>
    </th:block>
</ul>

index.html内の<div id="stringList" th:insert="~{elements/sample/strings}"></div>の部分にstrings.htmlが挿入されます。

Spring Bootアプリケーションを起動してsample/indexにアクセスすると、以下のようなフォームが表示されます。

フォームに適当に文字列を入力して、「追加」ボタンを押すと上に入力した文字列が追加されます。

リクエスト後にJavaScriptコードを実行する

それでは、「追加」ボタンを押して文字列をリストに追加した後に任意のJavaScriptコードを実行してみます。
今回は、リクエスト後にアラートを出すような処理を追加します。

HTMXのEventsの中にある、htmx:afterRequestを利用します。

htmx:afterRequestイベントは、AJAXリクエストが終了したときにトリガーされるイベントです。

hx-onを使って、このイベントをトリガーにして実行する処理を記述します。

<form id="numberForm"
      hx-post="/sample/add"
      hx-target="#stringList"
      hx-swap="innerHtml"
      hx-on::after-request="alert('追加しました。')">
    <input type="text" name="string">
    <input type="submit" value="追加">
</form>

イベントはhtmx:afterRequestですが、hx-onで記述する際にはafter-requestとする必要があります。 また、htmx:afterRequestのようにHTMX固有のイベントでは、本来はhx-on:htmx:after-requestと書くところをhx-on::after-requestのように省略して記述することができます。

このコードを追加した上で、適当に入力して追加ボタンを押してみます。

アラートが表示され、リクエスト終了後にJavaScriptコードが実行されたことがわかります。

当然ながら、処理を関数として定義してその関数を呼び出すこともできます。

<form id="numberForm"
      hx-post="/sample/add"
      hx-target="#stringList"
      hx-swap="innerHtml"
      hx-on::after-request="displayAlert()">
    <input type="text" name="string">
    <input type="submit" value="追加">
</form>

<script>
    function displayAlert() {
        alert('失敗しました。');
    }
</script>

また、特別なシンボルとしてeventを利用することができます。
event.detailにはそのイベント固有の情報が入っています。
例えば、htmx:afterRequestでは実行されたリクエストのパラメータやパス、レスポンスの情報を参照することができます。

<form id="numberForm"
      hx-post="/sample/add"
      hx-target="#stringList"
      hx-swap="innerHtml"
      hx-on::after-request="displayAlert(event)">
    <input type="text" name="string">
    <input type="submit" value="追加">
</form>

<script>
    function displayAlert(event) {
        if (event.detail.xhr.status === 400) {
            const string = event.detail.requestConfig.parameters.string;

            alert("失敗しました。パラメータ`string: " + string + "`が不正です。");
        }
    }
</script>

displayAlert関数にeventを渡しています。

ここでは、event.detail.xhr.statusでレスポンスのステータスコードを取得しており、ステータスコード400でレスポンスが返ってきた時のみアラートを出すようにしています。
また、event.detail.requestConfig.parametersでリクエストに使用したパラメータを取得できるので、リクエストパラメータの情報を使ってアラートの内容を補足しています。

この状態で、例えばフォームにToo long stringと入力して「追加」ボタンを押すと以下のようなアラートが出ます。

なお、hx-onを利用せず、生のJavaScriptを利用して、document全体やHTML要素に対して直接イベントリスナを追加することもできます。

全てのHTMXリクエストの後に実行したい処理があるといった場合は、こちらを利用しても良いかもしれません。

<script>
    document.addEventListener('htmx:afterRequest', function (evt) {
        if (evt.detail.failed) {
            alert("失敗しました。");
        }
    });
</script>

おわりに

今回は、HTMXでhx-gethx-postを使ってリクエストを実行した後に任意のJavaScriptコードを実行する方法をご紹介しました。

では、また次回。

参考文献