inblog logo
|
하쎄의 기술 일기장
    TeamProject

    [Travel] Ajax 통신 하기(여러 파라미터가 추가된)

    Ajax 통신 하기(여러 파라미터가 추가된)
    하세연's avatar
    하세연
    Sep 29, 2024
    [Travel] Ajax 통신 하기(여러 파라미터가 추가된)
    Contents
    문제상황.1. 지역과 연동하여 시군구 출력하기.2. 클릭시 적절한 내부 컨텐츠 출력하기3. 다양한 파라미터를 매개변수로 받는 서버 코드
    완성된 모습. 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

    하쎄의 기술 일기장

    RSS·Powered by Inblog