바닐라 JS로 SPA 만들기4
검색 API & 무한 스크롤
이번 포스팅에서는 카카오 API를 활용하여 책 검색 기능을 구현하고,
무한 스크롤 기능을 추가해보겠다. 결과물을 미리 살펴보면 다음과 같다.
1. 내부 페이지 구현
먼저 필요한 것은 내부 페이지인 HomeSearchPageView를 만드는 것이다.
그리고 HomePageView와 HomeSearchPageView 간의 이동, 즉 라우팅을 구현할 것이다.
시작해 보자.
현재 HomePageView는 두 개의 탭으로 구성되어 있다. (헤더는 제외)
하나는 ‘검색 탭’이고 다른 하나는 ‘달력 탭’이며,
두 개의 탭을 .home__tabs
라는 컨테이너가 담고 있는 구조다.
이 중 검색 탭을 클릭했을 때 HomeSearchPageView로 이동하게 만들 것이다.
우선 마크업은 아래와 같다.
<div class="home__tabs">
<a class="home__tab home__tab--search" href="/home/search">
<h2>책을 검색해 보세요.</h2>
<p>읽고 있는 책이 있나요?</p>
</a>
<a class="home__tab home__tab--calendar" href="/home/calendar">
<div>
<h2>독서 달력</h2>
<p>이번 달은 얼마나 읽었나요?</p>
</div>
<i class="fa-solid fa-calendar-days"></i>
</a>
</div>
다음은 자바스크립트다.
// HomePageView.js
HomePageView.bindElement = function () {
this.tabs = this.element.querySelector('.home__tabs');
};
HomePageView.setEvent = function () {
this.tabs.addEventListener('click', (e) => this.onClickTab(e));
};
HomePageView.onClickTab = function (e) {
const tab = e.target.closest('.home__tab');
if (tab) {
e.preventDefault();
const path = tab.getAttribute('href');
this.dispatch('@clickTab', { path });
}
};
.home__tabs
를 변수로 바인딩 한 뒤, click 이벤트 핸들러로 onClickTab
을 등록하였다.
이러면 .home__tabs
내부 요소에서 click 이벤트가 발생해도 onClickTab
이 호출된다.
이벤트의 전파 때문이다.
클릭이 발생한 탭을 tab 변수에 저장한 뒤, a 태그의 기본 동작을 막는다.
그리고 href 속성에서 path를 가져와서
커스텀 이벤트 @clickTab
를 디스패치하고 path를 전달한다.
이 @clickTab
은 HomeController에서 처리한다.
// HomeController.js
addCustomEvent() { // 커스텀 이벤트 핸들러 등록
HomePageView
.on('@clickTab', (e) => this.onClickTab(e.detail.path));
},
onClickTab(path) {
history.pushState(null, null, path);
MainController.route();
},
@clickTab
의 핸들러는 onClickTab
라는 메서드다.
전달받은 path 문자열로 url을 변경한 후,
MainController의 route 메서드를 호출한다.
MainController의 route 메서드에서 다음 코드를 추가한다.
// MainController.js
route() {
const path = window.location.pathname;
if (path === '/' || path === '/home') {
HomePageView.setup(page);
HomeController.init();
NavigationView.show(); // 추가한 코드
return;
}
// ---------- 추가한 코드 ----------
if (path === '/home/search') {
HomeSearchPageView.render(page);
NavigationView.hide();
return;
}
path가 /home/search
일 때, HomeSearchPageView를 렌더링한다.
그리고 NavigationView.hide
를 통해 하단 네비게이션 바를 감춘다.
자 이제 HomeSearchPageView.js를 생성하고 다음과 같이 작성한다.
import View from './View.js';
const HomeSearchPageView = Object.create(View);
HomeSearchPageView.setup = function (element) {
this.init(element);
};
HomeSearchPageView.render = function () {
const html = this.getHtml();
this.replaceChildren(html);
this.bindElement();
this.setEvent();
};
HomeSearchPageView.getHtml = function () {
return /* html */ `
<header class="search-page__header">
<button class="search-page__btn">
<i class="fa-solid fa-arrow-left"></i>
</button>
</header>
<div class="search-page__content">
<h1 class="search-page__title">책을 찾아보세요!</h1>
<form class="search-page__form">
<i class="fa-solid fa-magnifying-glass"></i>
<input
class="search-page__input"
type="text"
placeholder="검색할 책을 입력해주세요."
autofocus
>
</form>
</div>
<ul class="search-list"></ul>
`;
};
아래와 같은 화면이 만들어졌다. CSS는 생략하도록 하겠다.
뒤로 가기 버튼, 즉 search-page__btn
을 클릭했을 때
다시 HomePageView로 이동하게 만들 것이다.
다음과 같이 코드를 추가한다.
HomeSearchPageView.bindElement = function () {
this.btn = this.element.querySelector('.search-page__btn');
};
HomeSearchPageView.setEvent = function () {
this.btn.addEventListener('click', () => this.onClickPrev());
};
HomeSearchPageView.onClickPrev = function () {
this.dispatch('@clickPrev');
};
search-page__btn
을 변수로 바인딩하고, click 이벤트 핸들러를 등록하였다.
이벤트 핸들러에서는 @clickPrev
라는 커스텀 이벤트를 디스패치한다.
이 @clickPrev
는 HomeController에서 처리한다.
// HomeController.js
addCustomEvent() {
HomePageView
.on('@clickTab', (e) => this.onClickTab(e.detail.path));
HomeSearchPageView // 추가한 코드
.on('@clickPrev', () => this.onclickPrev());
},
onClickTab(path) {
history.pushState(null, null, path);
MainController.route();
},
// 추가한 코드
onclickPrev() {
history.pushState(null, null, '/home');
MainController.route();
},
onclickPrev에서는 /home
으로 URL을 변경하고
MainController의 route 메서드를 호출한다.
이렇게 내부페이지의 구현 및 라우팅이 완료되었다.
2. 카카오 OpenAPI
(미완성입니다.)
드리는 말
학습용 프로젝트를 진행하며 작성한 글이며, 부정확한 내용이 있을 수 있습니다.
참고로만 읽으실 것을 권장드립니다. 감사합니다.