この記事に書いてあること
Thymeleafの画面で、JavaからModelへ渡したURLを使い、ボタンを押したら別の画面へGETで移動させる例です。この記事はSpring MVC 5.1.16、Thymeleaf 3.0.11、Java 11で試した記録です。生成されたHTMLを見比べながら、URLが置換されない原因と引用符の組み立てを確認します。
その中で、ボタンのonClick属性を使用して他の画面にGETで遷移するようなパターンでちょっと迷ったので備忘メモ。
thymeleafの属性をよく調べればなんてことはないのですが、なかなか公式ドキュメントから漁るのも体力入りますからね・・・誰かの助けになれば。
前提
- Spring MVC 5.1.16
- thymeleaf 3.0.11
- Java11
- ボタンを押した時に動作するonClickのリンク先URLはmodel.attributeを使ってurlという名前でテンプレートに引き渡す
結論
詳細画面へGETで移るだけなら th:href を使います。確認ダイアログなどボタン固有の操作が必要な場合は、ThymeleafでURLを data-url 属性へ出し、JavaScriptから dataset.url として読みます。
実行用サンプル
JDK 17とMaven 3.9系で mvn test を実行します。ZIPにはソース全文・テスト・pom.xml・READMEを収録しています。
navigation.html の全文
<!doctype html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>画面遷移</title>
<script th:src="@{/assets/navigation.js}" defer="defer"></script>
</head>
<body>
<a id="details-link" th:href="@{/details(q=${query})}">詳細へ</a>
<button type="button" id="details-button"
th:data-url="@{/details(q=${query})}">確認して詳細へ</button>
</body>
</html>navigation.js の全文
const button = document.querySelector('#details-button');
button.addEventListener('click', () => {
if (window.confirm('詳細画面を開きますか?')) {
window.location.assign(button.dataset.url);
}
});MockMvcでコンテキストパス /demo と、引用符・アンパサンドを含む検索語を使いました。リンクの href とボタンの data-url が同じ値になり、GET先が元の検索語を受け取ることを確認しました。
以下は旧環境で onClick の引用符を組み立てたときの比較記録です。現在の実装では、値をイベント属性のJavaScriptへ連結せず、上のリンク式と data-url を使います。
<input type="button" name="SUBMIT" th:attr="onClick='location.href=\''+${url}+'\''">
概要
thymeleafを使うとなるとQuitaのこの辺りの記事にお世話になることが多いと思うのですが、ボタン押下時の画面遷移についての情報がありません。
自分で試行錯誤してみたところ、無事に想定通りの動きをさせることができました。
ダメな例1:すぐ思いつくパターン
とりあえず思いつくのが以下のパターンではないでしょうか。
<input type="button" name="SUBMIT" onclick="location.href="+th:text=${url}>
location.href=までを書いた後、URLをth:text属性で埋め込んでやろうとしました。
この結果得られたHTMLはボタンを押しても何も反応しませんでした。
この時、ページのソースを表示して中身を確認すると以下のような値が設定されていました。
<input type="button" name="SUBMIT" onclick="location.href="+th:text=${url}>
onclick以下のダブルクオートで囲んだエリアがそのまま文字列として認識されてしまいました。
ダメな例2:1を改善したパターン
先の結果を受けて、onclickなどの属性を使用するときはth:attrを使うことがわかりました。
valueを入れたければth:value,textを表示したければth:textというようになりますが、その他の任意の属性を使用したい場合はth:attrを使います。
th:attr="onclick"としてやることで、thymeleafの機能を使ってonclickを動的に設定することができるようになります。
ということで以下のようにしました。
<input type="button" name="SUBMIT" th:attr="onclick='location.href=${url}'">
この結果得られたページのソースは
<input type="button" name="SUBMIT" onclick="location.href=${url}">
先ほどよりは可能性がありそうです。しかし、肝心のurlが置換されていません。
ダメな例3:シングルクオートを活用したパターン
先の例でダメなポイントは、${url}がシングルクオートの中に入っており、そのまま文字列として扱われている点でした。
そこで、${url}をシングルクオートの外に置いてみます。
<input type="button" name="SUBMIT" th:attr="onclick='location.href='+${url}">
この結果得られたHTMLのソースは以下でした。
<input type="button" name="SUBMIT" onclick="location.href=http://localhost:8080/SampleAP/getendpoint">
これでボタンを押してみると、何も反応しません。よくみたらlocation.href=の後のURLがシングルクオートで囲まれていません。
これによりonclick自体は動いていもlocationn.hrefが動いていないのでしょう。あとはこのURLをシングルクオートで囲ってやれば良さそうです。
いけた例:エスケープしたシングルクオートで${url}を囲うパターン
先の例が非常に惜しかったので、単純な発想でシングルクオートをエスケープしてthymeleafのテンプレートに書いてみることにしました。
<input type="button" name="SUBMIT" th:attr="onClick='location.href=\''+${url}+'\''">
location.hrefの部分にエスケープしたシングルクオートを追加しています。
これにより、以下のHTMLソースを得ることができ、ボタンも無事に動作しました!
<input type="button" name="SUBMIT" onclick="location.href='http://localhost:8080/SampleAP/getendpoint'">
この旧例の動作は単純なURLでの観測です。URLにクエリや引用符が入り得る場合は、結論のサンプルのようにURLを属性値として扱います。
