나의 개발 일지

javascript로 검색 기능 구현 (서울시 유치원 정보 사이트 프로젝트 ep.3)

designer DK 2024. 9. 1. 14:52
728x90

이번 서울시 유치원 정보 사이트 프로젝트를 설계할 때 서울시 구 이름 버튼으로 유치원을 찾을 수 있으면서도 홈 화면에서 직접 검색 기능을 넣으면 ux가 더 좋을 것 같다고 판단되었다.

홈 화면 와이어프레임

 

 

 

검색 인풋에 유치원 명을 넣으면 리스트 페이지에서 검색된 유치원 목록 나오게 구현되도록 계획하였다.

검색 시 리스트페이지 와이어프레임

 

 

 

인풋에 밸류를 입력하고 검색버튼을 누르면 searchedKeyword 변수에 검색어를 localStorage로 저장 후 리스트 페이지로 이동된다.


// 검색창 인풋값으로 검색해주는 버튼
document.querySelector(".search-btn")
.addEventListener("click", function () {
// 인풋 값 가져오기
searchedKeyword = searchInput.value.trim(); // 공백 제거

// 인풋이 비어있지 않은 경우에만 검색 수행
if (searchedKeyword !== "") { // 부정연산자
localStorage.setItem('searchedKeyword', searchedKeyword);
location.href = 'list.html';
} else {
// 인풋이 비어있을 때 처리 (선택 사항: 경고 메시지 등을 표시 가능)
console.log("검색어를 입력해주세요.");
}
});

 

 

리스트페이지에서는 저장되었던 searchedKeyword를 localStorage를 통해 가져온다.

document.addEventListener("DOMContentLoaded", function () {
let clickedGuName = localStorage.getItem('clickedGuName');
let searchedKeyword = localStorage.getItem('searchedKeyword');

 

 

리스트페이지에는 이전에 작업한 것 처럼 서울시 구 이름 버튼을 클릭해서 리스트가 나올 수도 있고 검색을 통해서도 리스트가 나올 수 있게 하고 싶었다. 그리고 이걸 구현 할 때 왠만해서는 리스트 페이지를 분리하지 않고 한 파일에서 처리하고 싶었다.

 

중간중간 시행착오도 있었지만 gpt에게 물어보며 그렇게 두가지를 할 수 있는 리스트 페이지로 구축할 수 있었다.

document.addEventListener("DOMContentLoaded", function () {
let clickedGuName = localStorage.getItem('clickedGuName');
let searchedKeyword = localStorage.getItem('searchedKeyword');

// 검색 키워드가 있을 경우 우선 처리
if (searchedKeyword) {
directSearchList(searchedKeyword);
listTitle = `
<h1>검색된 유치원 정보</h1>
`;
document.querySelector(".list-title").insertAdjacentHTML("beforeend", listTitle);
}
// 검색 키워드가 없을 때 clickedGuName 처리
else if (clickedGuName) {
searchList(clickedGuName);
listTitle = `
<h1>${clickedGuName} 유치원 정보</h1>
`;
document.querySelector(".list-title").insertAdjacentHTML("beforeend", listTitle);
}
else {
console.log("No guName or searchedKeyword!");
}
});

 

검색 키워드(searchedKeyword)가 있는 경우 우선적으로 처리하고, 검색 키워드가 없을 때에는 clickedGuName으로 처리.

두 케이스에 대해 페이지 타이틀명도 다르게 나올 수 있게 구성하였다.

 

 

리스트 생성에 있어서 위에서 설명한 두 케이스는 받아오는 데이터가 달라서  다른 function을 사용했다.

구 이름 버튼으로 리스트를 뽑을 때에는 기존에 만든 searchList라는 기능을 사용하고,

function searchList(clickedGuName) {
fetch("./json/kinderGeneral.json")
.then((response) => response.json())
.then((data) => {

 

검색 인풋에 내용입력으로 리스트를 생성할 때에는 directSearchList라는 기능을 새로 진행했다.

function directSearchList(searchedKeyword) {
fetch("./json/kinderGeneral.json")
.then((response) => response.json())
.then((data) => {

 

 

searchedKeyword로 검색한 내용을 포함한 유치원 명을 모두 포문으로 돌려서 해당하는 유치원명 데이터들을 가져왔다.

//인풋에 입력한 searchedKeyword 포함한 모든 유치원명을 searchedArray라는 배열로 넣어줌
for (let i = 0; i < data.DATA.length; i++) {
if (data.DATA[i].kindername.includes(searchedKeyword)) {
searchedArray.push(data.DATA[i].kindername);
}
}

 

그렇게 얻은 유치원명은 단수일 수도 있고 복수일 수도 있어서 배열 형태로 변수에 저장했다. searchedArray라는 변수를 배열형태로 하나 만들어주고 방금 서치해준 유치원명 데이터들을 모두 배열형태로 푸시해서 넣어주었다.

//검색창에서 검색 했을 때 그 키워드를 포함하는 유치원명을 모두 전역변수 배열로 저장
let searchedArray = [];

 

그러면 유치원 명들이 배열 형태로 searchedArray에 저장되는데 push로 들어가다보니 계속 배열안에 데이터가 쌓여나가는 현상이 일어났고 그걸 해결하기 위해 fetch 안쪽에 searchedArray가 쌓이지 않도록 초기화해주는 구문을 넣었다.

function directSearchList(searchedKeyword) {
fetch("./json/kinderGeneral.json")
.then((response) => response.json())
.then((data) => {
//searchedArray가 push되면서 배열이 계속 쌓이는 현상을 초기화 해줌
searchedArray = [];

//인풋에 입력한 searchedKeyword 포함한 모든 유치원명을 searchedArray라는 배열로 넣어줌
for (let i = 0; i < data.DATA.length; i++) {
if (data.DATA[i].kindername.includes(searchedKeyword)) {
searchedArray.push(data.DATA[i].kindername);
}
}

 

그 후 전체 데이터 유치원 명들과 searchedArray에 담긴 유치원 명들을 filter를 통해 해당 유치원들 전체 데이터 배열을 뽑아냈다.

//searchedArray에 있는 유치원명들을 kindername으로 가지고 있는 데이터들을 배열로 가져옴
let directKinderInfos = data.DATA.filter((item) =>
searchedArray.includes(item.kindername.trim())
);

for (let i = 0; i < directKinderInfos.length; i++) {
if (directKinderInfos) {

let listCard = `html 내용`

 

그리고는 기존 searchList때 했던 것과 같은 html 정보를 넣어주었고

<div class="list-card" id="list${[i]}">
<div class="list-contentsGrp">
<div class="list-contents">
<div class="kinder-title">
<div class="kinder-tag" id="list-establish${[i]}">
${directKinderInfos[i].establish}
</div>
<div class="kinder-name" id="list-name${[i]}">
${directKinderInfos[i].kindername}
</div>
</div>
<div class="list-infoGrp">
<div class="list-info" id="list-num${[i]}">
${directKinderInfos[i].telno}
</div>
<div class="list-info" id="list-addr${[i]}">
${directKinderInfos[i].addr}
</div>
</div>
.......
 

 

이렇게 검색결과를 리스트페이지로 넣는것도 성공

검색 인풋 입력
검색된 정보를 list.html에서 나타냄

 

 

이제 그 리스트를 클릭했을 때 해당 유치원에 대한 상세정보를 디테일 페이지에서 보여주는 작업이다.

 

이건 생각보다 에러가 많이났고 처음엔 구이름 버튼으로 진행한 케이스와 검색으로 진행한 케이스에 대해 디테일 페이지를 나눠서 어찌저찌 해결 했었다. 하지만 디테일 페이지가 두 개로 나눠졌다는 게 계속 찝찝했었고 향후 유지보수도 힘들거라는 판단에 gpt와 함께 하나의 디테일 페이지로 진행할 수 있는 방법을 최대한 찾아보았다.

 

gpt가 중간에 좋은 힌트를 주었었는데 유치원 json 데이터에 아마 각 데이터들 '고유 아이디'가 있을 거라는 것이었다. 찾아보니 kindercode라는 고유 코드가 존재했다. (역시 갓 gpt!)

json에 적혀있는 kindercode

 

 

클릭한 리스트의 kindercode를 저장해주는 변수 clickedkinderCode를 만들어주고 그걸 이용해서 이렇게 디테일 페이지로가는 이벤트 function을 만들었고 이를 통해 두 케이스 모두 공통된 기능으로 디테일페이지로 진행될 수 있게 만들 수 있었다.

//리스트 목록을 클릭 했을 때 clickedKinderName과 clickedkinderCode를 저장하고 디테일페이지로 이동
document.getElementById(`list${[i]}`)
.addEventListener("click", function () {
clickedKinderName = document.getElementById(`list-name${[i]}`).textContent;
localStorage.setItem('clickedKinderName', clickedKinderName);

clickedkinderCode = directKinderInfos[i].kindercode;
localStorage.setItem('clickedkinderCode', clickedkinderCode);

location.href = 'detail.html';
});

 

 

디테일 페이지에서는 리스트 페이지로부터 clickedKinderName과 clickedkinderCode 값을 가져오고

// list.js에서 저장된 전역변수들을 localStorage로 저장했다가 가져오기
document.addEventListener("DOMContentLoaded", function () {
let clickedkinderCode = localStorage.getItem('clickedkinderCode');
let clickedKinderName = localStorage.getItem('clickedKinderName');

 

그 데이터들로 정확한 디테일 정보를 가져오는 작업을 진행했다.

if (clickedkinderCode, clickedKinderName) {
searchDetail(clickedkinderCode, clickedKinderName);
} else {
console.error("No Name found in localStorage.");
}
});

 

searchDetail이라는 function에서 먼저 clickedKinderName을 이용해서 필터기능으로 일치하는 유치원 명을 가진 데이터 배열을 가져왔고

function searchDetail(clickedkinderCode, clickedKinderName){
fetch("./json/kinderGeneral.json")
.then((response) => response.json())
.then((data) => {

// filter: data.DATA 속 kindername이 특정 명칭(clickedKinderName)과 일치하는지 여부를 판단하고 일치하는 것을 배열로 반환
// .trim().toLowerCase()를 쓰면 띄어쓰기나 대소문자로 인한 불일치를 해소
let kinderInfos = data.DATA.filter((item) =>
item.kindername.trim().toLowerCase() === clickedKinderName.trim().toLowerCase()
);

 

이 데이터 배열은 복수일 수 도 있기 때문에 그걸 clickedkinderCode를 통해서 일치하는 kindercode를 찾고 그 데이터를 kinderInfoPick라는 변수로 넣어주었다.

// kinderInfos는 복수의 데이터가 들어올 수 있으므로 clickedkinderCode를 통해 클릭한 리스트의 데이터와 일치하는 데이터만 찾아냄
let kinderInfoPick = kinderInfos.filter((item) =>
item.kindercode === clickedkinderCode
)

 

그러고나서는 kinderInfoPick을 통해 html에 모든 데이터를 넣어주었다.

const detailTitle = `
<h1>${clickedKinderName} 정보</h1>
`

const detailBasic = `
<div class="detail-basicGrp">
<div class="detail-basic">
<div class="kinder-title">
<div class="kinder-tag">
${kinderInfoPick[0].establish}
</div>
<div class="kinder-name">
${kinderInfoPick[0].kindername}
</div>
</div>
<div class="detail-basicInfo">
<div class="key-value">
<div class="key">
전화번호
........
 

 

이렇게 검색을 통해서도 디테일 페이지까지 잘 도달할 수 있도록 큰 기능들 구현을 모두 성공하였다 👏👏

디테일페이지 구현

 

 

참고로 백버튼을 눌렀는데 검색 결과가 인풋에 남이있는 현상이 있었는데 index.js에 이 구문을 넣어줌으로써 인풋을 초기화 할 수 있었다.

//백버튼시 검색 초기화
window.addEventListener("pageshow", function () {
if (searchInput) {
searchInput.value = "";
}
});

 

그리고 인풋에 아무것도 입력되지 않았을 때에는 페이지 이동을 시키지 않기 위해 이런 처리를 했다. 여기서 ! 는 부정 연산자이다.

// 검색창 인풋값으로 검색해주는 버튼
document.querySelector(".search-btn")
.addEventListener("click", function () {
// 인풋 값 가져오기
searchedKeyword = searchInput.value.trim(); // 공백 제거

// 인풋이 비어있지 않은 경우에만 검색 수행
if (searchedKeyword !== "") { // 부정연산자
localStorage.setItem('searchedKeyword', searchedKeyword);
location.href = 'list.html';
} else {
// 인풋이 비어있을 때 처리 (선택 사항: 경고 메시지 등을 표시 가능)
console.log("검색어를 입력해주세요.");
}

 

이 외에도 검색 버튼을 키보드 엔터로 실행할 수 있게하는 기능과

//검색버튼을 키보드 엔터 눌렀을 때도 동작하게
searchInput.addEventListener("keyup", function(event){
if(event.keyCode === 13){
document.querySelector(".search-btn").click();
}
});

 

일치하는 검색결과가 없을 때 리스트페이지에서 검색결과 없음을 표시해주는 부분도 진행하였다.

// 일치하는 검색결과가 없을 때
if (searchedArray.length == 0) {

let listEmpty = `
<div class="empty-contents">
<div class="empty-msg">
<div class="empty-img">이미지</div>
<div class="empty-text">일치하는 유치원명이 없습니다.</div>
</div>
<a class="empty-back-btn" href="index.html">돌아가기</a>
</div>
`

document.querySelector(".list-container")
.insertAdjacentHTML("beforeend", listEmpty);
}

 

 

이렇게 큰 기능들은 잘 진행된 것 같다. 이제 그래프 기능과 페이지네이션이 남았다.