관리 메뉴

나구리의 개발공부기록

타임리프 - 기본기능, 주석, 블록, 자바스크립트 인라인, 템플릿 조각, 템플릿 레이아웃 본문

인프런 - 스프링 완전정복 코스 로드맵/스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술

타임리프 - 기본기능, 주석, 블록, 자바스크립트 인라인, 템플릿 조각, 템플릿 레이아웃

소소한나구리 2024. 5. 19. 17:30

  출처 : 인프런 - 스프링 MVC 2편 - 백엔드 웹 개발 핵심 기술 (유료) / 김영한님  
  유료 강의이므로 정리에 초점을 두고 코드는 일부만 인용  

https://inf.run/GMo43


1. 주석

1) 주석

(1) BasicController - comments() 추가

@GetMapping("/comments")
public String comments(Model model) {
    model.addAttribute("data", "Spring!");
    return "basic/comments";
}

 

(2-1) comments.html

<!DOCTYPE html>
<!-- 타임리프 선언 및 head태그 -->

<body>
<h1>예시</h1>
<span th:text="${data}">html data</span>

<h1>1. 표준 HTML 주석</h1>
<!--
<span th:text="${data}">html data</span>
-->

<h1>2. 타임리프 파서 주석</h1>
<!--/* [[${data}]] */-->

<!--여러줄을 주석 처리 할 때-->
<!--/*-->
<span th:text="${data}">html data</span>
<!--*/-->

<!--로컬 웹 브라우저에서 직접 열때는 주석처리가 되고, 실제로 렌더링 된 경우에는 정상 렌더링이 됨-->
<h1>3. 타임리프 프로토타입 주석</h1>
<!--/*/
<span th:text="${data}">html data</span>
/*/-->

</body>
</html>

 

(2-2) 표준 HTML 주석: <!-- 내용 -->

  • 자바 스크립트의 표준 HTML 주석은 타임리프가 렌더링 하지 않고 그대로 남겨 둠

(2-3) 타임리프 파서 주석: <!--/* ${data}... 등의 내용 */-->

  • 타임리프의 진짜 주석
  • 렌더링에서 아예 주석 부분을 제거해버림
  • 여러줄을 주석처리할 때는 아래처럼 작성(*의 위치를 잘 보고 작성)
    <!--/*-->
    실행코드n개
    <!--*/-->

(2-4) 타임리프 프로토타입 주석: <!--/*/ 내용 /*/-->

  • 잘 사용하지 않는 특이한 주석
  • HTML 파일을 웹 브라우저에서 그대로 열면 HTML 주석이기 때문에 렌더링 되지 않지만, 타임리프 렌더링을 거치면 해당 부분의 내용이 정상 렌더링 되어 출력이 됨

2. 블록

1) 블록

(1) 타임리프 자체 태그

  • <th:block>은 HTML 태그가 아닌 타임리프의 유일한 자체 태그

(2) BasicController - block() 추가

@GetMapping("/block")
public String block(Model model) {
    addUsers(model);
    return "basic/block";
}

 

(3) block.html

  • 타임리프 특성상 HTML 태그안에 속성으로 기능을 정의해서 사용하는데 예제처럼 th:each만으로 해결하기 어려운 경우에 사용하면 됨
  • <th:block>은 렌더링시 태그가 제거됨, (th:each만 있으면 적용한 태그가 제거되지 않음)
  • 조건부 렌더링과 반복을 함께 사용하는 경우에도 사용함
  • 그러나 이기술은 타임리프에만 있는 특별한 태그이기 때문에 사용하지 않는 것을 권장함
<!DOCTYPE html>
<!-- 타임리프 선언 및 head태그 -->

<body>
<!--    div를 n개 이상 반복 시키고 싶을 때 th:block으로 묶어서 작성
        th:each만으로 해결하기 어려울 때 사용-->
<!-- th:block 없이 별도의 div태그로 감싸도 th:each만으로도 div태그를 n개 감쌀 수 있음-->
<th:block th:each="user : ${users}">
    <div>
        사용자 이름1 <span th:text="${user.username}"></span>
        사용자 나이1 <span th:text="${user.age}"></span>
    </div>
    <div>
        요약 <span th:text="${user.username} + ' / ' + ${user.age}"></span>
    </div>
</th:block>

<th:block th:if="${users.size() > 0}" th:each="user : ${users}">
    <div>
        사용자 이름: <span th:text="${user.username}"></span>
    </div>
</th:block>

</body>
</html>

3. 자바스크립트 인라인

1) 자바스크립트 인라인

(1) 설명

  • 자바스크립트에서 타임리프를 편리하게 사용할 수 있는 자바스크립트 인라인 기능을 지원함
  • <script th:inline="javascript"> 로 적용
@GetMapping("javascript")
public String javascript(Model model) {
    model.addAttribute("user", new User("userA", 10));
    addUsers(model);

    return "basic/javascript";
}

 

(2-1) javascript.html 

  • IDE에서 태그 부분에 빨간줄이 뜨지만 서버는 정상적으로 가동되고 렌더링도 정상적으로 됨
  • 실행 후 페이지 소스보기를 하면 자바스크립트에 정상적으로 값들이 적용된 것을 확인할 수 있음
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<!-- 자바스크립트 인라인 사용 전 -->
<script>
    var username = [[${user.username}]];
    var age = [[${user.age}]];

    //자바스크립트 내추럴 템플릿
    var username2 = /*[[${user.username}]]*/ "test username";

    //객체
    var user = [[${user}]];

</script>

<!-- 자바스크립트 인라인 사용 후 -->
<script th:inline="javascript">
    var username = [[${user.username}]];
    var age = [[${user.age}]];

    //자바스크립트 내추럴 템플릿
    var username2 = /*[[${user.username}]]*/ "test username";

    //객체
    var user = [[${user}]];
</script>

</body>
</html>

 

(2-2) 텍스트 렌더링

  • var username = [[${user.username}]];
    - 인라인 사용 전: var username = userA;
    - 인라인 사용 후: var username = "userA";
  • 인라인 사용 전 렌더링 결과는 userA라는 변수 이름이 그대로 남아 있는데 선언하지 않는 userA라는 변수가 var username에 대입하는 결과로 자바스크립트가 동작하면서 자바스크립트 문법 오류가 발생함
  • 실제로 개발자 모드로 들어가서 확인해보면 'Uncaught ReferenceError: userA is not defined' 로 오류가 발생하고 있는 것을 확인할 수 있음
  • 타임리프는 제대로 렌더링 했지만 userA가 변수명으로 사용되어서 자바스크립트가 오류가 발생하였고 실제로 개발자가 기대한 것은 "userA"라는 문자였을 것임(숫자의 경우에는 ""가 필요 없기 때문에 정상 렌더링이 됨)
  • 인라인 사용 후 렌더링 결과를 보면 문자 타입인 경우 자동으로 "를 포함해 줌
  • 추가로 자바 스크립트에서 문제가 될 수 있는 문자가 포함 되어있으면 자동으로 이스케이프 처리도 해줌 ex) " → \"

(2-3) 자바스크립트 내추럴 템플릿

  • 타임리프는 HTML 파일을 직접 열어도 동작하는 내추럴 템플릿기능을 제공하여 자바스크립트 인라인 기능을 사용하면 주석을 활용하여 이 기능을 사용할 수 있음
  • var username2 = /*[[${user.username}]]*/ "test username";
    - 인라인 사용 전: var username2 = /*userA*/ "test username";
    - 인라인 사용 후: var username2 = "userA";
  • 인라인 사용 전 결과는 순수하게 그대로 해석해 버려서 내추럴 템플릿 기능이 동작하지 않고 심지어 렌더링 내용이 주석처리 되어 버림
  • 인라인 사용 후 결과에서는 주석 부분이 제거되고 "userA"가 정확하게 적용 됨

(2-4) 객체

  • 타임리프의 자바스크립트 인라인 기능을 사용하면 객체를 JSON으로 자동 변환해줌
  • var user = [[${user}]];
    - 인라인 사용 전: var user = BasicController.User(username=userA, age=10);
    - 인라인 사용 후: var user = {"username" : "userA", "age":10};
  • 인라인 사용 전은 객체의 toString()이 호출된 값임
  • 인라인 기능을 사용하면 객체를 JSON 형태로 자동으로 변환해줌

(2-5) 자바스크립트 인라인 each(반복)

  • 자바스크립트 인라인은 each를 지원하며 아래와 같이 작성하면 반복문 처럼 요소를 하나씩 꺼내서 값을 꺼낼 수 있음
<!-- 자바스크립트 인라인 each -->
<script th:inline="javascript">
    [# th:each = "user, stat : ${users}"]
    var user[[${stat.count}]] = [[${user}]];
    [/]
</script>

4. 템플릿 조각

1) 템플릿 조각

(1) 설명

  • 웹 페이지를 개발할 때는 상단 영역, 하단영역, 좌측 카테고리 등등의 여러 페이지에서 함께 사용하는 공통 영역이 많이 있음
  • 그런 부분을 코드를 복사해서 사용한다면 해당 부분이 변경 되었을 때 여러 페이지를 모두 수정해야 하므로 비효율 적이게 됨
  • 이런 문제를 해결하기 위해 템플릿 조각과 레이아웃 기능을 지원함

(2) TemplateController

package hello.thymeleaf.basic;

@Controller
@RequestMapping("/template")
public class TemplateController {
    @GetMapping("/fragment")
    public String fragment() {
        return "template/fragment/fragmentMain";
    }
}

 

(3) footer.html

  • templates하위에 /template/fragment 디렉토리 경로를 만든 후 작성
  • "copy"와 "copyParam (param1, param2)" 부분은 사용자가 직접 이름을 지정하는 것으로 자유롭게 지정하되 해당 템플릿 조각을 불러오는 HTML 템플릿에서 작성한 이름으로 적어주면 됨
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">

<body>

<!--여러 html 파일에서 불러와서 쓸 수 있는 조각들,
여기서 "copy"는 사용자가 지정하는 fragment의 이름(변수)라고 보면 됨 -->
<footer th:fragment="copy">
    푸터 자리 입니다.
</footer>

<!--파라미터도 넣을 수 있음
마찬가지로 "copyParam(param1, param2)"도 사용자가 모두 자유롭게 이름을 지정할 수 있음 -->
<footer th:fragment="copyParam (param1, param2)">
    <p>파라미터 자리 입니다.</p>
    <p th:text="${param1}"></p>
    <p th:text="${param2}"></p>
</footer>

</body>
</html>

 

(4-1) fragmentMain.html

  • template/fragment/footer :: copy: template/fragment/footer.html에 있는 th:fragment="copy" 부분을 템플릿 조각으로 가져와서 사용한다는 의미임
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>부분 포함</h1>

<!--insert는 div부분 안에 template/fragment/footer 위치의 copy 부분을 가져옴-->
<h2>부분 포함 insert</h2>
<div th:insert="~{template/fragment/footer :: copy}"></div>

<!--replace는 div태그 자체를 대체 해버림-->
<h2>부분 포함 replace</h2>
<div th:replace="~{template/fragment/footer :: copy}"></div>

<h2>부분 포함 단순 표현식</h2>
<div th:replace="template/fragment/footer :: copy"></div>

<h1>파라미터 사용</h1>
<div th:replace="~{template/fragment/footer :: copyParam('데이터1', '데이터2')}"></div>

</body>
</html>

 

(4-2) 부분 포함 insert

  • <div th:insert="~{template/fragment/footer :: copy}"></div>
  • insert를 사용하면 현재 태그 내부에 조각을 추가함
  • 예제에서는 div 태그 내부에 조각이 추가 됨

(4-3) 부분 포함 replace

  • <div th:replace="~{template/fragment/footer :: copy}"></div>
  • replace는 div태그 자체를 대체해버림
  • 페이지 소스보기를 해보면 div 태그 자체가 없어지고 완전히 대체 된 것을 확인할 수 있음

(4-4) 부분 포함 단순 표현식

  • <div th:replace="template/fragment/footer :: copy"></div>
  • 템플릿 조각을 사용하는 코드가 경로와 이름정도만 있는 것처럼 단순하면 ~{ ... } 부분을 생략하여 사용할 수 있음
  • 복잡해지면 사용불가능함

(4-5) 파라미터 사용

  • <div th:replace="~{template/fragment/footer :: copyParam('데이터1', '데이터2')}"></div>
  • 파라미터를 전달하여 동적으로 조각을 렌더링 할 수 있음

5. 템플릿 레이아웃

1) 템플릿 레이아웃

(1) 설명

  • 일부 코드 조각을 가져와서 사용하는 개념을 더 확장해서 코드 조각을 레이아웃에 넘겨서 사용하는 방법
  • 예를 들어 <head>에 공통으로 사용하는 css, javascript 같은 정보들을 한 곳에 모아두고, 공통으로 사용하지만 각 페이지마다 필요한 정보를 더 추가해서 사용하는 방법임
  • 레이아웃 틀에 나의 코드를 추가하는 방법이라고 보면 됨

(2) TemplateController - layout() 추가

@GetMapping("/layout")
public String layout() {
    return "template/layout/layoutMain";
}

 

(3) base.html

  • template하위에 layout디렉토리를 생성 후 작성
  • 전체 사이트에서 공통으로 사용하는 레이아웃
<!--전체 사이트에서 공통으로 사용하는 레이아웃-->
<html xmlns:th="http://www.thymeleaf.org">

<!--매개 변수로 받은 값들을 각 위치에 삽입-->
<head th:fragment="common_header(title,links)">

    <title th:replace="${title}">레이아웃 타이틀</title>

    <!-- 공통 -->
    <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>

    <!-- 추가 -->
    <th:block th:replace="${links}"/>

</head>

 

(4) layoutMain.html

  • common_header(~{::title},~{::link})이 핵심
  • ::title은 현재 페이지의 title태그들을 전달함
  • ::link은 현재 페이지의 link태그들을 전달함
<!DOCTYPE html>
<!--base.html을 가져다 쓰는 html-->
<html xmlns:th="http://www.thymeleaf.org">

<!--::title -> title 태그의 내용을 넘김, ::link -> link 태그들을 넘김-->
<!--template/layout/base 위치의 common_header의 코드를 전부 replace-->
<head th:replace="template/layout/base :: common_header(~{::title},~{::link})">

<!--    레이아웃에 넣을 타이틀-->
    <title>메인 타이틀</title>

<!--    레이아웃에 추가할 link 태그들-->
    <link rel="stylesheet" th:href="@{/css/bootstrap.min.css}">
    <link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}">

</head>
<body>
메인 컨텐츠
</body>
</html>

 

(5) 결과 - 페이지 소스보기

  • th:replace로 base.html의 head가 layoutMain.html의 head들로 교체가 됨
  • 즉, layoutMain.html에 적힌 <title>태그와 <link> 태그 자체를 포함하여 common_header(~{::title}, ~{::link})로 인해base.html의 common_header(title, links) 부분과 완전히 교체가 됨
  • base의 공통부분은 그대로 유지되고 넘어간 link 태그들이 추가되어 출력되고, title도 메인 타이틀로 변경되었음
  • 레이아웃 개념을 두고 그 레이아웃에 필요한 코드 조각을 전달하여 완성되는 방식으로 이해하면 됨
<!DOCTYPE html>
<!--base.html을 가져다 쓰는 html-->
<html>

<!--::title -> title 태그의 내용을 넘김, ::link -> link 태그들을 넘김-->
<!--template/layout/base 위치의 common_header의 코드를 전부 replace-->
<head>

    <title>메인 타이틀</title>

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

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

</head>
<body>
메인 컨텐츠
</body>
</html>

2) 템플릿 레이아웃 확장

  • 템플릿 레이아웃의 개념을 head만 적용하는 것이 아니라 html 전체에 적용

(1) TemplateController - layoutExtend() 추가

@GetMapping("/layoutExtend")
public String layoutExtend() {
    return "template/layoutExtend/layoutExtendMain";
}

 

(2) layoutFile.html

  • template 하위에 layoutExtend디렉토리를 만든 후 작성
  • 레이아웃이 같은 여러개의 사이트가 있을 때 title과 
  • <html> 태그의 th:fragment= "layout(매개변수)": 매개변수에 넘어온 값들을 ${매개변수} 부분에 치환하여 값들을 출력
<!DOCTYPE html>

<!--레이아웃이 같은 여러개의 사이트가 있을 때 ${title}과 ${content} 부분만 각 페이지마다 달라야 한다고 가정-->
<html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:replace="${title}">레이아웃 타이틀</title>
</head>
<body>
<h1>레이아웃 H1</h1>
<div th:replace="${content}">
    <p>레이아웃 컨텐츠</p>
</div>
<footer>
    레이아웃 푸터
</footer>
</body>
</html>

 

(3) layoutExtendMain.html

  • html 태그 영역에 th:replace를 사용하여 layoutFile의 html 태그에 있는 layout(title, content)를 대체
  • ~{::title}과 ~{::section}으로 <title>과 <section>태그 자체를 layoutFile로 교체함
  • 위에서 했던 동일한 개념을 html 태그에 적용하여 html파일 자체에 적용했다고 보면 됨
<!DOCTYPE html>
<!--html부분에 th:replace를 작성하여 통으로 layoutFile로 replace, title과 section을 넘김-->
<html th:replace="~{template/layoutExtend/layoutFile :: layout(~{::title}, ~{::section})}"
      xmlns:th="http://www.thymeleaf.org">
<head>
    <title>메인 페이지 타이틀</title>
</head>
<body>
<section>
    <p>메인 페이지 컨텐츠</p>
    <div>메인 페이지 포함 내용</div>
</section>
</body>
</html>

 

 

(4) 결과

  • base.html의 title인 레이아웃 페이지 타이틀이 layoutExtendMain.html의 <title> 태그로 완전히 replace되어 메인 페이지 타이틀로 변경이 되었음
  • base.html의 content인 레이아웃 컨텐츠도 layoutExtendMain.html의 <section> 태그로 완전히 replace되어 메인 페이지 컨텐츠, 메인 페이지 포함 내용으로 바뀌었음
  • base.html의 <footer>태그는 아무것도 적용하게 없으므로 그대로 출력 되었음

(5) 정리

  • 템플릿 조각:
    - 사이트가 작으면 템플릿 조각으로 조각들을 불러와서 편리하게 사용하여 쉽게 사이트를 만들 수 있음
    - 레이아웃을 한번에 바꾸어야 할 때 작업이 많아지고 중복이 생기는 단점이 생김
  • 레이아웃:
    - 페이지가 많아지고 관리가 중요할 때에는 레이아웃 개념(템플릿 레이아웃)을 사용하는 것이 좋음
    - 중복을 최소화 하고 레이아웃 변경이 수월하나 체계적으로 관리해야 하는 단점이 존재함