springBootのFormのあれこれ

# 概要
業務でspringBootを使ってwebアプリを作っているのですが、そこで学んだForm関連のTipsを紹介します。

# FormのFieldに初期値を設定する方法
viewでFormを表示する際に初期値を設定したい場合もあると思います。
色々やり方があります。

### コンストラクタで設定
formは@ModelAttributeを設定することでControllerが呼ばれる際に生成され、自動的にModelに詰められます。
なので、生成時に値を設定するようなコンストラクタを設定すれば初期化時に値がセットされます

```java:Form.java
@Getter
@Setter
public class Form {
  private String name;
  private int age;

  Form() {
    this.name = "hoge";
    this.age = 18;
  }
}
```

ただし以下のように引数ありのコンストラクタの指定の場合は注意が必要です。

```java:Form.java
@Getter
@Setter
public class Form {
  private String name;
  private int age;

  Form(String name, int age) {
    this.name = name;
    this.age = age;
  }
}
```

これだけだとフォームの値をPOSTした際に

```
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: No primary or default constructor found for class com.example.demo.ParsonForm] with root cause
```

のようなerrorが発生します。これは引数なしのコンストラクタがないため発生します。
なので、引数ありのコンストラクタを使う場合は、以下のように引数なしのコンストラクタも合わせて記述しましょう。

```java:Form.java
@Getter
@Setter
public class Form {
  private String name;
  private int age;

  Form(String name, int age) {
    this.name = name;
    this.age = age;
  }

  Form() {
  }
}
```


### Controllerにてセッターで設定
Controllerにてセッターで指定することもできます。
ただし、その際は値を追加したFormをmodelに忘れず渡しましょう。

```java:indexController.java
@RequestMapping("/new")
public String index(Form form) {
  form.setName("hoge");
  form.setAge(18);
  model.addAttribute("form",form);
  return "index";
}
```

### thymeleafにて設定
viewにてth:valueで値を追加することもできます。
ただし、その場合は、以下注意が必要です。

- th:fieldと共用できないのでformのプロパティと同名のnameを設定すること
- **th:valueにて三項演算子を使い**、validateでリダイレクトされた際にプロパティの値が保持される用にすること

```html:index.html
<form method="post" action="/" th:object="${form}">
  <input type="text" name="name" th:value="*{name == null ? 'hoge' : name">
  <input type="text" name="age" th:value="*{age == null ? 18 : age}">
</form>
```


# Formでネストしたオブジェクトを扱う
springのFormはアノテーションでvalidationが手軽に設定できるので便利ですよね。
ただ更に複雑なデータ構造を扱いたい場合、ネストしたオブジェクトを使うことが可能です。

例えば、以下のようなParsonFormは複数のitemNameとitemSizeを保つ場合。。

```java:ParsonForm
@Getter
@Setter
public class ParsonForm {

  private String name;

  private itemName1;
  
  private itemName2;

  private itemSize1;

  private itemSize2;
}

```

と書くことも出来ますが、以下の用にitemモデルを作り、、

```java:Item.java
@Getter
@Setter
public class Item {

  private String name;

  private int size;

}
```

Collection<item>として書いた方がスッキリするでしょう。

```java:ParsonForm
@Getter
@Setter
public class ParsonForm {

  private String name;

  private Collection<Item> items;

}
```

### ネストしたオブジェクトのvalidation
さらにネストしたオブジェクト内の要素でvalidationを行いたい場合は、
ParsonFormのitemsで@Validアノテーションを付与するだけで、
Item.java内のvalidationが機能するようになります。


```java:Parson
@Getter
@Setter
public class ParsonForm {

  private String name;

  @Valid
  private Collection<Item> items;

}
```

```java:Item.java
@Getter
@Setter
public class Item {
  
  @NotBlank
  private String name;

  @Max(100)
  @Min(1)
  private int size;

}
```

### ネストしたオブジェクトの展開での注意
th:fieldで指定するためには必ず配列の[]を追加しましょう。

```html:form.html
  <form class="parsonForm" th:action="@{/}" th:method="post" th:object="${parsonForm}">
    <input type="text" th:field="*{name}">
    <input type="text" th:field="*{items[0].name}">
    <input type="text" th:field="*{items[0].size}">
    <input type="text" th:field="*{items[1].name}">
    <input type="text" th:field="*{items[1].size}">
    <input type="submit">
  </form>
```



## 参考
https://qiita.com/yo1000/items/b58a1ae8b3e51fdda479
https://area-b.com/blog/2015/02/04/2009/