[카카오 지도 API] AJAX를 이용해 선택된 장소 데이터를 백단으로 전송하기
검색 완료 후 콜백 함수로 전달되는 데이터 살펴보기
키워드로 장소를 검색한 뒤 검색 결과는 콜백함수인 placesSearchCB()의 매개변수인 data에 담아진다.
콘솔에 data를 찍어보면 아래처럼 결과 목록에 있는 장소 데이터들이 출력된다. (결과 목록 페이지 당 15개씩 출력된다)
장소 데이터를 하나씩 전송하기
우선 항목 당 선택 버튼을 만들고, 선택한 항목들을 모아서 넘겨주기 위한 선택 완료 버튼도 미리 만들었다.
항목 당 선택 버튼을 만드는 것은 아래 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를 찍어보면 아래와 같이 나온다.
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은 기본 타입이라 항상 기본값(0.0)을 가지므로 null 비교가 불가능하다!
Double로 선언해 null 비교 해결 완료😓