##はじめに
Spring+Thymeleafで開発を行っていて、
formの作成の際、
<input type="radio" th:value="○" th:text="○" th:field="○">
のように、複数Thymeleafタグを使っています。
これって全部書く意味があるのか?必要ないものはないのか?
と思ったので、自分で検証してみた結果を以下にまとめます。
概要
inputのtypeによって、th:fieldの挙動が違うので、記載すべきものが違います。
-
text,number,range
th:fieldは、「id」「name」「value」の役割を果たします。
th:fieldとth:valueが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先されます。 -
textarea
th:fieldは、「id」「name」「text」の役割を果たします。
th:fieldとth:textが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先されます。 -
radio,checkbox,select
th:fieldは「id」「name」の役割を果たします。
つまり、他2パターンのように「value」や「text」を補うことはできません。
th:valueとth:textは別途指定する必要があります。
##前提
・コントローラークラス
コード説明は後述します。
public class ThFieldTestController {
// import文省略
@ModelAttribute
private ThFieldTestForm setUpForm() {
ThFieldTestForm thFieldTestForm = new ThFieldTestForm();
thFieldTestForm.setGender("2");
return thFieldTestForm;
}
@RequestMapping("")
public String index(Model model) {
Map<Integer, String> genderMap = new LinkedHashMap<>();
genderMap.put(1, "woman");
genderMap.put(2, "man");
model.addAttribute("genderMap", genderMap);
model.addAttribute("gender", "1"); // ${"gender"}で1を呼べるように
return "th-field-test";
}
setUpForm()メソッドでは、
thFieldTestForm.setGender("2");
でフォームのgenderの初期値に2を渡しています。
index()メソッドでは、リクエストスコープにデータを格納し、
<○ th:each="gender:${genderMap}" th:○="${gender.key}">
<th:○="${gender}">
のどちらでも値を呼べるようにしています。
この時、マップでないgenderの値は1を渡しています。
・フォームクラス
public class ThFieldTestForm {
private String gender;
public void setGender(String gender) {
this.gender = gender;
}
public String getGender() {
return gender;
}
}
##詳細
####textの場合
<div class="text">
<h4>text</h4>
リクエストスコープのgender:1<br>
formの初期値:2<br>
【text】th:field="*{gender}"<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<input type="text" th:field="*{gender}"><br>
<button>送信</button>
</form>
【text】th:field="*{gender}" th:value="${gender}"<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<input type="text" th:field="*{gender}" th:value="${gender}"><br>
<button>送信</button>
</form>
th:value="${gender}"が優先されるなら1<br>
th:field="*{gender}"が優先されるなら2<br>
→th:fieldの入力値保存のほうが強く働いている<br>
【text】th:value="${gender}"
<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<input type="text" th:value="${gender}"><br>
<button>送信</button>
</form>
【text】th:text="${gender}"
<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<input type="text" th:text="${gender}"><br>
<button>送信</button>
</form><br>
</div>
ブラウザだと・・・
コンソールで確認すると・・・
→th:fieldがあると、id,name,valueが入力されている。
(th:fieldとth:valueが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先される。)
(numberとrangeも同じ挙動のため割愛します。)
####textareaの場合
<div class="textarea">
<h4>textarea</h4>
リクエストスコープのgender:1<br>
formの初期値:2<br>
【textarea】th:field="*{gender}"<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<textarea th:field="*{gender}"></textarea><br>
<button>送信</button>
</form>
【textarea】th:id="gender" th:name="gender" th:text="${gender}"<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<textarea th:id="gender" th:name="gender" th:text="${gender}"></textarea><br>
<button>送信</button>
</form>
【textarea】th:field="*{gender}" th:value="${gender}"
<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<textarea th:field="*{gender}" th:value="${gender}"></textarea>
<button>送信</button>
</form>
【textarea】th:value="${gender}"
<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<textarea th:value="${gender}"></textarea>
<button>送信</button>
</form>
【textarea】th:text="${gender}"
<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<textarea th:text="${gender}"></textarea>
<button>送信</button>
</form><br>
【textarea】th:field="*{gender}" th:text="${gender}"
<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<textarea th:field="*{gender}" th:text="${gender}"></textarea>
<button>送信</button>
</form>
th:text="${gender}"が優先されるなら1<br>
th:field="*{gender}"が優先されるなら2<br>
</div>
→th:fieldがあると、id,nameが入力されており、textを指定したときと同じ挙動をしている。
(th:fieldとth:textが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先される。)
####radioの場合
<div class="radio">
<h4>radio</h4>
リクエストスコープのgender:1<br>
formの初期値:2<br>
【radio】th:field="*{gender}" <br>
→ エラー(org.attoparser.ParseException: Attribute "value" is required in "input(radio)" tags)<br><br>
<!-- <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> -->
<!-- <span th:each="gender:${genderMap}"> -->
<!-- <input type="radio" th:field="*{gender}"><br> -->
<!-- </span> -->
<!-- <button>送信</button> -->
<!-- </form><br> -->
【radio】th:field="*{gender}" th:value="${gender.key}<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<span th:each="gender:${genderMap}">
<input type="radio" th:field="*{gender}" th:value="${gender.key}"><br>
</span>
<button>送信</button>
</form><br>
【radio】th:value="${gender.key}<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<span th:each="gender:${genderMap}">
<input type="radio" th:value="${gender.key}"><br>
</span>
<button>送信</button>
</form><br>
【radio】th:value="${gender.key} th:text="${gender.value}"<br>
→ 送れるけどデータとしてgenderのデータとして受け取られない<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<span th:each="gender:${genderMap}">
<input type="radio" th:value="${gender.key}" th:text="${gender.value}"><br>
</span>
<button>送信</button>
</form><br>
【radio】th:field="*{gender}" th:text="${gender.value}"<br>
→ エラー(org.attoparser.ParseException: Attribute "value" is required in "input(radio)" tags)<br><br>
<!-- <form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}"> -->
<!-- <span th:each="gender:${genderMap}"> -->
<!-- <input type="radio" th:field="*{gender}" th:text="${gender.value}"><br> -->
<!-- </span> -->
<!-- <button>送信</button> -->
<!-- </form><br> -->
【radio】th:field="*{gender}" th:value="${gender.key} th:text="${gender.value}"<br>
<form th:action="@{/th-field-test/form-sent}" method="post" th:object="${thFieldTestForm}">
<span th:each="gender:${genderMap}">
<input type="radio" th:field="*{gender}" th:value="${gender.key}" th:text="${gender.value}"><br>
</span>
<button>送信</button>
</form><br>
</div>
→th:fieldがあると、id,nameが入力されていますが、
別途valueを指定しないとエラーになる。また、textを指定しないとボタンの横に文字が現れません。
th:valueとth:textは別途指定する必要があります。
(checkboxとselectも同じ挙動のため割愛します。)
##まとめ
th:fieldは以下図のように挙動していました。
・検証に使用したコードは以下に格納しておりますので、よろしければこちらからご確認ください。
https://github.com/MasayukiFurumoto-rks/th-field-test
##参考
以下記事を参考にさせていただきました。
ありがとうございました。
・Thymeleafのth:fieldの意味とSpring Frameworkの値の受け渡しの仕組み (Qiita)
・th:field と th:object によるフォームバインディング機能(inputタグ・基本入力系編) (Hatena Blog)
##所感
最後までご覧下さりありがとうございます。
今回、初めてQiita記事を投稿させていただきました。
マークダウン記法もままならないところからのスタートでしたが、アウトプットしていく中で理解が深まることもあり、ぜひまた投稿しようと思います。
もし訂正・加筆すべき箇所がございましたら、コメントにてご教示いただけますと幸いです。
何卒よろしくお願いいたします。