1차 프로젝트를 진행하며 바닐라 자바스크립트로 웹페이지를 구현했어야 했다.
우리의 웹페이지는 총 90개 정도의 데이터를 렌더링했어야했기에
페이지네이션이 필수로 구현되어야만 했다.
그전에 새로 알게된 함수에 대해 설명하도록 하겠다.
javascript에서 url의 쿼리 파라미터들을 읽거나 수정할 때 사용하는 URLSearchParams 사용법이다.
http://kdt-sw-4-team08.elicecoding.com/?page=5
에서의 결과값
location.search
// 현재 위치한 endpoint뒤에 params를 리턴한다.
const params = location.search;
console.log(params); // ?page=5
URLSearchParams
urlSearchParams
에 .get('paramName')
은 해당 paramName
으로 조회되는 첫번째 값을 return한다.urlSearchParams.getAll("paramName")
은 paramName
으로 조회되는 모든 값을 배열로 return한다.// URLSearchParams 객체로 변환하여 param의 키값을 이용해 해당 값을 불러올 수 있다.
const param = new URLSearchParams(params);
const page = param.get('page'); // 5
urlSearchParams
에 .set("paramName", "value")
를 사용하면, paramName
의 value를 변경할 수 있다.parameterName
이라면 append된다.parameterName
으로 중복되는 값이 있을 경우 1개만 변경된 값으로 남기고 모두 제거된다.let urlParams = new URLSearchParams("?hayeong=test&log=yes");
urlParams.set("hayeong", "cute");
urlParams.set("log", "good");
console.log(urlParams);
// ?hayeong=cute?log=good
대충 이정도로 개념 설명 마무리하고, 구현한 코드를 보자.
우선 홈화면 렌더링 방식은 동적으로 DOM요소를 생성하며 데이터를 넣어주고 있다.
....
function createCard(product) {
return `
<div id="card" style="width:350px; height:480px;" class="mb-5">
<a id="card-link" href="/product-detail?pid=${product.shortId}">
<img
src="${product.productImage}"
id="card-img-top"
class="rounded-lg"
style="width:350px; height:376px;"
/>
<div id="card-body">
<div id="card-text card-title" class="text-lg mt-3 mb-1">
${product.productName}
</div>
<div id="card-text card-hashtags" class="text-gray-500 mt-1 mb-1">
#${product.category}
</div>
<div id="card-text card-price" class="font-bold text-base mt-1 mb-1">
${product.productPrice} 원
</div>
</div>
</a>
</div>`;
}
getProductList();
위는 동적으로 생성할 카드 컴포넌트 1개이다.
아래는 전체 상품을 조회하고 페이지네이션을 하는 함수이다.
/********************* 전체상품 조회 && 페이지네이션 **********************/
// 현재 url의 쿼리 파라미터를 가져와서 URLSearchParams 객체로 변환한다. ex) ?page=5
const urlParams = new URLSearchParams(window.location.search);
// get메서드로 page키의 값을 가져온다. 있으면 해당 값을 page변수에 저장하고, 없으면 1페이지만 존재한다는 것이므로 1로 저장한다.
let page = parseInt(urlParams.get('page')) || 1;
async function getProductList() {
// axios로 데이터를 받아오고 있다. param으로 현재 페이지 그리고 한 페이지에 보여질 데이터 수를 넘겨준다.
const response = await axios.get(`/api/products?page=${page}&perPage=9`);
const products = await response.data.pagenatedProducts.results;
const productCount = await response.data.total;
// #item-cards-list는 동적으로 카드를 생성할 container부분이다.
// querySelector로 컨테이너 요소를 잡아준다.
const cards = document.querySelector('#item-cards-list');
// 받아온 상품 데이터를 한 페이지 당 보여질 개수로 나눠주면 총 생성할 페이지가 정의된다.
const totalPages = Math.ceil(productCount / 9);
productCounter.innerText = productCount;
// 페이지 버튼을 만들 컨테이너 요소
const pageButtons = document.querySelector('#page-buttons');
// 위의 버튼 컨테이너를 제일 먼저 초기화시켜준다. -> 전체상품만 페이지네이션하는 것이 아니고 카테고리별로도 페이지네이션을 구현하기 때문
pageButtons.innerHTML = '';
// 위에서 정의한 총 페이지 수만큼 link태그를 생성하고 classList.add로 tailwind css를 입힌다.
for (let i = 1; i <= totalPages; i++) {
const link = document.createElement('a');
link.classList.add('mt-20', 'mb-10', 'mr-10', 'text-xl');
// 패이별로 url href params를 붙여준다.
link.href = `?page=${i}`;
// 1,2,3, 4, 5... 버튼에 나타날 숫자
link.textContent = i;
// 현재 페이지를 누르고 있다면 강조 효과 css
if (i === page) {
link.classList.add('text-[#69b766]', 'font-bold');
}
// 하나씩 pageButton요소에 추가한다.
pageButtons.appendChild(link);
}
// product 각 요소마다 createCard함수 호출하여 productList에 담음
const productList = [];
for (const product of products) {
const newCard = createCard(product);
cards.innerHTML += newCard;
productList.push(product);
}
return productList;
}
카테고리별로 페이지네이션하는 것도 동일하지만, 조금 더 url에 신경써줘야한다.
예를 들어 카테고리를 눌렀을 시에 전에 있던 url을 새롭게 변경해야하는 작업이 필요하다.
따라서 나는 아래와 같이 구현했다.
...
// 카테고리 클릭시 url업데이트
function updateUrl(categoryPage) {
const clickedCategoryName = sessionStorage.getItem('selectedCategory');
const newUrl = `?category=${clickedCategoryName}&categoryPage=${categoryPage}`;
window.history.pushState(null, null, newUrl);
}
...
async function categoryFilter() {
const clickedCategoryName = sessionStorage.getItem('selectedCategory');
const searchByCategoryProductList = [];
let products = [];
try {
const response = await axios.get(
`/api/products/categories?category=${clickedCategoryName}&page=${categoryPage}&perPage=9`,
);
const cards = document.querySelector('#item-cards-list');
products = await response.data.pagenatedProducts.results;
const productCount = await response.data.total;
const totalPages = Math.ceil(productCount / 9);
productCounter.innerText = productCount;
// 똑같이 pageBtn 동적 생성해주는 코드이다.
const pageButtons = document.querySelector('#page-buttons');
pageButtons.innerHTML = '';
for (let i = 1; i <= totalPages; i++) {
const link = document.createElement('a');
link.classList.add('mt-20', 'mb-10', 'mr-10', 'text-xl');
link.href = `?category=${clickedCategoryName}&categoryPage=${i}`;
link.textContent = i;
if (i === categoryPage) {
link.classList.add('text-[#69b766]', 'font-bold');
}
// 여기서 다른 부분이다. link.addEvenListener로 link를 클릭할 때 마다 카테고리 페이지를 다시 만들어주는 작업을 했다.
//이렇게 하지 않으면 각 카테고리에서 다른 페이지번호를 누를 때 전체페이지로 넘어가는 기이한 현상이 발생한다.
// 캬테고리 페이지를 현재 페이지로 지정해주고 url을 현재 카테고리 페이지로 업데이트 시킨다음, categoryFilter함수를 다시 재호출해 해당하는 url로 get 요청을 다시 보내준다.
link.addEventListener('click', (e) => {
e.preventDefault();
categoryPage = i;
updateUrl(categoryPage);
categoryFilter();
});
pageButtons.appendChild(link);
}
products.forEach((product) => {
searchByCategoryProductList.push(product);
});
if (searchByCategoryProductList.length === 0) {
productCounter.innerText = 0;
cards.innerHTML = `
<div class="col-start-1 col-end-4 pt-[150px] pb-[170px]" style="margin:0 auto">
<div class="col-start-1 col-end-4 text-2xl font-semibold text-gray-500" id="empty-product-list" >상품이 없습니다.</div>
</div>
`;
} else {
cards.innerHTML = '';
searchByCategoryProductList.forEach((product) => {
const newCard = createCard(product);
cards.innerHTML += newCard;
});
}
} catch (err) {
const spinner = document.getElementById('spinner');
spinner.innerHTML = `
<section
id="item-cards-list"
class="grid grid-cols-3 justify-items-center items-center mb-50"
style="width: 1200px"
></section>
`;
const cards = document.querySelector('#item-cards-list');
cards.innerHTML = `
<div class="col-start-1 col-end-4 pt-[150px] pb-[170px]" style="margin:0 auto">
<div class="col-start-1 col-end-4 text-2xl font-semibold text-gray-500" id="empty-product-list" >상품이 없습니다.</div>
</div>
`;
const pageButtons = document.querySelector('#page-buttons');
pageButtons.innerHTML = '';
productCounter.innerText = 0;
}
}