본문 바로가기

공부기록/독서 - 문서

Thymeleaf 6~12

https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html#creating-a-form

 

Tutorial: Thymeleaf + Spring

Preface This tutorial explains how Thymeleaf can be integrated with the Spring Framework, especially (but not only) Spring MVC. Note that Thymeleaf has integrations for both versions 3.x and 4.x of the Spring Framework, provided by two separate libraries c

www.thymeleaf.org

 

6 Creating a Form

6.1 Handling the command object

 Command 객체는 Spring MVC가 form-backing 빈에게 준 이름이다. 즉, 폼의 필드를 모델링하고, 브라우저 측에서 사용자에 의해 입력된 값들을 설정하고 얻기위하여 사용되는 프레임워크의 getter, setter 메소드를 제공하는 객체이다.

타임리프는 <form> 태그에서 th:object 속성을 사용하여 command object를 구분하도록 요구한다.

<form action="#" th:action="@{/seedstartermng}" th:object="${seedStarter}" method="post">
    ...
</form>

이것은 th:object의 연속된 다른 사용으로, 그러나 사실은 이러한 특정 시나리오는 Spring MVC 인프라와 올바르게 통합하는 데, 몇 가지 한계를 가진다.

  • form tag의 th:object 속성의 값들은 반드시 변수표현식(${...}) 모델 속성의 오직 하나의 이름을 구분하기 위하여, 프로퍼티 네비게이션 없이 동작하도록. 이것은 ${seedStart}은 유효하고, 그러나 {seedStarter.data}는 유효하지 않다.
  • <form> 태그 내부로 들어가면, 다른 th:object 속성은 특정될 수 없다. 이것은 HTML form을 중첩할 수 없다는 사실과 일치한다.

6.2 Inputs

form에 어떻게 input을 추가하는지 살펴보자:

<input type="text" th:field="*{datePlanted}" />

 당신이 보는 것과 같이, 우리는 th:field라는 새로운 속성을 소개한다. 이것은 Strping MVC 통합에서 매우 중요한 속성으로, 이것은 당신의 input과 form-backing 빈의 속성을 묶어주는 무거운 모든 일을 하고 있기 때문이다. 이것을 Spring MVC의 JSP 태그 라이브러리의 path attribute 와 동일하다는 것을 알 수 있다.

 th:field 속성은 이것이 <input>, <select> 또는 <textarea>(또한 어떠한 특정 타입의 input 태그냐에 따라)에 붙어있느냐에 따라 다르게 행동한다. input[type=text]의 경우, 위의 코드는 아래의 코드와 비슷하다.

<input type="text" id="datePlanted" name="datePlanted" th:value="*{datePlanted}" />

 

 ...그러나 실제로는 이것보다 조금더 하다, 왜냐하면 th:field는 또한 등록된 Spring 변환 서비스에 적용하기 때문이다, 이전에 봤던 DateFormatter를 포함하여( 심지어 필드 표현식이 이중 괄호가 아닌 경우에도). 이러한 점 덕분에, 날짜가 올바른 형식으로 보여진다.

 th:field 속성의 값들은 반드시 선택 표현식이어야 한다(*{...}), 이것은 컨텍스트 변수(또는 Spring MVC 전문 용어의 모델 속성)가 아닌 form-backing 빈에서 평가된다는 사실을 감안할 때 의미가 있다.

 th:boject의 것들과 다르게, 이런 표현식들은 속성 탐색(실제로 어떤 표현식은 form:input JSP 태그가 여기서 허용되는 path 속성을 위해 허용한다. )을 포함할 수 있다.

 th:field는 또한 HTML 5에서 소개된 새로운 <input> 원소의 새로운 타입인 <input type="datatime" .. />, <input type="color" ... />, 기타 등등을 이해할 수 있다, 효과적으로 완벽한 HTML5 지원을 Spirng MVC에 추가하였다.

6.3 Checkbox fields

 th:field 또한 우리에게 체크박스 input들을 정의하는 것을 허용한다. 아래의 HTML 페이지에서 예제 form을 보자:

<div>
<label th:for="${#ids.next('covered')}" th:text="#{seedstarter.covered}">Covered</label>
<input type="checkbox" th:field="*{covered}" />
</div>

 

 체크박스 옆에 어떤 괜찮은 것이 있다, 외부화된 레이블과 #ids.next('covered') 함수로 체크 박스 input의 id 속성에 추가되는 값을 받아오는 것의 사용과 같은.

 왜 우리가 필드의 아이디 속성의 동적인 생성을 필요로 하는 가? 왜냐하면 체크박스들은 잠재적으로 여러개의 값을 가지고, 따라서 그들의 아이디 값들은 언제나 접미어로 연속적인 번호들을 가진다(내부적으로 #ids.seq(...) 함수를 사용하여), 같은 프로퍼티를 가진 각각의 체크박스 input들이 다른 id 값을 가지도록 하기위하여.

 만약 우리가 여러 값을 가지는 체크박스 필드를 보면, 더욱 쉽게 이해할 수 있다:

<ul>
  <li th:each="feat : ${allFeatures}">
    <input type="checkbox" th:field="*{features}" th:value="${feat}" />
    <label th:for="${#ids.prev('features')}" 
           th:text="#{${'seedstarter.feature.' + feat}}">Heating</label>
  </li>
</ul>

 

 우리가 이번시간에 th:value 속성을 추가한 것을 기억하라, 왜냐하면 속성 필드는 covered와 같은 불리언 필드가 아니며, 대신에 값들의 배열이다.

 이 코드로 인해 생겨난 HTML output을 확인하자:

<ul>
  <li>
    <input id="features1" name="features" type="checkbox" value="SEEDSTARTER_SPECIFIC_SUBSTRATE" />
    <input name="_features" type="hidden" value="on" />
    <label for="features1">Seed starter-specific substrate</label>
  </li>
  <li>
    <input id="features2" name="features" type="checkbox" value="FERTILIZER" />
    <input name="_features" type="hidden" value="on" />
    <label for="features2">Fertilizer used</label>
  </li>
  <li>
    <input id="features3" name="features" type="checkbox" value="PH_CORRECTOR" />
    <input name="_features" type="hidden" value="on" />
    <label for="features3">PH Corrector used</label>
  </li>
</ul>

 

우리는 어떻게 연속적인 접미어가 각각의 input 아이디 속성으로 추가되었는 지를 확인할 수 있고, 그러고 #ids.prev(...) 함수가 우리에게 특정 input 아이디로부터 지난 시퀀스 값을 가져오도록 허용한다.

숨겨진 인풋들( name= "_features")에 대해 걱정하지 마라: 그것들은 자동으로 추가되었다, form 전송시 브라우저의 체크되지 않은 체크박스의 값을 서버로 보내지 않는 문제를 회피하기 위하여.

또한 우리의 기능 프로퍼티는 form-backing 빈에서 일부의 선택된 값들을 포함한다, th:field는 그것을 살펴야하고, checked="checked" 속성을 대응하는 input 태그에 붙여야한다.

6.4 Radio Button fields

라디오 버튼 필드는 불리언이 아닌 체크박스와 비슷한 방식으로 특정된다 - 멀티밸류가 아닌 경우를 제외하고, 당연히:

<ul>
  <li th:each="ty : ${allTypes}">
    <input type="radio" th:field="*{type}" th:value="${ty}" />
    <label th:for="${#ids.prev('type')}" th:text="#{${'seedstarter.type.' + ty}}">Wireframe</label>
  </li>
</ul>

6.5 Dropdown/List selectors

셀렉트 필드는 두개의 파트가 있다: <select> 태그와 그것에 중첩된 <option> 태그들. 이러한 종류의 필드를 생성할 때, <select> 태그는 th:field 속성을 포함한다, 그러나 <option> 태그의 th:value 속성은 매우 중요하다, 왜냐하면 그들은 무엇이 현재 선택된 옵션인지를 제공하기 때문이다(불리언이 아닌 체크박스와 라디오 버튼과 비슷한 방식으로).

드롭다운 셀렉트를 타입필드로 다시세우자:

<select th:field="*{type}">
  <option th:each="type : ${allTypes}" 
          th:value="${type}" 
          th:text="#{${'seedstarter.type.' + type}}">Wireframe</option>
</select>

이러한 점에 있어서, 코드 조각을 이해하는 것은 꽤 쉽다. 그저 어떻게 속성 순위가 어떻게 <option> 태그 자체에 th:each 속성을 설정할 수 있는지 주목하자.

6.6 Dynamic fields

스프링 MVC의 진보된 form-필드 바인딩 능력으로 인해, 우리는 복잡한 Spring EL 표현식을 동적인 form 필들에 form-backing 빈을 묶을 수 있다. 이것은 우리가 SeedStarter 빈에서 새로운 행 객체를 만드는 것을 허용하고, 이러한 행의 필드을 유저 요청에 우리의 form을 추가한다.

이것을 하기 위해, 우리는 컨트롤러에서 몇개의 새로운 매핑된 메소드들이 필요하고, SeedStarter에서 행을 추가 및 삭제하는, 특정한 요청 피라미터의 존재에 의존하는:

@RequestMapping(value="/seedstartermng", params={"addRow"})
public String addRow(final SeedStarter seedStarter, final BindingResult bindingResult) {
    seedStarter.getRows().add(new Row());
    return "seedstartermng";
}

@RequestMapping(value="/seedstartermng", params={"removeRow"})
public String removeRow(
        final SeedStarter seedStarter, final BindingResult bindingResult,
        final HttpServletRequest req) {
    final Integer rowId = Integer.valueOf(req.getParameter("removeRow"));
    seedStarter.getRows().remove(rowId.intValue());
    return "seedstartermng";
}

그리고 우리의 form에 동적 테이블을 추가한다:

<table>
  <thead>
    <tr>
      <th th:text="#{seedstarter.rows.head.rownum}">Row</th>
      <th th:text="#{seedstarter.rows.head.variety}">Variety</th>
      <th th:text="#{seedstarter.rows.head.seedsPerCell}">Seeds per cell</th>
      <th>
        <button type="submit" name="addRow" th:text="#{seedstarter.row.add}">Add row</button>
      </th>
    </tr>
  </thead>
  <tbody>
    <tr th:each="row,rowStat : *{rows}">
      <td th:text="${rowStat.count}">1</td>
      <td>
        <select th:field="*{rows[__${rowStat.index}__].variety}">
          <option th:each="var : ${allVarieties}" 
                  th:value="${var.id}" 
                  th:text="${var.name}">Thymus Thymi</option>
        </select>
      </td>
      <td>
        <input type="text" th:field="*{rows[__${rowStat.index}__].seedsPerCell}" />
      </td>
      <td>
        <button type="submit" name="removeRow" 
                th:value="${rowStat.index}" th:text="#{seedstarter.row.remove}">Remove row</button>
      </td>
    </tr>
  </tbody>
</table>

 위에는 볼 것이 많다, 그러나 우리가 이해 못할 것은 많지 않다. 다음의 이상한 것을 제외하고는:

<select th:field="*{rows[__${rowStat.index}__].variety}">

    ...

</select>

 만약 당신이 "Using Thymleaf" 튜토리얼을 다시 상기해보면, 저 ${..} 문법은 표현을 전처리하는 것이고, 모든 표현식을 평가하기전에 평가되는 내부 표현이다. 그러나 왜 행 인덱스를 특정하는 가? 이것으로 부족할까?:

<select th:field="*{rows[rowStat.index].variety}">

    ...

</select>

...음, 실제로는 아니다. 문제는 Spring EL이 배열 인텍스 대괄호 내부의 변수들을 평가할 수 없기 때문에, 위의 표현식을 실행할 때 rows[rowStat.index](rows[0], rows[1] 등 대신) 라는 오류가 발생한다는 것이고, 행 컬렉션에서 유효한 위치가 아니다. 여기에서 전처리가 필요한 이유이다.

"Add Row"를 몇번 누르고 난 결과 HTML의 조각들을 살펴보자:

<tbody>
  <tr>
    <td>1</td>
    <td>
      <select id="rows0.variety" name="rows[0].variety">
        <option selected="selected" value="1">Thymus vulgaris</option>
        <option value="2">Thymus x citriodorus</option>
        <option value="3">Thymus herba-barona</option>
        <option value="4">Thymus pseudolaginosus</option>
        <option value="5">Thymus serpyllum</option>
      </select>
    </td>
    <td>
      <input id="rows0.seedsPerCell" name="rows[0].seedsPerCell" type="text" value="" />
    </td>
    <td>
      <button name="removeRow" type="submit" value="0">Remove row</button>
    </td>
  </tr>
  <tr>
    <td>2</td>
    <td>
      <select id="rows1.variety" name="rows[1].variety">
        <option selected="selected" value="1">Thymus vulgaris</option>
        <option value="2">Thymus x citriodorus</option>
        <option value="3">Thymus herba-barona</option>
        <option value="4">Thymus pseudolaginosus</option>
        <option value="5">Thymus serpyllum</option>
      </select>
    </td>
    <td>
      <input id="rows1.seedsPerCell" name="rows[1].seedsPerCell" type="text" value="" />
    </td>
    <td>
      <button name="removeRow" type="submit" value="1">Remove row</button>
    </td>
  </tr>
</tbody>

7 Validation and Error Messages

대부분의 form들은 검증 메시지가 필요하다, 그/그녀가 만드는 에러의 정보를 알리기 위하여.

타임리프는 이것을 위한 도구를 제공한다: #fields 객체와 th:errors 와 th:errorclasss 속성에 대한 몇개의 함수들.

7.1 Field errors

 어떻게 우리가 만약 에러가 발생했을 때, 특정 CSS 클래스를 필드에 넣는지 알아보자:

<input type="text" th:field="*{datePlanted}" 
                   th:class="${#fields.hasErrors('datePlanted')}? fieldError" />

 당신이 보듯이, #fields.hasErrors(...) 함수는 필드 표현식을 파라미터(datePlanted)로 받는다, 그리고 불리언 값을 리턴한다, 어떠한 검증 에러가 존재하는 지 아닌지를 말해주는.

 우리는 또한 모든 필드에 대한 에러를 얻고, 이터레이트 해야한다:

<ul>
  <li th:each="err : ${#fields.errors('datePlanted')}" th:text="${err}" />
</ul>

 이터레이팅 대신에, 우리는 또한 th:errors를 사용해야한다, <br />로 구분되는 특정한 셀럭터를 위한 모든 에러의 리스트를 빌드하는.

<input type="text" th:field="*{datePlanted}" />
<p th:if="${#fields.hasErrors('datePlanted')}" th:errors="*{datePlanted}">Incorrect date</p>

Simplifying error-based CSS styling: th:errorclass

우리가 위에서 본 것과 같이, 필드에 에러가 있을 시, CSS 클래스를 form input에 세팅하는 것은 매우 흔하다, 타임리프가 그것을 정확히 하는 것에 특정한 속성을 제공하기 때문에: th:errorclass

form 필드 태그(input, select, textarea)에 적용하여, 동일한 태그의 기존 이름 또는 th:field 속성에서 감사할 필드의 이름을 읽는 다음 지정된 CSS 클래스를 태그에 추가한다. 해당 필드에 관련 오류가 있는 경우:

<input type="text" th:field="*{datePlanted}" class="small" th:errorclass="fieldError" />

datePlanted가 에러가 있는 경우, 이것은 다음과 같이 그려진다:

<input type="text" id="datePlanted" name="datePlanted" value="2013-01-01" class="small fieldError" />

7.2 All errors

그리고 form의 모든 오류를 표시하려면 어떻게 해야 할까? #fields.hasErrors(...) 및 #fields.errors(..) 메서드를 '*' 또는 'all' 상수(동등한)로 쿼리하기만 하면 된다.

<ul th:if="${#fields.hasErrors('*')}">
  <li th:each="err : ${#fields.errors('*')}" th:text="${err}">Input is incorrect</li>
</ul>

예제 위에서 보듯이, 우리는 에러들을 얻고, 이터레이트 할 수 있다.

<ul>
  <li th:each="err : ${#fields.errors('*')}" th:text="${err}" />
</ul>

분리된 리스트를 빌드하는 것처럼:

<p th:if="${#fields.hasErrors('all')}" th:errors="*{all}">Incorrect date</p>

마지막으로, #fields.hasError('')이 #fields.hasAnyErrors()와 같고, #fields.errors('')는 $fields.allErrors()와 같다. 당신이 선호하는 구문을 사용하라

<div th:if="${#fields.hasAnyErrors()}">
  <p th:each="err : ${#fields.allErrors()}" th:text="${err}">...</p>
</div>

7.3 Global errors

 스프링 form에는 세번째 타입의 에러가 있다. 이것은 form의 특정한 필드에는 관련되지 않지만, 여전히 존재한다.

타임리프는 global 상수를 제공한다, 이러한 에러들에 접근하기 위하여:

<ul th:if="${#fields.hasErrors('global')}">
  <li th:each="err : ${#fields.errors('global')}" th:text="${err}">Input is incorrect</li>
</ul>
<p th:if="${#fields.hasErrors('global')}" th:errors="*{global}">Incorrect date</p>

... #fields.hasGlobalErrors() 와 #fields.globalErrors()는 동등한 편의 메소드들:

<div th:if="${#fields.hasGlobalErrors()}">
  <p th:each="err : ${#fields.globalErrors()}" th:text="${err}">...</p>
</div>

7.4 Displaying errors outside forms

 Form 검증 에러들은 form의 바깥에서도 표시될 수 있다, (${...})를 사용하여 (*{...} 선택 표현식 대신에, form-backing 빈의 이름에 접두어를 붙임으로서

<div th:errors="${myForm}">...</div>
<div th:errors="${myForm.date}">...</div>
<div th:errors="${myForm.*}">...</div>

<div th:if="${#fields.hasErrors('${myForm}')}">...</div>
<div th:if="${#fields.hasErrors('${myForm.date}')}">...</div>
<div th:if="${#fields.hasErrors('${myForm.*}')}">...</div>

<form th:object="${myForm}">
    ...
</form>

7.5 Rich error objects

 타임리프는 form 에러 정보를 얻을 가능성을 제공한다, beans(애매한 String 대신에)의 형태로, fieldName(String)과 message(String)과 global(boolean) 속성과 함께.

 이러한 오류들은 #fields.detailedErrors() 유틸리티 메서드를 통해 얻을 수 있다:

<div th:errors="${myForm}">...</div>
<ul>
    <li th:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
        <span th:text="${e.global}? '*' : ${e.fieldName}">The field name</span> |
        <span th:text="${e.message}">The error message</span>
    </li>
</ul>

8 It’s still a Prototype!

 애플리케이션은 준비되었다. 그러나 우리가 생성한 html 페이지를 다시 한번 살펴보자.

 타이림프로 작업하는 멋진 결과 중 하나는 결국 우리의 HTML에 추가하는 이 기능이, 이것을 여전히 prototype으로( 이것을 natural 템플릿이라고 부른다.) 사용할 수 있다는 것이다. 애플리케이션을 실행하지 말고 seedstartermng.html을 열어보자. 

이것이다. 애플리케이션을 실행시키지 않아도, 이것은 실제 데이터가 아니다, 그러나 이것은 완벽하게 유효한 프로토타입이다, HTML 코드를 보여주는.

9 The Conversion Service

9.1 Configuration

 이전에 설명했듯이, 타임리프는 Application Context에 등록된 변환 서비스를 사용할 수 있다. 애플리케이션 설정 클래스, 스프링의 WebMvcConfigurerAdapter 헬퍼를 상속받아 생성한, 자동으로 그러한 전호나 서비스를 등록하여, 우리가 필요로하는 이러한 포매터들을 등록할 수 있게 한다. 아래의 코드를 살펴보자.

@Override
public void addFormatters(final FormatterRegistry registry) {
    super.addFormatters(registry);
    registry.addFormatter(varietyFormatter());
    registry.addFormatter(dateFormatter());
}

@Bean
public VarietyFormatter varietyFormatter() {
    return new VarietyFormatter();
}

@Bean
public DateFormatter dateFormatter() {
    return new DateFormatter();
}

9.1 Double-brace syntax

 변환 서비스는 어떤 객체든 String으로 쉽게 전환/포맷팅할 수 있다. 이것은 이중 괄호 표현식 구문에 의해 수행된다. 

  • 변수 표현식 : ${{...}}
  • 선택 표현식 : *{{...}}

그래서, 예를 들자면, 정수-문자열 전환기가 주어졌을 때, 천단위로 콤마를 추가하는:

<p th:text="${val}">...</p>
<p th:text="${{val}}">...</p>

이것의 결과는:

<p>1234567890</p>
<p>1,234,567,890</p>

9.2 Use in forms

 우리는 모든 th:field 속성을 봤다, 항상 변환 서비스에 적용되는:

<input type="text" th:field="*{datePlanted}" />

..이것은 아래와 동일하다.

<input type="text" th:field="*{{datePlanted}}" />

 Spring의 요구사항에 이것은 오직 변환 서비스의 시나리오다, 괄호 구문을 사용하는 표현식이 적용된.

9.3 #conversions utility object

#conversions 표현 유틸 객체는 변환 서비스의 수동 실행을 허용한다 필요로 할때마다:

<p th:text="${'Val: ' + #conversions.convert(val,'String')}">...</p>

유틸 객체를 위한 구문:

  • #conversions.convert(Object,Class): 객체를 특정 클래스로 변환한다.
  • #conversions.convert(Object,String): 위와 같지만, 특정 타겟 클래스를 String으로 변환한다. (java.lang 패키지가 생략되었다.) 

10 Rendering Template Fragments

 타임리프는 가능성을 제공한다, 실행의 결과로 템플릿의 일부만 렌더링 되는 것을: fragment.

 이것은 유용한 컴포넌트화 도구이다. 예를 들어, 이것은 컨트롤러에서 AJAX 호출을 실행하는 데 사용할 수 있따, 그것은 아마 페이지의 markup fragment를 호출한다, 이미 브라우저에 로딩되어 있는(select를 업데이트하거나, button을 활성/비활성화하는...).

 조각화 렌더링은 타임리프 조각 스펙에 의해 달성될 수 있다: 객체를 org.thymeleaf.fragment.IFragmentSpec로 구현하여.

 가장 흔한 구현체중 하나는 org.thymeleaf.standard.fragment.StandardDOMSelectorFragmentSpec이고, 이것은 조각을 특하는 것을 가능하게 한다, DOM Selector를 사용하여 th:include나 th:replace를 사용한.

10.1 Specifying fragments in view beans

  View 빈은 org.thymeleaf.spring4.view.ThymeleafView 클래스의 빈들이다, application context에서 선언된.( @Bean 선언 만약 당신이 자바 설정을 사용한다면). 그것은 다음과 같이 조각의 특정을 허용한다:

@Bean(name="content-part")
@Scope("prototype")
public ThymeleafView someViewBean() {
    ThymeleafView view = new ThymeleafView("index"); // templateName = 'index'
    view.setMarkupSelector("content");
    return view;
}

 위의 빈이 주어졌을 때, 우리의 컨트롤러는 content-part(위의 bean의 이름)를 리턴한다.

@RequestMapping("/showContentPart")
public String showContentPart() {
    ...
    return "content-part";
}

...타임리프는 index 템플릿의 content 조각만 돌려준다 - 그것은 /WEB-INF/templates/index.html 와 같은 location일 것이다, 한번 접두어와 접미어가 정해지면. 그래서 결과는 완벽하게 index :: content : 를 특정하는 것과 같다:

<!DOCTYPE html>
<html>
  ...
  <body>
    ...
    <div th:fragment="content">
      Only this div will be rendered!
    </div>
    ...
  </body>
</html>

타임리프 Markup 선택자의 힘으로, 우리는 조각을 선택할 수 있다, 오직 th:fragment만 있으면. id 속성을 사용해보자:

@Bean(name="content-part")
@Scope("prototype")
public ThymeleafView someViewBean() {
    ThymeleafView view = new ThymeleafView("index"); // templateName = 'index'
    view.setMarkupSelector("#content");
    return view;
}

이것은 완벽하게 다음을 선택한다:

<!DOCTYPE html>
<html>
  ...
  <body>
    ...
    <div id="content">
      Only this div will be rendered!
    </div>
    ...
  </body>
</html>

10.2 Specifying fragments in controller return values

 view 빈을 정의하는 대신에, 조각들을 컨트롤러로 특정하자, 조각 표현식 구문을 사용하여. th:insert나 th:replace 속성을 사용하여.

@RequestMapping("/showContentPart")
public String showContentPart() {
    ...
    return "index :: content";
}

 물론, DOM 선택자의 힘이 사용가능하므로, 우리는 표준 HTML 속성인 id="content" 같은 것을 사용하여 우리의 조각을 선택할 수 있다:

@RequestMapping("/showContentPart")
public String showContentPart() {
    ...
    return "index :: #content";
}

그리고 우리는 또한 파라미터를 사용할 수 있다, 다음과 같은:

@RequestMapping("/showContentPart")
public String showContentPart() {
    ...
    return "index :: #content ('myvalue')";
}

11 Advanced Integration Features

11.1 Integration with RequestDataValueProcessor

 타임리프는 원활하게 스프링으 RequestDataValueProcessor 인터페이스와 통합한다. 이 인터페이스는 link URL의 간섭을 허용하고, URL을 형성하고, 필드 값들을 형성한다, 그들이 markup result에 작성되기 전에, 투명하게 CSRF에 대한 보호로서 보안 기능을 활상화시키는 투명한 폼 필드.

 RequestDataValueProcessor의 하나의 구현체는 Application Context에서 쉽게 설정되어질 수 있다. 그것은 org.springframework.web.servlet.support.RequestDataValueProcessor을 구현해야하며, requestDataValueProcessor를 빈의 이름으로 가진다:

@Bean
public RequestDataValueProcessor requestDataValueProcessor() {
  return new MyRequestDataValueProcessor();
}

..그리고 타임리프는 이것을 이런 방식으로 사용한다:

  • th:href와 th:src는 RequestDataValueProcessor.processUrl(...)를 URL이 렌더링되기전에 호출한다.
  • th:action은 RequestDataValueProcessor.processAction(...)을 호출한다, form의 action 속성이 렌더링되기 전에, 그리고 추가적으로 그것은 이 속성이 <form> 태그에 붙어있는 지 감지한다 - 그것은 하나의 장소, 그리고 그 케이스는 RequestDataValueProcessor.getExtraHiddenFields(...)를 호출한다, 그리고 </form> 전에 리턴된 숨겨진 히든 필드를 추가한다.
  • th:value는 RequestDataValueProcessor.processFormFieldValue(...)를 호출한다, 그것이 참조하는 값을 렌더링하기 위하여, 그것이 th:field가 같은 태그에서 표현되더라도(그 경우, th:field에서 처리함)
  •  th:field는 RequestDataValueProcessor.processFormFieldValue(...)를 호출한다, 필드의 값을 렌더링하기 위하여, 그것을 적용할( 또는 <textarea>가 있으면 태그 바디에)
더보기

RequestDataValueProcessor를 애플리케이션에서 구현하게 될 일은 거의 없다. 대부분의 경우, 투명하게 사용되는 보안 라이브러리에 의해 자동으로 사용된다, Spring Security CSRF 지원과 같은.

11.1 Building URIs to controllers

4.1버전 이후에, Spring은 가능성을 허용한다, view에서 직접적으로 애노테이션으로 컨트롤러를 링크하는 것을, 컨트롤러가 매핑된 URI를 모르더라도.

 타임리프에서는, #mvc.url(...) 표현 객체 함수의 결과로 얻어진다, 이것은 컨트롤러 메소드의 명세를 허용한다, 컨트롤러 클래스가 있는 대문자에 의해), 함수의 이름에 따라서. 이것은 JSP의 spring:mvcUrl(...) 커스텀 메소드와 동일하다.

예를 들면:

public class ExampleController {

    @RequestMapping("/data")
    public String getData(Model model) { ... return "template" }

    @RequestMapping("/data")
    public String getDataParam(@RequestParam String type) { ... return "template" }

}

다음의 코드는 아래와 같은 링크를 생성한다.

<a th:href="${(#mvc.url('EC#getData')).build()}">Get Data Param</a>
<a th:href="${(#mvc.url('EC#getDataParam').arg(0,'internal')).build()}">Get Data Param</a>

이 메커니즘에 대해서 더 자세한 것은 다음을 참조하라: You can read more about this mechanism at http://docs.spring.io/spring-framework/docs/4.1.2.RELEASE/spring-framework-reference/html/mvc.html#mvc-links-to-controllers-from-views

12 Spring WebFlow integration

12.1 Basic configuration

 타임리프 + 스프링 통합 패키지는 Spring WebFlow와의 통합을 포함한다.

 WebFlow는 일부의 AJAX 수용력을 가진다, 표시된 페이지의 조각들을 렌더링하는, 특정한 이벤트가 트리거 됐을 때, 그리고 타임리프가 AJAX 요청에 참여할 수 있도록, 우리는 다른 ViewResolver 구현체를 사용해야한다, 다음과 같이 설정하여:

<bean id="thymeleafViewResolver" class="org.thymeleaf.spring4.view.AjaxThymeleafViewResolver">
    <property name="viewClass" value="org.thymeleaf.spring4.view.FlowAjaxThymeleafView" />
    <property name="templateEngine" ref="templateEngine" />
</bean>

...그리고 이런 ViewResolver는 WEbFlow의 ViewFactoryCreator로 설정될 수 있다:

<bean id="mvcViewFactoryCreator" 
      class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator">
    <property name="viewResolvers" ref="thymeleafViewResolver"/>
</bean>

여기서부터, 당신은 당신의 view-state에 타임리프 템플릿을 특정할 수 있따.

<view-state id="detail" view="bookingDetail">
    ...
</view-state>

 위의 예제에서, bookingDetail은 타임리프 템플릿이다, 일반적인 방법으로 특정된, 이해될 수 있는 TemplateEngine에서 설정된 Template Resolver에 이해될 수 있는.

12.2 AJAX fragments in Spring WebFlow

더보기

여기서는 Spring WebFlow를 사용하여, AJAX 조각을 만드는 방법을 설명한다. 당신이 WebFlow를 사용중이 아니면, Spring MVC 컨트롤러를 생성한다, AJAX 요청에 응답하고  HTML의 결과를 리턴하는 Spring MVC 컨트롤러를 생성하는 것은 "main :: admin " 을 컨트롤러 메서드에서 가져온다. 

WebFlow는 조각의 명세를 허용한다, AJAX에 <render> 태그를 사용함으로서:

<view-state id="detail" view="bookingDetail">
    <transition on="updateData">
        <render fragments="hoteldata"/>
    </transition>
</view-state>

이런 조각들(hotel, 이 경우)는 조각의 콤마로 분리된 리스트를 가능케 한다, th:fragment에 의해 마크업되어 특정된:

<div id="data" th:fragment="hoteldata">
    This is a content to be changed
</div>

 특정된 조각은 반드시 id 속성을 가져야하는 것을 기억하라, 그래야 브라우저에서 돌아가는 스프링 자바스크립트 라이브러리가 markup의 대체가 가능해진다.
<render> 태그는 DOM 선택자에 의해 특정될 수 있다:

<view-state id="detail" view="bookingDetail">
    <transition on="updateData">
        <render fragments="[//div[@id='data']]"/>
    </transition>
</view-state>

...그리고 이것은 no:fragment가 필요한 것을 의미하진 않는다:

<div id="data">
    This is a content to be changed
</div>

updateData 전환을 트리거하는 코드는 다음과 같다:

<script type="text/javascript" th:src="@{/resources/dojo/dojo.js}"></script>
<script type="text/javascript" th:src="@{/resources/spring/Spring.js}"></script>
<script type="text/javascript" th:src="@{/resources/spring/Spring-Dojo.js}"></script>

  ...

<form id="triggerform" method="post" action="">
    <input type="submit" id="doUpdate" name="_eventId_updateData" value="Update now!" />
</form>

<script type="text/javascript">
    Spring.addDecoration(
        new Spring.AjaxEventDecoration({formId:'triggerform',elementId:'doUpdate',event:'onclick'}));
</script>

'공부기록 > 독서 - 문서' 카테고리의 다른 글

타임리프 스프링 통합 문서 1~5  (0) 2021.07.05