본문 바로가기
웹개발

quenswer(프로젝트)-QuestionList

by nyeongha 2024. 1. 8.

이제 기능을 구현할 차례입니다.

첫 메인페이지에는 질문 리스트와 페이징처리를 할것입니다.

질문리스트를 구현하기에 앞서 testclass를 통해 질문을 여러개 저장하려고합니다.

@Test
void questionCreateTest(){
    Random random = new Random(System.nanoTime());
    for (int i=0; i<200; i++){
        String subject=String.format((int)(random.nextInt(10000))+"번 문제 해결 팁 좀 알려주세요");
        String content=String.format((int)(random.nextInt(10000))+"코드 로직에 무슨 문제가 있는지 확인해주세요,,,문제를 잘모르겠어요.나온예시는 일단 다 맞는데..틀렸다고 나와서 고민이 커졌어요ㅠㅠ");
        this.questionService.createQuestion(subject,content);

    }
}

random.nextInt()함수를 사용하면 0에서 1사이의 소수가 랜덤으로 생성됩니다.

(int)random.nextInt(10000)을 통해 0에서 10000사이의 정수를 생성하고

String.format을 사용하여 문자열으로 변환시켜서 subject객체에 넣어줍니다. 

총 200개의 게시물을 생성해줍니다.

 

페이징 처리를 위해 QustionRepository에 메소드를 추가해줍니다.

Pageable한 객체를 모두찾아 Page<Question>을 반환해줍니다.

package com.example.quenswer.question;

import org.springframework.data.domain.Page;		#추가
import org.springframework.data.domain.Pageable;		#추가
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface QuestionRepository extends JpaRepository<Question,Integer> {
    List<Question> findBySubjectLike(String subject);
    Page<Question> findAll(Pageable pageable);		#추가
}

 

이제 서비스클래스를 만들어줍니다.

@Service애너테이션으로 서비스객체를 스프링에 등록합니다.

페이지를 반환하기 위해 질문리스트를 최신순으로 내림차순 정렬합니다.

이후 요청한 페이지를 PageRequest.of(요청 페이지번호,한페이지에 몇개의 질문을 보여줄건지,정렬방법)를 통해 pageable객체에 담습니다.

@Service
@RequiredArgsConstructor
public class QuestionService {
    private final QuestionRepository questionRepository;
    
    public Page<Question> getList(int page){
        List<Sort.Order> sorts = new ArrayList<>();
        sorts.add(Sort.Order.desc("createDate"));
        Pageable pageable = PageRequest.of(page, 6,Sort.by(sorts));
        return this.questionRepository.findAll(pageable);   //pageable객체가 Page<Question>타입임
    }
    
    }

 

Controller를 생성해줍니다.

@Controller애너테이션을 통해 Controller로 등록합니다.

private final로 service클래스를 선언하고,final이 붙은 속성을 포함하는 생성자를 자동으로 생성해주는 @RequiredArgsConstructor애너테이션을 추가합니다.

그리고 QuestionnController에서 사용하는 모든 url은 "/question"이 앞에 붙으므로 @RequestMapping("/question")애너테이션을 추가해줍니다.

redirect:<URL>:리다이렉트는 완전히 새로운 URL로 요청이 됩니다.
forward:<URL>:포워드는 기존 요청 값들이 유지된 상태로 URL이 전환됩니다.
@Controller
@RequiredArgsConstructor    //final이 붙은 속성을 포함하는 생성자를 자동으로 생성
@RequestMapping("/question")
public class QuestionController {

    private final QuestionRepository questionRepository;
    private final QuestionService questionService;
    
    @GetMapping("/")
    public String index(){
        return "redirect:/question/list";
    }
    
    @GetMapping("/list")
    public String QuestionList(Model model,@RequestParam(value="page", defaultValue="0") int page){
        Page<Question> Pq=questionService.getList(page);
        model.addAttribute("paging",Pq);
        return "question_list";
    }
    }

 

QuestionList메서드는 question_List.html을 찾아 클라이언트로 보내줍니다.

매개변수로는 model객체와 url의 page값(기본적으로 0)이 사용됩니다.

변수 Pq에 서비스에서 구현한 getList메서드를 통해 page값을 가진 Page<Question>타입의 객체를 담고,

model객체에 Pq를 paging이라는 변수로 추가해줍니다.

 

그러면 main/resorces/templates폴더안에 question_list화면을 만들어봅시다.

그전에 공통되는 layout부터 만들것입니다.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet"
          integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"
            integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm"
            crossorigin="anonymous"></script>
    <meta charset="UTF-8">
</head>
<body>
<header th:replace="~{header::headerFragment}"></header>
<th:block layout:fragment="content"></th:block>
</body>
</html>

 

부트스트랩을 사용할것이기때문에 부트스트랩파일을 link태그와 script태그로 가져왔습니다.

그리고 body안에 header를 넣을 것인데 헤더는 코드길이가 길어서 header.html로 생성하여 넣어줬습니다.

레이아웃 안에 삽입될 내용은 th:block태그 사이에 들어갈것입니다.

 

header.html은 아래와같습니다.

<header class="p-3 text-bg-dark" th:fragment="headerFragment">
  <div class="container">
    <div class="d-flex flex-wrap align-items-center justify-content-center justify-content-lg-start">
      <a href="/" class="d-flex align-items-center mb-2 mb-lg-0 text-white text-decoration-none">
        <svg class="bi me-2" width="40" height="32" role="img" aria-label="Bootstrap">
          <use xlink:href="#bootstrap"></use>
        </svg>
      </a>

      <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0">
        <li><a href="/question/" class="nav-link px-2 text-white">Home</a></li>
        <li><a href="#" class="nav-link px-2 text-white">Features</a></li>
        <li><a href="#" class="nav-link px-2 text-white">Pricing</a></li>
        <li><a href="#" class="nav-link px-2 text-white">FAQs</a></li>
        <li><a href="#" class="nav-link px-2 text-white">About</a></li>
      </ul>

      <form class="col-12 col-lg-auto mb-3 mb-lg-0 me-lg-3" role="search">
        <input type="search" class="form-control form-control-dark text-bg-dark" placeholder="Search..."
               aria-label="Search">
      </form>

      <div class="text-end">
        <button type="button" class="btn btn-outline-light me-2">Login</button>
        <button type="button" class="btn btn-warning" th:onclick="|location.href='@{/member/signup}'|">Sign-up</button>
      </div>
    </div>
  </div>
</header>

 

이제 본격적으로 question_list.html을 작성할것입니다.

이전에 모델객체에 paging을 넘겨받았습니다.

<div class="col" th:each="question:${paging}">를 통해 paging안에 있는 객체들을 question변수로 접근할것입니다.

각각 question변수를 통해 subject와 content를 보여주고,th:onclick을 통해 카드객체를 클릭하면 해당 아이디에 해당하는 질문 화면으로 넘어가게 했습니다.

question객체의 answerList의 길이를 사용하여 질문갯수도 표시하였습니다.

<!DOCTYPE html>
<html layout:decorate="~{layout}" xmlns:layout="http://www.w3.org/1999/xhtml">
<div layout:fragment="content" class="container my-3">
    <link rel="stylesheet" type="text/css" th:href="@{/questionList.css}">
<div class="p-5 mb-4 bg-body-tertiary rounded-3">
    <div class="container-fluid py-5">
        <h1 class="display-5 fw-bold">quenswer</h1>
        <p class="col-md-8 fs-4">한국형 stackoverflow</p>
        <button class="btn btn-primary btn-lg" type="button" th:onclick="|location.href='@{/question/create}'|">질문하기</button>
</div>
</div>

<div class="row row-cols-1 row-cols-md-3 g-4" id="posting">

    <div class="col" th:each="question:${paging}">
        <div class="card h-100" id=th:text="${question.id}">
            <div class="card-body" th:onclick="|location.href='@{/question/details/{id}(id=${question.id})}'|">
                <h5 class="card-title" th:text="${question.subject}"></h5>
                <p class="card-text" th:text="${question.content}"></p>

                <div class="d-flex justify-content-end">
                    <div class="badge bg-light text-dark p-2 text-start" >
                        <div th:if="${#lists.size(question.answerList) > 0}"
                             th:text="${#lists.size(question.answerList)}"></div>
                    </div>
                </div>
            </div>
        </div>
    </div>

</div>

<!-- 페이징처리 시작 -->
<div th:if="${!paging.isEmpty()}">
    <ul class="pagination justify-content-center">
        <li class="page-item" th:classappend="${!paging.hasPrevious} ? 'disabled'">
            <a class="page-link"
               th:href="@{|?page=${paging.number-1}|}">
                <span>이전</span>
            </a>
        </li>
        <li th:each="page: ${#numbers.sequence(0, paging.totalPages-1)}"
            th:if="${page >= paging.number-5 and page <= paging.number+5}"
            th:classappend="${page == paging.number} ? 'active'"
            class="page-item">
            <a th:text="${page}" class="page-link" th:href="@{|?page=${page}|}"></a>
        </li>
        <li class="page-item" th:classappend="${!paging.hasNext} ? 'disabled'">
            <a class="page-link" th:href="@{|?page=${paging.number+1}|}">
                <span>다음</span>
            </a>
        </li>
    </ul>
</div>
<!-- 페이징처리 끝 -->
</div>
</html>

 

questionList.css는 아래와 같습니다.

제목과 내용이 긴경우 박스밖으로 넘칤수있기에 제목(card-title)은 한줄,내용(p)는 3줄만 보이게하였고 나머지는 안보이게 overflow:hidden을 설정하였습니다.

.card-body{
    height:170px;
}
.card-title{
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 1;
    overflow: hidden;
}
.pagination{
    margin-top: 10px;
}
p{
    display:-webkit-box;
    -webkit-box-orient:vertical;
    -webkit-line-clamp:3;
    overflow:hidden;
}

 

page가 0(default)일때의 화면입니다.

page가 5일때의 화면입니다.