Project 1 : Movie Seat

Author: Traversy Media


Đây là project mình học được từ tác giả Traversy Media, mình chia nhỏ vấn đề ra cho các bạn dễ hiểu. Quan trọng là qua project này mình học được điều gì để áp dụng. Hãy cùng xem nhé.

Project này sẽ chọn các ghế, đếm số ghế được chọn, lưu thông tin ghế được chọn vào localStorage và load thông tin ghế lên UI. Ta sẽ có ghế occupied là ghế không chọn được, sẽ có màu cam. ghế ta chọn sẽ có mày xanh.

Choose your seat in javascript

You select 0 seats

Ta xem source code của project này nhé.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Movie Seat Booking</title>

<style>

 .movie-container {
   background-color: lightyellow;
   width: 500px;
   margin: 30px auto;
   padding: 10px;
  }
 .row {
   display:flex;
   flex-direction: row;
   justify-content: center;
 }
 .seat{
   width: 12px;
   height: 15px;
   background-color:#444451;
   margin: 3px;
   border-top-left-radius: 10px;
   border-top-right-radius: 10px;
 }
.seat.selected {
  background-color: #6feaf6;
}

.seat.occupied {
  background-color: orange;
}

.seat:not(.occupied):hover {
  cursor: pointer;
  transform: scale(1.2);
}

</style>

</head>
<body>
  <div class="movie-container">
  
  <div class="row">
      <h3>Choose your seat in javascript</h3>
    </div>

  <div class="row">
      <div class="seat"></div>
      <div class="seat"></div>
      <div class="seat occupied"></div>
      <div class="seat"></div>
      <div class="seat"></div>
      <div class="seat"></div>
      <div class="seat occupied"></div>
    </div>


   <div class="row">
      <div class="seat"></div>
      <div class="seat"></div>
      <div class="seat occupied"></div>
      <div class="seat"></div>
      <div class="seat"></div>
      <div class="seat"></div>
      <div class="seat occupied"></div>
    </div>
  
  <div class="row">
      <p>You select <span id='count'>0</span> seats</p>
     </div>
  </div>
<script>
 const count = document.getElementById('count');

 const container = document.querySelector('.movie-container');
 const seats = document.querySelectorAll('.row .seat:not(.occupied)');
 // convert to array
 let arraySeats = [...seats];
 console.log(arraySeats);
 console.log(arraySeats.length);

 container.addEventListener('click', (e) => {
   
   console.log(e.target);
   if( e.target.classList.contains('seat') &&
     !e.target.classList.contains('occupied') ){
     e.target.classList.toggle('selected');
     updateSelectedSeat();
   }
 });

 function updateSelectedSeat(){
   const selected = document.querySelectorAll('.row .seat.selected');
   count.innerText = selected.length;

   const selectedIndex = [...selected].map(seat => {
      return [...seats].indexOf(seat);
   });

   localStorage.setItem('selectedSeats', JSON.stringify(selectedIndex));  
   console.log(selectedIndex);
 }

populateUI();

function populateUI(){
  const selectedSeats = JSON.parse(localStorage.getItem('selectedSeats'));
  console.log(selectedSeats);
  if(selectedSeats !== null && selectedSeats.length > 0) {
    seats.forEach((seat,index) => {
      if(selectedSeats.indexOf(index) > -1 ){
	seat.classList.add('selected');
      }
    }); 
  }
}

updateSelectedSeat();
</script>

Đầu tiên ta học được cách đưa một div vào giữa trang

.movie-container {
   background-color: lightyellow;
   width: 500px;
   margin: 30px auto;
   padding: 10px;
  }

Ta định nghĩa div này có width là 500px và div này nằm giữa trang.

margin: 30px auto;

Trên dưới có margin là 30px, trái, phải auto, thì div sẽ nằm giữa.

Ta học được cách tạo các ghế dùng class "seat"


<div class="row">
  <div class="seat"></div>
  <div class="seat"></div>
  <div class="seat occupied"></div>
  <div class="seat"></div>
  <div class="seat"></div>
  <div class="seat"></div>
  <div class="seat occupied"></div>
</div>

<style>
.seat{
  width: 12px;
  height: 15px;
  background-color:#444451;
  margin: 3px;
  border-top-left-radius: 10px;
  border-top-right-radius: 10px;
}

</style>

Tại seat với with và height. Mỗi ghế cách nhau 3px. Và cạnh trên bên trái và phải được bo tròn 10px. Nhưng lúc này các ghế chưa xếp thành hàng. Ta dùng flex để chuyển các div này thành hàng.

 .row {
   display:flex;
   flex-direction: row;
   justify-content: center;
 }

Ta chỉnh các ghế có màu khác nhau dựa trên class. class seat màu khác , class occupied màu khác, class selected màu khác.

.seat.selected {
  background-color: #6feaf6;
}

.seat.occupied {
  background-color: orange;
}

Ta muốn khi đưa chuột vào các ghế chỉ có ghế có class là seat thì chuyển con trỏ thành bàn tay, và phóng to lên 1 tí. Còn class occupied thì không chuyển thành bàn tay và cũng không phóng to lên.

.seat:not(.occupied):hover {
  cursor: pointer;
  transform: scale(1.2);
}

Ok như vậy là ta đã thiết kế xong giao diên. Giờ tới phần tương tác click vào ghế thì sẽ thêm class "selected" vào, nếu click vào lần nữa sẽ xóa class "selected". Còn nếu click vào ghế có class "occupied" thì sẽ không thêm class "selected".

Vậy đầu tiên ta phải addEventListener để bắt sự kiện click.

<div class="movie-container">
  ...
</div>

<script>
const container = document.querySelector('.movie-container');

container.addEventListener('click', (e) => {
   console.log(e.target);
   if( e.target.classList.contains('seat') && 
     !e.target.classList.contains('occupied') ){
     e.target.classList.toggle('selected');
     updateSelectedSeat();
   }
 });

</script>

console.log(e.target) sẽ in div mà ta click vào. Ta dùng if để kiểm tra nếu div ta click vào có chứa class là seat và không có chứa class là occupied thì sẽ toggle (click 1 lần thì thêm class, click 1 lần nữa thì xóa class) class selected.

Bây giờ làm sao biết ta có bao nhiêu ghế ? Ta dùng querySelectorAll để lấy ra tất cả các ghế, ngoại trừ ghế occupied.

 const seats = document.querySelectorAll('.row .seat:not(.occupied)');

Nhưng querySelectorAll trả về là một NodeList. Ta phải chuyển vể array.

 // convert to array
 let arraySeats = [...seats];
 console.log(arraySeats);
 console.log(arraySeats.length);

Tính tổng số phần tử trong array ta dùng arraySeats.length.

Bây giờ làm sao tìm có bao nhiêu ghế được chọn. Ta thấy ví dụ trên khi ta click vào 1 ghế , thì ghế đó sẽ được thêm class selected. Ta sẽ dùng class selected để tìm ra các ghế ta đã chọn.

   const selected = document.querySelectorAll('.row .seat.selected');

Ta sẽ cập nhật số lượng ghế được chọn vào id "count".


  const count = document.getElementById('count');
  container.addEventListener('click', (e) => {
     .... 
     updateSelectedSeat();
   }
 });

 function updateSelectedSeat(){
   const selected = document.querySelectorAll('.row .seat.selected');
   count.innerText = selected.length;

    ...
 }

Hiện nay ta chưa lưu ghế được chọn, nếu ta nhấn refresh trang lại thì những ghế ta chọn sẽ mất. Ta dùng localStorage để lưu lại vị trí các ghế (index). Trước khi lưu vào localStorage ta phải tìm ra vị trí các ghế được chọn.

function updateSelectedSeat(){
   const selected = document.querySelectorAll('.row .seat.selected');
   count.innerText = selected.length;
   const selectedIndex = [...selected].map(seat => {
      return [...seats].indexOf(seat);
   });

   console.log(selectedIndex);
 }
  

Đoạn code này sẽ tìm được những ghế được chọn.

const selectedIndex = [...selected].map(seat => {
  return [...seats].indexOf(seat);
});

[...selected] chuyển NodeList thành array. Ta tiếp tục dùng map để loop qua tất cả phần tử của selected và ta sẽ tìm các vị trí của từng ghế được chọn trong tất cả ghế(seats), dùng indexOf(...), indexOf sẽ trả về index của array seats nếu có, còn nếu giá trị đưa vào indexOf không có trong seats thì sẽ return về -1. Ta sẽ có được 1 array chứa các vị trí ghế được chọn.

Bây giờ ta sẽ dùng localStorage để lưu lại.

localStorage.setItem('selectedSeats', JSON.stringify(selectedIndex));  

Ta có thông tin vị trí các ghế, bây giờ ta sẽ load thông tin lên và add class selected cho các ghế này, thì ta refresh lại trang thì thông tin cũng không bị mất.


populateUI();
function populateUI(){
  const selectedSeats = JSON.parse(localStorage.getItem('selectedSeats'));
  console.log(selectedSeats);
  if(selectedSeats !== null && selectedSeats.length > 0) {
    seats.forEach((seat,index) => {
      if(selectedSeats.indexOf(index) > -1 ){
	seat.classList.add('selected');
      }
    }); 
  }
}

 updateSelectedSeat();

Lấy ra các ghế từ localStorage

  const selectedSeats = JSON.parse(localStorage.getItem('selectedSeats'));

Ta sẽ kiểm tra selectedSeats khác rỗng và phải có ghế được chọn thì lúc đó ta sẽ loop qua tất cả các ghế seats. Ta sẽ kiểm tra từng index xem index nào có trong selectedSeats, nếu index của seats nào có thì thêm class selected vào. Lúc này ta refresh lại trang thì sẽ ko mất nữa, Nhưng ta sẽ thấy "You select 0 seats". Ta phải gọi hàm updateSelectedSeat() để cập nhật lại số lượng ghế đã được chọn. Như vậy ta đã hoàn tất project này rồi, hi vọng nó dễ hiểu hihi .Qua project này các bạn học được gì ? ^_^