- 이번에는 사용자가 입력한 검색 키워드에 따라 해당 키워드와 연관되어 있는 검색 결과를 미리 보여주는 연관 검색어 기능을 구현해보았습니다.
- 제가 생각한 연관 검색어 기능의 요구사항 아래와 같습니다.
- 첫 번째, 계정과 태그에 따라 분류하여 연관 검색어를 출력할 수 있어야 한다.
- 두 번째, 연관 검색어를 클릭할 시 해당 계정 또는 게시글 검색이 이루어져야 한다.
- 세 번째, ESC 키를 누르거나 페이지의 다른 영역을 클릭할 시 연관 검색어 창이 닫혀야 한다.
📝 jsp, js
- 해당 기능은 구현하는데에는 서버 쪽 구현보단 클라이언트 쪽 구현이 복잡하였습니다.
- 아래 코드에서 클라이언트 쪽 구현을 살펴볼 수 있습니다.
<input type="text" id="search" name="keyword" placeholder="검색">
<div class="relative-tab">
<ul class="relative-tabnav">
<li><a href="#account">계정</a></li>
<li><a href="#tag">태그</a></li>
</ul>
<div class="relative-tabcontent">
<div id="account">
<div class="relative-keyword" id="relative-keyword-account"></div>
</div>
<div id="tag">
<div class="relative-keyword" id="relative-keyword-tag"></div>
</div>
</div>
</div>
let input = document.getElementById("search");
let relative_tab = $(".relative-tab")[0];
input.addEventListener("keyup", async (e) => {
let keyword = $("#search").val();
/* 검색 상자가 비어있거나 ESC 키를 누를 경우 */
if(!keyword || e.key === "Escape") {
$("#relative-keyword-account > ul").remove();
$("#relative-keyword-tag > ul").remove();
relative_tab.style.display = "none";
return;
}
$.ajax({
type: "GET",
url: `/api/relative/${keyword}`
}).done(resp => {
let relative_keyword_account = $("#relative-keyword-account");
let relative_keyword_tag = $("#relative-keyword-tag");
$("#relative-keyword-account > ul").remove();
$("#relative-keyword-tag > ul").remove();
relative_tab.style.display = "none";
let ul_account = document.createElement("ul");
let ul_tag = document.createElement("ul");
resp.data.relativeUserDto.forEach((user) => {
let li = document.createElement("li");
li.className = "relative-keyword-box";
li.onclick = function() {
location.href=`/user/${user.id}`;
};
li.innerHTML =
`<div class="flex">
<div class="relative-keyword-image">
<img class="profile-image" src="/upload/${user.profile_image_url}"
onerror="this.src='/images/person.jpeg'" />
</div>
<div class="relative-keyword-name">${user.name}</div>
</div>`
ul_account.append(li);
});
let tags = [];
resp.data.relativeTagDto.forEach((image) => {
let tagList = image.hashtag.split(",");
tagList.forEach((tag) => {
if(tag.startsWith("#" + keyword)) {
tags.push(tag);
}
});
});
/* 중복 제거 */
let set = new Set(tags);
let uniqueTags = [...set];
uniqueTags.forEach((tag) => {
tag = tag.slice(1); // # 제거
let li = document.createElement("li");
li.className = "relative-keyword-box";
li.onclick = function() {
location.href=`/search?keyword=${tag}`;
};
li.innerHTML =
`<div>#${tag}</div>`
ul_tag.append(li);
});
relative_keyword_account.append(ul_account);
relative_keyword_tag.append(ul_tag);
relative_keyword_account[0].style.display = "block";
relative_keyword_tag[0].style.display = "block";
relative_tab.style.display = "block";
}).fail(error => {
console.log(error);
});
});
/* 마우스 포인터로 다른 영역을 클릭할 경우 연관 검색어 창이 닫힘 */
document.addEventListener("click", e => {
if(relative_tab.style.display === "block") {
relative_tab.style.display = "none";
}
});
$(function() {
$('.relative-tabcontent > div').hide();
$('.relative-tabnav a').click(function() {
$('.relative-tabcontent > div').hide().filter(this.hash).fadeIn();
$('.relative-tabnav a').removeClass('active');
$(this).addClass('active');
return false;
}).filter(':eq(0)').click();
});
- 위 코드를 요약하자면 검색 상자에 이벤트를 달아두어, 키보드 입력이 들어올 때마다 ajax 요청을 통해 입력된 키워드와 연관된 데이터를 DB에서 찾아 가져오고, 해당 데이터를 계정, 태그에 따라 분류한 뒤 엘리먼트를 생성하여 출력한 것입니다.
- 부가적으로 요구사항에 필요한 기능 또한 구현되어 있는 것을 확인할 수 있습니다.
📝 Controller
- 다음으로 위의 ajax 요청을 받는 컨트롤러는 아래와 같이 구현해주었습니다.
package com.cos.photogram.web.api;
...
@RequiredArgsConstructor
@RestController
public class SearchApiController {
private final UserService userService;
private final ImageService imageService;
@GetMapping("/api/relative/{keyword}")
public ResponseEntity<?> relative(@PathVariable String keyword) {
List<User> relativeUserList = userService.userSearch(keyword);
List<Image> relativeTagList = imageService.imageSearch(keyword);
RelativeDto relativeDto = new RelativeDto();
relativeDto.setRelativeUserDto(relativeUserList);
relativeDto.setRelativeTagDto(relativeTagList);
return new ResponseEntity<>(new CMRespDto<>(1, "연관 검색어 목록 가져오기 성공", relativeDto), HttpStatus.OK);
}
}
- 해당 컨트롤러는 ajax 요청을 받아 해당되는 키워드와 연관된 데이터를 DB에서 꺼내어 리스트 형태로 저장한 뒤, 응답으로 전달해주고 있습니다.
- 여기서 계정과 태그에 따라 분류하기 위해서 총 두 개의 리스트로 저장하고 있는 것을 확인할 수 있습니다.
📝 Result
📝 Reference
'🚗 Backend Toy Project > Photogram' 카테고리의 다른 글
[Photogram] 실시간 알림 (0) | 2023.03.02 |
---|---|
[Photogram] 해시태그 기능 추가 (0) | 2023.01.03 |
[Photogram] 로그인 실패 처리 (0) | 2022.12.25 |
[Photogram] 검색 기능 구현 (0) | 2022.12.22 |
[Photogram] OAuth2 페이스북 로그인 (0) | 2022.07.19 |