1. 기존 HTML 속성의 대체

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>My Community</title>
</head>
<body>
    This is Index Page via Controller
    <!-- 1. 기존 속성의 대체 -->
    <button th:id="${ idName }" th:value="${ btnName }"></button>

</body>
</html>

 

package com.scala.personal.mycomm.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
    @GetMapping("")
    public String index(Model model) {
        model.addAttribute("idName", "my-id");
        model.addAttribute("btnName", "my-button");
        return "index";
    }
}
  • HTML 태그에 기본적으로 존재하는 속성들은 앞에 th: 를 붙이는 것으로 thymeleaf 문법을 사용할 수 있게 된다.

  • button 태그에 th:id, th:value 를 사용하여 id, value 속성을 정의하고 Controller에서 변수 값을 추가해보자

 

  • 그러고나서 서버를 구동하고 다시 접속해보면 button 태그의 id와 value가 작성한 thymeleaf 문법대로 들어가있는 것을 확인할 수 있다. 이처럼 th:<속성이름> 을 사용하면 thymeleaf 문법을 사용하여 기존 html의 속성값도 동적으로 설정하는 것이 가능하다. 

 

 

2. 반복문(순회)

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>My Community</title>
</head>
<body>
    This is Index Page via Controller
    <!-- 2. 반복문 -->
    <div th:each="num: ${ numList }">
        <p th:text="${ num }"></p>
    </div>

</body>
</html>

 

package com.scala.personal.mycomm.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

@Controller
public class HomeController {
    @GetMapping("")
    public String index(Model model) {
        model.addAttribute("numList", List.of(1, 6, 2, 7, 3, 9, 0, 13));
        return "index";
    }
}
  • thymeleaf 에서는 th:each 속성을 통해 순회 가능한 객체의 요소에 순차적으로 접근하는 기능을 제공한다.

  • th:each="<변수 이름>: ${ <순회할 객체 이름> }" 의 형태로 th:each 속성을 지정해주면 속성이 지정된 태그 내부에서는 변수 이름을 통해서 그 값에 접근할 수 있다.  위 코드에서는 Controller가 등록한 numList 의 각 요소를
    p 태그의 text로 사용하고있다.

 

  • 그 상태로 서버를 구동하고 접속해보면 위처럼 리스트에 넣어준 정수값들이 순차적으로 출력된 것을 볼 수 있다.

 

 

3. th:if / th:unless

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>My Community</title>
</head>
<body>
    This is Index Page via Controller
    <!-- 3. if문 -->
    <div th:if="${ score == 100 }">
        만점입니다!
    </div>
    <div th:unless="${ score == 100 }">
        만점이아닙니다.
    </div>

</body>
</html>

 

package com.scala.personal.mycomm.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
    @GetMapping("")
    public String index(Model model) {
        model.addAttribute("score", 100);
        return "index";
    }
}
  • th:ifth:unless 속성을 사용하면 조건식을 만족할 경우나 만족하지 않을 경우에만 해당 태그를 나타내도록 할 수 있다.

  • th:if=${ <조건문> }, th:unless=${ <조건문> } 의 형태로 조건문을 설정한 뒤 Controller에서 scroe를 100으로 설정해보자.

 

  • 기대한대로 조건문이 잘 동작하는 것을 확인할 수 있다.

  • 주의할 점은 일반적인 프로그래밍 언어의 if/else 관계와는 달리 th:unless 에도 조건문을 써줘야한다는 점이다.

  • 즉, th:unless 는 엄밀히 말하면 else 보다는 if not ~ 에 가까운 속성이다.

 

 

4. th:switch / th:case

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>My Community</title>
</head>
<body>
    This is Index Page via Controller
    <!-- 4. switch 문 -->
    <div th:each="input: ${ inputs }">
        <div th:switch="${ input }">
            <p th:case="1">입력된 숫자는 1입니다.</p>
            <p th:case="2">입력된 숫자는 2입니다.</p>
            <p th:case="3">입력된 숫자는 3입니다.</p>
            <p th:case="*">입력된 숫자는 1, 2, 3 중 어느것도 아닙니다.</p>
        </div>
    </div>

</body>
</html>

 

package com.scala.personal.mycomm.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

@Controller
public class HomeController {
    @GetMapping("")
    public String index(Model model) {
        model.addAttribute("inputs", List.of(3, 2, 6, 1, 9));
        return "index";
    }
}
  • th:switch / th:case 속성을 사용하면 switch 문을 사용할 수 있다. 

  • th:switch="${ <조건 변수 이름> }" 의 형태로 조건값을 지정하고 태그 내부에서 th:case="<케이스값>" 의 형태로 케이스를 지정해주는 것으로 케이스값이 매칭되는 태그만이 보여지도록 할 수 있다.

  • case 값으로 "*" 을 줄 경우 default 케이스의 역할을 한다.

 

  • 서버를 구동하고 접속해보면 정상적으로 switch문이 동작하는 것을 확인할 수 있다.

 

 

5. th:block

  • 크롬 개발자도구에서 위에서 사용했던 반복문과 조건문을 사용한 태그들이 실제 html로 어떻게 변환되고있는지 살펴보면 한가지 사실을 확인할 수 있다.  바로 속성이 적용된 태그 내부가 아닌 속성이 적용된 태그 자체가 반복되거나 조건에 따라 표시되고 있다는 것이다. 

  • 물론 div를 쓰지 않고 내부의 태그들마다 th:each나 th:if 등을 사용하는 방법도 있지만 내부의 태그가 많아지면 굉장히 번거로운 작업인데다 switch문의 경우 조건값을 넣어줄 바깥 태그의 존재가 필수적이다. 그렇다면 지금의 방식을 그대로 사용하면서 내부의 내용만을 반복하거나 조건에 따라 표시하고싶다면 어떻게 해야할까?

 

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>My Community</title>
</head>
<body>
    This is Index Page via Controller
    <!-- 2. 반복문 -->
    <th:block th:each="num: ${ numList }">
        <p th:text="${ num }"></p>
    </th:block>

    <!-- 3. if문 -->
    <th:block th:if="${ score == 100 }">
        만점입니다!
    </th:block>
    <th:block th:unless="${ score == 100 }">
        만점이아닙니다.
    </th:block>

    <!-- 4. switch 문 -->
    <th:block th:each="input: ${ inputs }">
        <th:block th:switch="${ input }">
            <p th:case="1">입력된 숫자는 1입니다.</p>
            <p th:case="2">입력된 숫자는 2입니다.</p>
            <p th:case="3">입력된 숫자는 3입니다.</p>
            <p th:case="*">입력된 숫자는 1, 2, 3 중 어느것도 아닙니다.</p>
        </th:block>
    </th:block>

</body>
</html>

 

package com.scala.personal.mycomm.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.util.List;

@Controller
public class HomeController {
    @GetMapping("")
    public String index(Model model) {
        model.addAttribute("numList", List.of(1, 6, 2, 7, 3, 9, 0, 13));
        model.addAttribute("score", 100);
        model.addAttribute("inputs", List.of(3, 2, 6, 1, 9));
        return "index";
    }
}
  • thymeleaf는 th:block이라는 태그를 지원한다.(thymeleaf가 지원하는 유일한 태그이다)

  • th:block은 thymeleaf 문법상으론 일반적인 태그처럼 동작하지만 html 생성시 자기 자신은 존재하지 않는 태그로 취급되기 때문에 th:block 태그에 th:each 나 th:if, th:switch 등을 사용하면 블록 내부의 내용만을 반복하거나 조건에 따라 표시할 수 있다.

  • 위와 같이 지금까지의 모든 예제에서 바깥의 div 태그를 th:block으로 대체해보자.

 

  • 깔끔하게 내부의 내용만이 반복되거나 조건에 따라 표시된 것을 볼 수 있다.

 

6. th:object

  • 태그에 th:object 속성을 사용하여 해당 태그 내에서 사용할 객체를 지정할 수 있다. 
  • 이 기능은 주로 데이터를 객체단위로 표시하거나 form 태그의 전송값을 객체에 매핑시키기 위해 사용된다.

 

package com.scala.personal.mycomm;

import org.springframework.stereotype.Component;

public class FormData {
    String attr1;
    int attr2;
    String attr3;

    public String getAttr1() {
        return attr1;
    }
    public int getAttr2() {
        return attr2;
    }
    public String getAttr3() {
        return attr3;
    }
    public void setAttr1(String attr1) {
        this.attr1 = attr1;
    }
    public void setAttr2(int attr2) {
        this.attr2 = attr2;
    }
    public void setAttr3(String attr3) {
        this.attr3 = attr3;
    }
}
  • 먼저 FormData 클래스를 정의한다.  FormData 클래스는 attr1, attr2, attr3의 3개의 필드를 가진다.
  • 필드값을 가져오거나 설정하기 위해 getter, setter를 만들어줘야한다.
<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>My Community</title>
</head>
<body>
    This is Index Page via Controller

    <!-- 5. object -->
    <form method="post" action="" th:object="${formData}">
        <input type="text" th:field="*{attr1}">
        <input type="number" th:field="*{attr2}">
        <select th:field="*{attr3}">
            <option value="a">A</option>
            <option value="b">B</option>
            <option value="c">C</option>
        </select>
        <input type="submit" value="제출">
    </form>

</body>
</html>
  • index.html에는 th:object를 사용한 form 태그를 작성하자. 주의할 점은 input 값마다 th:field 속성을 사용하여 formData의 각 속성과 매핑시켜줘야한다는 점이다. 여기서 앞서 알아봤던 thymeleaf의 표현식중 하나인 선택 변수 표현식을 사용한다. 

  • 다른 thymeleaf 속성들이 변수 표현식 내부에서 변수 이름의 앞뒤로 공백이 들어가도 변수를 인식하는데 문제가 없었던 것과 달리 th:object나 th:field에서 사용되는 변수 표현식이나 선택 변수 표현식에서는 객체명, 필드명 앞뒤로 공백이 들어가선 안된다는 점에도 주의하자.

 

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>My Community</title>
</head>
<body>
    <!-- 5. object -->
    <table th:object="${formData}">
        <tr>
            <th>attr1</th>
            <th>attr2</th>
            <th>attr3</th>
        </tr>
        <tr>
            <td th:text="*{attr1}"></td>
            <td th:text="*{attr2}"></td>
            <td th:text="*{attr3}"></td>
        </tr>
    </table>

</body>
</html>
  • form_result.html에서는 마찬가지로 th:object를 사용하여 formData를 사용할 객체로 지정하고 각 속성값을 테이블 형태로 표시한다. 여기서는 객체의 속성을 참조할 뿐이기 때문에 th:field 는 사용할 필요가 없다.

 

package com.scala.personal.mycomm.controller;

import com.scala.personal.mycomm.FormData;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class HomeController {
    @GetMapping("")
    public String index(@ModelAttribute FormData formData, Model model) {
        return "index";
    }

    @PostMapping("")
    public String formResult(@ModelAttribute FormData formData, Model model) {
        return "form_result";
    }
}
  • Controller에서는 formData 객체를 model에 추가해준다.  model.addAttribute("formData", new FormData()); 와 같이 직접 객체를 생성하여 추가하는 방법도 있지만 위와 같이 메소드의 매개변수에 formData를 추가하고 @ModelAttribute 어노테이션을 붙여주면 Spring이 자동으로 의존성을 주입하고 model에 추가까지 해준다.

  • @ModelAttribute(<속성이름>) 의 형태로 속성의 이름도 지정할 수 있다. 지정하지 않을 경우 매개변수 이름을 속성 이름으로 사용한다.

  • 물론 form의 post타입 요청에 매핑될 메소드도 정의해주고 이 메소드에서도 formData를 model에 추가해줘야한다.

 

 

  • 서버를 구동하고 접속해보면 attr1, attr2, attr3를 입력하는 form을 볼 수 있으며 이를 입력하고 제출버튼을 클릭하면 제출한 값이 정상적으로 테이블 형태로 표시되는 것을 볼 수 있다.
  • 구글링을 통해 알아본 @ModelAttribute 어노테이션에 대한 정보글에는 대부분 @ModelAttribute를 사용할 대상 클래스는 Bean 으로 등록되어있어야 한다고 되어있었는데, 코드를 보면 알다시피 FormData는 Bean으로 등록되어있는 상태가 아니다. @ModelAttribute가 내부적으로 인스턴스를 Bean으로 등록하여 사용하는것인지 애초에 Bean일 필요가 없는 것인지는 아직 모르겠다.

 

 

7. th:inline

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>My Community</title>
</head>
<body>
    This is Index Page via Controller

    <!-- 6. inline -->
    <p th:text="${ name }">My name is </p>

</body>
</html>

 

package com.scala.personal.mycomm.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
    @GetMapping("")
    public String index(Model model) {
        model.addAttribute("name", "David");
        return "index";
    }
}

 

  • th:text 나 th:utext 등을 사용하여 서버로부터 전달받은 값을 표시할 수 는 있지만 이 방식은 태그 내부에 작성한 값을 무시해버린다. 위의 코드의 경우에도 p태그 내부의 My name is 는 사라지고 David만이 출력된다.

  • 물론 이전에 배운 Literal Substitution 문법을 사용해서 th:text="|My name is ${ name }|" 와 같이 사용할 수는 있지만 보기에 썩 좋지는 않다. thymeleaf로 참조한 변수의 값을 HTML상의 텍스트와 함께 사용할 방법은 없을까?

 

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>My Community</title>
</head>
<body>
    This is Index Page via Controller

    <!-- 6. inline -->
    <p th:inline="text">My name is [[${ name }]]</p>

</body>
</html>
  • 이런 경우에 사용 가능한 것이 th:inline 속성이다. th:inline 속성값을 "text"로 지정하면 HTML상의 텍스트와 함께 thymeleaf 변수값을 사용할 수 있다.  [[${<변수이름>}]] 의 형태로 사용 가능하다.

 

<!DOCTYPE html>
<html lang="ko" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>My Community</title>
</head>
<body>
    This is Index Page via Controller

    <!-- 6. inline -->
    <script th:inline="javascript">
        let myName = [[${ name }]];
        console.log(myName);
    </script>

</body>
</html>

 

  • th:inline은 script 타입의 사용 또한 가능하다. th:inline 속성값을 "javascript"로 하면 script 태그 내에서 js 코드를 작성할 때도 thymeleaf 변수 값을 사용할 수 있다. 코드를 위와 같이 작성하고 서버를 구동하여 접속해보면 변수 name이 정상적으로 javascript에서 사용되고 있는 것을 볼 수 있다.

  • 이 기능은 dart 라는 구글이 javascript의 대체재로 개발한 스크립트언어에도 사용 가능하지만(th:inline="dart") 여기서 굳이 예시를 들지는 않기로 한다.

 

 

 이번 포스팅으로 thymeleaf를 사용하여 템플릿을 작성할 준비는 거의 끝났다. 다음 포스팅에서는 마지막으로 thymeleaf가 지원하는 fragment 기능에 대해서 알아보기로 하자. 

'프레임워크 > Spring' 카테고리의 다른 글

#13 모델 설계 1  (0) 2022.05.13
#12 Thymeleaf 3  (0) 2022.05.10
#10 Thymeleaf 1  (0) 2022.05.06
#9 MVC 구조 갖추기 - Controller/View 구현 1  (0) 2022.05.05
#8 Spring Boot 프로젝트  (0) 2022.05.04

+ Recent posts