Vanilla JS Toy 프로젝트 - 2. Movie Seat Booking (javascript편)

falconlee236

·

2022. 3. 1. 23:49

반응형

여기에 올라온 모든 프로젝트와 소스코드는 here 여기서 확인할 수 있다.

vanilla javascript Toy project 2. Movie Seat Booking

이번 프로젝트에서는 ES6에서 새로 지원하는 문법을 다수 사용했다. 생각보다 함수는 적은데, 함수의 내용이 알차서 그런지 공부가 엄청 되었다. 이번 글에서 소개할 내용은 다음과 같다.

  1. javascript의 localstorage
  2. 프로젝트에서 사용되는 모든 함수 설명
  3. 자바스크립트 파일의 로직 설명
반응형

0. DOM Element 가져오기


이 프로그램에서 자바스크립트로 해야하는 기능을 요약하자면 총 4가지이다.

  1. 영화를 선택할때 값 localstorage에 저장하기
  2. 선택한 좌석의 개수를 좌석의 변화에 따라 계속 갱신하기
  3. 선택한 좌석의 개수와 선택한좌석의 위치를 localstorage에 저장하기
  4. 저장한 값을 이용해서 맨 마지막에 있는 텍스트 값 변경하기

(1)에서는 맨 위에 있는 영화 선택 select태그를 가져와야 하고, (2)에서는 이미 차있는 좌석을 제외하고 모두를 가져와야 하며, (3)에서는 좌석의 위치를 알아야 하기 때문에 container 클래스 자체를 가져와야 하며, 마지막 (4)에서는 맨 마지막에 있는 텍스트 값을 변경해야 하므로 개수와 총 금액을 나타내는 요소를 가져와야 한다. 각각 필요한 요소를 가져오기 위해서는 다음과 같은 코드가 필요하다.

    const container = document.querySelector(".container");
    const seats = document.querySelectorAll(".row .seat:not(.occupied)");
    const count = document.getElementById("count");
    const total = document.getElementById("total");
    const movieSelect = document.getElementById("movie");
반응형

1. localStorage란?


간단하게 설명하자면 간단한 데이터를 클라이언트 상에서, 즉 브라우저에 저장하는 기술인 웹 스토리지를 localstorage라고 한다. 이 localstorage는 웹 브라우저 상에서만 동작하기 때문에 windows 객체의 속성으로 정의되어 있으며, node.js와 같은 환경에서 사용할 수는 없다. windows객체는 대부분 생략하기 때문에 localStorage라고 선언해서 그냥 사용이 가능하다. 웹 스토리지는 기본적으로 키와 값으로 이루어진 데이터를 저장할 수 있다. 그냥 해시테이블 자료구조라고 생각하면 이해하기 쉽다.
localStorage의 기본 API
💎 데이터를 저장하기

    localStorage.setItem("key", value);

💎 데이터를 불러오기

    localStorage.getItem("key");

💎 데이터를 삭제하기

    localStorage.removeItem("key");

💎 모든 키의 데이터 삭제하기

    localStorage.clear();
  • 주의할 점은 웹스토리지에서는 오직 문자열(string)데이터 타입만 지원한다는 것이다. value값으로 숫자나 객체를 넣어버리면 모두 문자열로 형변환 하기 때문에 예상치 못한 오류가 생길 수 있다.
  • 이 문제를 해결하기 위해서 value값을 JSON형태로 데이터를 읽고 쓰는 것이다. 위와 같이 로컬 스토리지에 쓸 데이터를 JSON 형태로 직렬화(serialization)하고, 읽은 데이터를 JSON 형태로 역직렬화(deserialization)해주면 원본의 데이터를 그대로 얻을 수 있다.
  • 💎 직렬화
  • localStorage.setItem('key', JSON.stringify([1, 2, 3]))
  • 💎 역직렬화
  • JSON.parse(localStorage.getItem('key'))
  • 웹 스토리지에 저장된 데이터는 웹 페이지를 닫는다고 해서 사라지지 않으므로 불필요한 데이터가 남지 않도록 청소하는 것이 좋다. 최대 용량은 5MB라고 한다. 이때 유효기간은 컴퓨터를 포맷하지 않는 이상 계속 저장된다고 하니 가급적 세션 스토리지를 사용하는것이 좋아보인다.
반응형

2. 함수 설명


  1. populateUI()
    브라우저가 새로고침될때 마다 맨 처음에 반드시 실행되는 함수이다. 이 함수의 역할은 로컬 스토리지에 저장된 값을 불러오고, 만약 값이 존재하거나 선택된 좌석의 수가 1개 이상이라면 다음 작업을 수행한다.
  • 현재 있는 모든 좌석을 foreach문을 이용해서 순회하며 현재 좌석 index가 로컬스토리지 배열에 존재한다면 selected클래스를 추가해서 선택 현황을 유지한다.

그리고 선택한 영화현황도 유지해야 하기 때문에 그 값도 로컬 스토리지에 존재한다면 현황을 유지해준다.

  • 이때 select태그 속성중에서 selectedindex 속성을 사용하면 편하다. select 태그에서 원소를 선택한다면 selectedindex값이 대응하는 원소의 index로 변하기 때문에 따라서 저 값을 바꾼다면 초기 선택 원소를 설정할 수 있다.
function populateUI(){
    const selectedSeats = JSON.parse(localStorage.getItem("selectedSeats"));

    if(selectedSeats !== null && selectedSeats.length > 0){
        seats.forEach((seat, index) => {
            if(selectedSeats.indexOf(index) > -1){
                seat.classList.add("selected");
            }
        });
    }

    const selectedMovieIndex = localStorage.getItem("selectedMovieIndex");
    if(selectedMovieIndex !== null){
        movieSelect.selectedIndex = selectedMovieIndex;
    }
}
  1. setMovieData(movieIndex, moviePrice)
    선택한 영화 select 값에 대한 정보를 저장하는 함수이다. key "selectedMovieIndex"에서는 선택한 영화의 index값을, key "selectedMoviePrice"에서는 선택한 영화의 price값을 로컬스토리지에 저장한다.
    function setMovieData(movieIndex, moviePrice){
     localStorage.setItem("selectedMovieIndex", movieIndex);
     localStorage.setItem("selectedMoviePrice", moviePrice);
    }
  1. updateSelectedCount()
    좌석을 선택/해제할때마다, 영화를 선택할때마다 계속 반복 수행하게될 코드이다. 현재 선택되어 있는 좌석의 DOM요소를 가져온다음, 그 DOM요소가 원래 좌석에 어느 위치에 있는지 seatsIndex배열에 저장하는데 저장하는 방식이 정말 javascript스러운 방식이어서 추가 설명을 하도록 하겠다.
  • 자바스크립트의 스프레드 문법
    • ...유사 배열 이런식으로 코드를 작성하면 유사 배열에 있는 모든 원소를 전부 풀어 해칠 수 있다. 그리고 그 풀어 해친 원소들을 다시 배열로 감싸면 [...유사 배열], 유사 배열을 실제 배열값으로 변환할 수 있다!!
    • 왜 스프레드 문법을 사용하냐면 querySelectorAll 함수로 가져온 값은 정적 NodeList API를 가져오기 때문이다. 물론 NodeList도 Array-like이기 때문에 배열과 관련된 함수를 사용할 수 있지만 그냥 배열로 변환해서 사용하면 편하기 때문에 저 문법을 사용한다.
  • selected된 좌석의 배열이 각각 전체 좌석에 어느 위치, 어느 index에 있는지 알고 싶기 때문에 각 원소에 1대 1대응이 되는 map함수를 사용한다. map함수안에 있는 화살표 함수 안에는 전체 좌석의 배열인 [...seats]에서 indexOf()함수를 사용해서 해당 선택된 좌석이 전체에 어느 index에 있는지를 반환하는 코드가 있다. 따라서 좌석원소를 각각에 대응하는 좌석 index원소로 변환한다.
  • index 원소 배열을 로컬 스토리지에 저장하고 싶은데 로컬스토리지는 문자열 파일 형식만 지원하기 때문에 JSON으로 직렬화가 필요하다. 그럴때 JSON.stringify를 사용한다.
  • 그리고 전체 선택한 좌석의 총 개수가 나오기 때문에 그 개수와 그 개수 * 현재 영화 값을 innertext를 이용해서 갱신해 주고 마지막으로 영화 정보를 갱신한다. 이때 movieSelect.value값은 현재 선택된 원소인 selectIndex가 가리키는 값의 value를 문자열 형식으로 return한다.
  • 로컬스토리지는 문자열 형식을 사용하기 때문에 숫자 연산을 하기 위해서는 숫자 타입으로 형변환 해야하는데 자바스크립트에서는 문자열 앞에 "+"를 붙여서 쉽게 숫자로 변환 할 수 있다. 이것이 이해가 안되는 사람은 자바스크립트 문법이 탄탄하지 않다는 뜻이므로 문법 공부부터 하자.^ 문자열 값인 movieSelect.value앞에 +를 붙여서 숫자 자료형으로 변환하는 모습
  • let ticketPrice = +movieSelect.value;
function updateSelectedCount(){
    const selectedSeats = document.querySelectorAll(".row .seat.selected");
    const seatsIndex = [...selectedSeats].map((seat) => [...seats].indexOf(seat));

    localStorage.setItem("selectedSeats", JSON.stringify(seatsIndex));

    const selectedSeatsCount = selectedSeats.length;
    count.innerText = selectedSeatsCount;
    total.innerText = selectedSeatsCount * ticketPrice;

    setMovieData(movieSelect.selectedIndex, movieSelect.value);
}
반응형

3. 이벤트 등록하기


총 2가지 이벤트를 등록했다.

  1. 영화선택 select태그에서 값이 변환 경우 -> change event 발생
    티켓 값을 갱신해주고, 그 변환 영화 원소의 정보를 setMovieData를 사용해서 로컬스토리지에 저장, 티켓 값이 갱신되었기 때문에 전체 영화 값이 바뀌었을 것이고 따라서 updateSelectCount를 사용해서 값을 갱신해준다.
  2. movieSelect.addEventListener("change", e => { ticketPrice = +e.target.value; setMovieData(e.target.selectedIndex, e.target.value); updateSelectedCount(); })
  3. 좌석이 눌렸을 경우 -> click event 발생
    그런데 무작정 눌렸을 경우 이벤트가 발생되면 안된다. 왜냐하면 이미 차있는 occupied 좌석을 눌렀을 때는 전체 선택한 좌석의 수가 변함이 없기 때문에 쓸데없는 작업 시간만 늘어나기 때문이다. 따라서 선택한 target이 seat이면서, occupied클래스를 포함하지 않는 경우 이벤트를 수행하게 한다.
  • toggle(p)함수를 사용하면 쉽게 클래스를 추가, 삭제를 할 수 있다. 이 함수는 만약 해당 p 클래스가 존재한다면 그 class를 제거하고, 해당 p클래스가 존재하지 않는다면 그 class를 추가한다.
    클래스의 변동이 있다면 당연히 전체 영화 값이 변화했으므로 updateSelectCount는 필수이다.
container.addEventListener("click", e => {
    if(e.target.classList.contains('seat') && 
    !e.target.classList.contains("occupied")){
        e.target.classList.toggle("selected");
        updateSelectedCount();
    }
});

마지막으로 아무 일이 일어나지 않았더라도 전체 영화 값과 선택한 좌석 개수는 갱신되어야 하기 때문에 마지막으로 updateSelectCount를 한번 더 선언해 준다.

반응형