💙 들어가며
Spring Boot에서 사용하는 View
타임리프(Thymeleaf)에 입문해보자.
layout이라는 라이브러리를 이용해서
얼마나 효율적으로 header와 footer를 처리하는지
잘 알아두고 써먹어보자!
✏️ 학습내용 정리
#타임리프(Thymeleaf)
Spring Boot를 사용하게 되면서
새롭게 배우게 된 템플릿 엔진 타임리프
무엇이 새로워진 것일까?
💡 타임리프(Themeleaf) 사용시 장점
타임리프는 자바 웹 애플리케이션 개발을 위한 서버 사이드 템플릿 엔진이다.
HTML의 태그속성을 활용하는 방식이다.
따라서 타임리프 문법은 HTML의 태그에 속성으로 들어가는 것을 잊지말자.
1. HTML 템플릿과 유사하게 생겼으며, 일반적인 HTML 문법을 사용한다.
이는 웹 디자이너와 개발자 간의 협업을 용이하게 만들며,
템플릿을 더 이해하기 쉽게 만든다.
(HTML의 일부를 EL로 대체할 필요 없이 그대로 살릴 수 있다.)
2. 서버 사이드 템플릿 엔진이기 때문에, 서버 측에서 동적 컨텐츠를 생성할 수 있다.
이는 클라이언트 측에서 렌더링하는 것보다
보안과 검증 측면에서 더 안전하고 예측 가능한 방식을 제공한다.
3. 스프링 프레임워크와 원활하게 통합되어 있으며,
스프링 기반 웹 애플리케이션을 개발하는 데 특히 효과적이다.
(스프링의 여러 기능과 연동하기 쉽다.)
출처: 챗지피티(https://chat.openai.com/)
기존에 JSP를 사용할 때는 협업할 때
퍼블리셔가 준 파일을
일부 코드블록이나 EL로 대체하는 부분이 필요했는데
타임리프는 그렇게 수정할 필요가 없다는 것이
가장 큰 장점이라고 볼 수 있겠다.
#STS 파일경로 관리
💡 홈 디렉토리는 어디?
홈디렉토리라고 생각할 수 있는 곳 3곳
우선순위는 순서대로
1. controller
2. static
3. webapp
CSS나 image와 같은 일반 리소스는 static에 둔다.
static이라는 이름 그대로 정적문서를 저장하는 경로이다.
static은 홈디렉토리다. (또 다른 홈디렉토리는 webapp)
동적으로 생성될 View인 HTML은 template에 둔다.
#layout 라이브러리
타임리프의 fragment 속성을 사용해서
문서를 생성할 때마다
반복되는 header와 footer 부분을
layout이라는 HTML 양식을 만들어
한 줄의 코드로 처리하는 방법을 알아보자.
이를 위해서는 먼저 pom.xml 설정파일에
<thymeleaf-layout-dialect> Dependency를 추가해야 한다.
💡 layout 라이브러리 추가하기
※ dialect는 타임리프를 사용하기 위한 지시자를 뜻한다.
(여기서는 layout을 사용하기 위한 dialect를 의미)
<dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
</dependency>
스프링 부트를 사용하고 있기 때문에
따로 객체를 만들 필요가 없이
위와 같이 라이브러리만 추가하는 방법으로
간단하게 해결할 수 있다.
#layout 준비 1: header, footer
실습을 위해서 먼저 template>inc라는 폴더를 생성하고
각각 header, footer, layout이라는 HTML 파일을
inc 디렉토리로 옮겨놓았다.
준비한 header와 footer HTML파일에는
각각 타임리프 속성을 사용하기 위한 준비작업으로
몇 가지 내용을 HTML 안에 추가해주어야 한다.
먼저, 부품으로 사용될 header와 footer의 HTML파일에는
각각 <html> 태그에 xml의 네임스페이스로
타임리프를 사용한다는 속성을 추가해준다.
💡 header, footer의 <html>에 추가
★★★ <head>아님!! ★★★
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<body>안에 각각 <header>와 <footer> 영역태그에는
th:fragment 속성을 태그에 추가해서
header이고 footer라는 것을 밝혀주면 된다.
참고로 이것은 필수가 아니다.
HTML 태그에 id나 class 속성이 이미 있다면
그것으로도 활용이 가능하기 때문!
💡 <header>, <footer>에 추가 (필수아님)
조각내서 붙이고 싶은 태그 맨 마지막에 th:fragment="이름" 속성 넣기
<header th:fragment="header" class="header md:header">
그러나 사실 꼭 이름을 새로 만들지 않아도 id나 class로도 지정할 수 있어서 필수 아님!
그래서 여기서는 th:fragment: header라는 속성 사용 안했음
<header class="header md:header">
코드로 확인해보자.
💡 header.html파일 부품으로 만들기
<!DOCTYPE html>
<!-- ★★★ 1. 가장 먼저 네임스페이스 지시자부터 붙이기 ★★★ -->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- ★★★ 아래부분은 원래 header에서 사용하는 CSS ★★★ -->
<link rel="stylesheet" href="../reset.css" type="text/css">
<link rel="stylesheet" href="../team-root.css" type="text/css">
<link rel="stylesheet" href="../util.css" type="text/css">
<link rel="stylesheet" href="../button2.css" type="text/css">
<link rel="stylesheet" href="../icon2.css" type="text/css">
<link rel="stylesheet" href="/css/component/header.css" type="text/css">
</head>
<body>
<!-- ★★★ 2. 조각내서 붙이고 싶은 태그 맨 마지막에 th:fragment="이름" 속성 넣기 ★★★ -->
<!-- ★★★ 그러나 사실 꼭 이름을 새로 만들지 않아도 id나 class로도 지정할 수 있어서 필수 아님! ★★★ -->
<!-- ★★★ 그래서 여기서는 th:fragment: header라는 속성 사용 안했음 ★★★ -->
<header class="header md:header">
<div class="content-box">
<h1>Rland</h1>
<nav class="main-menu">
<ul class="md:d:none">
<li><a class="icon icon-menu md:icon-size:2 icon-size-17 icon-color-white" href="">메뉴</a>
</li>
</ul>
<ul class="d:none md:d:flex">
<li>
<a class="icon icon-cart icon-size-17 icon-color-white" href="">장바구니</a>
</li>
<li>
<a class="d:inline-block icon icon-hover icon-alert icon-size-bell icon-color-white icon-count-with"
href="">알림
<span>2</span>
</a>
</li>
<li>
<a class="icon icon-person icon-size-17 icon-color-white" href="">로그인</a>
</li>
<li><a class="d:none icon icon-sign-out icon-size-17 icon-color-white" href="">로그아웃</a></li>
</ul>
</nav>
</div>
</header>
</body>
</html>
#layout 준비 2: layout
header와 footer가 준비되었다면
사용될 layout HTML에
부품을 붙여넣자.
먼저 header, footer와는 다르게
layout HTML의 <html> 태그에는
"타임리프"와 "layout 라이브러리" 2개의 xml 네임스페이스가 들어간다.
💡 layout의 <html>에 추가
★★★ <head>아님!! ★★★
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<body>부분에는 header와 footer, main을 각각
한 줄의 코드로 대체한다.
💡 main은 아직 안만들었는데?
header와 footer를 가진 layout HTML이
main의 내용을 가진 HTML파일에 붙을 것이기 때문에
main은 맨 마지막에 준비한다.
layout HTML이 main의 내용을 가진 HTML파일에
붙는 것이란 점을 잊지말자.
(layout HTML이 main HTML에 붙음!!!!!)
따라서 layout HTML파일에는
header와 footer의 CSS를 모두 붙여넣고
CSS경로는 반드시 절대경로로 기재한다.
header와 footer는 각각 타임리프의 th:replace 속성을 사용한다.
여기서 ~{}는 절대경로를 의미하고,
header와 footer에 th:fragment 속성을 사용하지 않고
class를 사용하기로 했기 때문에
앞에 경로 :: 뒤에는 class를 의미하는 .을 반드시 적어주어야 한다.
main은 layout 라이브러리의 layout:fragment 속성을 사용한다.
💡 layout의 <body>의 <div>에 추가
<body>
<!-- ★★★ 1. th:replace는 header와 footer부분, 완전히 대체되는 것 ★★★ -->
<div th:replace="~{inc/header :: .header}" />
<!-- ★★★ 2. layout:fragment는 main부분, 완전히 대체되는 것 / th가 아니다 ★★★ -->
<div layout:fragment="main"> 이 내용이 나오면 망한거임... 나오지 마라... </div>
<!-- ★★★ 1. th:replace는 header와 footer부분, 완전히 대체되는 것 ★★★ -->
<div th:replace="~{inc/footer :: .footer}" />
</body>
코드로 확인해보자.
💡 layout 준비하기
<!DOCTYPE html>
<!-- ★★★ 1. 가장 먼저 지시자부터 붙이기 th랑 layout 2개임!! ★★★ -->
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!--여기있는 css들이 layout:fragment="main"에 붙을거라서
얘네의 경로는 절대경로로 적어주어야 한다.-->
<link rel="stylesheet" href="/css/reset.css" type="text/css">
<link rel="stylesheet" href="/css/team-root.css" type="text/css">
<link rel="stylesheet" href="/css/style.css" type="text/css">
<link rel="stylesheet" href="/css/util.css" type="text/css">
<link rel="stylesheet" href="/css/button2.css" type="text/css">
<link rel="stylesheet" href="/css/icon2.css" type="text/css">
<link rel="stylesheet" href="/css/component/header.css" type="text/css">
<link rel="stylesheet" href="/css/component/footer.css" type="text/css">
</head>
<body>
<!-- ★★★ 2. th:replace는 header와 footer부분, 완전히 대체되는 것 ★★★ -->
<div th:replace="~{inc/header :: .header}" />
<!-- ★★★ 3. layout:fragment는 main부분, 완전히 대체되는 것 / th가 아니다 ★★★ -->
<div layout:fragment="main">
이 내용이 나오면 망한거임... 나오지 마라...
</div>
<!-- ★★★ 2. th:replace는 header와 footer부분, 완전히 대체되는 것 ★★★ -->
<div th:replace="~{inc/footer :: .footer}" />
</body>
</html>
#layout 준비 3: main
마지막으로 실제 내용이 될 main을 준비해보자.
💡 layout과 main 구분하기 (헷갈렸던 부분)
layout은 한 번 준비해놓으면
계속 반복되어서 사용될 것이기 때문에
파일이 1개이지만
main은 원하는 내용이
계속 달라지는 부분이기 때문에
여러개가 된다.
각각의 main HTML에 layout HTML이 붙는 것이다.
layout의 main부분에 들어갈 내용을 가진
list HTML파일을 준비하고
<html>태그에 타임리프와 layout 속성 2개를 모두 추가하고
추가로 준비된 layout이 있음을 밝히는
layout:decorate까지 적어준다.
💡 list(main)의 <html>에 추가
★★★ <head>아님!! ★★★
1. 가장 먼저 지시자부터 붙이기 th랑 layout 2개임!!
2. (여기는 main을 담당, 중심임)
그래서 나 레이아웃 데코있음을 밝혀줘서 헤더랑 푸터를 자동으로 붙일것임!
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{admin/inc/layout.html}">
참고로 모든 공통된 CSS는 layout에 붙여넣었으니
각각의 main이 될 HTML파일에는
공통으로 묶이는 CSS를 제외하고
각각의 main에만 필요한 CSS를 추가한다.
💡 layout의 <head> 부분이 main의 <head>에 붙는다.
따라서 중복되는 CSS경로를 없애기 위해서
main에는 공통분모에 해당하는 reset, root 등의 CSS는 적지 않는다.
마지막으로 <body>의 <main> 영역태그에
layout의 fragment 속성을 붙여준다.
💡list(main)의 <main>에 추가
<main layout:fragment="main">
코드로 확인해보자.
💡 list HTML 준비하기
<!DOCTYPE html>
<!-- ★★★ 1. 가장 먼저 지시자부터 붙이기 th랑 layout 2개임!! ★★★ -->
<!-- ★★★ 2. (여기는 main을 담당, 중심임) -->
<!-- 그래서 나 레이아웃 데코있음을 밝혀줘서 헤더랑 푸터를 자동으로 붙일것임! ★★★ -->
<html lang="en"
xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{admin/inc/layout.html}">
<head>
<!--layout에 붙을 때 head부분이 자동으로 같이 붙음-->
<!--title같이 중복되는 부분은 알아서 제거되고
링크한 css파일 같은것이 자동으로 같이 붙어서 별도로 관리하기 수월
(layout.html에 온갖 css 다 갖다 붙이지 않아도 된다.)-->
<!-- 각각의 main에 필요한 css만 여기 기재하면 됨 -->
<!--layout이 이 페이지로 붙는거임, 이 페이지가 layout으로 붙는게 아님-->
<title>Document</title>
</head>
<body>
<!-- ★★★ 3. 여기에 넣은 녀석이 곧 layout의 main을 덮어씌울 콘텐츠 ★★★ -->
<main layout:fragment="main">
<!--a링크 안에는 url을 쓰고 그 url을 타고 들어가서 경로에 있는 파일을 연다.-->
<a class="btn btn-primary" href="/index">홈으로</a><br> <!--BootStrap util 사용해서 버튼 만들기-->
<a href="/menu/detail">메뉴상세</a>
<!--================================================-->
<section class="search-section">
<!-- 1번 자식 -->
<h1>Rland Menu <span class="d:none">검색</span></h1>
<!-- 2번 자식 -->
<nav>
<h1 class="d:none">카테고리 검색 메뉴 목록</h1>
<ul>
<li><a href="">전체메뉴</a></li>
<li><a href="">커피</a></li>
<li><a href="">수제청</a></li>
<li><a href="">샌드위치</a></li>
<li><a href="">쿠키</a></li>
</ul>
</nav>
<!-- 3번 자식 -->
<section>
<h1 class="d:none">이름 검색 폼</h1>
<form action="">
<fieldset>
<legend class="d:none">이름 검색</legend>
<input type="text">
<input type="submit" value="검색">
</fieldset>
</form>
</section>
</section>
<section>
<h1 class="d:none">메뉴목록</h1>
<div>
<!--/menu/list와 같은 root이니 detail을 적을 때 /menu/detail이라고 하지 않고 그냥 detail이라고 한다.-->
<!--a태그는 인라인(텍스트)이다. 따라서 안에 블록(h1, div 등..)을 가질 수 없다.-->
<section th:each="m : ${list}">
<!--★★★여러개의 속성값을 전달하고 싶다면 쉼표(, )로 구분한다.★★★-->
<h1><a href="detail?id=" th:href="@{detail(id=${m.id})}"><span th:text="${m.korName}">카페라떼</span></a></h1>
<h2 th:text="${m.engName}">Cafe Latte</h2>
<div th:text="${m.id}">메뉴 아이디</div>
<div><span th:text="${#numbers.formatInteger(m.price,3,'COMMA')}">4,500</span><span>원</span><span>SOLD OUT</span></div>
<div><a href="detail?id="><img th:src="${m.img}" src="커피.png"></a></div>
<div><a class="" href="">좋아요<span th:text="${m.likeCount}">2</span></a></div>
<div>
<a class="" href="">장바구니</a>
<a class="" href="">결제하기</a>
</div>
</section>
<!-- <section>
<h1>카페라떼</h1>
</section>
<section>
<h1>카페라떼</h1>
</section>-->
</div>
</section>
<!--================================================-->
</main>
</body>
</html>
이렇게 되면 header와 footer를
layout이라는 양식으로 한 번에 처리할 수 있다!
💙 마치며
1.
★★★ 헷갈렸던 부분 ★★★
layout에 main이 붙는게 아니라
각각의 main에 layout이 붙는것이다.
따라서 main의 <html> 태그에는
layout:decorate를 이용해서
나를 꾸며줄 layout이 있음을 밝혀준다!