【セレクトボックス連動】WebAPI を叩く際に Enum を JSON で渡す

# Kotlin API を叩く際に Enum を JSON から変換する
- SpringBoot2 
- Thymeleaf
- Jackson （Jsonのシリアライズ・デシリアライズで利用）
- パラメータで渡すのが一番簡単ですが、折角（？）なので JSON で渡してみる
- Jsonで渡すと書いてありますが実際は、Json -> Enum 変換
- Kotlin

# 実施環境
開発PC: Windows 10
Java: 8
Kotlin: 0.8.19
Eclipse: 2019-06 (4.12.0)

# 想定される場面
- 連動するセレクトボックスなど
    - １つ目のセレクトボックスの選択によって２つ目のセレクトボックスの内容が変わる場合
    - Enum の入れ子でやってみる
    - Enum は継承出来ないのが今ひとつ

## １つ目のセレクトボックス用 Enum
- JsonSerialize, JsonDeserialize アノテーションでシリアライズ・デシリアライズのクラスを指定

```kotlin
@JsonSerialize(using = EnumSerializer::class)
@JsonDeserialize(using = EnumDeserializer::class)
enum class Select1Enum(val text:String, val arr:Array<*>) {

    SELECT1("セレクト２－１", Select21Enum.values()),
    SELECT2("セレクト２－２", Select22Enum.values()),
    SELECT3("セレクト２－３", Select23Enum.values()),
    ;
        // 名称により Enum 取得
        fun getByText(text: String):Select1Enum? {

            var result: Select1Enum? = null
            enumValues<Select1Enum>().forEach {
                if (it.text == text) {
                    result = it
                }
            }
            return result
        }

    }

    override fun toString():String {
        return ReflectionToStringBuilder.toString(this)
   }
}
```

## 2つめのセレクトボックス用 Enum
- 同様に複数用意

```kotlin
enum class Select21Enum(val text:String) {

    SELECT1("セレクト２－１ー１"),
    SELECT2("セレクト２－１－２"),
    SELECT3("セレクト２－１－３"),
    ;
        // 名称により Enum 取得
        fun getByText(text: String):Select21Enum? {

            var result: Select21Enum? = null
            enumValues<Select21Enum>().forEach {
                if (it.text == text) {
                    result = it
                }
            }
            return result
        }

    }
}
```

## シリアライズ・デシリアライズ用のクラス
- 簡単に Enum の text で enum の項目が指定できるように

```kotlin
class EnumDeserializer:JsonDeserializer<Any>() {

	@Throws(IOException::class)
	override fun deserialize(jp:JsonParser, ctxt:DeserializationContext):Select1Enum? {

        val jsonNode:JsonNode = jp.getCodec().readTree(jp)

	    val jsonValue = jsonNode.get("text").asText()
	    for (enumValue in Select1Enum.values()) {
	    	if (enumValue.text.equals(jsonValue)) {
	    		return enumValue
	    	}
	    }
	    return null
  	}
}

class EnumSerializer:JsonSerializer<Select1Enum>() {

    @Throws(IOException::class)
    override fun serialize(value:Select1Enum, jgen:JsonGenerator, provider:SerializerProvider) {
        jgen.writeStartObject()
        jgen.writeFieldName("text")
        jgen.writeString(value.text)
        jgen.writeEndObject()
    }
}
```

## APIで出力するテンプレートを用意
- fragment_select2.html（thymeleaf）

```html
<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>Insert title here</title>
</head>
<body id="fragmentBody" th:fragment="fragmentBody()">
            <select name="select2" id="select2" th:fragment="fragmentSelect()">
              <option value="">選択してください。</option>
              <option th:if="${select2List != null}" th:each="i : ${select2List}" th:value="${i.text}" th:text="${i.text}" th:selected="${i.text} == *{select2}"> --- </option>
            </select>
            <span th:if="${#fields.hasErrors('select2')}" th:errors="*{select2}" id="select2.errors" class="help-block error" th:style="|display: block;|">正しく入力してください。</span>
</body>
</html>
```

## API出力用コントローラ
- /api/select/getSelect2
- @RequestBody アノテーションをつける
- 後述 javascript で呼び出される
- Json 形式で受け取ったパラメータを Enum へデシリアライズ

```kotlin
@Controller
@RequestMapping("api/select")
open class SelectApiController {

    @PostMapping(path=["/getSelect2"],
        consumes=[MediaType.APPLICATION_JSON_VALUE])
    open fun getSelect2(@RequestBody(required=false) select1:Select1Enum?, model:Model): String {

    	if (select1 != null) {
            model.addAttribute("select2List", select1.arr)
        }

        return "fragment_select2::fragmentSelect()"
    }
}
```
## 入力用フォームのHTML（抜き出し）
```html
            <div>
              <select name="select1" id="select1" th:field="*{select1}">
                <option value="">選択してください。</option>
                <option th:each="i : ${selectList}" th:value="${i.text}" th:text="${i.text}">選択１</option>
              </select>
              <span th:if="${#fields.hasErrors('select1')}" th:errors="*{select1}" id="select1.errors" class="help-block error" th:style="|display: block;|">セレクト１を正しく入力してください。</span>
            </div>
            <div id="fragment_select2" th:include="select/fragment_select2::fragmentBody()">
```

## セレクトボックス連動用 javascript
- jquery 利用
- Json 形式で渡す
- 結果は #fragment_select2 に注入

```javascript
// ページロード後に呼び出す
function initializeSelect1Events() {

	// 業種セレクト連動
	$("#select1").on('change', function(e) {
		select1OnChange();

	});
}

function select1OnChange() {
	var url = contextPath + '/api/select/getSelect2';
	var value = $("#select option:selected").val();
	var data = JSON.stringify({'text': value});

	$.ajax({
		headers: {
			'Content-Type': 'application/json'
		},
		url: url,
		type: 'POST',
		cache: false,
		dataType: 'html',
		data: data,
		traditional: true,
		beforeSend: function(xhr, settings) {}
	})
	.done(function(data, textStatus, xhr) {
		if (data.error !== undefined) {
			alert(data.error);
		}
		else if (data.length == 0) {
			alert("セレクト２が取得できませんでした");
		}
		else {
			$("#fragment_select2").html(data);
		}
	})
	.fail(function(xhr, textStatus, errorThrown){
		alert("セレクト２が取得できませんでした");
	})
	.always(function(xhr, msg){
	});
}
```

以上、お疲れさまでした！