Project/project-board

[Project] 게시판 검색기능

수수한개발자 2022. 7. 15.
728x90

우리는 만약 게시판에 검색기능이 없다면 찾고자 하는 글을 일일이 찾아야 할것이다.

 

검색 기능은 꼭 있어야 할 기능이라고 생각해 구현해보려 한다. 

 

이미 전의 글 부터 순차적으로 하고 있기 때문에 추가해야할 코드는 적다.

 

SearchType

package com.jisu.projectboard.domain.type;

import lombok.Getter;

public enum SearchType {
    TITLE("제목"),
    CONTENT("본문"),
    ID("유저 ID"),
    NICKNAME("닉네임"),
    HASHTAG("해시태그");

    @Getter
    private final String description;

    SearchType(String description) {
        this.description = description;
    }
}

 

 

ArticleController

@GetMapping
public String articles(@RequestParam(required = false) SearchType searchType,
                       @RequestParam(required = false) String searchValue,
                       @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable,
                       ModelMap modelMap) {
    Page<ArticleResponse> articles = articleService.searchArticles(searchType, searchValue, pageable).map(ArticleResponse::from);
    List<Integer> paginationBarNumbers = paginationService.getPaginationBarNumbers(pageable.getPageNumber(), articles.getTotalPages());
    modelMap.addAttribute("articles", articles);
    modelMap.addAttribute("paginationBarNumbers", paginationBarNumbers);
    modelMap.addAttribute("searchTypes", SearchType.values());

    return "articles/index";
}

searchTypes 라는 것만 모델에 담아서 넘겨준다. SearchType은 enum인데 values 하면 리스트형태로 나온다.

 

ArticleControllerTest

@DisplayName("[view][GET] 게시글 리스트 (게시판) 페이지 - 검색어와 함께 기능")
    @Test
    public void givenSearchKeyword_whenSearchingArticlesView_thenReturnsArticlesView() throws Exception {
        // Given
        SearchType searchType = SearchType.TITLE;
        String searchValue = "title";
        given(articleService.searchArticles(eq(searchType), eq(searchValue), any(Pageable.class))).willReturn(Page.empty());
        given(paginationService.getPaginationBarNumbers(anyInt(), anyInt())).willReturn(List.of(0, 1, 2, 3, 4));

        // When & Then
        mvc.perform(get("/articles")
                        .queryParam("searchType", searchType.name())
                        .queryParam("searchValue", searchValue)
                )
                .andExpect(status().isOk())
                .andExpect(content().contentTypeCompatibleWith(MediaType.TEXT_HTML))
                .andExpect(view().name("articles/index"))
                .andExpect(model().attributeExists("articles"))
                .andExpect(model().attributeExists("searchTypes"));
        then(articleService).should().searchArticles(eq(searchType), eq(searchValue), any(Pageable.class));
        then(paginationService).should().getPaginationBarNumbers(anyInt(), anyInt());
    }

기존의 테스트 메소드에서 searchType과 searchValue를 같이 넣어서 호출되는지만 확인한다. SearchType은 enum이므로 toString()을 붙여준다.

테스트가 통과 되었으니 뷰에서 값만 받아오면 된다.

 

Index.html

<select class="form-control" id="search-type" name="searchType">
    <option>제목</option>
    <option>본문</option>
    <option>id</option>
    <option>닉네임</option>
    <option>해시태그</option>
</select>

Index.th.xml

<attr sel="main" th:object="${articles}">
    <attr sel="#search-type" th:remove="all-but-first">
        <attr sel="option[0]"
              th:each="searchType : ${searchTypes}"
              th:value="${searchType.name}"
              th:text="${searchType.description}"
              th:selected="${param.searchType != null && (param.searchType.toString == searchType.toString)}"
              />
    </attr>

이런식으로 id = search-type를 찾아서 하나만 빼고 다 지워줍니다. 그러면 제목만 남는데 거기에서 반복을해 SearchType을 value 가져 옵니다. 그 후의 SearchType의 한글로 써주었던 부가정보는 text로 뽑아내줍니다.

그리고 th: selected 는 검색하는 부분의 제목, 해시태그 등등 이 널이 아니면 param.searchType.toString url의 get 파라미터를 가져와서 현재의 검색하려는 searchtype과 같다면 해시태그로 검색을 하고 다시 페이지가 로드됐을때 다시 해시태그가 선택되어있기 하기 위해 넣었습니다.

위와같은 사진입니다. 

#pink로 검색은 잘 되어있는데 검색어는 지워져있습니다. 보통 네이버나 구글의 검색을 하면 검색한 내용도 남아있습니다. 그래서 이것도 유지되도록 해주도록 하겠습니다. index.th.xml 의 다음과 같이 넣어줍시다.

<attr sel="#search-value" th:value="${param.searchValue}"/>

 

 

url 의 searchValue=pink 라고 써있는부분을 가져와서  검색한 후에도 pink가 유지되고 있습니다.

 

근데 여기서 저번글의 제목, 해시태그, 작성자, 작성일을 누르면 정렬이 되도록 구현했습니다. 현재 상태에서 해시태그를 누르거나 페이징 처리한 페이지를 누르면 searchtype과 searchValue를 넘겨주지 않고 있기때문에 검색을 안한 상태의 밑의 사진 처럼 나옵니다.

이것도 잡아줘야 할 것 같습니다. 간단합니다. 

<attr sel="#article-table">
    <attr sel="thead/tr">
        <attr sel="th.title/a" th:text="'제목'" th:href="@{/articles(
    page=${articles.number},
    sort='title' + (*{sort.getOrderFor('title')} != null ? (*{sort.getOrderFor('title').direction.name} != 'DESC' ? ',desc' : '') : ''),
    searchType=${param.searchType},
    searchValue=${param.searchValue}
)}"/>
        <attr sel="th.hashtag/a" th:text="'해시태그'" th:href="@{/articles(
    page=${articles.number},
    sort='hashtag' + (*{sort.getOrderFor('hashtag')} != null ? (*{sort.getOrderFor('hashtag').direction.name} != 'DESC' ? ',desc' : '') : ''),
    searchType=${param.searchType},
    searchValue=${param.searchValue}
)}"/>
        <attr sel="th.user-id/a" th:text="'작성자'" th:href="@{/articles(
    page=${articles.number},
    sort='userAccount.userId' + (*{sort.getOrderFor('userAccount.userId')} != null ? (*{sort.getOrderFor('userAccount.userId').direction.name} != 'DESC' ? ',desc' : '') : ''),
    searchType=${param.searchType},
    searchValue=${param.searchValue}
)}"/>
        <attr sel="th.created-at/a" th:text="'작성일'" th:href="@{/articles(
    page=${articles.number},
    sort='createdAt' + (*{sort.getOrderFor('createdAt')} != null ? (*{sort.getOrderFor('createdAt').direction.name} != 'DESC' ? ',desc' : '') : ''),
    searchType=${param.searchType},
    searchValue=${param.searchValue}
)}"/>
    </attr>

    <attr sel="tbody" th:remove="all-but-first">
        <attr sel="tr[0]" th:each="article : ${articles}">
            <attr sel="td.title/a" th:text="${article.title}" th:href="@{'/articles/' + ${article.id}}"/>
            <attr sel="td.hashtag" th:text="${article.hashtag}"/>
            <attr sel="td.user-id" th:text="${article.nickname}"/>
            <attr sel="td.created-at/time" th:datetime="${article.createdAt}"
                  th:text="${#temporals.format(article.createdAt, 'yyyy-MM-dd')}"/>
        </attr>
    </attr>
</attr>

<attr sel="#pagination">
    <attr sel="li[0]/a"
          th:text="'previous'"
          th:href="@{/articles(page=${articles.number - 1}, searchType=${param.searchType}, searchValue=${param.searchValue})}"
          th:class="'page-link' + (${articles.number} <= 0 ? ' disabled' : '')"
    />
    <attr sel="li[1]" th:class="page-item" th:each="pageNumber : ${paginationBarNumbers}">
        <attr sel="a"
              th:text="${pageNumber + 1}"
              th:href="@{/articles(page=${pageNumber}, searchType=${param.searchType}, searchValue=${param.searchValue})}"
              th:class="'page-link' + (${pageNumber} == ${articles.number} ? ' disabled' : '')"
        />
    </attr>
    <attr sel="li[2]/a"
          th:text="'next'"
          th:href="@{/articles(page=${articles.number + 1}, searchType=${param.searchType}, searchValue=${param.searchValue})}"
          th:class="'page-link' + (${articles.number} >= ${articles.totalPages - 1} ? ' disabled' : '')"
    />
</attr>

모든 곳의 searchType과 searchValue를 같이 넘겨 주면 잘 동작 됩니다.

728x90

댓글