🛠️ Project/🗺️ Kakao Maps API

[카카오 지도 API] AJAX를 이용해 선택된 장소 데이터를 백단으로 전송하기

별이⭐ 2024. 9. 12. 22:07

검색 완료 후 콜백 함수로 전달되는 데이터 살펴보기

키워드로 장소를 검색한 뒤 검색 결과는 콜백함수인 placesSearchCB()의 매개변수인 data에 담아진다.

콘솔에 data를 찍어보면 아래처럼 결과 목록에 있는 장소 데이터들이 출력된다. (결과 목록 페이지 당 15개씩 출력된다)

콘솔에 찍어보자!
console.log(data)로 찍어본 결과

 

장소 데이터를 하나씩 전송하기

우선 항목 당 선택 버튼을 만들고, 선택한 항목들을 모아서 넘겨주기 위한 선택 완료 버튼도 미리 만들었다.

선택 버튼, 선택 완료 버튼 생성

항목 당 선택 버튼을 만드는 것은 아래 getListItem() 함수 코드 내부에 itemStr+= 로 선택, 취소 버튼을 innerHTML에 추가해주었다.

// 검색결과 항목을 Element로 반환하는 함수입니다
function getListItem(index, places) {

    const el = document.createElement('li');
    let itemStr = '<span class="markerbg marker_' + (index+1) + '"></span>' +
            '<div class="info">' +
            '   <h5>' + places.place_name + '</h5>';

    if (places.road_address_name) {
        itemStr += '    <span>' + places.road_address_name + '</span>' +
            '   <span class="jibun gray">' +  places.address_name  + '</span>';
    } else {
        itemStr += '    <span>' +  places.address_name  + '</span>';
    }

    itemStr += '  <span class="tel">' + places.phone  + '</span>' +
        '</div>';

    itemStr += '<div><button class="select-btn">선택</button></div>';
    itemStr += '<button class="remove-btn">취소</button>';

    el.innerHTML = itemStr;
    el.className = 'item';

    return el;
}

 

원래 개별 항목 요소 어디든 누르면 onClick 이벤트가 발생되었는데, 선택 버튼을 만들었으니 선택 버튼을 눌렀을 때 이벤트가 발생하도록 displayPlaces() 함수에 for문 내부 코드를 수정했다.

// 항목 당 선택 버튼 클릭 이벤트
const selectBtn = itemEl.querySelector('.select-btn');
selectBtn.addEventListener('click', function (event) {
    handleSelectBtnClick(event, places[i], placePosition, i);
    console.log(places);
    console.log(places[i]);
    console.log(placePosition);
});

 

console.log를 찍어보면 아래와 같이 나온다.

place, places[i], placePosition

places는 콜백 함수의 data가 그대로 넘어온 것이니 data와 똑같고,

places[i]는 선택한 항목(장소)의 상세 데이터를 나타낸다.

그리고 placePosition은 new kakao.maps.LatLng(places[i].y, places[i].x)로 생성되며 위도, 경도 값을 나타낸다.

 

places[i] 값에는 장소 이름, 주소, 위도, 경도 내용 등이 다 포함되어있다.

선택한 항목에 대한 정보만 알면 되니, places[i] 값만 활용해서 백단에 넘겨주면 된다.

place[i] 값을 handleSelectBtnClick() 함수에서 매개변수 place로 받도록 하고, 함수 내에 sendPlaceData(place)라는 백단으로 전송하는 함수를 선언하자.

/**
 * 선택 버튼 클릭 이벤트 핸들러
 */
function handleSelectBtnClick(event, place, placePosition, index) {
    event.stopPropagation();    // 이벤트 버블링 방지
    addSelectedMarker(placePosition, index);
    sendPlaceData(place);
}

 

sendPlaceData(place)는 ajax를 사용해 장소 이름, 경도, 위도를 백단으로 넘긴다.

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
/**
 * 장소 데이터를 백단으로 전송
 */
function sendPlaceData(place) {
    const placeData = {
        name: place.place_name,
        longitude: place.x,
        latitude: place.y
    };

    $.ajax({
        url: '/api/save-location',
        method: 'POST',
        contentType: 'application/json',
        data: JSON.stringify(placeData),
        success: function(data) {
            console.log('success:', data);
        },
        error: function(error) {
            console.error('error:', error);
        }
    });
}

 

백단에서는 아래 방식으로 프론트에서 보낸 데이터를 받았다.

TripLocationDto
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class TripLocationDto {
    private String name;
    private Double longitude;
    private Double latitude;
}
MapApiController
@RestController
@RequiredArgsConstructor
@Slf4j
public class MapApiController {
    private final TripLocationService tripLocationService;
    @PostMapping("/api/save-location")
    public ResponseEntity<String> saveLocation(@RequestBody TripLocationDto tripLocationDto) {
        if (tripLocationDto.getName() == null || tripLocationDto.getName().isEmpty()) {
            return ResponseEntity.badRequest().body("장소명이 존재하지 않습니다.");
        }
        if (tripLocationDto.getLatitude() == null) {
            return ResponseEntity.badRequest().body("위도가 존재하지 않습니다.");
        }
        if (tripLocationDto.getLongitude() == null) {
            return ResponseEntity.badRequest().body("경도가 존재하지 않습니다.");
        }
        tripLocationService.saveLocation(tripLocationDto);
        return ResponseEntity.ok("장소가 추가되었습니다.");
    }
}

 

선택한 장소 데이터를 모아서 리스트로 백단에 보내기

이번엔 선택 완료 버튼을 누르면 넘겨주자

위 코드에서는 선택 버튼을 누르자마자 백단으로 데이터가 전송되었다면, 이번엔 선택 버튼을 누른 장소 데이터를 모아서 선택 완료 버튼을 누를 시 전송되도록 해보자.

 

우선 선택된 장소들을 저장할 selectedPlaces 배열을 선언한다.

그 다음 selectedPlaces 배열의 개별 요소에서 필요한 장소명, 위도, 경도 데이터만 추출해서 filteredPlaces 배열로 생성해 filteredPlaces를 AJAX로 넘겨준다.

 

백단에서는 List<>로 받으면 끝!

// 선택된 장소들을 저장할 배열
let selectedPlaces = [];

/**
 * 장소 데이터를 백단으로 전송
 */
function sendPlaceData(places) {
    // 백단에서 필요한 데이터만 포함된 배열 생성
    const filteredPlaces = selectedPlaces.map(place => ({
        name: place.place_name,
        longitude: place.x,
        latitude: place.y
    }));

    $.ajax({
        url: '/api/save-location',
        method: 'POST',
        contentType: 'application/json',
        data: JSON.stringify(filteredPlaces),
        success: function(data) {
            console.log('success:', data);
        },
        error: function(error) {
            console.error('error:', error);
        }
    });
}

/**
 * 선택 버튼 클릭 이벤트 핸들러
 */
function handleSelectBtnClick(event, place, placePosition, index) {
    event.stopPropagation();    // 이벤트 버블링 방지
    addSelectedMarker(placePosition, index);

    // selectedPlaces 배열의 각 요소 p의 id가 인자로 전달된 place의 id와 같은지 확인
    // .some()은 배열의 각 요소에 대한 테스트. true 또는 false 반환
    const isAlreadySelected = selectedPlaces.some(p => p.id === place.id);
    if (!isAlreadySelected) {
        // 같은 id를 가진 장소가 없다면 배열에 추가 (장소 중복 방지)
        selectedPlaces.push(place);
    }
}

/**
 * 선택 완료 버튼 클릭 이벤트 핸들러
 */
function handleSelectCompleteBtnClick() {
    if (selectedPlaces.length === 0) {
        alert('선택된 장소가 없습니다. 장소를 추가해 주세요.');
        return;
    }
    sendPlaceData(selectedPlaces);
}

/**
 * 선택 완료 버튼에 클릭 이벤트 리스너 추가
 */
document.querySelector('button[type="button"]').addEventListener('click', handleSelectCompleteBtnClick);

 

DTO 작성 시 겪은 문제

장소의 위도, 경도를 받을 때 DTO에 double 형으로 선언했는데 null 비교가 안 되는 것이였다.

원랜 double으로 선언했었다..

 

이유는 double은 기본 타입이라 항상 기본값(0.0)을 가지므로 null 비교가 불가능하다!

Double로 선언해 null 비교 해결 완료😓