장바구니 페이지는 역시 금방 끝낼 수 없었다. 체크박스일 뿐인데 시간이 좀 걸렸다. 회원가입이나 로그인 페이지처럼 한 페이지를 한 파일로 끝낸 경우에는 script를 import해서 복잡한 기능을 쉽게 처리할 수 있었는데, 이번 장바구니 페이지는 컴포넌트로 분리되어 있다. 그래서 체크박스도 제목행에 있는 체크박스와 장바구니 상품별 체크박스를 구분해서 기능을 만들어야 했다.
1. 제목행 체크박스는 CartList 클래스에서 생성
2. 장바구니 상품별 체크박스는 CartListItem 클래스에서 생성
// CartList(제목행 체크박스)
buttonCartCheck.addEventListener("click", () => {
buttonCartCheck.classList.toggle("fill");
});
// CartListItem(장바구니 상품별 체크박스)
checkButton.addEventListener("click", () => {
checkButton.classList.toggle("fill");
});
css에서 fill 클래스에 대한 스타일을 만들어 놓고 toggle 기능을 추가하는 간단한 코드이다. CartList 클래스에는 이 기능만 추가하고 나머지 체크박스 기능은 CartListItem 클래스에서 처리했다.
const allCheckBox = document.querySelectorAll(".button-cart-check");
let itemAllCheck = false;
for (let i = 0; i < allCheckBox.length; i++) {
allCheckBox[0].addEventListener("click", () => {
// 제목행 체크되어 있으면 나머지도 체크
if (allCheckBox[0].className.includes("fill")) {
allCheckBox[i].classList.add("fill");
itemAllCheck = true;
// 제목행 체크해제하면 나머지도 해제
} else {
allCheckBox[1].classList.remove("fill");
allCheckBox[i] && allCheckBox[i].classList.remove("fill");
itemAllCheck = false;
}
});
...(3.으로 이어짐)
allCheckBox
변수로 체크박스 클래스를 잡아줬다. 제목행 체크박스와 장바구니 상품별 체크박스 모두 ".button-cart-check"으로 처리했다. itemAllCheck
변수로는 제목행 체크박스 포함 모든 체크박스의 상태를 나타내고 있다.
for (let i = 0; i < allCheckBox.length; i++) {
...(2.내용)
if (itemAllCheck) {
!allCheckBox[i].className.includes("fill") &&
allCheckBox[0].classList.remove("fill");
itemAllCheck = false;
}
});
itemAllCheck
가 true인 상태일 경우로 전체 선택 상황을 만들고, 어떤 체크박스든 클래스명에 fill을 포함하고 있지 않으면 제목행 클래스명에서 fill을 제거하는 방식으로 처리할 수 있었다. 여기까지 매우 간단했다.
for (let i = 0; i < allCheckBox.length; i++) {
...(2.내용)
allCheckBox[i].addEventListener("click", () => {
// 1) NodeList 배열화
const arrAllCheckBox = Array.from(allCheckBox);
// 2) className 배열 변수화
const arrClassNameAllCheckBox = arrAllCheckBox.map(
(item) => item.className
);
// 3) 배열 맨앞 요소 제거
arrClassNameAllCheckBox.shift();
// 4) 모든 배열에 fill 문자가 있는지 확인
let everyElHasFill = arrClassNameAllCheckBox.every((el) => {
return el.includes("fill");
});
// 3.의 내용
if (itemAllCheck) {
!allCheckBox[i].className.includes("fill") &&
allCheckBox[0].classList.remove("fill");
itemAllCheck = false;
} else {
everyElHasFill
? allCheckBox[0].classList.add("fill")
: allCheckBox[0].classList.remove("fill");
}
});
장바구니 상품별로 체크박스가 있는데, 그 체크박스들이 개별적으로 모두 선택이 되는 경우도 있으므로 그 경우에는 제목행을 컨트롤해야 했다. 어떻게 모두 체크되었는지 확인할 수 있을지 그 방법을 많이 고민했다. 나는 클래스명으로 체크여부를 확인할 수 있다는 것을 활용해서 클래스명을 모두 배열로 반환했다.
querySeletorAll
로 돔을 잡게되면 NodeList
로 확인되는데, 이 NodeList
는 DOM API가 여러 개의 결과 값을 반환하기 위한 DOM 컬렉션 객체이다. 즉, 배열처럼 확인이 되도 곧바로 배열로 사용할 수는 없으므로 변환을 해줘야 한다.
// 위와 같은 NodeList를 진짜 배열로 만들기 위한 방법
Array.from(querySeletorAll을 잡아준 변수)
Array.from
으로 변환시켜주면 아래 그림처럼 확인된다. 모양은 똑같지만 배열 메서드를 사용하기 위해서는 이렇게 변환을 꼭 시켜줘야 한다.
지금은 노드 자체를 잡은것에 불과하기 때문에 그 안으로 들어가서 클래스명만 남긴다. item을 찍어서 확인해보면 여러 속성들을 잡을 수 있다. 지금 필요한 내용은 className이다. 이렇게 map으로 잡아주면 각 체크박스 노드의 클래스명만 배열에 남게 된다. 이 배열만 남은 것도 변수화 해준다.
arrAllCheckBox.map((item) => item.className);
// 결과 예시: ['button-cart-check fill', 'button-cart-check', 'button-cart-check']
const arrClassNameAllCheckBox = arrAllCheckBox.map(
(item) => item.className
);
그리고 또 중요한 것. 제목행 체크박스의 상태는 결과이지 조건에 들어가지 않으므로 배열의 첫번째 요소를 제거해서 사용한다. shift()
를 쓰면 첫 요소를 삭제할 수 있다. 마지막 요소를 제거하는 건 pop()
이다.
// 배열 맨앞 요소 제거 후 변수 사용(메서드 포함해서 사용하지 않는다)
arrClassNameAllCheckBox.shift();
마지막으로 배열의 모든 요소에 "fill"이 있는지 확인하는 메서드를 사용해서 everyElHasFill
이라는 변수에 저장한다.
let everyElHasFill = arrClassNameAllCheckBox.every((el) => {
return el.includes("fill");
});
염두에 둬야 하는 점은, 장바구니 데이터를 이 클래스에서 세팅하기 때문에 결과가 여러번 반복된다는 것이다. 예를 들어, 장바구니에 담긴 상품이 3가지면 함수가 3번 호출되는 식이다. 각 실행 값이 다를 수 있다. false-true-false 이런 식으로. 복잡해 보이지만 마지막 값으로 처리를 하면 되는 거였다.
그래서 everyElHasFill
의 불리언 값을 사용해서 제목행 체크박스를 컨트롤 하려면 everyElHasFill
값이 true일 때와 false일 때를 모두 대응해서 어떻게 처리해야 할지 코드를 짜면 된다.
만약에 everyElHasFill && allCheckBox[0].classList.add("fill")
처럼 작성할 경우 true가 한번이라도 나오면 제목행이 체크되어 버린다. 마지막 값이 실제와 부합하므로 삼항연산자를 사용해서 false까지 처리해줘야 한다.
else {
everyElHasFill ? allCheckBox[0].classList.add("fill")
: allCheckBox[0].classList.remove("fill");
}
장바구니 체크박스일 뿐인데 필요한 배열을 찾고 활용하는 데 시간이 걸리는 것 같다. 한번 해봤으니 다음에는 더 빨리 생각났으면 좋겠다. 이번 달까지 데브 스토어 완성할 수 있을까?;