JavaScriptで非同期処理作ってみた

# 概要
セレクトボックスの値が非同期で変更するものを作ってみました。

例
1. セレクトボックスで群馬県を選択
2. 次のセレクトボックスには群馬県内の市だけが選択肢に入る

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/566570/55fd3c0f-fcd0-9cc2-1408-7d2cc4186dbd.png)

## 環境
Java13/JPA/JavaScript

# 参考
[Ajaxを使った非同期通信](https://www.javadrive.jp/javascript/ajax/)
[@ResponseBodyについて](https://qiita.com/YAKINIKU/items/abaa54ba531a30850d10)
[ResponseEntityクラスについて](https://spring.pleiades.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html)

# ソースコード

```html:hello.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Ajax!</title>
</head>
<body>
	<form action="">
		<div class="form-group">
			<label th:for="ken">県</label>
			<select id="ken" class="form-control">
			<option>選択してください</option>
			<option id="kenOption" th:each="ken : ${kenList}"  th:text="${ken.kenName}" th:value="${ken.kenCode}"></option>
			</select>
		</div>
		<div class="form-group">
			<label th:for="city">市</label>
			<select id="city" class="form-control">
			<option>選択してください</option>
			</select>
		</div>
	</form>
	<script src="ajax.js"></script>
</body>
</html>
```

```javascript:ajax.js
'use strict';

	//要素の取得
	const ken = document.getElementById('ken');
	const city = document.getElementById('city');

	// slectboxのchangeイベントで発火する関数
	function ajax() {

		//選択された県コードの取得
		let kenCode = ken.value;

		let request = new XMLHttpRequest();

		// onreadystatechangeは非同期。状態が変化するとコールバック関数が呼び出される。
		request.onreadystatechange = function() {

			//readyState4はレスポンスの受信完了
			if(request.readyState === 4) {

				// status200はサーバーとの通信OK
				if(request.status === 200) {

					// レスポンスの取得
					let resultJson = request.response;
					console.log(resultJson);

					let cityData;

					// optionタグをresultJson配列の数だけ作る
					for(let i = 0; i < resultJson.length; i++) {
						cityData += `<option th:value="${resultJson[i].cityCode}">${resultJson[i].cityName}</option>`
					}
					city.innerHTML = cityData;
				}else {
					city.innerHTML = '<option>通信失敗(´;ω;｀)</option>';
				}
			}
		}
		request.open('GET', '/hello/ajax?kenCode=' + kenCode, true);
		request.responseType = 'json';
		request.send(null);
	}
	ken.addEventListener('change', ajax);
```

`request.send(null)`でリクエスト送信前に、`let resultJson = request.response`でレスポンス受け取ってるのは変な気がするけどこれでOK。
基本は同期処理完了→非同期処理完了の順番なので、リクエスト送信後にレスポンスを受け取る流れになる。

```java:AjaxController.java
@Controller
public class AjaxController {
	@Autowired
	AjaxService ajaxService;

	// 画面表示
	@GetMapping("/hello")
	public String getHello(Model model) {
		List<AjaxEntity> kenList = ajaxService.getKenNameList();
		model.addAttribute("kenList", kenList);
	    return "hello";
	}

	// 非同期通信。県コードを受け取ってcityリストを返す。
	@GetMapping("/hello/ajax")
	@ResponseBody
	public ResponseEntity<List<AjaxEntity>> postAjax(@RequestParam("kenCode") int kenCode) {

		List<AjaxEntity> list = ajaxService.getCityNameList(kenCode);
		try {
			//通信成功
			return new ResponseEntity<List<AjaxEntity>>(list, HttpStatus.OK);
		} catch(Exception e) {
			//通信失敗
			return new ResponseEntity<List<AjaxEntity>>(HttpStatus.BAD_REQUEST);
		}
	}
}
```

@ResponseBodyをつけると@Controllerの戻り値にString以外も指定できる。
今回はEntityクラスのList（配列）をResponseEntity入れて戻り値にしている。
@RestControllerでも似たようなことができるっぽい。

```java:AjaxService.java
@Service
public class AjaxService {
	@Autowired
	AjaxRepository ajaxRepository;

	/**
	 * 全県名と全県コードのリストを取得する
	 */
	public List<AjaxEntity> getKenNameList(){

		List<String[]> list = ajaxRepository.getKenNameList();
		List<AjaxEntity> aeList = new ArrayList<>();

		for(String[] array : list) {
			int kenCode = Integer.parseInt(array[0]);
			String kenName = String.valueOf(array[1]);

			AjaxEntity ae = new AjaxEntity();
			ae.setKenCode(kenCode);
			ae.setKenName(kenName);
			aeList.add(ae);
		}
		return aeList;
	}


	/**
	 * 同一県内の市をリストで返す
	 *
	 * 例：群馬県を選択すると、群馬県内の市リストが返る
	 */
	public List<AjaxEntity> getCityNameList(int kenCode){

		List<String[]> list = ajaxRepository.getCityNameList(kenCode);
		List<AjaxEntity> aeList = new ArrayList<>();

		for(String[] array : list) {
			int cityCode = Integer.parseInt(array[0]);
			String cityName = String.valueOf(array[1]);

			AjaxEntity ae = new AjaxEntity();
			ae.setCityCode(cityCode);
			ae.setCityName(cityName);
			aeList.add(ae);
		}
		return aeList;
	}
}
```

```java:AjaxRepository.java
@Repository
public interface AjaxRepository extends JpaRepository<AjaxEntity, Integer>{

	@Query(value = "select distinct kenCode, kenName from AjaxEntity a")
	List<String[]> getKenNameList();

	@Query(value = "select distinct cityCode, cityName from AjaxEntity a where kenCode = :kenCode")
	List<String[]> getCityNameList(@Param("kenCode") int kenCode);
}
```

```java:AjaxEntity.java
@Data
@Entity
@Table(name="address")
public class AjaxEntity {

	@Id
	@Column(name="kencode")
	private Integer kenCode;

	@Column(name="citycode")
	private Integer cityCode;

	@Column(name="kenname")
	private String kenName;

	@Column(name="cityname")
	private String cityName;
}
```