일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 스프링
- 혼공컴운
- 알고리즘
- VueDevTools
- 이벤트루프
- Java
- GitHub
- 한빛미디어
- 도메인 주도 개발 시작하기
- Git
- Junit5
- 구글 엔지니어는 이렇게 일한다
- WebTestClient
- AWS 비용 최적화 바이블
- 1436
- 이것이자바다
- 러닝GO
- 이것이안드로이드다
- pinia
- 네트워크
- 자바스크립트
- cicd
- 2817
- 헤드퍼스트 디자인패턴
- vue-router
- MySQL
- Vue
- JavaScript
- HTTP
- 백준
- Today
- Total
이시안 개발 블로그
auto complete 구현하기 본문
✨ 왜?
현재 하고있는 프로젝트에서 구현하고 싶었던 기능 중 하나인 auto complete
Spring에서 비동기로 데이터를 받아서 보여주는 것을 해보고 싶었습니다.
사용자 입장에서 현재 검색어가 유효한 것인지 아닌지 정보를 전달하는 것이 필요하지 않을까라는 생각이었습니다.
Controller
@RequestMapping("/search")
@RestController
public class SearchRestController {
@Setter(onMethod_ = @Autowired)
private SearchService service;
// 검색어 자동완성
@PostMapping(
value = "/{word}",
produces = "application/text; charset=utf8" // 설정하지 않으면 한글이 깨짐
)
public String getContainsWord(
@RequestBody String json
) {
log.debug("getContainsWord() invoked.");
log.info("json: {}", json);
// JSON 데이터 변환 Gson
Gson gson = new Gson();
SearchWordDTO searchWord = new SearchWordDTO();
// JSON -> String
JsonElement element = JsonParser.parseString(json);
String word = element.getAsJsonObject().get("word").getAsString();
searchWord.setWord(word);
List<PartyVO> list = this.service.getContainsWord(searchWord);
list.forEach(log::info);
// list -> JSON 변환
String serializeString = gson.toJson(list);
log.info("serializeString: {}", serializeString);
return serializeString;
} // getContainsWord
} // end class
우선 JSON 데이터를 받기 위해 @RestController
어노테이션을 사용했습니다.@RestController
는 @Controller
에 @ResponseBody
가 추가된 어노테이션으로 데이터를 JSON으로 주고 받을 수 있습니다.
RESTful 웹 서비스를 개발하는 데 쓰이는 기술입니다.
Html
<div class="container-sm p-0">
<input type="text" name="word" id="search-bar" class="form-control"
placeholder="찾고 있는 파티를 입력해보세요."
aria-label="Search">
<button class="search-btn">
<span>
<i class="fas fa-search"></i>
</span>
</button>
</div>
<div class="search-box hide">
<ul class="search-ul p-0"></ul>
</div>
뷰에서는 검색창과 검색 결과를 담아줄 ul
을 만들어 줬습니다.
자바스크립트를 이용해서 검색 결과를 li
에 담아줄 것입니다.
JavaScript
const searchBar = document.querySelector("#search-bar");
const searchBox = document.querySelector(".search-box");
const searchUl = document.querySelector(".search-ul");
let cache = ""; // url 값을 담을 변수
우선 선택자를 이용해서 필요한 요소를 변수로 지정했습니다.
// 타이머
const timer = (beforeInput) => {
// 0.5초마다 검색어를 확인
// 하나하나 변경될때마다 데이터를 넘기는 것보다 딜레이가 있는 것이 낫다 판단
setTimeout(() => {
if (searchBar.value === beforeInput) {
loadData(searchBar.value);
checkInput();
} else {
checkInput();
} // if-else
// 검색어가 없으면 요소를 숨김
if (searchBar.value !== "") {
searchBox.classList.remove("hide");
} else {
searchBox.classList.add("hide");
} // if-else
}, 500);
} // timer
// 일정 시간 간격으로 조회
const checkInput = () => {
const beforeInput = searchBar.value;
timer(beforeInput);
} // checkInput
검색창의 데이터를 input의 값이 바뀔 때마다 가져온다면 서버쪽과의 통신이 너무 자주 일어나게 됩니다.
때문에 입력값이 바뀌더라도 0.5초에 한번 씩 실행하도록 함수를 만들어 사용했습니다.
// 검색어 불러오기
const loadData = (word) => {
let url = `/search/${word}`;
// 검색어를 입력하면 url 값이 변경된다
if (cache !== url) {
cache = url;
let data = {};
data.word = word;
console.log(data); // {word = "축구"}
fetch(cache,
{
method: "POST",
headers: {
"Content-Type": "application/json;",
},
body: JSON.stringify(data),
}
)
// response 객체를 json 변환
.then((res) => res.json())
.then((data) => {
console.log(data);
fillSearch(data); // data로 list 만드는 함수 실행
})
.catch((err) => {
console.log(err);
});
}
} // loadData
데이터를 주고 받는 비동기 통신수단으로 fetch api
를 사용했습니다. $.ajax()
나 xhr api
를 사용해도 똑같이 구현할 수 있지만fetch api
는 데이터 전송을 하고나서 Promise
객체를 넘겨주며 성공했거나 실패했을 시에 대한 처리가 쉽습니다.
데이터를 받는 데 성공했다면 .then()
을 실행시키고 받은 데이터를 가공하도록 처리합니다.
// 검색어 자동완성 리스트 채우기
const fillSearch = (data) => {
searchUl.innerHTML = "";
// 데이터 가공하기
data.forEach((el, idx) => {
// html 요소 생성
const li = document.createElement("li");
const leftBox = document.createElement("div");
const thumbBox = document.createElement("div");
const thumbnail = document.createElement("img");
const party = document.createElement("span");
const rightBox = document.createElement("div");
const hobby = document.createElement("span");
const local = document.createElement("span");
const link = document.createElement("a");
// 요소에 필요한 class, 속성 정의하기
leftBox.classList.add("left-box");
thumbBox.classList.add("thumb-box");
party.classList.add("mx-3");
thumbnail.setAttribute("src", el.logoPic);
party.innerHTML = el.partyName;
thumbBox.appendChild(thumbnail);
leftBox.appendChild(thumbBox);
leftBox.appendChild(party);
rightBox.classList.add("right-box");
badgeClass(hobby);
badgeClass(local);
rightBox.appendChild(hobby);
rightBox.appendChild(local);
hobby.innerHTML = el.hobbyName;
local.innerHTML = el.localName;
link.setAttribute("href", `/`);
link.appendChild(leftBox);
link.appendChild(rightBox);
li.appendChild(link);
searchUl.appendChild(li);
})
} // fillSearch
비동기로 받은 데이터로 html 요소를 자바스크립트에서 만들어서 뿌려주는 함수입니다.
☄️ 결과
개선사항
- 다른 서비스 사이트들처럼 입력값이 있을 때만 리스트를 생성하기
- 검색창이 아닌 다른 부분을 클릭하면 리스트를 숨기고 다시 활성화하는 토글 기능
- 리스트를 생성하는 다른 방식의 여부?
백과 프론트 간 데이터를 주고 받는 것을 살짝 맛만 보았습니다.
참고: [https://velog.io/@goody/%EA%B2%80%EC%83%89%EC%96%B4-%EC%9E%90%EB%8F%99%EC%99%84%EC%84%B1]