はじめに
自宅の趣味管理システムで thymeleaf を使っており、selectタグ を fragmentで共通化していました。
ある日、別のselectタグを追加したら意図しない動きになりハマりました。
パターンがわかったので、記事に残します。
同じようにハマった人がいたらご参考になれば幸いです。
環境
spring-boot-starter-thymeleaf 3.4.4
thymeleaf の fragment の前提知識
本家サイトの 8.1 テンプレートフラグメントのインクルード をご参照
何をやった?
同一ファイル内で th:fragment="select(引数) に加えて、th:fragment="selectTrainingMenu(引数) を追加しました。
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Parts</title>
</head>
<body>
<select
th:fragment="select(selectMap, selectedKey, fieldName, hasBlank)"
class="form-select" th:field="*{__${fieldName}__}">
<option th:if="${hasBlank}" value=""></option>
<option th:each="item : ${selectMap}" th:value="${item.key}"
th:text="${item.value}" th:selected="${item.key == selectedKey}"></option>
</select>
<select
th:fragment="selectTrainingMenu(selectList, selectedKey, fieldName, hasBlank)"
class="form-select" th:field="*{__${fieldName}__}">
<option th:if="${hasBlank}" value=""></option>
<option th:each="item : ${selectList}" th:value="${item.key}"
th:data-parent-key="${item.parentKey}"
th:data-max-weight="${item.maxWeight}"
th:data-max-reps="${item.maxReps}"
th:data-max-sets="${item.maxSets}"
th:text="${item.value}" th:selected="${item.key == selectedKey}"></option>
</select>
</body>
</html>
何が起こった?
「th:insert="~{parts/parts :: select(引数)」の読み込んでいる箇所で、select と selectTrainingMenuの2つのselectタグが表示されました。
意図としては、元の select だけの気持ちでした。
<div class="col-sm-10"
th:insert="~{parts/parts :: select(${trainingTargetAreaMap}, trainingAreaId, 'trainingAreaId', true)}">
</div>
原因
HTMLタグと一致する名前を指定すると、HTMLタグで一致する fragmentを表示してしまうようでした。
select と書いたので、select と selectTrainingMenu の両方が一致してしまったと。
検証
SELECTタグとDIVタグで、前方一致、後方一致、部分一致、一致しない、HTMLタグと同じ。パターンを用意して試してみました。
GitHub
ソース
templates/parts.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"></meta>
<title>Fragment Sample</title>
</head>
<body>
<h1>Fragment Sample</h1>
<h2>SELECTタグ</h2>
<div>前方一致:th:insert="~{parts :: select1}"
<div th:insert="~{parts :: select1}"></div>
</div>
<div>後方一致:th:insert="~{parts :: 2select}"
<div th:insert="~{parts :: 2select}"></div>
</div>
<div>部分一致:th:insert="~{parts :: 3select3}"
<div th:insert="~{parts :: 3select3}"></div>
</div>
<div>一致しない:th:insert="~{parts :: 4sel4}"
<div th:insert="~{parts :: 4sel4}"></div>
</div>
<div>th:insert="~{parts :: select}"
<div th:insert="~{parts :: select}"></div>
</div>
<h2>DIVタグ</h2>
<div>前方一致:th:insert="~{parts :: div1}"
<div th:insert="~{parts :: div1}"></div>
</div>
<div>後方一致:th:insert="~{parts :: 2div}"
<div th:insert="~{parts :: 2div}"></div>
</div>
<div>部分一致:th:insert="~{parts :: 3div3}"
<div th:insert="~{parts :: 3div3}"></div>
</div>
<div>一致しない:th:insert="~{parts :: 4div4}"
<div th:insert="~{parts :: 4div4}"></div>
</div>
<div>th:insert="~{parts :: div}"
<div th:insert="~{parts :: div}"></div>
</div>
<!--
<h2>SPANタグ</h2>
<div>th:insert="~{parts :: span}"
<div th:insert="~{parts :: span}"></div>
</div>
-->
</body>
</html>
templates/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Parts</title>
</head>
<body>
<select
th:fragment="select1">
<option value="1">select11</option>
<option value="2">select12</option>
<option value="3">select13</option>
</select>
<select
th:fragment="2select">
<option value="1">2select1</option>
<option value="2">2select2</option>
<option value="3">2select3</option>
</select>
<select
th:fragment="3select3">
<option value="1">3select31</option>
<option value="2">3select32</option>
<option value="3">3select33</option>
</select>
<select
th:fragment="4sel4">
<option value="1">4sel41</option>
<option value="2">4sel42</option>
<option value="3">4sel43</option>
</select>
<select
th:fragment="select">
<option value="1">select1</option>
<option value="2">select2</option>
<option value="3">select3</option>
</select>
<div th:fragment="div1">div1</div>
<div th:fragment="2div">2div</div>
<div th:fragment="3div3">3div3</div>
<div th:fragment="4div4">4div4</div>
<div th:fragment="div">div</div>
</body>
</html>
結果
余談
templates/parts.html に存在しないHTMLタグ=SPANタグを書いてみたら解析エラーでした。
Caused by: org.attoparser.ParseException: Error resolving fragment: "~{parts :: span}": template or fragment could not be resolved (template: "index" - line 43, col 11)