htmxでデータ送信時、親form内のすべてのデータが送信される

こんにちは。 エキサイト株式会社の三浦です。

皆さんは、htmxをご存知でしょうか?(HTMLのtypoではありません!)

htmx.org

htmxはHTML用のライブラリの一つで、HTMLページ上での動的なデータ通信やそれによる表示の置き換えなどを簡単に行えるようにしてくれるものです。

エキサイト TechBlogでも以前紹介しているので、詳しくはぜひ公式サイトやブログを御覧ください!

tech.excite.co.jp

今回はこのhtmxでデータを送信する際、親form内のすべてのデータが送信されるということを紹介していきます。

htmxとは

htmxはHTML用ライブラリの1つで、HTMLページ上での動的なデータ通信や通信結果によるページのパーツの置き換えなど、自分でやろうとすると少々面倒な処理を簡単に行えるようにしてくれるものです。

詳しくはぜひこちらを御覧ください!

htmx.org

tech.excite.co.jp

ここでは簡単に、公式のサンプルに少し手を加えたものをSpring Boot / Thymeleafで試してみます。

<script src="https://unpkg.com/htmx.org@1.9.6"></script>

<!-- have a button POST a click via AJAX -->
<button hx-post="/sample" hx-swap="none">
    Click Me
</button>
@Controller
@RequestMapping
public class SampleController {
    /**
     * サンプル用ページを表示する
     * 
     * @return サンプル用ページのテンプレート
     */
    @GetMapping("sample")
    public String showSamplePage() {
        return "sample";
    }

    /**
     * サンプル用ページから、POSTアクセスを受け取る
     * 
     * @param param リクエストのパラメータ
     */
    @PostMapping("sample")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void sample(@RequestParam Map<String, String> param) {
        System.out.println(param);
    }
}

このようなコードを書くと、下記のようなページが表示されます。

サンプルページ

ボタンをクリックすると、開発者ツール等で通信が発生したことが確認できます。

このとき、画面遷移は発生せず、リクエストの送信のみが行われています。

sampleへのリクエス

Spring Boot側のコンソールからも、アクセスが来たことが確認できます。

なお今回は特にデータを送っていないため、リクエストの中身は空です。

受け取ったリクエス

このように、非常に簡単に動的なデータ通信ができました。

formを使ってデータ通信を行う

上記の例は非常に単純なものであり、実際はformを使った通信が多いでしょう。

今度はformを使ったサンプルを考えてみます。

要件は以下です。

  • テキストフィールドで、入力するごとにリクエストを送信してほしい
  • CSRF等の攻撃を回避するため、リクエスト送信時はトークンも一緒に送りたい

これを解決するコードは、以下のようになります。

<script src="https://unpkg.com/htmx.org@1.9.6"></script>

<!-- formでデータを送信 -->
<form hx-post="/sample"
      hx-swap="none"
      hx-trigger="input changed from:#sample_text"
>
    <!-- トークンを追加 -->
    <input type="hidden" name="token" value="トークン"/>

    <!-- テキストフィールドを追加 -->
    <label>
        サンプルテキストフィールド
        <input id="sample_text" type="text" name="sample_text" />
    </label>
</form>

サンプルページ

今回は要件に沿って、buttonタグは取り除き、かわりにformタグ、トークン用のhiddenなinputタグ、およびテキストフィールド用のinputタグを追加しています。

また、テキストフィールドへの入力ごとにリクエストを送るため、formタグに以下の属性をつけています。

hx-trigger="input changed from:#sample_text"

ほぼ読んだままですが、

  1. sample_textをIDに持つタグが、
  2. 入力されると、
  3. それをトリガーとしてリクエストを送信する

という内容です。

formタグ内にあるhiddenなinputタグとテキストフィールド用のタグの両方のデータを一緒に送るため、テキストフィールド用のタグではなくformタグの方にこの属性を付けています。

結果、 1111 を入力すると以下のようになりました。

開発者ツール

sampleへのリクエス

Spring Bootのコンソール

受け取ったリクエス

これで無事、要件である

  • テキストフィールドで、入力するごとにリクエストを送信してほしい
  • CSRF等の攻撃を回避するため、リクエスト送信時はトークンも一緒に送りたい

に沿ったページを作ることができました。

ですがこちら、formタグにテキストフィールドのタグのトリガーを書いており、記述が少々煩雑になってしまっています。

できればテキストフィールドのタグ自体にトリガーを書きたいところですが、hiddenなinputタグのデータも一緒に送るにはしょうがないのでしょうか?

データ送信時、親form内のすべてのデータが送信される

実はそんな事はありません。

よくhtmxのドキュメントを読んでみると、以下のように記述されています。

htmx.org

if the element causes a non-GET request, the values of all the inputs of the nearest enclosing form will be included.

つまり、「データ送信時、親form内のすべてのデータが送信される」ということです。

実際に試してみます。

<script src="https://unpkg.com/htmx.org@1.9.6"></script>

<form>
    <input type="hidden" name="token" value="トークン"/>

    <label>
        サンプルテキストフィールド
        <input id="sample_text"
               type="text"
               name="sample_text"

               hx-post="/sample"
               hx-swap="none"
               hx-trigger="input changed"
        />
    </label>
</form>

テキストフィールド側に、送信先やイベントのトリガー情報を書いています。

なお、自身がトリガーの対象のタグであるため、 from の記述は不要になりました。

ページの表示は変わりません。

サンプルページ

では、 1111 と入力してみましょう。

開発者ツール

sampleへのリクエス

Spring Bootのコンソール

受け取ったリクエス

先程のformの属性に書き込んだときと同じく、tokenのデータまで一緒に送られることが確認できました!

最後に

htmxは基本的な機能だけでも便利なライブラリですが、ドキュメントをよく見ると細かいところでも便利な機能があることがわかります。

今回紹介した機能だけでも、知っているのとそうでないのとではコードの複雑さ等がかなり変わってくるでしょう。

ぜひ読み込んで、使いこなしていきましょう!