Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- d
- /etc/network/interfaces
- 선형대수
- 스프링부트핵심가이드
- 서버설정
- 처음 만나는 AI수학 with Python
- 구멍가게코딩단
- 알파회계
- network configuration
- 스프링 시큐리티
- 자바편
- iterator
- 이터레이터
- 네트워크 설정
- ㅒ
- resttemplate
- Kernighan의 C언어 프로그래밍
- 코드로배우는스프링부트웹프로젝트
- 티스토리 쿠키 삭제
- 데비안
- GIT
- baeldung
- 처음 만나는 AI 수학 with Python
- 리눅스
- 페이징
- 친절한SQL튜닝
- 자료구조와함께배우는알고리즘입문
- 목록처리
- 코드로배우는스프링웹프로젝트
- 자료구조와 함께 배우는 알고리즘 입문
Archives
- Today
- Total
bright jazz music
Thymeleaf 타임리프 기본 + Simple Sidebar 본문
이 글은 구멍가게코딩단의 "코드로 배우는 스프링 부트 웹 프로젝트" 챕터 3의 내용을 다룬다.
챕터 4를 진행하려면 챕터 3가 선행되어야 하는 부분이 있기 때문이다.
난 그걸 모른 채로 챕터 4부터 시작했다.
따라서 이 포스팅에서 중간에 챕터 3의 내용을 끼워 둔다.
//SampleController.java
package com.example.guestbook.controller;
import com.example.guestbook.dto.SampleDTO;
import javassist.tools.rmi.Sample;
import lombok.extern.java.Log;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@Controller
@RequestMapping("/sample")
@Log4j2
public class SampleController {
@GetMapping("/ex1")
public void ex1(){
log.info("this is ex1-----------");
}
@GetMapping({"/ex2", "/exLink"}) //value의 속성값을 {}로 설정. 하나 이상의 url 매핑가능.
public void exModel(Model model) {
List<SampleDTO> list = IntStream.rangeClosed(1, 20)
.asLongStream()
.mapToObj(i -> {
SampleDTO dto = SampleDTO.builder()
.sno(i)
.first("First.. " + i)
.last("Last.. " + i)
.regTime(LocalDateTime.now())
.build();
return dto;
}).collect(Collectors.toList());
model.addAttribute("list", list);
/*
* SampleDTO 타입의 객체를 20개 추가하고 이를 Model에 담아서 전송
*
* public String exModel(Model model) {
return "/sample/ex2"; 이렇게 하지 않아도 페이지에 바인딩 됨. 신기.
* /exLink 경로에 관한 데이터는 exLink.html로 전달된다.
* */
}
@GetMapping({"/exInline"})
public String exInline(RedirectAttributes redirectAttributes) {
log.info("exInline....................");
SampleDTO dto = SampleDTO.builder()
.sno(100L)
.first("First...100")
.last("Last...100")
.regTime(LocalDateTime.now())
.build();
redirectAttributes.addFlashAttribute("result", "success");
redirectAttributes.addFlashAttribute("dto", dto);
//위 두 줄의 코드를 이용하여 result와 dto를 '/ex3'에 전달
return "redirect:/sample/ex3";
}
@GetMapping({"/ex3"})
public void ex3() {
log.info("ex3");
}
@GetMapping({"/exLayout1", "/exLayout2", "/exTemplate", "/exSidebar"})
public void exLayout1(){
log.info("exLayout..........");
}
}
<!--ex1.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<!-- xmlns 설정은 코드완성을 도와준다. -->
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${'Hello World'}"></h1>
</body>
</html>
<!--ex2.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<!-- <li th:each="dto : ${list}">-->
<li th:each="dto, state : ${list}">
[[${state.index}]] --- [[${dto}]]
<!--
대괄호+대괄호: 인라인태그로 별도의 태그 속성으로 지정하지 않고 사용하고 싶을 때 사용.
state : 상태 객체. 상태객체를 이용하면 순번이나 인덱스 번호, 홀/짝수 등을 지정 가능
상태객체에서는 index 또는 count라는 속성을 이용할 수 있다.
index는 0부터 시작하고 count는 1부터 시작한다는 것이 차이이다.
-->
</li>
</ul>
<ul>
<li th:each="dto, state : ${list}">
[[${state.count}]] --- [[${dto}]]
<!--
대괄호+대괄호: 인라인태그로 별도의 태그 속성으로 지정하지 않고 사용하고 싶을 때 사용.
state : 상태 객체. 상태객체를 이용하면 순번이나 인덱스 번호, 홀/짝수 등을 지정 가능
상태객체에서는 index 또는 count라는 속성을 이용할 수 있다.
index는 0부터 시작하고 count는 1부터 시작한다는 것이 차이이다.
-->
</li>
</ul>
<ul>
<li th:each="dto, state : ${list}" th:if="${dto.sno % 5 == 0}">
[[${dto}]]
<!--
Thymeleaf의 제어문 처리는 th:if ~ unless 등을 이용할 수도 있고,
삼항연산자 스타일을 사용할 수도 있다.
-->
</li>
</ul>
<ul>
<li th:each="dto, state : ${list}">
<span th:if="${dto.sno % 5 ==0}" th:text="${'------------------' + dto.sno}"></span>
<span th:unless="${dto.sno % 5 == 0}" th:text="${dto.first}"></span>
<!--
다른 언어들은 if~else를 하나의 묶음으로 처리하지만 Thymeleaf는 단독으로 처리한다.
위의 코드는 (th:if)sno가 5의 배수인 경우 sno만을 출력하고,
(th:unless)가 5의 배수가 아닌 경우 SampleDTO의 first만을 출력하라는 코드이다.
-->
</li>
</ul>
<ul>
<li th:each="dto, state : ${list}" th:text="${dto.sno % 5 == 0} ? ${dto.sno} : ${dto.first}">
<!--
타임리프는 단순 if와 같이 두개의 항만으로도 삼항연산자를 처리할 수 있다.
위의 예제는 sno가 5의 배수인 경우 sno만을 출력하고 나머지는 first를 출력하는 예제이다.
-->
</li>
</ul>
<style>
.target {
background-color: red;
/*
이와 같은 방식을 적용하면 특정 조건에만 css 클래스를 지정하는 작업 등을 쉽게 할 수 있다.
아래 코드는 sno가 5의 배수인 경우 특정한 css를 적용하는 예제이다.
*/
}
</style>
<ul>
<li th:each="dto, state : ${list}" th:class="${dto.sno % 5 == 0} ? 'target'" th:text="${dto}">
</li>
</ul>
<ul>
<th:block th:each="dto: ${list}">
<li th:text="${dto.sno % 5 == 0} ? ${dto.sno} : ${dto.first}"></li>
</th:block>
<!--
th:block은 별도의 태그가 필요하지 않기 때문에 반드시 태그에 붙어서 th:text나 th:value 등을 써야 하는
제약이 없다. 위 코드는 삼항연산자를 블록을 사용해서 나타낸 것이다.
th:block은 실제 화면에서는 html로 처리되지 않기 때문에 위와 같이 루프 등을 별도로 처리하는 용도로 자주 사용됨.
-->
</ul>
</body>
</html>
<!--ex3.html-->
<!DOCTYPE html>
<html lang="en" xmlns="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1 th:text="${result}"></h1>
<h1 th:text="${dto}"></h1>
<script th:inline="javascript">
var msg = [[${result}]];
var dto = [[${dto}]];
</script>
<!--
inline 속성은 주로 javaScript 처리에 유용하다. 위에서는 속성값이 javascript로 지정되었다.
브라우저에서 이 페이지를 개발자 모드로 확인하면 문자열은 자동으로 ""이 붙어 문자열이 된다.
객체의 경우는 자동으로 그 속성이 Json 포맷에 문자열이 되어 들어간다.
var msg = "success";
var dto = {"sno":100,"first":"First...100","last":"Last...100","regTime":"2022-06-27T12:19:26.2162662"};
위 코드를 자바스크립트 객체화 해서 사용하려면 JSON.parse(); 와 같은 형태로 ""를 추가해서 사용할 수 있다.
-->
</body>
</html>
<!--exLayout1.html-->
<!--
타임리프의 기능 중 특정 부분을 다른 내용으로 변경할 수 있는 것이 있음.
th:insert와 th:replace임. th:include도 있었지만 폐기됨.
th:replace : 기존 내용을 완전히 대체하는 방식
th:insert : 기존 내용의 바깥쪽 태그는 그대로 유지하면서 추가되는 방식
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Fragment Test</h1>
<div style="border: 1px solid blue">
<th:block th:replace="~{/fragments/fragment2}"></th:block>
</div>
<h1>Layout 1 - 1</h1>
<div th:replace="~{/fragments/fragment1 :: part1}"></div>
<h1>Layout 1 - 2</h1>
<div th:insert="~{/fragments/fragment1 :: part2}"></div>
<h1>Layout 1 - 3</h1>
<th:block th:replace="~{/fragments/fragment1 :: part3}"></th:block>
<!--
개발자 모드로 브라우저 페이지를 보면 th:insert를 이용하는 경우에는
<div> 태그 내에 다시 <div> 태그가 생성된 것을 확인 가능하다.
'th:replace'를 이용할 때 '::' 뒤에는 fragment의 이름을 지정하거나 css의 #id와 같은 선택자를 이용 가능
만약 :: 이하를 생략하면 해당 파일 전체 내용을 가져온다. 맨 위의 fragment2.html 참조
-->
</body>
</html>
<!-- fragment1.html
타임리프의 기능 중 특정 부분을 다른 내용으로 변경할 수 있는 것이 있음.
th:insert와 th:replace임. th:include도 있었지만 폐기됨.
th:replace : 기존 내용을 완전히 대체하는 방식
th:insert : 기존 내용의 바깥쪽 태그는 그대로 유지하면서 추가되는 방식식
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:fragment="part1">
<h2>Part 1 내용</h2>
</div>
<div th:fragment="part2">
<h2>Part 2 내용</h2>
</div>
<div th:fragment="part3">
<h2>Part 3 내용</h2>
</div>
</body>
</html>
<!--fragment2.html-->
<div>
<hr/>
<h2>Fragment2 File</h2>
<h2>Fragment2 File</h2>
<h2>Fragment2 File</h2>
<hr/>
</div>
<!--
exLayout2.html
화면 구성과 관련된 기능은 없다.
대신 상단에 target을 사용할 때 파라미터 2개를 사용한다.-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/fragments/fragment3:: target(~{this:: #ulFirst}, ~{this:: #ulSecond})}">
<!-- target에 들어갈 값-->
<ul id="ulFirst">
<li>AAA</li>
<li>BBB</li>
<li>CCC</li>
</ul>
<!-- target에 들어갈 값-->
<ul id="ulSecond">
<li>111</li>
<li>222</li>
<li>333</li>
</ul>
</th:block>
<!--
fragment3.html
기존의 jsp와 달리 Thymeleaf를 이용하면 특정한 태그를 파라미터처럼 전달해서
다른 th:fragment에서 사용할 수 있다.
-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div th:fragment="target(first, second)">
<!-- target 부분에는 first와 second라는 파라미터를 받을 수 있도록 구성.
내부적으로는 th:block으로 표현.
실제 target을 사용하는 작업은 exLayout2.html에서 진행-->
<style>
.c1 {
background-color: red;
}
.c2 {
background-color: blue;
}
</style>
<div class="c1">
<th:block th:replace = "${first}"></th:block>
</div>
<div class="c2">
<th:block th:replace = "${second}"></th:block>
</div>
</div>
</body>
</html>
<!--exLink.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<ul>
<li th:each="dto : ${list}">
<a th:href="@{/sample/exView}">[[${dto}]]</a>
</li>
</ul>
<!-- 모든 링크가 /exView로 연결됨. sno와 같은 파라미터를 추가해 주기 위해서는 아래와 같이 써야함 -->
<ul>
<li th:each="dto : ${list}">
<a th:href="@{/sample/exView(sno=${dto.sno})}">[[${dto}]]</a>
</li>
</ul>
<!-- 링크르 누르면 해당 링크의 sno가 표시됨. http://localhost:8080/sample/exView?sno=11 -->
<ul>
<li th:each="dto : ${list}">
<a th:href="@{/sample/exView/{sno}(sno = ${dto.sno})}">[[${dto}]]</a>
</li>
</ul>
<!-- /exLink/3 과 같이 sno를 path로 사용하려는 경우 http://localhost:8080/sample/exView/11 -->
<!--
Thymeleaf의 기본 객체와 LocalDateTime
타임리프는 내부적으로 여러 종류의 basic objects를 지원한다. 이를 사용하여 문자, 숫자, 웹에서 쓰이는 파라미터,
request, response, session 등을 응용할 수 있다.
JSP에서 이러한 기능을 이용하려면 별도의 JSTL 설정이 필요하다. 그러나 타임리프는 #numbers나, #dates 등을
별도의 설정 없이 사용할 수 있다. 예를 들어 화면에 출력하는 sno를 모두 5자리로 만들어야 하는 경우. 아래와 같다,
-->
<ul>
<li th:each="dto : ${list}">
[[${#numbers.formatInteger(dto.sno, 5)}]]
</li>
</ul>
<!--
내장객체들이 많은 기능을 지원하지만 java8의 LocalDate, LocalDateTime에 대해서는 복잡하다.
따라서 https://github.com/thymeleaf/thymeleaf-extras-java8time 을 이용하자.
이를 build.gradle에 추가해 줘야 한다. implementation()
-->
<ul>
<li th:each="dto : ${list}">
[[${dto.sno}]] ------ [[${#temporals.format(dto.regTime, 'yyyy/MM/dd')}]]
</li>
</ul>
</body>
</html>
<!--exTemplate.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/layout1 :: setContent(~{this::content})}">
<th:block th:fragment="content">
<h1>exTemplate page 하하하</h1>
</th:block>
</th:block>
<!--exSidebar.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:replace="~{/layout/basic :: setContent(~{this::content})}"> <!-- basic.html -->
<th:block th:fragment="content">
<h1>exSidebar Page</h1>
</th:block>
</th:block>
<!--basic.html-->
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<th:block th:fragment="setContent(content)">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<title>Simple Sidebar - Start Bootstrap Template</title>
<!-- Favicon-->
<link rel="icon" type="image/x-icon" th:href="@{/dist/assets/favicon.ico}" />
<!-- Core theme CSS (includes Bootstrap)-->
<!-- <link href="css/styles.css" rel="stylesheet" />-->
<link th:href="@{/dist/css/styles.css}" rel="stylesheet" />
</head>
<body>
<div class="d-flex" id="wrapper">
<!-- Sidebar-->
<div class="border-end bg-white" id="sidebar-wrapper">
<div class="sidebar-heading border-bottom bg-light">Start Bootstrap</div>
<div class="list-group list-group-flush">
<a class="list-group-item list-group-item-action list-group-item-light p-3" href="#!">Dashboard</a>
<a class="list-group-item list-group-item-action list-group-item-light p-3" href="#!">Shortcuts</a>
<a class="list-group-item list-group-item-action list-group-item-light p-3" href="#!">Overview</a>
<a class="list-group-item list-group-item-action list-group-item-light p-3" href="#!">Events</a>
<a class="list-group-item list-group-item-action list-group-item-light p-3" href="#!">Profile</a>
<a class="list-group-item list-group-item-action list-group-item-light p-3" href="#!">Status</a>
</div>
</div>
<!-- Page content wrapper-->
<div id="page-content-wrapper">
<!-- Top navigation-->
<nav class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
<!-- <div class="container-fluid"> 실제로 전달되는 부분 -->
<div class="container-fluid">
<button class="btn btn-primary" id="sidebarToggle">Toggle Menu</button>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ms-auto mt-2 mt-lg-0">
<li class="nav-item active"><a class="nav-link" href="#!">Home</a></li>
<li class="nav-item"><a class="nav-link" href="#!">Link</a></li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" id="navbarDropdown" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Dropdown</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="#!">Action</a>
<a class="dropdown-item" href="#!">Another action</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item" href="#!">Something else here</a>
</div>
</li>
</ul>
</div>
</div>
</nav>
<!-- Page content-->
<div class="container-fluid">
<th:block th:replace="${content}"></th:block>
<!-- <h1 class="mt-4">Simple Sidebar</h1>-->
<!-- <p>The starting state of the menu will appear collapsed on smaller screens, and will appear non-collapsed on larger screens. When toggled using the button below, the menu will change.</p>-->
<!-- <p>-->
<!-- Make sure to keep all page content within the-->
<!-- <code>#page-content-wrapper</code>-->
<!-- . The top navbar is optional, and just for demonstration. Just create an element with the-->
<!-- <code>#sidebarToggle</code>-->
<!-- ID which will toggle the menu when clicked.-->
<!-- </p>-->
</div>
</div>
</div>
<!-- Bootstrap core JS-->
<script th:src="@{https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js}"></script>
<!-- Core theme JS-->
<script th:src="@{js/scripts.js}"></script>
</body>
</th:block>
</html>
'Framework > Spring' 카테고리의 다른 글
guestbook : 03. 자동 일자/시간 처리(등록/수정 등) +Querydsl (1) (0) | 2022.06.29 |
---|---|
guestbook : 02. 기본 화면 레이아웃 구성 (0) | 2022.06.28 |
JpaRepository + @Query, nativeQuery = true (0) | 2022.06.23 |
JpaRepository + 페이징, 쿼리메소드 (0) | 2022.06.23 |
JPA 인터페이스 생성 + CRUD 테스트 (insert, findById, getOne, getReferenceById save, deleteById) (0) | 2022.06.12 |
Comments