스프링

스프링 MVC 2 정리 - 2. 타임리프 - 스프링 통합과 폼 / 3. 메세지 국제화 (22.8.7)

dodo4723 2024. 2. 13. 17:29
728x90
반응형

사용자가 특정 차트를 고르면, 전 종목의 과거(10년) 차트들을 모두 탐색하여 가장 유사한 차트 10개를 골라 사용자에게 보여줍니다.

 

웹프로젝트 링크

 

비슷한 차트 검색기

전 종목의 최근 10년간 모든 차트를 탐색합니다. 내 종목의 차트는 과연 상승하는 차트일까요?

www.similarchart.com

 

 

 

 

 

 

 

 

 

 

 

김영한 선생님의 스프링 MVC 2편 강의를 듣고 정리한 내용이다.

2. 타임리프 - 스프링 통합과 폼

2.1. 입력 폼 처리

th:object : 커맨드 객체를 지정한다.
*{...} : 선택 변수 식이라고 한다. th:object에서 선택한 객체에 접근한다.
th:field : HTML 태그의 id , name , value 속성을 자동으로 처리해 준다.

<form action="item.html" th:action th:object="${item}" method="post">
 <div>
 <label for="itemName">상품명</label>
 <input type="text" id="itemName" th:field="*{itemName}" class="formcontrol" placeholder="이름을 입력하세요">
 </div>

th:field

* 렌더링 전**
`<input type="text" th:field="
{itemName}" />`

렌더링 후
<input type="text" id="itemName" name="itemName" th:value="*{itemName}" />

 

  • th:object="${item}" : <form>에서 사용할 객체를 지정한다. 선택 변수 식( *{...} )을 적용할 수 있다.
  • th:field="*{itemName}"
    *{itemName}는 선택 변수 식을 사용했는데, ${item.itemName}과 같다. 앞서 th:object로 item을 선택했기 때문에 선택 변수 식을 적용할 수 있다.

렌더링 전

<input type="text" id="itemName" th:field="*{itemName}" class="form-control"
placeholder="이름을 입력하세요">

렌더링 후

<input type="text" id="itemName" class="form-control" placeholder="이름을
입력하세요" name="itemName" value="">

 

2.2. 체크박스 - 단일

  • 체크박스는 체크 시 HTML에서 open = on이라는 값이 넘어가고 스프링 타입 컨버터가 ontrue 타입으로 변환해 준다.
  • 다만 체크박스 선택하지 않을 시 open field 자체가 서버로 전송되지 않는다. 수정의 경우에는 이게 문제가 될 수 있다.
  • 이를 해결하기 위해 MVC는 히든 필드를 하나 만드는데, 체크 박스 이름 앞에 _를 붙여서 전송하면 체크를 헤제했다고 인식할 수 있다. 체크를 해제한 경우 '_open'만 전송돼서 이를 통해 체크 해제를 판단한다.
<input type="checkbox" id="open" name="open" class="form-check-input">
<input type="hidden" name="_open" value="on" > // 히든필드

2.3. 체크박스 - 단일 (타임리프로 간단하게)

  • 타임필드가 자동으로 위의 코드를 생성해 준다
<input type="checkbox" id="open" th:field="*{open}" class="form-check-input">

 

2.4. 체크박스 - 멀티

  • 위의 체크박스를 확장해서 다중 선택이 가능케 한다.
  • 여기서 ModelAttribute를 기존과는 조금 다르게 활용한다.
  • 체크 박스를 반복해서 보여주어야 하는데, 이를 위해선 각각의 컨트롤러에서 model.addAttribute(...)를 사용해서 체크 박스를 구성하는 데이터를 반복해서 넣어주어야 한다. 이를 위해 컨트롤러에 아래와 같이 ModelAttribute를 이용하면,
    @ModelAttribute("regions")// 일반적인 ModelAttribute와 다른 기능
    public Map<String, String> regions(){
        Map<String, String> regions = new LinkedHashMap<>();
        regions.put("SEOUL", "서울");
        regions.put("BERLIN", "베를린");
        regions.put("LIVERPOOL", "리버풀");
        return regions;
    } // 컨트롤러 호출시 항상 ModelAttribute를 통해 regions의 반환 값이 Model에 자동으로 담긴다.

 

#ids

th:for="${#ids.prev('regions')}"
멀티 체크박스는 같은 이름의 여러 체크박스를 만들 수 있다. 그런데 문제는 이렇게 반복해서 HTML 태그를 생성할 때, 생성된 HTML 태그 속성에서 name 은 같아도 되지만, id는 모두 달라야 한다. 따라서 타임리프는 체크박스를 each 루프 안에서 반복해서 만들 때 임의로 1 , 2 , 3 숫자를 뒤에 붙여준다.

HTML의 id 가 타임리프에 의해 동적으로 만들어지기 때문에 <label for="id 값">으로 label의 대상이 되는 id 값을 임의로 지정하는 것은 곤란하다. 타임리프는 ids.prev(...) , ids.next(...)을 제공해서 동적으로 생성되는 id 값을 사용할 수 있도록 한다.

 

2.5. 라디오 버튼

  • 여러 선택지 중에 하나만 고르는 것이 라디오 버튼이다. ENUM을 통해서 개발해 본다.
  • 먼저 앞서 설명한 ModelAttribute의 기능을 활용한다.
    @ModelAttribute("itemTypes")
    public ItemType[] itemTypes(){
        ItemType[] values = ItemType.values();
        return values; // ENUM의 정보를 배열로 반환 
    }
<div>
    <div>상품 종류</div>
    <div th:each="type : ${itemTypes}" class="form-check form-check-inline">
        <input type="radio" th:field="*{itemType}" th:value="${type.name()}" class="form-check-input">
        <label th:for="${#ids.prev('itemType')}" th:text="${type.description}" class="form-check-label">
            BOOK
        </label>
    </div>
</div>
  • 한번 선택하면 다시 NULL로 그 값을 바꿀 수 없다는 특징이 있다. 그래서 별도의 히든 필드 사용의 필요성이 떨어진다.

 

2.6. 셀렉트 박스

  • 여러 선택지 중에 하나를 선택할 때에 사용한다.
  • 먼저 자바 객체를 만들고 반환해서 ModelAttribute를 활용한다.
  • <!-- SELECT --> <div> <div> 배송 방식 </div> <select th:field="*{deliveryCode}" class="form-select"> <option value="">==배송 방식 선택==</option> <option th:each="deliveryCode : ${deliveryCodes}" th:value="${deliveryCode.code}" th:text="${deliveryCode.displayName}"> FAST </option> </select> </div>
  • th:object를 사용하는지 등의 여부를 주목한다.
<select th:field="${item.deliveryCode}" class="form-select" disabled>
  • disabled를 사용하면 셀렉트 박스를 선택되지 않게 할 수 있다.



3. 메시지 국제화

3.1. 메시지, 국제화 소개

3.1.1. 메시지

HTML 파일에 메시지가 하드코딩 되어있어서 바꾸기 곤란한 경우에, 다양한 메시지를 한 곳에서 관리하여 바꾸기 쉽게 할 수 있는 기능이 메시지 기능이다.

3.1.2. 국제화

메시지에서 설명한 메시지 파일(messages.properteis)을 각 나라별로 별도로 관리하면 서비스를 국제화할 수 있다(접속 지역에 따라 언어를 다르게 설정)

 

3.2. 스프링 메시지 소스 설정

  • 직접 등록하는 방식도 있다.
  • 스프링 부트를 사용하면 부트에서 자동으로 MessageSouce를 스프링 빈으로 등록한다.

application properties에 다음과 같은 코드를 넣으면 스프링 빈에 메시지 소스가 등록이 된다.
spring.messages.basename=messages

 

3.3. 스프링 메시지 소스 사용

/resources/messages.properties

hello=안녕
hello.name=안녕 {0}

/resources/messages_en.properties

hello=hello
hello.name=hello {0}
@Test
void helloMessage() {
        String result = ms.getMessage("hello", null, null);
        // 지역 설정이 default로 되어있어서 "안녕"이 실행이 됨
        assertThat(result).isEqualTo("안녕");
}

@Test
void argumentMessage() {
        String result = ms.getMessage("hello.name", new Object[]{"Spring"}, null);
        // 값을 넘겨서 치환하는데 Object[] 배열을 사용
        assertThat(result).isEqualTo("안녕 Spring");
}

@Test
void enLang() {
 assertThat(ms.getMessage("hello", null, Locale.ENGLISH)).isEqualTo("hello");
}
  • 메시지가 없는 경우에는 NoSuchMessageException 이 발생한다.
  • 메시지가 없어도 기본 메시지(defaultMessage)를 사용하면 기본 메시지가 반환된다.

3.4. 웹 애플리케이션에 메시지 적용하기

메시지를 활용하여 $대신 #을 쓰면 된다.
messages.propertieslabel.item=상품 등록 시 아래 코드는

<div th:text="#{label.item}"></h2>

<div> 상품 </h2>으로 렌더링 된다.

 

3.5. 웹 애플리케이션에 국제화 적용하기

크롬 -> 브라우저 설정 -> 언어를 검색하고, 우선순위를 변경하면 된다.

웹 브라우저의 언어 설정 값을 변경하면 요청 시 Accept-Language의 값이 변경된다.

영어버전 메시지 파일을 수정한 후 크롬의 언어 설정에서 영어를 제일 위로 적용하면 적용되는 것을 확인할 수 있다.

반응형