Thymeleaf#2 Spring๊ณผ HTML Form ์ฒ๋ฆฌ
by JiwonDevํ์๋ฆฌํ๋ ์คํ๋ง์ด ์์ด๋ ๋์ํ๋ค. ๋ค๋ง ์คํ๋ง๊ณผ ํจ๊ป ์ฌ์ฉํ๊ธฐ ์ ๋ง ํธํ๊ฒ ๋ง๋ค์ด์ ธ์๋ค.
- ๊ธฐ๋ณธ ๋ฉ๋ด์ผ: https://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html
- ์คํ๋ง ํตํฉ ๋ฉ๋ด์ผ: https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html
# ์คํ๋ง์์ ํ์๋ฆฌํ ์ฌ์ฉํ๊ธฐ
์๋๋ @Bean์ผ๋ก ํ์๋ฆฌํ ์์ง๊ณผ ํ์๋ฆฌํ ๋ทฐ๋ฆฌ์กธ๋ฒ๋ฅผ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋กํด์ผ ํ์ผ๋, ์คํ๋ง ๋ถํธ์์๋ ํ์ค๋ง ์ถ๊ฐํ๋ฉด ๋๋ค.
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
# HTML Form ๋ง๋ค๊ธฐ
- ํผ ํ๊ทธ์ th:object="${item}" ์ ์ด์ฉํด ์ ํ๋ณ์ *{name} ํด๋น ๊ฐ์ฒด์ ํ๋๋ฅผ ํธํ๊ฒ ์ฌ์ฉํ ์ ์๋ค.
- ์ธํ ํ๊ทธ์ th:filed="..." ๋ฅผ ์ง์ ํด์ฃผ๋ฉด ํ๊ทธ ์์ฑ id, name, value๊ฐ ์๋ ์์ฑ๋๋ค.
- th:action, th:value๋ ๊ธฐ์กด ํ๊ทธ์ ๊ฐ์ผ๋ ํ์๋ฆฌํ ๋ฌธ๋ฒ์ ์ ์ฉํ๊ธฐ ํธํ๊ฒ ๋ ๋๋ง ํด์ค๋ค.
+ ๋ฐํ์์ ๋ฐ์ํ HTML ์ค๋ฅ๋ฅผ IDE ๋๋ ๋ ๋๋ง์์ ์ฐพ์ ์ ์๊ฒ ๋ง๋ค์ด์ฃผ๋ ํจ๊ณผ๋ ์๋ค.
@GetMapping("/add")
public String addForm(Model model) {
model.addAttribute("item", new Item());
return "form/addForm";
}
<form action="item.html" method="post" th:action th:object="${item}">
<div>
<label for="itemName">์ํ๋ช
</label>
<input class="form-control" placeholder="์ด๋ฆ์ ์
๋ ฅํ์ธ์" th:field="*{itemName}"
type="text">
</div>
<div>
<label for="price">๊ฐ๊ฒฉ</label>
<input class="form-control" placeholder="๊ฐ๊ฒฉ์ ์
๋ ฅํ์ธ์" th:field="*{price}"
type="text">
</div>
<div>
<label for="quantity">์๋</label>
<input class="form-control" placeholder="์๋์ ์
๋ ฅํ์ธ์" th:field="*{quantity}"
type="text">
</div>
# ๋ณต์กํ Form ๋ง๋ค๊ธฐ
๋ณต์กํ Form์ ๋ง๋ค์ด๋ณด๋ฉฐ ํ์๋ฆฌํ๋ฅผ ์ฌ์ฉํ๋ฉด ์ด๋ป๊ฒ HTML์ ์ฒ๋ฆฌํ๋์ง ์์๋ณด์.
์์ ์์ ์ฌ์ฉ๋๋ ๋๋ฉ์ธ ๊ฐ์ฒด์ Model ์ฝ๋ ๋ฏธ๋ฆฌ๋ณด๊ธฐ
๋๋ฉ์ธ ๊ฐ์ฒด
@Data
public class Item {
private Long id;
private String itemName;
private Integer price;
private Integer quantity;
private Boolean open; //ํ๋งค ์ฌ๋ถ (true,false)
private List<String> regions; //๋ฑ๋ก ์ง์ญ (List)
private ItemType itemType; //์ํ ์ข
๋ฅ (Enum)
private String deliveryCode; //๋ฐฐ์ก ๋ฐฉ์ (String)
public Item() { }
public Item(String itemName, Integer price, Integer quantity) {
this.itemName = itemName;
this.price = price;
this.quantity = quantity;
}
}
@Data
@AllArgsConstructor
public class DeliveryCode {
/* FAST: "๋น ๋ฅธ ๋ฐฐ์ก"
NORMAL: "์ผ๋ฐ ๋ฐฐ์ก"
SLOW: "๋๋ฆฐ ๋ฐฐ์ก" */
private String code;
private String displayName;
}
public enum ItemType {
BOOK("๋์"), FOOD("์์"), ETC("๊ธฐํ");
private final String description;
ItemType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
Model
@ModelAttribute("regions")
public Map<String, String> regions() {
Map<String, String> regions = new LinkedHashMap<>();
regions.put("SEOUL", "์์ธ");
regions.put("BUSAN", "๋ถ์ฐ");
regions.put("JEJU", "์ ์ฃผ");
return regions;
}
@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
return ItemType.values();
}
@ModelAttribute("deliveryCodes")
public List<DeliveryCode> deliveryCodes() {
List<DeliveryCode> deliveryCodes = new ArrayList<>();
deliveryCodes.add(new DeliveryCode("FAST", "๋น ๋ฅธ ๋ฐฐ์ก"));
deliveryCodes.add(new DeliveryCode("NORMAL", "์ผ๋ฐ ๋ฐฐ์ก"));
deliveryCodes.add(new DeliveryCode("SLOW", "๋๋ฆฐ ๋ฐฐ์ก"));
return deliveryCodes;
}
@ ๋จ์ผ ์ฒดํฌ ๋ฐ์ค - ๊ธฐ์กด HTML
์๋์ ๊ฐ์ด ๋ง๋ค์์ ๋, open = on ์ ์ก๋๋ฉฐ ์ด๋ ์คํ๋ง ํ์ ์ปจ๋ฒํฐ์์ true, false๋ก ๋ฐ๊ฟ์ค๋ค.
- ํ์ง๋ง ์ค์ ๋์ํด๋ณด๋ฉด true๋ ์ ๋์๋ false๋ ๋์ค์ง ์๋๋ค. null์ด ๋ฐํ๋๋ค.
<!-- single checkbox -->
<div>ํ๋งค ์ฌ๋ถ</div>
<div>
<div class="form-check">
<input type="checkbox" id="open" name="open" class="form-check-input">
<label for="open" class="form-check-label">ํ๋งค ์คํ</label>
</div>
</div>
- ๊ทธ๋์ ๊ทธ๋ฅ HTML์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ์๋์ ๊ฐ์ด ํ๋ ํ๋๋ฅผ ์ถ๊ฐํ๋ ๊ผผ์๋ฅผ ์ด์ฉํ๋ค. ๊ทธ๋ฌ๋ฉด open ๊ณผ _open์ ํจ๊ป ์ ์กํ๊ณ , ์๋ฒ์์ ์ฒดํฌํ์ฌ null์ด ๋ฐํ๋์ด ์ฒดํฌ๋ฐ์ค ๊ฐ์ด ์์ ๋์ง์๋ ์ค๋ฅ๋ฅผ ๋ง์ ์ ์๋ค.
<input type="checkbox" id="open" name="open" class="form-check-input">
<input type="hidden" name="_open" value="on"/> <!-- ํ๋ ํ๋ ์ถ๊ฐ -->
<label for="open" class="form-check-label">ํ๋งค ์คํ</label>
@ ๋จ์ผ ์ฒดํฌ ๋ฐ์ค - ํ์๋ฆฌํ th:filed
๊ทธ๋ฅ th:filed ๋ฅผ ์ฌ์ฉํ๋ฉด id, name, value์ ํจ๊ป ์์์ ํ๋ ํ๋๊น์ง ๋ง๋ค์ด์ค๋ค. ๋๋ฌด๋ ํธํ๋ค.
- ๋ํ th:filed๋ open ๊ฐ์ true, false์ ๋ฐ๋ผ checked="checked" ์์ฑ์ ์์์ ์ถ๊ฐ/์ญ์ ํด์ค๋ค.
<!-- single checkbox -->
<div>ํ๋งค ์ฌ๋ถ</div>
<div>
<div class="form-check">
<input class="form-check-input" id="open" th:field="*{open}" type="checkbox">
<label class="form-check-label" for="open">ํ๋งค ์คํ</label>
</div>
</div>
@ ๋ฉํฐ ์ฒดํฌ ๋ฐ์ค - ํ์๋ฆฌํ th:each
@ModelAttribute("myregions") // ํด๋น ํด๋์ค์ ๋ชจ๋ Controller์ myregions ๋ชจ๋ธ ์ถ๊ฐ
public Map<String, String> regions() {
Map<String, String> regions = new LinkedHashMap<>();
regions.put("SEOUL", "์์ธ");
regions.put("BUSAN", "๋ถ์ฐ");
regions.put("JEJU", "์ ์ฃผ");
return regions;
}
- th:each๋ฅผ ์ด์ฉํ์ฌ ๊ฐ๊ฐ์ <input>๊ณผ <label> ์ ๋ฐ๋ณตํด์ ๋ง๋ ๋ค.
- ๋ฐ๋ณต๋ฌธ์์ ์ฌ์ฉ๋๋ id๋ th:filed์์ [ region1, region2, region3... ]์ผ๋ก ์๋์ผ๋ก ์์ฑํ๋ค.
๋ค๋ง ์ด ๊ฒฝ์ฐ <label>์์ <input>๊ณผ ๋์ผํ id๋ฅผ ์ ์ด์ฃผ๊ธฐ๊ฐ ์ด๋ ต๋ค. - ๊ทธ๋์ ํ์๋ฆฌํ ์ ํธ์์ ${#ids} ๋ผ๋ ๊ฒ์ ์ ๊ณตํ๋ค.
ids.prev{'regions'}๋ ์ด์ ์ ์๋ ์์ฑ๋ *{regions} id ๊ฐ์ ์ฌ์ฉํ๋ผ๋ ์๋ฏธ์ด๋ค.
<!-- multi checkbox -->
<form action="item.html" method="post" th:action th:object="${item}">
<div>๋ฑ๋ก ์ง์ญ</div>
<div class="form-check form-check-inline" th:each="rg : ${myregions}">
<input class="form-check-input" th:field="*{regions}" th:value="${rg.key}"
type="checkbox"> <!-- th:filed="${item.regions}" -->
<label class="form-check-label"
th:for="${#ids.prev('regions')}" th:text="${rg.value}">์์ธ</label>
</div>
</div>
</form>
์ด HTML Form ์๋์ ๊ฐ์ ํ๋ผ๋ฉํ๋ฅผ ๋ง๋ ๋ค. ์ด๋ ์คํ๋ง ํ์ ์ปจ๋ฒํฐ์์ ์ ์ ํ๊ฒ ๋ฐ๊ฟ์ค๋ค.
- ์์์ ์ค๋ช ํ ๊ฒ ์ฒ๋ผ, null์ ๋ฐฉ์งํ๊ธฐ ์ํด _regions๋ผ๋ ํ๋ ํ๋๋ฅผ ์์์ ์ถ๊ฐํด์ค๋ค.
# ๋ผ๋์ค ๋ฒํผ - ํ์๋ฆฌํ
๋ผ๋์ค ๋ฒํผ๋ ๊ฐ์ฒด๋ฅผ ์ด์ฉํด๋ ์๊ด์์ง๋ง, Enum์ ์ด์ฉํด์ ๋ง๋ค์ด๋ณด์.
- Enum.name()์ ํด๋น ์ด๋ ๊ฐ์ฒด์ toString() ์ ๋ฐํํ๋ค. (BOOK, FOOD, ETC)
- ์ฒดํฌ๋ฐ์ค์ ๊ฒฝ์ฐ null์ด ๋ฐํ๋์ด๋ ์๊ด์์ด์ ๋ฐ๋ก ํ๋ ํ๋๋ฅผ ๋ง๋ค์ง๋ ์๋๋ค.
public enum ItemType {
BOOK("๋์"), FOOD("์์"), ETC("๊ธฐํ");
private final String description;
ItemType(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
@ModelAttribute("itemTypes")
public ItemType[] itemTypes() {
return ItemType.values();
}
<!-- radio button -->
<form action="item.html" method="post" th:action th:object="${item}">
<div>
<div>์ํ ์ข
๋ฅ</div>
<div class="form-check form-check-inline" th:each="type : ${itemTypes}">
<input class="form-check-input" th:field="*{itemType}" th:value="${type.name()}"
type="radio"> <!-- th:filed = "${item.itemType}" -->
<label class="form-check-label" th:for="${#ids.prev('itemType')}"
th:text="${type.description}">
</label>
</div>
</div>
</form>
์ฐธ๊ณ ๋ก ์คํ๋ง-ํ์๋ฆฌํ ์ฐ๋๊ธฐ๋ฅ์ผ๋ก Enum์ Model์ ๊ฑฐ์น์ง ์๊ณ ์คํ๋ง ๋๋ฉ์ธ ๊ฐ์ฒด์ ์ง์ ์ ๊ทผํด์ ์ฌ์ฉํ ์ ์๋ค. ๋ค๋ง ํจํค์ง ๊ฒฝ๋ก๋ฅผ ๋ค ์ ๋ ฅํด์ค์ผํด์ ๊ถ์ฅํ๋ ๋ฐฉ๋ฒ์ ์๋๋ค.
- ${ T(ํจํค์ง ๊ฒฝ๋ก).values() } ๋ก ENUM์ ๋ชจ๋ ์ ๋ณด๋ฅผ ๋ฐ์์ฌ ์ ์๋ค. ์ด๋ฅผ ์คํ๋งEL ๋ฌธ๋ฒ์ด๋ผ๊ณ ํ๋ค.
- ${ @mybean.doSomthing() } ์ผ๋ก ์ปจํ ์ด๋์ ๋ฑ๋ก๋ ์คํ๋ง ๋น์ ์ง์ ์ฌ์ฉํ ์๋ ์๋ค.
<!-- model์ ๋ด์์ ์ ์ฒด Enum์ ์ ๋ฌํ๋ ๋ฐฉ๋ฒ -->
<div th:each="type : ${itemTypes}">
<!-- ์คํ๋ง ๊ฐ์ฒด๋ฅผ ์ฝ์ด ์ ์ฒด Enum์ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ -->
<div th:each="type : ${T(hello.itemservice.domain.item.ItemType).values()}">
# Select ๋ฐ์ค - ํ์๋ฆฌํ
์์์ ํ๋ ๊ฒ๊ณผ ํน๋ณํ๊ฒ ๋ค๋ฅธ๊ฑด ์๋ค. ๊ทธ๋ฅ ์ฌ์ฉํ๋ฉด ๋๋ค.
@Data
@AllArgsConstructor
public class DeliveryCode {
/* FAST: "๋น ๋ฅธ ๋ฐฐ์ก"
NORMAL: "์ผ๋ฐ ๋ฐฐ์ก"
SLOW: "๋๋ฆฐ ๋ฐฐ์ก" */
private String code;
private String displayName;
}
@ModelAttribute("deliveryCodes")
public List<DeliveryCode> deliveryCodes() {
List<DeliveryCode> deliveryCodes = new ArrayList<>();
deliveryCodes.add(new DeliveryCode("FAST", "๋น ๋ฅธ ๋ฐฐ์ก"));
deliveryCodes.add(new DeliveryCode("NORMAL", "์ผ๋ฐ ๋ฐฐ์ก"));
deliveryCodes.add(new DeliveryCode("SLOW", "๋๋ฆฐ ๋ฐฐ์ก"));
return deliveryCodes;
}
<!-- SELECT -->
<div>
<div>๋ฐฐ์ก ๋ฐฉ์</div>
<select class="form-select" th:field="*{deliveryCode}">
<option value="">==๋ฐฐ์ก ๋ฐฉ์ ์ ํ==</option>
<option th:each="deliveryCode : ${deliveryCodes}" th:text="${deliveryCode.displayName}"
th:value="${deliveryCode.code}">
</option>
</select>
</div>
# ์คํ๋ง์์ ์ค๋ฅ, ์์ธ์ฒ๋ฆฌ(BindingResult)
https://jiwondev.tistory.com/169#head5
์คํ๋ง์ bindingErrors๋ฅผ ์ฌ์ฉํ๋ฉด ํ์๋ฆฌํ์์๋ ๊ฐ๊ฒฐํ ๋ฌธ๋ฒ์ ์ด์ฉํ ์ ์๋ค.
<form action="item.html" method="post" th:action th:object="${item}">
<div th:if="${#fields.hasGlobalErrors()}">
<p class="field-error" th:each="err : ${#fields.globalErrors()}" th:text="${err}">๊ธ๋ก๋ฒ ์ค๋ฅ
๋ฉ์์ง</p>
</div>
<div>
<label for="itemName" th:text="#{label.item.itemName}">์ํ๋ช
</label>
<input class="form-control" id="itemName" placeholder="์ด๋ฆ์ ์
๋ ฅํ์ธ์"
th:errorclass="field-error" th:field="*{itemName}" type="text">
<div class="field-error" th:errors="*{itemName}">
์ํ๋ช
์ค๋ฅ
</div>
</div>
...
</form>
'๐ฑ Spring Framework > Spring MVC' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring Vaildation#1 ๊ฒ์ฆ (0) | 2021.08.29 |
---|---|
Spring ๋ฉ์์ง, ๊ตญ์ ํ ๊ธฐ๋ฅ (0) | 2021.08.28 |
Thymeleaf#1 ๊ธฐ๋ณธ๊ธฐ๋ฅ (0) | 2021.08.28 |
#2 HTTP API ์์ฒญ ๋งคํ, ํค๋ ์กฐํ (0) | 2021.08.11 |
#1 HTTP ์์ฒญ ๋งคํ๊ณผ ๊ธฐ๋ณธ ๋ก๊น (0) | 2021.08.11 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev