【Themeleaf初心者向け】formにおけるth:fieldの挙動まとめ

##はじめに

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は別途指定する必要があります。

##前提   
・コントローラークラス
コード説明は後述します。

```ThFieldTestController.java
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を渡しています。

・フォームクラス

```ThFieldTestForm.java
public class ThFieldTestForm {
	private String gender;

	public void setGender(String gender) {
		this.gender = gender;
	}

	public String getGender() {
		return gender;
	}
}
```

##詳細

####textの場合
```th-field-test.html    
<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>
```
ブラウザだと・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1165334/2e367667-bf46-e5e6-4974-b082710ce2ee.png)
コンソールで確認すると・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1165334/c9f8a476-9c9b-816f-9ce2-9d4c3345b06f.png)
→**th:fieldがあると、id,name,valueが入力されている。**
（th:fieldとth:valueが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先される。）
（numberとrangeも同じ挙動のため割愛します。）

####textareaの場合
```th-field-test.html    
　　<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>
```
ブラウザだと・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1165334/fff150fe-1988-482d-40da-dd8604a0d0a1.png)

コンソールだと・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1165334/2695396d-2077-c194-4056-cf894f4446e1.png)


→**th:fieldがあると、id,nameが入力されており、textを指定したときと同じ挙動をしている。**
（th:fieldとth:textが重複している時、th:objectで指定したformの値がある場合は、th:fieldが優先される。）


####radioの場合
```th-field-test.html    
	<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>
```
ブラウザだと・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1165334/b04c8719-c7d6-5b57-ea74-e07397573de7.png)

コンソールだと・・・
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1165334/19f669ee-44fc-5268-3043-de57bec5ab67.png)

→th:fieldがあると、id,nameが入力されていますが、
別途valueを指定しないとエラーになる。また、textを指定しないとボタンの横に文字が現れません。
**th:valueとth:textは別途指定する必要があります。**
（checkboxとselectも同じ挙動のため割愛します。）

##まとめ
th:fieldは以下図のように挙動していました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1165334/eb8ad2f3-6a7f-e30a-bccd-ac12dab83a9a.png)

・検証に使用したコードは以下に格納しておりますので、よろしければこちらからご確認ください。
https://github.com/MasayukiFurumoto-rks/th-field-test


##参考
以下記事を参考にさせていただきました。
ありがとうございました。

・<a href="https://qiita.com/beeeyan/items/5bd820e55cb53f176b3e">Thymeleafのth:fieldの意味とSpring Frameworkの値の受け渡しの仕組み (Qiita)</a>
・<a href="https://casual-tech-note.hatenablog.com/entry/2018/10/10/224250">th:field と th:object によるフォームバインディング機能（inputタグ・基本入力系編） (Hatena Blog)</a>

##所感
最後までご覧下さりありがとうございます。

今回、初めてQiita記事を投稿させていただきました。
マークダウン記法もままならないところからのスタートでしたが、アウトプットしていく中で理解が深まることもあり、ぜひまた投稿しようと思います。

もし訂正・加筆すべき箇所がございましたら、コメントにてご教示いただけますと幸いです。
何卒よろしくお願いいたします。