오늘은 백엔드 db에 있는 데이터를 검색하는 기능을 만들었습니다. 단순히 사용자에게 검색어를 입력받고 버튼을 누를때마다 검색이 되게 하는방식도 방법이지만 저는 이번 프로젝트의 작업물이 좀 더 살아있는 느낌을 주기 위해 검색엔진의 검색처럼 타이핑을 하는 순간마다 그에 맞는 검색 결과들이 아래에 배치되길 바랬습니다.
해당 기능을 담을 html입니다.
<div class="col-xxs-12 col-xs-12 mt">
<div class="input-field">
<label for="spot">목적지:</label>
<input type="text" class="form-control" id="route-spot" placeholder="올레길" />
<div id="spot-results"></div>
</div>
</div>
input태그에는 사용자가 검색어를 입력하는 공간이고 그 밑 div가 검색결과를 넣어줄 박스가 되겠습니다. 현재는 기능에 충실한 상태라 css를 입혀주지 못했으나 기능구현이 마무리되면 css를 입혀주겠습니다..
이제 js를 보겠습니다.
async function searchSpot() {
const input = document.getElementById('route-spot');
const query = input.value.trim(); //앞뒤 공백제거
// 비어있는 경우 검색 결과 영역 숨기기
if (query === '') {
document.getElementById('spot-results').innerHTML = '';
return;
}
try {
//백엔드에 데이터 요청
const response = await fetch(`${proxy}/spots/?search=${query}`);
const data = await response.json();
// id로 태그 찾은다음 할당
const resultsContainer = document.getElementById('spot-results');
resultsContainer.innerHTML = '';
// 데이터가 하나라도 있는 경우
if (data.results.length > 0) {
// for문을 이용 하나씩 관광지를 불러옴
for (const spot of data.results) {
const resultElement = document.createElement('div');// div 생성
resultElement.textContent = spot.title; // 관광지명 채우기
resultElement.classList.add('search-result'); // class 삽입
resultElement.addEventListener('click', () => {
input.value = spot.title; // 클릭된 검색 결과를 입력창에 채우기
resultsContainer.innerHTML = ''; // 드롭다운 영역 숨기기
});
resultsContainer.appendChild(resultElement);
}
resultsContainer.style.display = 'block'; // 드롭다운 영역 보이기
} else {
//데이터가 없는 경우 결과없다고 공지
resultsContainer.style.display = 'block';
resultsContainer.innerHTML = `<p>검색된 결과가 없습니다.<p>`
}
} catch (error) {
console.error(error);
}
}
//관광지 입력 필드에 입력이 변경되면 검색 함수 호출
document.getElementById('route-spot').addEventListener('input', searchSpot);
// 클릭 이벤트가 발생한 곳 이외의 영역을 클릭하면 드롭다운 숨기기
document.addEventListener('click', (event) => {
const resultsContainer = document.getElementById('spot-results');
if (!event.target.closest('.input-field')) {
resultsContainer.style.display = 'none';
}
});
//관광지 입력 필드에 입력이 변경되면 검색 함수 호출
document.getElementById('route-spot').addEventListener('input', searchSpot);
// 클릭 이벤트가 발생한 곳 이외의 영역을 클릭하면 드롭다운 숨기기
document.addEventListener('click', (event) => {
const resultsContainer = document.getElementById('spot-results');
if (!event.target.closest('.input-field')) {
resultsContainer.style.display = 'none';
}
});
const createButton = document.getElementById("route-button");
createButton.addEventListener('click', handleCreateRoute);
하나씩 설명드리며 나아가겠습니다.
1
const input = document.getElementById('route-spot'); const query = input.value.trim(); //앞뒤 공백제거
route-spot
이라는 id를 가진 html태그를 찾아input
이라는 변수에 할당해줍니다.
query
변수는input
변수에 입력된 값을 가져오는데trim()
함수를 이용해 앞뒤로 공백이 있는경우 제거 해줍니다.
2
if (query === '') { document.getElementById('spot-results').innerHTML = ''; return; }
이 구문은 없어도 기능이 정상적으로 작동합니다.
하지만 사용자 입력값이 없을 때 값이 없다고 나오거나 비어있는 결과창이 나오는 것 보다는 결과창을 안보이게 하는 것이 훨씬 깔끔해 보일 수 있습니다.맨 처음 페이지에 진입했을 때는 결과창이 보이지 않지만 사용자가 기입한 후 모두 지웠을 때 마지막 글자의 결과창이 계속 남아있는게 보기 싫어 넣게된 기능입니다.
3
// const proxy = "http://127.0.0.1:8000" const response = await fetch(`${proxy}/spots/?search=${query}`); const data = await response.json();
백엔드 db에 있는 자료를 요청합니다. 해당 주소는 백엔드에 만들어진 주소에 따라 달라질 수 있습니다. 데이터를 받게되면 data에 json화 해서 넣어줍니다.
4
const resultsContainer = document.getElementById('spot-results'); resultsContainer.innerHTML = '';
결과를 저장할 html태그를 찾아 할당합니다.
5
if (data.results.length > 0) { // for문을 이용 하나씩 관광지를 불러옴 for (const spot of data.results) { const resultElement = document.createElement('div');// div 생성 resultElement.textContent = spot.title; // 관광지명 채우기 resultElement.classList.add('search-result'); // class 삽입 resultElement.addEventListener('click', () => { input.value = spot.title; // 클릭된 검색 결과를 입력창에 채우기 resultsContainer.innerHTML = ''; // 드롭다운 영역 숨기기 }); resultsContainer.appendChild(resultElement); } resultsContainer.style.display = 'block'; // 드롭다운 영역 보이기 } else { //데이터가 없는 경우 결과없다고 공지 resultsContainer.style.display = 'block'; resultsContainer.innerHTML = `<p>검색된 결과가 없습니다.<p>` }
data.results
에 제가 원하는 데이터가 담겨져 있는데 해당 데이터 안에 값이 있느냐 아니냐로 조건문을 만들었습니다.만약 데이터가 잘 넘어왔다면 for문을 이용해 원하는 데이터를 하나씩 가져옵니다.
resultElement
에 생성한 div를 할당해주고resultElement
에 가져온 데이터의 title을 적고search-result
라는 클래스를 삽입해줍니다.search-result
클래스는 사실 css를 아직 만들지않아서 있으나 마나하지만 앞으로 css도 만들어 줄거라서 미리 넣어줬습니다!이후
resultElement
가 클릭되었을 때 지금은 해당 결과물의 title을 맨 위에 선언되어 있던 사용자의 입력창에 기입해주게 만들었지만 추후 다른 기능을 추가해 더 쓰임새 있게 만들 예정입니다. 정보가 입력되면 결과창을 숨깁니다.마지막으로 해당 div를
resultsContainer
검색결과를 담고있는 div에 추가해주며 반복 1회가 끝나고resultsContainer
를 보일 수 있게 style을 수정해줍니다.그리고 데이터가 없을 경우에는 검색결과가 없다고 공지하기 위해
resultsContainer
의 style을 수정해 보일 수 있게 만들고 해당 div에 결과가 없다는 공지문구를 적어줍니다.
6
document.getElementById('route-spot').addEventListener('input', searchSpot);
사실 이 방법이 맞는지는 알지 못하겠으나 제 수준에서 생각했을때 타이핑을 할 때마다 검색 결과가 바뀌기 위해선 타이핑을 할때 마다 해당 문구로 요청을 보내야겠다는 생각이였습니다.
그래서
route-spot
이라는 html태그를 찾고 이 태그안에 input이 일어날 때마다 지금까지 소개한 함수인searchSpot
함수를 실행시키게 만들었습니다.이렇게 무자비하게 요청을 보내도 되나 싶으면서도 현재로서 제가 생각한 기능을 표현하기 위해선 이 방법이 최선이었다고 생각하고 있습니다!
7
document.addEventListener('click', (event) => { const resultsContainer = document.getElementById('spot-results'); if (!event.target.closest('.input-field')) { resultsContainer.style.display = 'none'; } });
해당 기능은 검색기능을 사용하다가 페이지의 다른 부분을 클릭했을 시에 검색결과창을 숨겨주는 기능입니다.
input-field
라는 클래스를 가지고있는 태그를 클릭한게 아니라면 검색결과창 태그의 style을 none으로 바꿔 보이지않게 만들었습니다.
최근들어 자신감을 찾게해줄 동기가 부족했던 것 같은데 오늘 이 기능을 구현하면서 팀원들에게 어떻게했냐고 질문도 받고 코드리뷰도 진행하고 자신감을 꽤 되찾는 하루였던 것 같습니다. 다른 날도 오늘처럼 색다른 기능을 구현하며 나아갈 수 있는 사람이 되고 싶습니다.