1. 컨트롤러와 화면 처리
프로젝트 controller 패키지에 BoardController 구성
🚀 가장 우선적으로 구현해야 하는 기능은 목록 기능이므로 list() 메서드를 추가하고 PageRequestDTO를 이용해서 페이징 처리와 검색에 이용
@Controller
@RequestMapping("/board")
@Log4j2
@RequiredArgsConstructor
public class BoardController {
private final BoardService boardService;
@GetMapping("/list")
public void list(PageRequestDTO pageRequestDTO, Model model) { }
}
1) 화면 구성을 위한 준비
화면 구성은 Thymeleaf를 이용해서 레이아웃을 적용할 수 있도록 준비. build.gradle 파일에 Thymeleaf의 레이아웃 관련 라이브러리의 존재 여부를 먼저 확인
implementation group: 'nz.net.ultraq.thymeleaf', name: 'thymeleaf-layout-dialect', version: '3.1.0'
2) 템플릿 디자인 적용
Thymeleaf에는 레이아웃 기능을 적용할 수 있어서 본격적인 디자인을 적용. 부트스트랩의 여러 무료 디자인 템플릿 중 'Simple Sidebar'를 이용
https://startbootstrap.com/template/simple-sidebar
- 무료로 이용할 수 있으므로 내려받은 후에 압축을 해제하고 모든 파일을 resources 의 static 폴더에 복사
- 프로젝트를 실행하고 브라우저에 'http://localhost:8080/'를 실행했을 때 index.html 파일이 실행되는지 확인
- 적용한 템플릿이 반응형이라 브라우저의 크기에 따라 다르게 보임
Thymeleaf 레이아웃으로 변경하기
✓ 레이아웃을 적용하려면 layout 폴더에 basic.html을 추가하고 index.html 내용을 그대로 복사해서 추가
레이아웃 적용
✓ basic.html의 상단에는 Thymeleaf의 네임스페이스들을 추가
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:th="http://www.thymeleaf.org">
✓ <head> 태그에는 링크가 존재하므로 이를 Thymeleaf 스타일로 변경. 경로를 '@{/..}'와 같은 형태로 처리. ('/'로 경로가 시작하는 것을 주의)
<link rel="icon" type="image/x-icon" th:href="@{/assets/favicon.ico}" />
<!-- Core theme CSS (includes Bootstrap)-->
<link th:href="@{/css/styles.css}" rel="stylesheet" />
</head>
✓ 'Page content' 부분에 layout:fragment를 적용
<!-- Page content-->
<div class="container-fluid" layout:fragment="content">
✓ 파일의 마지막 부분에 자바 스크립트를 위한 <th:block>을 추가하고 링크를 수정
<script th:src="@{/js/scripts.js}"></script>
<th:block layout:fragment="script">
</th:block>
컨트롤러를 통한 확인
✓ BoardController에는 list() 기능을 작성해 두었으므로 이를 활용해서 레이아웃까지 적용된 화면을 구성
✓ templates 폴더에 board 폴더를 생성하고 list.html을 추가
✓ list.html은 레이아웃 적용여부를 확인할 수 있도록 작성.
<html lang="en"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:th="http://www.thymeleaf.org"
layout:decorate="~{layout/basic.html}">
<div layout:fragment="content">
<h1>Board List</h1>
</div>
<script layout:fragment="script" th:inline="javascript">
console.log('script...');
</script>
2. 목록 화면 개발
1) BoardController
🚀 BoardController의 list()에 화면에 목록 데이터를 출력하는 코드 작성
🚀 list()가 실행되면 PageRequestDTO와 PageResponseDTO 객체가 화면으로 전달
@GetMapping("/list")
public void list(PageRequestDTO pageRequestDTO, Model model) {
PageResponseDTO<BoardDTO> responseDTO = boardService.list(pageRequestDTO);
log.info(responseDTO);
model.addAttribute("responseDTO", responseDTO);
}
2) list.html 수정
🚀 화면 상단에는 여러개의 <div>를 이용해서 화면 구성을 처리
🚀 실제 내용물의 출력은 <tbody>를 이용하고 Thymeleaf의 반복문을 이용해서 처리
<div layout:fragment="content">
<div clsss="col">
<div class="card">
<div class="card-header">
Board List
</div>
<div class="card-body">
<h5 class="card-title">Board List</h5>
<table class="table">
<thead>
<tr>
<th scope="col">Bno</th>
<th scope="col">Title</th>
<th scope="col">Writer</th>
<th scope="col">RegDate</th>
</tr>
</thead>
<tbody>
<tr th:each="dto:${responseDTO.list}">
<th scope="col">[[${dto.bno}]]</th>
<td>[[${dto.title}]]</td>
<td>[[${dto.writer}]]</td>
<td>[[${dto.regDate}]]</td>
</tr>
</tbody>
</table>
</div><!-- end card body -->
</div><!-- end card -->
</div><!-- end col -->
</div><!-- end row -->
3) 날짜 포멧팅 처리
🚀 화면의 등록일 regDate 이 너무 길고 상세하게 나오는 것을 날짜만 나오도록 처리
🚀 Thymeleaf의 #temporals라는 유틸리티 객체를 이용해서 처리
<td>[[${#temporals.format(dto.regDate, 'yyyy-MM-dd')}]]</td>
4) 페이지 목록의 출력
🚀 <table> 태그가 끝나는 부분과 이어지게 <div>를 구성해서 페이지 번호들을 화면에 출력
🚀 PageResponseDTO는 시작 번호 start와 끝 번호 end 만을 가지고 있으므로 특정 범위의 숫자를 만들기 위해서 Thymeleaf의 numbers를 이용
<div class="float-end">
<ul class="pagination flex-wrap">
<li class="page-item" th:if="${responseDTO.prev}">
<a class="page-link" th:data-num="${responseDTO.start -1}">Previous</a>
</li>
<th:block th:each="i: ${#numbers.sequence(responseDTO.start,responseDTO.end)}">
<li th:class="${responseDTO.page == i} ? 'page-item active' : 'page-item'">
<a class="page-link" th:data-num="${i}">[[${i}]]</a>
</li>
</th:block>
<li class="page-item" th:if="${responseDTO.next}">
<a class="page-link" th:data-num="${responseDTO.end + 1}">Next</a>
</li>
</ul>
</div>
📍 코드에서 중요한 부분은 #numbers.sequence()로 특정한 범위의 연속된 숫자를 만드는 부분과 <a> 태그에 'data-num'이라는 속성으로 페이지 처리하는 부분
📍 브라우저에서 직접 URL을 변경하는 방법으로 '/board/list?page=2'와 같이 페이지 번호를 쿼리 스트링으로 추가해서 페이지 변경되는 것을 확인
5) 검색 화면 추가
🚀 list.html 페이지에는 검색이 이루어질수 있도록 <table> 위에 별도의 card 영역을 만들어 검색 조건을 선택할 수 있도록 구성.
🚀 검색 조건은 페이지 이동과 함께 처리될 수 있도록 <form> 태그로 감싸서 처리
<div class="row mt-3">
<form action="/board/list" method="get">
<div class="col">
<input type="hidden" name="size" th:value="${pageRequestDTO.size}">
<div class="input-group">
<div class="input-group-prepend">
<select class="form-select" name="type">
<option value="">---</option>
<option value="t" th:selected="${pageRequestDTO.type =='t'}">제목</option>
<option value="c" th:selected="${pageRequestDTO.type =='c'}">내용</option>
<option value="w" th:selected="${pageRequestDTO.type =='w'}">작성자</option>
<option value="tc" th:selected="${pageRequestDTO.type =='tc'}">제목 내용</option>
<option value="tcw" th:selected="${pageRequestDTO.type =='tcw'}">제목 내용 작성자</option>
</select>
</div>
<input type="text" class="form-control" name="keyword" th:value="${pageRequestDTO.keyword}">
<div class="input-group-append">
<button class="btn btn-outline-secondary searchBtn" type="submit">Search</button>
<button class="btn btn-outline-secondary clearBtn" type="button">Clear</button>
</div>
</div>
</div>
</form>
</div>
📍 브라우저에 /board/list 뒤에 type과 keyword를 지정하면 화면에서 검색 항목과 키워드 부분으로 처리 되는것 확인
6) 이벤트 처리
🚀 페이지 번호를 클릭하거나 검색/필터링 조건을 눌렀을 때 이벤트 처리를 추가
- 페이지 번호를 클릭하면 검색 창에 있는 <form> 태그에 <input type='hidden'>으로 page를 추가한 후에 submit
- Clear 버튼을 누르면 검색 조건 없이 /board/list 호출
🚀 JSP에서 자바스크립트의 문자열을 템플릿으로 적용하고자 EL과 구분하기 위해 \${} 와 같은 방식을 사용했지만, Thymeleaf는 \ 없이 적용한다는 점을 제외하면 기존 코드를 그대로 사용
list.html의 마지막 부분에 <script> 영역을 작업
<script layout:fragment="script" th:inline="javascript">
document.querySelector(".pagination").addEventListener("click", function(e) {
e.preventDefault(); // a 태그일 경우 기본 이벤트 제거
e.stopPropagation();
const target = e.target;
if(target.tagName !== 'A') { // a 태그가 아니면 종료
return;
}
const num = target.getAttribute("data-num"); // 클릭한 a 태그의 data-num 속성(이동해야할 페이지번호)를 가져옴.
const formObj = document.querySelector("form");
formObj.innerHTML += `<input type='hidden' name='page' value='${num}'>`; // form 태그에 page 태그 추가
formObj.submit();
}, false)
document.querySelector('.clearBtn').addEventListener("click", function(e){
e.preventDefault();
e.stopPropagation();
self.location = '/board/list';
}, false)
</script>
[ 내용 참고 : IT 학원 강의 ]
'Spring & Spring Boot' 카테고리의 다른 글
[Spring Boot] 조회 처리 (0) | 2024.05.25 |
---|---|
[Spring Boot] 등록 처리 (0) | 2024.05.25 |
[Spring Boot] 서비스 계층과 DTO의 구현 (0) | 2024.05.25 |
[Spring Boot] Spring Data JPA (2) Querydsl (0) | 2024.05.25 |
[Spring Boot] Spring Data JPA (1) (0) | 2024.05.25 |