JS에서 획득한 데이터를 컨트롤러로 혹은 컨트롤러에서 가공한 파일을 JS파일로 이동시켜 정보를 사용해야 하는 경우가 있다.
이번 프로젝트에선 clickDeparturePoint 라는 JS 배열을 Controller에서 사용하고,
컨트롤러에서 위 배열을 사용해 viaPoints라는 JSON형태의 데이터를 JS파일로 가져와 사용해야했다.
(ajaxLoad 는 구성 되어있음)
const commonLib = {
/**
* ajax 요청 공통 기능
*
* @param responseType : 응답 데이터 타입(text - text로, 그외는 json)
*/
ajaxLoad(url, method = "GET", data, headers, responseType) {
if (!url) {
return;
}
const csrfToken = document.querySelector("meta[name='csrf_token']")?.content?.trim();
const csrfHeader = document.querySelector("meta[name='csrf_header']")?.content?.trim();
if (!/^http[s]?/i.test(url)) {
let rootUrl = document.querySelector("meta[name='rootUrl']")?.content?.trim() ?? '';
rootUrl = rootUrl === '/' ? '' : rootUrl;
url = /^http[s]?/i.test(rootUrl) ? rootUrl + url : location.protocol + "//" + location.host + rootUrl + url;
// url = location.protocol + "//" + location.host + rootUrl + url;
}
method = method.toUpperCase();
if (method === 'GET') {
data = null;
}
if (data && !(data instanceof FormData) && typeof data !== 'string' && data instanceof Object) {
data = JSON.stringify(data);
}
if (csrfHeader && csrfToken) {
headers = headers ?? {};
headers[csrfHeader] = csrfToken;
}
const options = {
method
};
if (data) options.body = data;
if (headers) options.headers = headers;
return new Promise((resolve, reject) => {
fetch(url, options)
.then(res => responseType === 'text' ? res.text() : res.json()) // res.json() - JSON / res.text() - 텍스트
.then(data => resolve(data))
.catch(err => reject(err));
});
},
/**
* 에디터 로드
*
*/
editorLoad(id) {
if(!ClassicEditor || !id?.trim()) return;
return ClassicEditor.create(document.getElementById(id.trim()), {});
}
};
(ajaxLoad 메서드에선 responseType을 명시하지 않는다면 JSON으로 응답받음)
JS파일에서
commonLib.ajaxLoad('walking/map', 'POST', {clickDeparturePoint}, {
"Content-Type": "application/json"
})
.then(response => {
console.log('Server Response:', response);
callback(response);
})
.catch(error => {
console.error('Error:', error);
});
요청 본문에 Controller로 보내야할 clickDeparturePoint 배열을 담아서 보낼 ContentType을 명시한 후, 보낸다.
그리고 응답 받아야하는 정보를 받기위해서
@ResponseBody
@PostMapping("/map")
public String postMainMap(@RequestBody Map<String, List<Map<String, String>>> data) throws JsonProcessingException {
// Ajax로 선택한 마커 "clickDeparturePoint" 데이터 받아옴
List<Map<String, String>> clickDeparturePoint = data.get("clickDeparturePoint");
String viaPoints = mainMapMarkerService.viaMarkerLocation(clickDeparturePoint);
System.out.println("viapoints:" + viaPoints);
return viaPoints;
}
Controller에서 위와 같이 @ResponseBody를 사용한다.
-> 메서드의 반환값을 HTTP 응답 본문에 직접 작성
JS에 사용할 데이터(viaPoints)를 응답 본문에 담아서 보낸다. (viaPoints 는 service 파트에서 JSON으로 가공하였다.)
이렇게 한 후, JS에서 ajaxLoad 에서 응답 본문을 처리한다.
.then(response => {
console.log('Server Response:', response);
callback(response);
})
위와같이 다소 복잡할 거 같아 callback 메서드를 작성하여 처리하였다.
callback 메서드에서 핵심은
function callback(response) {
const viaPoints = response;
.
.
.
}
응답 본문에 담긴 데이터를 객체로 받아 원하는대로 처리하면 된다.
응답받은 데이터는 JSON으로 응답을 보냈기때문에 JS에서 JSON으로 수신받는다.
위와 같이 ajax처리를 한다면 화면이동(url 이동) 없이 데이터를 주고 받으며 자연스러운 화면 처리를 할 수 있다.
ajax 는 fetch 형식의 전송이다.
위와 다르게 화면의 이동이 있을때 데이터를 주고받는 방법이 있는데,
Controller -> JS 파일로 주고 받을때 템플릿을 통해 주고 받는다.(타임 리프 사용)
간단하게
Controller에서
model.addAttribute("startMarker", startMarker);
모델에 추가하고,
<div th:data-startMarker="*{startMarker}"></div>
타임리프에서 data- 속성을 사용하여 JS에서 접근하게끔 구성하였다.
data-* 속성은 HTML5에서 사용자 정의 데이터를 HTML 요소에 저장하는 방법. data-startMarker와 같은 형식은 data-로 시작하는 속성을 사용자가 자유롭게 정의할 수 있음을 의미. 이 속성에 저장된 데이터는 자바스크립트에서 element.dataset.startMarker로 접근 가능.
const startMarkerElement = document.querySelector('[data-startMarker]');
const startMarkerData = startMarkerElement.getAttribute('data-startMarker');
const startMarkerArray = JSON.parse(startMarkerData);
그 후, js에서 위와 같이 querySelector, getAttribute 를 통해 데이터를 가져온 후 JSON 형식을 JS형식으로 변경해주었다.