SpringBoot + Thymeleaf + Ajaxで 2つのドロップダウンリストを連動させる方法


## 概要
* SpringBoot + thymeleaf + mysqlでWebアプリケーション開発時、二つの 2つのselectBoxを連動させる方法を紹介します。

## 機能
* ひとつ目のドロップダウンリストの都道府県により、2つのドロップダウンリストの値は該当県の市区町に変わる。

## 開発環境
* Windows 10
* Spring Tool Suite 4 Version: 4.8.1.RELEASE
* JavaSE-11
* MySql 5.8
* Thymeleaf 3.0.12 

## 事前準備
Mysqlに都道府県と市区町のテーブルを作ってデータを登録する

都道府県テーブルを作成する

```sql:create_pref
CREATE TABLE `pref` (
  `prefCode` int(11) NOT NULL,
  `prefName` varchar(50) NOT NULL,
  `disFlag` int(11) DEFAULT '1',
  PRIMARY KEY (`prefCode`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
```
市区町テーブルを作成する

```sql:create_city
CREATE TABLE `city` (
  `prefCode` int(11) NOT NULL,
  `cityCode` int(11) NOT NULL,
  `cityName` varchar(50) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
```
データを登録する

```sql:今回は首都圏付近の都道府県データの使用する
insert into pref(prefCode,prefName,disFlag)values(8,'茨城県',0);
insert into pref(prefCode,prefName,disFlag)values(9,'栃木県',0);
insert into pref(prefCode,prefName,disFlag)values(10,'群馬県',0);
insert into pref(prefCode,prefName,disFlag)values(11,'埼玉県',0);
insert into pref(prefCode,prefName,disFlag)values(12,'千葉県',0);
insert into pref(prefCode,prefName,disFlag)values(13,'東京都',0);
insert into pref(prefCode,prefName,disFlag)values(14,'神奈川県',0);
```

```sql:市区町データ（部分）
insert into city(`prefCode`,`cityCode`,`cityName`) values (8,201,'水戸市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (8,202,'日立市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (8,203,'土浦市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (8,204,'古河市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (8,205,'石岡市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (8,207,'結城市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (8,208,'龍ヶ崎市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (8,210,'下妻市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (8,211,'常総市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (9,201,'宇都宮市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (9,202,'足利市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (9,203,'栃木市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (9,204,'佐野市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (9,205,'鹿沼市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (9,206,'日光市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (9,208,'小山市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (9,209,'真岡市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (9,210,'大田原市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (9,211,'矢板市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (10,201,'前橋市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (10,202,'高崎市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (10,203,'桐生市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (10,201,'前橋市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (10,202,'高崎市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (10,203,'桐生市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (10,201,'前橋市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (10,202,'高崎市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (10,203,'桐生市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (10,204,'伊勢崎市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (10,205,'太田市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (10,206,'沼田市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,100,'さいたま市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,101,'西区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,102,'北区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,103,'大宮区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,104,'見沼区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,105,'中央区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,106,'桜区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,107,'浦和区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,108,'南区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,109,'緑区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,110,'岩槻区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,201,'川越市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,202,'熊谷市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (11,203,'川口市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,100,'千葉市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,101,'中央区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,100,'千葉市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,101,'中央区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,102,'花見川区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,103,'稲毛区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,104,'若葉区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,105,'緑区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,106,'美浜区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,202,'銚子市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,203,'市川市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,204,'船橋市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,205,'館山市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,206,'木更津市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,207,'松戸市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,208,'野田市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,424,'白子町');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,426,'長柄町');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,427,'長南町');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,440,'夷隅郡');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,441,'大多喜町');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,443,'御宿町');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,460,'安房郡');
insert into city(`prefCode`,`cityCode`,`cityName`) values (12,463,'鋸南町');
insert into city(`prefCode`,`cityCode`,`cityName`) values (13,100,'特別区部');
insert into city(`prefCode`,`cityCode`,`cityName`) values (13,101,'千代田区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (13,102,'中央区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (13,103,'港区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (13,104,'新宿区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (13,105,'文京区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (13,106,'台東区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (13,107,'墨田区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (13,108,'江東区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (13,109,'品川区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (13,110,'目黒区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,100,'横浜市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,101,'鶴見区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,102,'神奈川区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,103,'西区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,104,'中区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,105,'南区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,106,'保土ヶ谷');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,107,'磯子区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,108,'金沢区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,109,'港北区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,110,'戸塚区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,111,'港南区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,112,'旭区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,113,'緑区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,114,'瀬谷区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,115,'栄区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,116,'泉区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,117,'青葉区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,118,'都筑区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,130,'川崎市');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,131,'川崎区');
insert into city(`prefCode`,`cityCode`,`cityName`) values (14,132,'幸区');

```
## アプリの作成

Entity作成

```java:Pref.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Pref {
	private int prefCode;
	private String prefName;
}

```

```java:City.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class City {
	private int prefCode;
	private int cityCode;
	private String cityName;
}

```
HTML画面作成

```html:index.html

<!doctype html>
<html xmlns:th="http://www.thymeleaf.org">
<meta charset="utf-8">
<script>
//ここにAjaxコードを追加
</script>
<!-- main start -->
<div class="main">
	<div class="container-fluid">
		<!-- from start-->
		<form id="userform" name="userform" th:fragment="userform" th:method="post" th:action="@{/user}" th:object="${user}" class="needs-validation" novalidate>
			<!-- contents start-->
			<div class="contents" id="contents">
				<div class="contents-header">ユーザ登録</div>
				<!-- contents-body start-->
				<!-- 出身 -->
				<div class="form-group row  col-sm-12">
					<label for="nutag" class="col-sm-2 col-form-label"> 出 身<span class="badge badge-secondary ml-2">任意</span>
					</label>
					<div class="col-sm-3">
						<select class="form-control" th:field="*{nutagPref}">
							<option th:each="pref:${prefData}" th:value="${pref.key}" th:text="${pref.value}" />
						</select>
					</div>
					<div class="col-sm-2">
						<select class="form-control" th:field="*{nutagCity}">
							<option th:each="city:${cityList}" th:value="${city.cityCode}" th:text="${city.cityName}" />
						</select>
					</div>
				</div>
				<!-- contents-body end-->
				<div class="contents-footer border-0" id="contents-footer-insert">
					<button type="submit" id="insertbtn" class="btn btn-primary btn-lg btn-block">上記内容で登録する</button>
				</div>
			</div>
			<!-- contents end-->
		</form>
		<!-- from-end-->
	</div>
	<!-- container-fluid -->
</div>
<!-- main-end-->
<!-- Optional JavaScript -->
<!-- jQuery first, then Popper.js, then Bootstrap JS -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
</body>

</html>

```

* 説明
 * userオブジェクトから値を取得して`th:field="*{nutagPref}"`と`th:field="*{nutagCity}"`として
selectboxに設定している
 *  `th:field="*{nutagPref}"` - 都道府県ドロップダウンリスト
 *  `th:field="*{nutagCity}"`　- 市区町ドロップダウンリスト


```html
<select class="form-control" th:field="*{nutagPref}">
    <option th:each="pref:${prefData}" th:value="${pref.key}" th:text="${pref.value}" />
</select>
<select class="form-control" th:field="*{nutagCity}">
    <option th:each="city:${cityList}" th:value="${city.cityCode}" th:text="${city.cityName}" />
</select>
```
コントロールクラスの作成

```java:userController.java

@Controller
@RequestMapping
public class UserController {
	/**
	 * ユーザー登録画面初期表示
	 * 
	 * @param model Model
	 * @return ユーザー登録画面
	 */
	@GetMapping("/user/new")
	public String newUser(Model model) {
		User user = setUserForm();
        // 初期表示の時selectboxに埋め込む都道府県データ
		model.addAttribute("prefData", divisionManager.getPrefData());
        // 初期表示の時selectboxに埋め込む市区町リスト
		model.addAttribute("cityList", divisionManager.getCity(NUTAGCITY_TOKYO));
		model.addAttribute("user", user);
		return "index";
	}
}
```
ここまで設定した値は画面に表示することを確認できる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148710/cfa63dbc-241b-c267-c7d3-ae2136fa3231.png)

ここからはポイントです
index.htmlの`//ここにAjaxコードを追加`を以下のコードで切り替える。

```javascript
		// 出身情報処理
		$('#nutagPref').change(function() {
					//入力値をセット----①
					var param = {
						prefCode : $("#nutagPref").val(),
						prefName : $("#nutagPref option:selected").text(),
					}
					//県コードを送信URL
					var send_url = "/nutag";　//----②
					$.ajax({
						url : send_url,
						type : "POST",
						contentType : "application/json",
						cache : false,
						data : JSON.stringify(param),
						dataType : "json",
						success : function(res) {   //----③
							var cityList = [];
							for (var i = 0; i < res.length; i++) {
								var city = "<option value="+res[i].cityCode+">"
										+ res[i].cityName + "</option>";
								cityList.push(city);
							}
							$("#nutagCity").html(cityList);
						}
					});
				});

```

* 説明
 *  ①　画面で選択した都道府県データ、`prefCode `により、市区町を検索していますが`prefCode `だけを送信すると
 Pref.javaとマッチングできないため`prefName`も同時にサーバ側へ渡すしている
 *  ②`localhost:8080/nutag`へアクセスする時コントロールのメソッドを呼び出す。
 *  ③サーバ側から値を取得成功なら市区町のドロップダウンリストに設定している。
    
 > ポイント： 市区町のドロップダウンリストを静的に生成しているのでサーバ側とデータ通信しているが画面全体の更新を行わず、selectboxの中身だけを更新する

次にコントロールクラスで`localhost:8080/nutag`にマッチングするメソッドを作成する

```java:userController.java

@Controller
@RequestMapping
public class UserController {
	/**
	 * ユーザー登録画面初期表示
	 * 
	 * @param model Model
	 * @return ユーザー登録画面
	 */
	@GetMapping("/user/new")
	public String newUser(Model model) {
		User user = setUserForm();
        // 初期表示の時selectboxに埋め込む都道府県データ
		model.addAttribute("prefData", divisionManager.getPrefData());
        // 初期表示の時selectboxに埋め込む市区町リスト
		model.addAttribute("cityList", divisionManager.getCity(NUTAGCITY_TOKYO));
		model.addAttribute("user", user);
		return "index";
	}

	/**
	 * 出身連動選択
	 * 
	 * @param pref
	 * @return 出身市区のList
	 */
	@PostMapping("/nutag")
	@ResponseBody　//------②
	public List<City> setCitySelectBox(@RequestBody Pref pref) { //------①
		List<City> cityList = divisionManager.getCity(pref.getPrefCode());　//------③
		return cityList;
	}
}
```

* 説明
 *  ①　@RequestBodyを付けるとAjaxから送信したparamのデータが prefオブジェクトに入る
 *  ②　@ResponseBodyを付けるとメソッドの戻り値は　Ajaxの'success : function(res)`に設定される
 *  ③　市区町データを'PrefCode'を条件に検索する

##でき上げるもの 

![1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/148710/3f1a41d1-ca08-7eb6-a1a7-d798454bd3e9.gif)

## 終了