TS스터디 팀원들과 함께 매주 주차별 과제를 진행합니다
https://github.com/bradtraversy/vanillawebprojects
기능 동작 예시 링크 : https://vanillawebprojects.com/projects/movie-seat-booking/
const container = document.querySelector('.container') as HTMLDivElement;
const seats = document.querySelectorAll(
'.row .seat:not(.occupied)',
) as NodeListOf<Element>;
const count = document.getElementById('count') as HTMLSpanElement;
const total = document.getElementById('total') as HTMLSpanElement;
const movieSelect = document.getElementById('movie') as HTMLSelectElement;
type initialSettingFunc = () => void;
type changeTextFunc = () => void;
type updateSeatsFunc = () => void;
type seatIndex = {
seatNum: number[];
};
let seatStatus: seatIndex = {
seatNum: [],
};
type movieInfo = {
movieIndex: number;
moviePirce: number;
};
let movieStatus: movieInfo = {
movieIndex: movieSelect.selectedIndex,
moviePirce: Number(movieSelect.value),
};
// 브라우저 하단에 배치된 선택 좌석 갯수 , 총 금액 메세지 수정 메소드
const changeText: changeTextFunc = () => {
let status: seatIndex = JSON.parse(localStorage.getItem('selectedSeats')!);
let currentSeatsCount: number = status.seatNum.length;
count.innerText = String(currentSeatsCount);
total.innerText = String(currentSeatsCount * movieStatus.moviePirce!);
};
const initialSetting: initialSettingFunc = () => {
let arr: number[] = [];
seats.forEach((i, index) => {
arr.push(index);
});
let localStorageResult: string | null;
// 좌석 불러오기
localStorageResult = localStorage.getItem('selectedSeats');
// 브라우저 최초 실행 시
if (localStorageResult === null) {
localStorage.setItem('selectedSeats', JSON.stringify(seatStatus));
}
if (localStorageResult !== null) {
let status: seatIndex = JSON.parse(localStorageResult);
// 상태 변경
seatStatus = { ...status };
// css변경을 위한 클래스명 추가
seatStatus.seatNum.forEach((i) => {
if (arr.includes(i)) {
seats[i].classList.add('selected');
}
});
}
// 선택된 영화 불러오기
localStorageResult = localStorage.getItem('selectedMovieInfo');
// 브라우저 최초 실행 시
if (localStorageResult === null) {
localStorage.setItem('selectedMovieInfo', JSON.stringify(movieStatus));
}
if (localStorageResult !== null) {
let status: movieInfo = JSON.parse(localStorageResult);
// 상태 변경
movieStatus = { ...status };
// select태그 값 변경
movieSelect.selectedIndex = movieStatus.movieIndex;
// 하단 메세지 수정
changeText();
}
};
// 좌석 업데이트 메소드
const updateSeats: updateSeatsFunc = () => {
const newSelectedNodes: Element[] = Array.from(
document.querySelectorAll('.row .seat.selected') as NodeListOf<Element>,
);
let arr: number[] = newSelectedNodes.map((i) => {
return Array.from(seats).indexOf(i);
});
// 상태 변경
let status: seatIndex = {
seatNum: arr,
};
seatStatus = { ...status };
// 변경된 상태를 로컬스토리지에 등록
localStorage.setItem('selectedSeats', JSON.stringify(seatStatus));
changeText();
};
movieSelect.addEventListener('change', (e: Event) => {
let price: number = Number((e.target as HTMLSelectElement).value);
let index: number = Number((e.target as HTMLSelectElement).selectedIndex);
let status: movieInfo = {
movieIndex: index,
moviePirce: price,
};
movieStatus = { ...status };
localStorage.setItem('selectedMovieInfo', JSON.stringify(movieStatus));
changeText();
});
// Seat click event
container.addEventListener('click', (e: Event) => {
if (
(e.target as HTMLDivElement).classList.contains('seat') &&
!(e.target as HTMLDivElement).classList.contains('occupied')
) {
(e.target as HTMLDivElement).classList.toggle('selected');
updateSeats();
}
});
initialSetting();
이 로직은 좌석을 인덱스로 사용합니다
위의 사진처럼 div태그는 현재 48개가 존재하며
선택된(하늘색 좌석)좌석은 각각 0,1,3,5번째 div요소가 되는 것입니다
최상단에서 상태 값을 가지게 되며 상태값이 갱신 될 때마다 로컬스토리지도 갱신합니다
상태값을 기반으로 영화 선택 , 총 좌석 확인 , 금액 계산 등의 작업을 진행합니다
상태값을 기반으로 위와 같은 작업을 진행한다면 ,
로컬스토리지는 브라우저가 실행 될 때마다 로컬스토리지의 값으로 초기 상태를 업데이트 하고 이 상태를 기반으로 초기 셋팅을 진행하게 됩니다
(ex 마지막으로 선택했던 좌석과 영화 상태를 그대로 사용)
즉, 각종 이벤트에 대한 로직은 상태값을 기반으로 , 브라우저의 재 실행 시 데이터 보존은 로컬스토리지를 기반으로 진행하여 상태와 로컬스토리지의 사용처를 분리합니다
type seatIndex = {
seatNum: number[];
};
let seatStatus: seatIndex = {
seatNum: [],
};
type movieInfo = {
movieIndex: number;
moviePirce: number;
};
let movieStatus: movieInfo = {
movieIndex: movieSelect.selectedIndex,
moviePirce: Number(movieSelect.value),
};
브라우저 하단에 배치된 메세지를 수정하는 메소드입니다
const changeText: changeTextFunc = () => {
let status: seatIndex = JSON.parse(localStorage.getItem('selectedSeats')!);
let currentSeatsCount: number = status.seatNum.length;
count.innerText = String(currentSeatsCount);
total.innerText = String(currentSeatsCount * movieStatus.moviePirce!);
};
로컬스토리지에서 좌석 정보를 가져옵니다
로직상 이때 호출하는 로컬스토리지의 selectedSeats값은
(빈 배열이라 하더라도)무조건 null이 아니기에 Non-null Assertion을 사용 했습니다
좌석 정보로 상태값의 배열 길이가 현재 선택된 좌석 갯수입니다
그러므로 현재 선택된 좌석 갯수 , 현재 선택된 영화 상태를 기반으로 메세지를 수정합니다
브라우저가 실행 됐을 때 , 초기셋팅을 진행하는 메소드입니다
const initialSetting: initialSettingFunc = () => {
// occupied가 아닌 좌석들의 인덱스를 가지는 배열
let nonoOccupied: number[] = [];
seats.forEach((i, index) => {
//i는 div요소를 나타내고 이 로직에서는 div요소의 인덱스가 좌석 번호를 의미함
nonoOccupied.push(index);
});
let localStorageResult: string | null;
// 좌석 불러오기
localStorageResult = localStorage.getItem('selectedSeats');
// 브라우저 최초 실행 시
if (localStorageResult === null) {
localStorage.setItem('selectedSeats', JSON.stringify(seatStatus));
}
if (localStorageResult !== null) {
let status: seatIndex = JSON.parse(localStorageResult);
// 상태 업데이트
seatStatus = { ...status };
// css변경을 위한 클래스명 추가
seatStatus.seatNum.forEach((i) => {
if (nonoOccupied.includes(i)) {
seats[i].classList.add('selected');
}
});
}
// 선택된 영화 불러오기
localStorageResult = localStorage.getItem('selectedMovieInfo');
// 브라우저 최초 실행 시
if (localStorageResult === null) {
localStorage.setItem('selectedMovieInfo', JSON.stringify(movieStatus));
}
if (localStorageResult !== null) {
let status: movieInfo = JSON.parse(localStorageResult);
// 상태 업데이트
movieStatus = { ...status };
// select요소의 선택 인덱스 변경
// 브라우저 재 실행시, select의 값이 바뀌어져 있는 경우가 있으므로
// movieStatus의 값을 기반으로 변경해 줘야 함
movieSelect.selectedIndex = movieStatus.movieIndex;
// 하단 메세지 수정
changeText();
}
};
💡 이때 상태값은 최초의 상태이므로 영화좌석은 빈 배열, 영화정보는 첫번째 select태그의 정보를 넣습니다TS에서 로컬스트리지를 사용할 시에 이러한 방법으로
**무조건 null 을 막아줘야 합니다**
좌석 업데이트 메소드입니다
const updateSeats: updateSeatsFunc = () => {
const newSelectedNodes: Element[] = Array.from(
document.querySelectorAll('.row .seat.selected') as NodeListOf<Element>,
);
let arr: number[] = newSelectedNodes.map((i) => {
return Array.from(seats).indexOf(i);
});
// 상태 업데이트
let status: seatIndex = {
seatNum: arr,
};
seatStatus = { ...status };
// 변경된 상태를 로컬스토리지에 등록
localStorage.setItem('selectedSeats', JSON.stringify(seatStatus));
changeText();
};
마우스로 좌석을 선택하게 되면 발생하는 메소드입니다
.selected클래스가 토글 된 모든 div요소들을 가져와서
상태를 업데이트하고 , 변경된 상태를 로컬스토리지에 등록합니다
이제 어플리케이션을 종료하고 다시 실행해도 이 로컬스토리지 값을 기반으로 이어서 진행하게 됩니다
( css는 이미 .selected클래스가 토글 된 순간 사라지거나 추가되기 때문에 여기서 다루지 않습니다 )
그리고 changeText()를 호출해서 하단 텍스트를 수정합니다
영화 선택 이벤트 입니다
movieSelect.addEventListener('change', (e: Event) => {
let price: number = Number((e.target as HTMLSelectElement).value);
let index: number = Number((e.target as HTMLSelectElement).selectedIndex);
let status: movieInfo = {
movieIndex: index,
moviePirce: price,
};
movieStatus = { ...status };
localStorage.setItem('selectedMovieInfo', JSON.stringify(movieStatus));
changeText();
});
좌석 선택 이벤트 입니다
container.addEventListener('click', (e: Event) => {
if (
(e.target as HTMLDivElement).classList.contains('seat') &&
!(e.target as HTMLDivElement).classList.contains('occupied')
) {
(e.target as HTMLDivElement).classList.toggle('selected');
updateSeats();
}
});