[방방곡곡] info 페이지 출력 - 2차

여러 파라미터를 추가하여 출력하기
HootJem's avatar
Dec 04, 2024
[방방곡곡] info 페이지 출력 - 2차
완성된 모습. 3가지의 정렬 기준을 갖고 화면을 표시한다.
notion image
 

문제상황.

버튼을 누를때 마다 변경되어야 하는 요소가 여럿 존재하고 있다.
  1. 서울 버튼이 눌렸을 때 바뀌어야 하는 것
notion image
  1. 시군구 버튼이 눌렸을 때 바뀌어야 하는것
notion image
 
한번에 다 처리하기 보다 하나씩 차근히 처리하기로 했다.
 

1. 지역과 연동하여 시군구 출력하기.

페이지가 로드될때 전국 을 제외한 지역은 db에서 불러와 뿌리고 있다.
notion image
notion image
시군구는 지역의 code 를 외래키로 갖고 있으므로 연관 관계가 있다.
 
따라서 지역이 클릭될 때 지역 code 를 전달하는 클릭 이벤트를 생성한다.
notion image
클릭 이벤트 내부 코드는 다음과 같다.
선택된 값을 전역변수로 관리 하여 파라미터로 넘길 예정. (클릭 이벤트 호출 마다 해당 값을 여기 저장시킨다.)
// 이렇게 했어도 좋았을걸.. let currentFilter = { areaCode: 'all', // 기본값은 전국 sigunguCode: '', // 기본값은 빈 값 sortBy: '' // 기본값은 빈 값 };
let activeSigunguItem = null; let currentAreaCode = 'all'; // 기본값을 전국으로 설정 let currentSigunguCode = ''; // 기본값은 빈 값 let currentSortBy = ''; async function areaClick(areaCode, areaName, element) { // 선택된 지역 스타일 지정 위해 active 클래스 추가 if (activeItem) { activeItem.classList.remove("active"); } element.classList.add("active"); $(".place__name2").remove(); // 시군구가 나올 div activeItem = element; currentAreaCode = areaCode; // 선택한 areaCode 업데이트 // area 를 클릭하면 sigungu 는 선택되지 않은 상태이므로 빈문자열로 초기화 currentSigunguCode = ''; await fetchArea(areaCode, areaName); // 지역클릭시 시군구 찾는 fetch let contentUrl = `get-info?area=${areaCode}`; await fetchContent(contentUrl); // 지역 코드만 매개변수로 받아 내부 컨텐츠 찾기 // "전국"을 클릭한 경우 if (areaCode === 'all') { $(".place__box1__title").text("# 전국"); } }
 
async function fetchArea(areaCode, areaName){ let response = await fetch(`get-sigungu?area=${areaCode}`); let responseBody = await response.json(); $(".place__box1__title").text(`# ${areaName}`); //왼쪽 상단 #서울 에 해당 //2. 받아온 데이터로 div 만들기 let sigunguList = responseBody.body; let placeName2Div = $("<div>").addClass("place__name2"); for(sigungu of sigunguList){ let dom = sigunguItem(sigungu); placeName2Div.append(dom); } $(".place__name1").after(placeName2Div) requestAnimationFrame(() => {placeName2Div.css({opacity: 1})}); } function sigunguItem(responseBody){ return ` <div onclick="sigunguClick('${responseBody.code}','${responseBody.area.code}',this)" class="place__name2__item">${responseBody.name}</div>`; }
여기 까지 하면 왼쪽 상단의 #지역 과 지역에 맞는 시군구가 랜더링 된다.
 

2. 클릭시 적절한 내부 컨텐츠 출력하기

시군구나 정렬기준 클릭 시 내부 컨텐츠가 알맞게 랜더링 되어야 한다. 적절한 파라미터를 설정하여 요청을 보내면 되도록 코드를 구성했다.
  • 시군구 클릭시 → 지역+시군구 코드
    • `get-info?area=${areaCode}&sigungu=${code}`
  • 인기순, 최신순 클릭 시 → 현재 선택하고 있는 기준 모두 포함하여 생성
    • get-info?area=${currentAreaCode}&sigungu=${currentSigunguCode}&sortBy=${currentSortBy}
async function fetchSigungu(areaCode, code){ let response = await fetch(`get-info?area=${areaCode}&sigungu=${code}`); let responseBody = await response.json(); let contentList = responseBody.body; return contentList; } async function sortContent(sortBy) { currentSortBy = sortBy; // 클릭된 정렬 기준 업데이트 console.log(currentSortBy); let url = `get-info?area=${currentAreaCode}&sigungu=${currentSigunguCode}&sortBy=${currentSortBy}`; fetchContent(url); }
 
이렇게 하면 필요한
내부 컨텐츠의 랜더링 및 비동기 통신은 fetchContent(url) 이 해결한다. 백 로직을 어떻게 구성해야 좋을까?
 

3. 다양한 파라미터를 매개변수로 받는 서버 코드

notion image
기존에는 파라미터를 필수가 아닌 상태로 받았다.
@requestParam 이 4개..5개가 되어가자 너무 혼잡하여 DTO 를 생성하기로 했다.
@GetMapping("/get-info") public ResponseEntity<?> Infoilter(@ModelAttribute ContentRequest.InfoRequestDTO requestDTO) { ContentResponse.infoListDTO infoListDTO= contentService.infoContentListWithArea(requestDTO); return ResponseEntity.ok(Resp.ok(infoListDTO)); }
  • RequestDTO
    • public class ContentRequest { @Data public static class InfoRequestDTO { private final String contentTypeId= "12"; private String area =""; private String sigungu =""; private String sortBy =""; private int page= 0; } }
      이 내용들이 모두 파라미터에 들어있었다.
      내가 담당한 페이지는 contentTypeId = “12”; 에 해당하는 요소들만 출력하기 때문에 하드코딩 해 놓았다.
       
  • service 코드
    • public ContentResponse.infoListDTO infoContentListWithArea(ContentRequest.InfoRequestDTO requestDTO){ List<Content> contentList; long count = contentRepository.countByContentTypeIdWithOption(requestDTO.getContentTypeId(), requestDTO.getArea(), requestDTO.getSigungu()); Pageable pageable = PageRequest.of(requestDTO.getPage(), 16); if(requestDTO.getArea().equals("all")){ contentList = contentRepository.findByContentTypeId(requestDTO.getContentTypeId(), pageable); }else { contentList = contentRepository.findByContentTypeIdAndOption(requestDTO.getContentTypeId(), requestDTO.getArea(), requestDTO.getSigungu(), requestDTO.getSortBy(), pageable); } List<Area> areaList = new ArrayList<>(); return new ContentResponse.infoListDTO(contentList, count,areaList); }
      메서드 이름이 infoContentListWithArea 라는 의미는 infoContentList 가 이미 존재한다는 것이다.
      그렇다… 리팩토링을 한다면 합쳐야 된다고 생각하는 코드.
      일단 진행한다.
    • countByContentTypeIdWithOption
      • notion image
        찾은 컨텐츠가 몇 건인지 알기 위해 작성한 jqpl 이다.
        StringBuilder jpql = new StringBuilder("select count(*) from Content where contentTypeId = :contentTypeId");
         
        StringBuilder 를 사용하여 파라미터에 따라 문자열을 추가 할 수 있게 하였다. contentTypeId는 필수로 들어갈 것이기 때문에 쿼리문 생성할 때 함께 넣어놓았다.
        전체코드는 아래와 같다. 매개변수를 확인할 때 전체인 all 이 아니면 매개변수가 있거나, DTO 에서 빈문자열로 초기화 했기 때문에 isEmpty 면 충분하다.
        public long countByContentTypeIdWithOption(String contentTypeId, String areaCode, String sigungucode) { StringBuilder jpql = new StringBuilder("select count(*) from Content where contentTypeId = :contentTypeId"); //지역 코드가 비어있지 않고 all(=전체)가 아닐때 if (!areaCode.isEmpty() &&!"all".equals(areaCode)) { jpql.append(" and areaCode = :areaCode"); } // 시군구 코드가 비어있지 않을때 if(!sigungucode.isEmpty()) { jpql.append(" and sigunguCode = :sigungucode"); } Query query = em.createQuery(jpql.toString()); query.setParameter("contentTypeId", contentTypeId); if (!areaCode.isEmpty() && !"all".equals(areaCode)) { query.setParameter("areaCode", areaCode); } if (!sigungucode.isEmpty()) { query.setParameter("sigungucode", sigungucode); } return (Long) query.getSingleResult(); }
        이걸 동적 쿼리라고 한다.
         
    • 내부 컨텐츠 찾는 findByContentTypeId
      • 동적쿼리로 나누면 될텐데 굳이 서비스에서 분기를 나눈 이유는 여러가지 쿼리 작성을 위해서 이다.
        List<Content> contentList; if(requestDTO.getArea().equals("all")){ contentList = contentRepository.findByContentTypeId(requestDTO.getContentTypeId(), pageable); }else { contentList = contentRepository.findByContentTypeIdAndOption(requestDTO.getContentTypeId(), requestDTO.getArea(), requestDTO.getSigungu(), requestDTO.getSortBy(), pageable); }
        일단 얘 역시 여러가지 파라미터를 동적으로 할당받고 있기 때문에 동적쿼리 작성이 필요하다.
        public List<Content> findByContentTypeIdAndOption(String contentTypeId, String area, String sigunguCode,String sortBy, Pageable pageable) { StringBuilder queryStr = new StringBuilder("select c from Content c where c.contentTypeId = :contentTypeId and c.areaCode= :area"); if (sigunguCode != null && !sigunguCode.isEmpty()) { queryStr.append(" and c.sigunguCode = :sigunguCode"); } if (sortBy != null && !sortBy.isEmpty()) { if (sortBy.equals("createdTime")) { queryStr.append(" order by c.createdTime desc"); } else if (sortBy.equals("viewCount")) { queryStr.append(" order by c.viewCount desc"); } } Query query = em.createQuery(queryStr.toString(), Content.class); query.setParameter("contentTypeId", contentTypeId); query.setParameter("area", area); if (sigunguCode != null && !sigunguCode.isEmpty()) { query.setParameter("sigunguCode", sigunguCode); } query.setFirstResult((int) pageable.getOffset()); // 시작 위치 query.setMaxResults(pageable.getPageSize()); // 한 페이지에 표시할 최대 개수 return query.getResultList(); }
        정렬 기준이 있거나 없을 수 있어서 먼저 확인을 해 주었다.
 
여기까지 하면 이런 데이터가 리턴된다. (너무 길어서 몇 개 삭제함)
ContentResponse.infoListDTO( count = 877, contents = [ ContentResponse.infoListDTO.ContentDTO( title = "가계해수욕장", contentId = 126273, addr1 = "전라남도 진도군 고군면 신비의바닷길 47", areaCode = 38, contentTypeId = 12, firstImage = "http://tong.visitkorea.or.kr/cms/resource/36/3079736_image2_1.jpg" ), ContentResponse.infoListDTO.ContentDTO( title = "거금생태숲", contentId = 2032450, addr1 = "전라남도 고흥군 금산면 거금일주로 1877", areaCode = 38, contentTypeId = 12, firstImage = "http://tong.visitkorea.or.kr/cms/resource/80/3347880_image2_1.JPG" ) ], areas = [ ContentResponse.infoListDTO.AreaDTO(code = 1, name = "서울"), ContentResponse.infoListDTO.AreaDTO(code = 2, name = "인천"), ContentResponse.infoListDTO.AreaDTO(code = 38, name = "전라"), ContentResponse.infoListDTO.AreaDTO(code = 39, name = "제주") ] )
프론트에서 count, contents, areas 안에 있는 애들을 꺼내 쓰면 끝
Share article

[HootJem] 개발 기록 블로그