WEB 커리큘럼 4주차 - 부트스트랩을 통한 화면 꾸미기

이은지·2023년 10월 9일
0

GDSC-Web

목록 보기
5/7
  • 점프 투 스프링부트 2.11부터 2.14까지 진행
  • 질문에 답변을 등록하고 보여주는 기능 구현

🌻답변 등록 기능 구현

💡답변 등록 버튼 만들기

먼저, 질문 상세 화면에 답변을 입력하기 위한 텍스트 창과 답변등록 버튼을 생성한다.

경로: \src\main\resources\templates\question_detail.html

<h1 th:text="${question.subject}"></h1>
<div th:text="${question.content}"></div>

<form th:action="@{|/answer/create/${question.id}|}" method="post">
    <textarea name="content" id="content" rows="15"></textarea>
    <input type="submit" value="답변등록">
</form>
  • 질문 상세 템플릿(question_detail.html)에 답변 저장을 위한 form, textarea, input 엘리먼트를 추가함
  • th: text: 태그 안의 텍스트를 서버에서 전달 받은 값에 따라 표현하고자 할 때 사용됨
  • th: action: 답변 등록 버튼을 눌렀을 때 전송되는 form의 action은 이 명령어로 사용. 즉, 답변 등록 버튼을 클릭할 시 post 방식으로 localhost:8080/answer/create/(질문ID) url이 호출될 것

💡답변 컨트롤러 만들기

경로: \src\main\java\com\gdsc\webboard\answer\AnswerController.java

(... 생략 ...)
@RequestMapping("/answer") //url 프리픽스를 '/answer'로 고정 
@RequiredArgsConstructor
@Controller
public class AnswerController {

    private final QuestionService questionService;

    @PostMapping("/create/{id}") //'/answer/create/{id}'와 같은 URL 요청시 createAnswer 메서드가 호출되도록 @PostMapping으로 매핑
    public String createAnswer(Model model, @PathVariable("id") Integer id, @RequestParam String content) {
        Question question = this.questionService.getQuestion(id);
        // TODO: 답변을 저장한다. 
        return String.format("redirect:/question/detail/%s", id);
    }
}
  • Post요청 시 createAnswer 메서드가 호출되며, 해당 id가 없을 경우 404 에러 발생
  • @RequestParam String content : @RequestParam을 통해 textarea의 내용, 즉 content를 불러옴

💡서비스에서 답변 생성 및 저장을 위한 create 메서드 만들기

경로: \src\main\java\com\gdsc\webboard\answer\AnswerController.java

(... 생략 ...)
@RequiredArgsConstructor
@Service
public class AnswerService {

    private final AnswerRepository answerRepository;

    public void create(Question question, String content) {
        Answer answer = new Answer(); //답변 객체 생성 
        answer.setContent(content);
        answer.setCreateDate(LocalDateTime.now());
        answer.setQuestion(question);
        this.answerRepository.save(answer);
    }
  • create 메서드는 입력으로 받은 question과 content를 사용하여 답변을 만들고 저장

💡create 메서드 AnswerController에서 사용하기

(... 생략 ...)
public class AnswerController {

    private final QuestionService questionService;
    private final AnswerService answerService;

    @PostMapping("/create/{id}")
    public String createAnswer(Model model, @PathVariable("id") Integer id, @RequestParam String content) {
        Question question = this.questionService.getQuestion(id);
        this.answerService.create(question, content);
        return String.format("redirect:/question/detail/%s", id);
    }
}
  • AnswerService의 create 메서드를 호출하여 답변을 저장한다.

💡저장된 답변 보여주기

경로: \src\main\resources\templates\question_detail.html

<h1 th:text="${question.subject}"></h1>
<div th:text="${question.content}"></div>
<h5 th:text="|${#lists.size(question.answerList)}개의 답변이 있습니다.|"></h5>
<div>
    <ul>
        <li th:each="answer : ${question.answerList}" th:text="${answer.content}"></li>
    </ul>
</div>
<form th:action="@{|/answer/create/${question.id}|}" method="post">
    <textarea name="content" id="content" rows="15"></textarea>
    <input type="submit" value="답변등록">
</form>
  • #lists.size : 타임리프가 제공하는 유틸리티, 객체의 길이를 반환
  • th: each : Question객체의 answerList를 반복 조회하여 각 answer의 content를
  • 로 표시함

🌻부트스트랩

전 트위터(현 엑스)에서 만든 오픈소스 웹 프레임워크로 디자이너 도움 없이 그럴듯한 UI를 만들 수 있게 도와줌

💡부트스트랩 다운로드

💡표준 HTML 구조 생성 및 부트스트랩 적용

지금까지 작성한 질문 목록, 질문 상세 템플릿은 표준 HTML 구조가 아니다. 어떤 웹 브라우저를 사용하더라도 웹 페이지가 동일하게 보이고 정상적으로 작동하게 하려면 반드시 웹 표준을 지키는 HTML문서를 작성해야 한다.

먼저 표준 HTML 구조의 기본 틀이 되는 layout.html 템플릿을 다음처럼 작성하자.

경로: \src\main\resources\templates\layout.html

<!doctype html>
<html lang="ko">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS --> 
    <link rel="stylesheet" type="text/css" th:href="@{/bootstrap.min.css}">
    <!-- sbb CSS -->
    <link rel="stylesheet" type="text/css" th:href="@{/style.css}">
    <title>Hello, sbb!</title>
</head>
<body>
<!-- 기본 템플릿 안에 삽입될 내용 Start -->
<th:block layout:fragment="content"></th:block>
<!-- 기본 템플릿 안에 삽입될 내용 End -->
</body>
</html>
  • layout.html 템플릿은 모든 템플릿이 상속해야 하는 템플릿으로 표준 HTML 문서의 기본 틀이 됨
  • body 엘리먼트 안의 <th:block layout:fragment="content"></th:block> 부분이 바로 layout.html을 상속한 템플릿에서 개별적으로 구현해야 하는 영역
  • bootstrap.min.css 스타일시트를 사용할 수 있도록 링크를 추가

💡템플릿 작성

경로: \src\main\resources\templates\question_list.html

<html layout:decorate="~{layout}" xmlns:layout="http://www.w3.org/1999/xhtml">
<div layout:fragment="content" class="container my-3">
    <table class="table">
        <thead class="table-dark">
        <tr>
            <th>번호</th>
            <th>제목</th>
            <th>작성일시</th>
        </tr>
    </thead>
    <tbody>
    <tr th:each="question, loop : ${questionList}">
        <td th:text="${loop.count}"></td>
        <td>
            <a th:href="@{|/question/detail/${question.id}|}" th:text="${question.subject}"></a>
        </td>
        <td th:text="${#temporals.format(question.createDate, 'yyyy-MM-dd HH:mm')}"></td>
    </tr>
    </tbody>
</table>
</div>
</html>
  • Layout:decorate를 통해 템플릿의 레이아웃으로 사용할 템플릿 지정
  • 속성 값 layout은 layout.html 파일을 의미
  • Layout:fragement = “content”의 경우 layout.html에서 layout:fragment = “content”로 설정했기 때문에 통일함
  • #temporals.format(날짜객체, 날짜포맷) : 날짜객체를 날짜포맷에 맞게 변환
  • class="container my-3"class="table"class="table-dark 등은 부트스트랩 스타일시트에 정의되어 있는 클래스
profile
소통하는 개발자가 꿈입니다!

0개의 댓글