Framework | Library | Tool/Template Engine

Thymeleaf에서 레이아웃 만들기

주정용 2020. 11. 30. 15:26
728x90

레이아웃 만들기

발번역입니당ㅠㅠ 개선하겠습니다!

템플릿 조각 정의


템플릿을 작성할 때, 레이아웃을 이루는 fragment는 th:fragment="조각이름"으로 선언합니다.

<!DOCTYPE html>

<html xmlns:th="http://www.thymeleaf.org">

  <body>

    <div th:fragment="copy">
      &copy; 2011 The Good Thymes Virtual Grocery
    </div>

  </body>

</html>

레이아웃에 th:insert="~{templatename :: fragmentname}"으로 삽입할 수 있습니다.

fragment의 추가는 th:fragment, th:replace 둘 다 가능합니다.

"~{templatename :: fragmentname}" == "templatename :: fragmentname"

<body>

  ...

  <div th:insert="~{footer :: copy}"></div>

</body>

아래와 같이 Th:fragment 속성 없이 id 값만으로도 호출할 수 있습니다.

...
<div id="copy-section">
  &copy; 2011 The Good Thymes Virtual Grocery
</div>
...
<body>

  ...

  <div th:insert="~{footer :: #copy-section}"></div>

</body>

replace와 insert의 차이!


  • th:insert : 레이아웃에서 호출하고 있는 태그의 body로 fragment(tag 포함)를 삽입합니다.
  • th:replace : 레이아웃에서 호출하고 있는 태그까지 fragment로 대체합니다.
<footer th:fragment="copy">
  &copy; 2011 The Good Thymes Virtual Grocery
</footer>
<body>

  ...

  <div th:insert="footer :: copy"></div>

  <div th:replace="footer :: copy"></div>

</body>
<body>

  ...

  <div>
    <footer>
      &copy; 2011 The Good Thymes Virtual Grocery
    </footer>
  </div>

  <footer>
    &copy; 2011 The Good Thymes Virtual Grocery
  </footer>

</body>

fragment에서 변수로 값 표현하기


<div th:fragment="frag (onevar,twovar)">
    <p th:text="${onevar} + ' - ' + ${twovar}">...</p>
</div>

fragment는 아래 두가지 방법으로 파라미터를 받아서 활용할 수 있습니다.

<div th:replace="::frag (${value1},${value2})">...</div>
<div th:replace="::frag (onevar=${value1},twovar=${value2})">...</div>

2번째 문법에서 파라미터 순서는 중요하지 않습니다.

<div th:replace="::frag (twovar=${value2},onevar=${value1})">...</div>

인자 없는 fragment에서 변수 사용하기


fragment를 선언할 때, 파라미터를 선언하지 않아도 변수를 사용할 수 있습니다.

<div th:fragment="frag">
    ...
</div>

위의 2번째 문법으로만 파라미터 선언없이 변수 값을 사용할 수 있습니다.

<div th:replace="::frag (onevar=${value1},twovar=${value2})">

이러한 방식은 th:replaceth:with을 사용한 것과 같습니다.

<div th:replace="::frag" th:with="onevar=${value1},twovar=${value2}">

유연하게 Fragment 사용하기 : Mark-up 태그 재활용


조각 표현식 덕분에 fragment를 지정할 수 있습니다.

이로써 매우 유연한 템플릿 레이아웃 메커니즘이 생성 됩니다.

아래 예제에서 titlelinks변수를 사용합니다.

<head th:fragment="common_header(title,links)">

  <title th:replace="${title}">The awesome application</title>

  <!-- Common styles and scripts -->
  <link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}">
  <link rel="shortcut icon" th:href="@{/images/favicon.ico}">
  <script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script>

  <!--/* Per-page placeholder for additional links */-->
  <th:block th:replace="${links}" />

</head>

위 fragment의 title과 links는 부분은 아래의 title, link태그들로 대체됩니다.

...
<head th:replace="base :: common_header(~{::title},~{::link})">

  <title>Awesome - Main</title>

  <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
  <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">

</head>
...

아래에 보이는 코드처럼, 템플릿의 <title><link>태그를 titlelinks변수의 값으로 사용됩니다.

...
<head>

  <title>Awesome - Main</title>

  <!-- Common styles and scripts -->
  <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
  <link rel="shortcut icon" href="/awe/images/favicon.ico">
  <script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>

  <link rel="stylesheet" href="/awe/css/bootstrap.min.css">
  <link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">

</head>
...

빈 Fragment 사용


특별한 표현식 인 빈 조각 ( ~{})은 대체할 태그를 지정하지 않는 데 사용할 수 있습니다 .

<head th:replace="base :: common_header(~{::title},~{})">

  <title>Awesome - Main</title>

</head>
...

Fragment ( links) 의 두 번째 매개 변수가 빈 프래그먼트로 설정되어 <th:block th:replace="${links}" />블록에는 아무 것도 대체되지 않습니다.

...
<head>

  <title>Awesome - Main</title>

  <!-- Common styles and scripts -->
  <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
  <link rel="shortcut icon" href="/awe/images/favicon.ico">
  <script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>

</head>
...

작동하지 않는 토큰 사용


fragment가 현재 태그를 기본값으로 사용하도록하려면 no-op을 프래그먼트의 매개 변수로 사용할 수도 있습니다.

...
<head th:replace="base :: common_header(_,~{::link})">

  <title>Awesome - Main</title>

  <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
  <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">

</head>
...

title에 no-op이 적용되어 title 값은 대체되지 않습니다.

  <title th:replace="${title}">The awesome application</title>

따라서 결과는 다음과 같습니다.

...
<head>

  <title>The awesome application</title>

  <!-- Common styles and scripts -->
  <link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css">
  <link rel="shortcut icon" href="/awe/images/favicon.ico">
  <script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script>

  <link rel="stylesheet" href="/awe/css/bootstrap.min.css">
  <link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css">

</head>
...

Fragment 조건부 삽입


위의 표현식 덕분에 우리는 매우 간단하고 우아한 방법으로 fragment의 조건 삽입을 수행할 수 있습니다.

예를 들어, 사용자가 관리자인 경우에만common :: adminhead를 삽입하고 그렇지 않은 경우 아무것도 삽입하지 않기 위해 ~{}표현을 사용할 수 있습니다.

...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>
...

또한 지정된 조건이 충족되는 경우에만 fragment를 삽입하기 위해 no-operation 토큰을 사용할 수 있지만 조건이 충족되지 않으면 태그를 수정하지 않고 그대로 둘 수 있습니다.

...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : _">
    Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
...

또한, 플래그를 사용하여 템플릿 리소스가 있는지 확인하도록 template resolver를 구성한 경우 기본작업 checkExistence의 조건으로 fragment를 사용할 수 있습니다 .

...
<!-- The body of the <div> will be used if the "common :: salutation" fragment  -->
<!-- does not exist (or is empty).                                              -->
<div th:insert="~{common :: salutation} ?: _">
    Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>
...

프로토타이핑에서 유용한 th:remove


th:remove는 값에 따라 5가지 방식으로 작동할 수 있습니다.

  • all: 선언된 태그와 모든 자식을 제거합니다.
  • body: 하위 태그만 제거합니다.
  • tag: 선언된 태그만 제거합니다.
  • all-but-first: 첫 번째 태그를 제외하고 포함하는 태그의 모든 하위를 제거합니다.
  • none: 어떤 태그도 제거하지 않습니다.
<table>
  <thead>
    <tr>
      <th>NAME</th>
      <th>PRICE</th>
      <th>IN STOCK</th>
      <th>COMMENTS</th>
    </tr>
  </thead>
  <tbody th:remove="all-but-first">
    <tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'">
      <td th:text="${prod.name}">Onions</td>
      <td th:text="${prod.price}">2.41</td>
      <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
      <td>
        <span th:text="${#lists.size(prod.comments)}">2</span>
        comment/s
        <a href="comments.html" 
           th:href="@{/product/comments(prodId=${prod.id})}" 
           th:unless="${#lists.isEmpty(prod.comments)}">view</a>
      </td>
    </tr>
    <tr class="odd">
      <td>Blue Lettuce</td>
      <td>9.55</td>
      <td>no</td>
      <td>
        <span>0</span> comment/s
      </td>
    </tr>
    <tr>
      <td>Mild Cinnamon</td>
      <td>1.99</td>
      <td>yes</td>
      <td>
        <span>3</span> comment/s
        <a href="comments.html">view</a>
      </td>
    </tr>
  </tbody>
</table>

레이아웃 상속


단일 HTML파일을 레이아웃으로 이용하기 위해서, fragment가 사용될 수 있습니다.

<!DOCTYPE html>
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:replace="${title}">Layout Title</title>
</head>
<body>
    <h1>Layout H1</h1>
    <div th:replace="${content}">
        <p>Layout content</p>
    </div>
    <footer>
        Layout footer
    </footer>
</body>
</html>

위의 예제는 titlecontent를 파라미터로 포함하는 fragment를 레이아웃으로 선언하고 있습니다.

두 파라미터 태그 모두 상속하는 페이지에서 fragment 표현식에 의해서 대체될 것입니다.

<!DOCTYPE html>
<html th:replace="~{layoutFile :: layout(~{::title}, ~{::section})}">
<head>
    <title>Page Title</title>
</head>
<body>
<section>
    <p>Page content</p>
    <div>Included on page</div>
</section>
</body>
</html>

위의 예제에서 HTML 태그는 레이아웃 fragment로 대체될 것입니다. 반면, 레이아웃에서 title, content는 title과 section 태그로 각각 대체될 것입니다.

출처 : http://www.thymeleaf.org