vanillawebprojects.comで映画の座席表を作成してみた。（Javascript）

この度、vanillawebprojects.com　というサイトを使用して、Javascriptを使用した座席予約表を作成しました。
アウトプットも兼ねて投稿します。

## 完成品
### 画面１
![スクリーンショット 2022-05-27 22.16.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/464c052b-cade-7b56-05ab-df5081beccde.png)

### 画面２
![スクリーンショット 2022-05-27 22.16.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/8579bca1-5ed9-815d-2a56-26df9e66eb54.png)

### 画面３
![スクリーンショット 2022-05-27 22.16.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/64ac68f9-3c92-941f-61a3-1442f75a7472.png)

### 画面４
![スクリーンショット 2022-05-27 22.17.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/30c68790-4293-e3eb-bcfb-c020e6d64f4d.png)

### 仕様説明
![スクリーンショット 2022-05-27 22.17.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/754263/fccdfbb7-b9d2-6778-669a-2b758aca9d8a.png)

## ソースコード
各ソースコードはGitHubにアップされているので最悪コピペでも作成は可能です。
自分のは映画の画像も切り替わるというアレンジを少し加えています。
リンク先↓
https://github.com/bradtraversy/vanillawebprojects/tree/master/movie-seat-booking

## Javascript　ソースの解説
今回はJavascriptの学習を兼ねての作成であったため、Javascriptのみを解説していきます。
コメントアウトにもある程度解説は記載しております。

```javascript
'use strict'

// コンテナーの取得
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');

const movieImage = document.getElementById('selected-movie-image');
const bgImage = document.getElementById('bg');

// バットマン画像URL
const batManUrl = 'picture/the-batman.jpeg';
const batManBackUrl = 'picture/the-batman-back.jpg';

// ドクター・ストレンジ画像URL
const doctorStrangeUrl = 'picture/Doctor-Strange-2.jpeg';
const doctorStrangeBackUrl = 'picture/Doctor-Strang2-back.jpg';

// スパイダーマン画像URL
const spiderManUrl = 'picture/spiderman.jpg';
const spiderManBackUrl = 'picture/Spider-Man-No-Way-Home-back.jpg';

// ジャスティスリーグ画像URL
const justiceLeagueUrl = 'picture/justisleague.jpeg';
const justiceLeagueBackUrl = 'picture/justice-league-back.jpeg';


// メソッド実行
 populateUI();


// チケットの値段を取得
// 変数の前に + を書く理由は、代入する値を”数値（number）”として代入するため
// +　をつけないとticketPriceの方はany、+をつけるとnumberになる。
let ticketPrice = +movieSelect.value;


// プルダウンのインデックス番号と金額の取得メソッド
function setMovieData(movieIndex, moviePrice){

    if(movieIndex != null){

        if(movieIndex == 0){
            movieImage.style.backgroundImage = "url(" + batManUrl + ")";
            bgImage.style.backgroundImage = "url(" + batManBackUrl + ")";

        }else if(movieIndex == 1){
            movieImage.style.backgroundImage = "url(" + doctorStrangeUrl + ")";
            bgImage.style.backgroundImage = "url(" + doctorStrangeBackUrl + ")";

        }else if(movieIndex == 2){
            movieImage.style.backgroundImage = "url(" + spiderManUrl + ")";
            bgImage.style.backgroundImage = "url(" + spiderManBackUrl + ")";

        }else if(movieIndex == 3){
            movieImage.style.backgroundImage = "url(" + justiceLeagueUrl + ")";
            bgImage.style.backgroundImage = "url(" + justiceLeagueBackUrl + ")";
        }
    }
    
    // localStorage = Javascriptでブラウザにデータを登録するWeb API
    sessionStorage.setItem('selectedMovieIndex', movieIndex);
    sessionStorage.setItem('selectedMoviePrice', moviePrice);

}



// カウント（席数）とトータル（代金）を更新
function updateSelectedCount(){

    // 選択したシートを取得
    const selectedSeats = document.querySelectorAll('.row .seat.selected');

    /*
     * 1.[...]はスプレッド構文。変数の値を配列に変換する方法の１つ。

     * 2.定数　selectedSeats　は、selectedクラスを持つシート全てを値にもつ。

     * 3.定数　seatsIndex は、選択シートのインデックス番号を格納する定数。

     * 4.[...selectedSeats]にmapメソッドを使用し、新たに配列を生成し、seatsIndexに値を格納。

     * 5.mapメソッド内の seat は、selectedSeats配列にある値を一つ取り出し、格納されている。

     * 6.[...seats]は占領されていない全てのシートを格納した配列。

     * 7.[...seats]配列にindexOfメソッドを使用し、[...seats]配列内に選択したシートがあるか確認。
     * あれば[...selectedSeats]配列にindex番号を格納。
    
     * 
     * 
     */
    const seatsIndex = [...selectedSeats].map(seat => [...seats].indexOf(seat));

    console.log([...seats]);
    console.log(seatsIndex);

    // JSON.stringify = JSON形式の文字列に変換するメソッド
    // JSON.stringify = Javascriptオブジェクトを取得し、JSON文字列を返す
    // selectedSeats　と名して、選択したシートのインデックス番号を格納。
    sessionStorage.setItem('selectedSeats', JSON.stringify(seatsIndex));

    console.log(JSON.stringify(seatsIndex));

    // 選択した席数をカウントして格納
    const selectedSeatsCount = selectedSeats.length;

     // HTML内のid="count"タグの値を再設定
    count.innerText = selectedSeatsCount;

    // HTML内のid="total"タグの値を再設定
    total.innerText = selectedSeatsCount * ticketPrice;

    // 選択した映画のプルダウンのインデックスとvalue（値段）を記録
    setMovieData(movieSelect.selectedIndex, movieSelect.value);
}


// localStorageとpopulate UIからデータの取得
function populateUI() {

    // JSON.parse = JSON文字列を取得し、Javascriptオブジェクトにして返す
    // localStorage にセットしたselectedSeatsを取得
    const selectedSeats = JSON.parse(sessionStorage.getItem('selectedSeats'));

    console.log(selectedSeats);

    console.log(sessionStorage.getItem('selectedSeats'));

    console.log(JSON.parse(sessionStorage.getItem('selectedSeats')));

    // JSON.parseでJavascriptオブジェクトに変換されたselectedSeatsを確認。
    if(selectedSeats != null && selectedSeats.length > 0) {

       /*
         * seat　→ seats(占領されていない全てのシートを格納した変数)の値を１つ格納。
         * index　→ seatのインデック番号。
         * selectedSeatsにindexOfメソッドを使用。
         * selectedSeatsに格納されている値のインデックス番号とseats配列に格納されているシートのインデックス番号を比較し、
         * 同じものがあるか確認。
         * あればseatsの値に、selectedクラスを付与する。
         * ↑ 上記の処理を行わないと、その後クリックイベントでselectedクラスを外すことができない。
         */
        seats.forEach((seat, index) => {

            console.log(index);

            // indexOfメソッドは引数に
            if(selectedSeats.indexOf(index) > -1) {
                seat.classList.add('selected');
            }
        });
    }

    const selectedMovieIndex = sessionStorage.getItem('selectedMovieIndex');

    if(selectedMovieIndex != null){
        movieSelect.selectedIndex = selectedMovieIndex;
    }
}


// 映画を選択・取得
movieSelect.addEventListener('change', e=> {

    console.log(e.target.value);

    console.log(movieSelect.value);

    ticketPrice = +e.target.value;
    setMovieData(e.target.selectedIndex, e.target.value);
    updateSelectedCount();
});

// シートクリック時のイベント
container.addEventListener('click', e => {

    console.log(e.target);

    if(
        e.target.classList.contains('seat') && !e.target.classList.contains('occupied')
    ) {
        e.target.classList.toggle('selected');

        updateSelectedCount();
    }
});

updateSelectedCount();
```

### setMovieDataメソッド

```javascript
// プルダウンのインデックス番号と金額の取得メソッド
function setMovieData(movieIndex, moviePrice){

    if(movieIndex != null){

        if(movieIndex == 0){
            movieImage.style.backgroundImage = "url(" + batManUrl + ")";
            bgImage.style.backgroundImage = "url(" + batManBackUrl + ")";

        }else if(movieIndex == 1){
            movieImage.style.backgroundImage = "url(" + doctorStrangeUrl + ")";
            bgImage.style.backgroundImage = "url(" + doctorStrangeBackUrl + ")";

        }else if(movieIndex == 2){
            movieImage.style.backgroundImage = "url(" + spiderManUrl + ")";
            bgImage.style.backgroundImage = "url(" + spiderManBackUrl + ")";

        }else if(movieIndex == 3){
            movieImage.style.backgroundImage = "url(" + justiceLeagueUrl + ")";
            bgImage.style.backgroundImage = "url(" + justiceLeagueBackUrl + ")";
        }
    }
    
    // localStorage = Javascriptでブラウザにデータを登録するWeb API
    sessionStorage.setItem('selectedMovieIndex', movieIndex);
    sessionStorage.setItem('selectedMoviePrice', moviePrice);

}
```
このメソッドでは、以下の処理を行なっています。

movieIndex ・・・　プルダウンメニューで選択したメニューののインデックス番号
moviePrice ・・・プルダウンメニューで選択したメニューののvalue属性の値

① movieIndexの値に応じて、画像のURLリンク先をstyle属性に追加・変更する。
② sessionStorageにインデックス番号とvalue属性の値をsetメソッドで格納する。


sessionStorage　⇨　javascriptのweb APIの一種で、ブラウザにデータを保管させることができる。

### updateSelectedCountメソッド

```javascript
// カウント（席数）とトータル（代金）を更新
function updateSelectedCount(){

    // 選択したシートを取得
    const selectedSeats = document.querySelectorAll('.row .seat.selected');

    /*
     * 1.[...]はスプレッド構文。変数の値を配列に変換する方法の１つ。

     * 2.定数　selectedSeats　は、selectedクラスを持つシート全てを値にもつ。

     * 3.定数　seatsIndex は、選択シートのインデックス番号を格納する定数。

     * 4.[...selectedSeats]にmapメソッドを使用し、新たに配列を生成し、seatsIndexに値を格納。

     * 5.mapメソッド内の seat は、selectedSeats配列にある値を一つ取り出し、格納されている。

     * 6.[...seats]は占領されていない全てのシートを格納した配列。

     * 7.[...seats]配列にindexOfメソッドを使用し、[...seats]配列内に選択したシートがあるか確認。
     * あれば[...selectedSeats]配列にindex番号を格納。
    
     * 
     * 
     */
    const seatsIndex = [...selectedSeats].map(seat => [...seats].indexOf(seat));

    console.log([...seats]);
    console.log(seatsIndex);
    
    /*
     * JSON.stringify = JSON形式の文字列に変換するメソッド
     * JSON.stringify = Javascriptオブジェクトを取得し、JSON文字列を返す
　　　　　　　　　　* selectedSeats　と名して、選択したシートのインデックス番号を格納。
　　　　　　　　　　* JSON.stringifyは配列にも使用可能。
　　　　　　　　　　* 
     * なぜワザワザ配列に使用するのか？　→ cookieやlocalStorage・sessonStorageは、
     * 配列のままではセットできないので、JSON.でJSON形式に変換している。
     */
    sessionStorage.setItem('selectedSeats', JSON.stringify(seatsIndex));

    console.log(JSON.stringify(seatsIndex));

    // 選択した席数をカウントして格納
    const selectedSeatsCount = selectedSeats.length;

     // HTML内のid="count"タグの値を再設定
    count.innerText = selectedSeatsCount;

    // HTML内のid="total"タグの値を再設定
    total.innerText = selectedSeatsCount * ticketPrice;

    // 選択した映画のプルダウンのインデックスとvalue（値段）を記録
    setMovieData(movieSelect.selectedIndex, movieSelect.value);
}
```
このメソッドでは、以下の処理を行なっています。

①　選択したシートを selectedSeats に格納する。

②　選択したシートが占領されていないシートであることを確認し、そのインデックス番号を seatsIndex に格納し、sessonStorageにセット。

③　選択したシートの数を selectedSeatsCount に格納。

④ innerTextメソッドを使用し、該当するidを持つHTML要素内の文字を更新する。
```HTML
<!-- 金額と選択シート数の表示 -->
        <p class="text">
            You have selected <span id="count">0</span> seat for a price of $<span id="total">0</span>
        </p>
```
⑤ プルダウンで選択した映画のインデックス番号とvalue属性の値を引数に、setMovieDataメソッドを実行。

### populateUIメソッド
```javascript
// localStorageからデータの取得
function populateUI() {

    // JSON.parse = JSON文字列を取得し、Javascriptオブジェクトにして返す
    // localStorage にセットしたselectedSeatsを取得
    const selectedSeats = JSON.parse(sessionStorage.getItem('selectedSeats'));

    console.log(selectedSeats);

    console.log(sessionStorage.getItem('selectedSeats'));

    console.log(JSON.parse(sessionStorage.getItem('selectedSeats')));

    // JSON.parseでJavascriptオブジェクトに変換されたselectedSeatsを確認。
    if(selectedSeats != null && selectedSeats.length > 0) {

        /*
         * seat　→ seats(占領されていない全てのシートを格納した変数)の値を１つ格納。
         * index　→ seatのインデック番号。
         * selectedSeatsにindexOfメソッドを使用。
         * selectedSeatsに格納されている値のインデックス番号とseats配列に格納されているシートのインデックス番号を比較し、
         * 同じものがあるか確認。
         * あればseatsの値に、selectedクラスを付与する。
         * ↑ 上記の処理を行わないと、その後クリックイベントでselectedクラスを外すことができない。
         */
        seats.forEach((seat, index) => {

            console.log(index);

            // indexOfメソッドは引数に
            if(selectedSeats.indexOf(index) > -1) {
                seat.classList.add('selected');
            }
        });
    }
// sessionStorageにインデックス番号が保存されていたら、インデックス番号を更新。
    const selectedMovieIndex = sessionStorage.getItem('selectedMovieIndex');

    if(selectedMovieIndex != null){
        movieSelect.selectedIndex = selectedMovieIndex;
    }
}
```
① sessionStorage内の 'selectedSeats' の値を取得し、selectedSeats に格納。

② selectedSeatsに値があり、かつ占領されていないシートであれば、そのシート要素にselectedクラスを付与する。

③ sessionStorage内の 'selectedMovieIndex' の値を取得し、selectedMovieIndex に格納。

④ selectedMovieIndexに値があれば、現在の movieSelect の持っているインデックス番号を更新する。

このメソッドの役割は、ブラウザをリロード等した際に前回の処理で保存されたsessionStorage内のデータを再設定する処理をしている。
sessionStorageに保存しているのでブラウザを閉じてしまうとデータは消えてしまうが、sessionStorageをlocalStorageに変更すれば、閉じた後も永続的にデータは保存されるので、閉じても続きから処理を行うことができる。

### 映画選択(変更)時のイベントリスナーメソッド
```javascript
// 映画を選択・取得
movieSelect.addEventListener('change', e=> {

    console.log(e.target.value);

    console.log(movieSelect.value);

    // 変数の前に + を書く理由は、代入する値を”数値（number）”として代入するため
   // +　をつけないとticketPriceの方はany、+をつけるとnumberになる。
    ticketPrice = +e.target.value;
    setMovieData(e.target.selectedIndex, e.target.value);
    updateSelectedCount();
});
```

プルダウンメニューで変更した映画のインデックス番号とvalueの値を引数に、setMovieDataメソッドとupdateSelectedCountメソッドを実行。

### シートクリック時のイベント
```javascript
// シートクリック時のイベント
container.addEventListener('click', e => {

    console.log(e.target);

    if(
        e.target.classList.contains('seat') && !e.target.classList.contains('occupied')
    ) {
        e.target.classList.toggle('selected');

        updateSelectedCount();
    }
});

updateSelectedCount();
```
シートをクリックした時、そのシート要素に対し、toggleメソッドを実行。


### まとめると...

## ① ブラウザを開いたら

##### <font color="red">①1回目の場合</font>
→ populateUIが実行。
　しかし、sessionStorageには何もないのでif文で弾かれ何事もなく終了。

##### <font color="red">② 2回目以降の場合</font>
→ populateUIが実行。
1回目の時に何らかのアクションを行いStorage内にデータが保存されているなら、それらを再取得し、値の再設定を行う。


## ② 椅子を選択したら

##### <font color="red">① container.addEventListenerメソッドが実行。</font>
占領されていなければselectedクラスを付与する。

##### <font color="red">② updateSelectedCountメソッドを実行。
・selectedクラスを付与されているシートをselectedSeatsに格納。</font>

・selectedクラスを付与したシートのインデックス番号を確認し、JSON文字列にしてsessionStorageに保存。

・selectedSeatsに格納されている、selectedクラスが付与されているシートの数（席数）を確認し、id="count"のHTMLに反映。

・現在選択されているmovieのvalueの値が格納されているticketPriceに席数を掛け、料金を計算し、id="total"のHTMLに反映。

##### ③ <font color="red">setMovieDataメソッドを実行。</font>
. 現状選択されている映画のインデックス番号とvalue属性の値を引数に実行。

・インデックス番号の値に応じて、表示する画像のURLを変更。

・sessionStorageに選択している映画のインデックス番号とvalue属性の値を保存。


## ③ 映画を変更したら

##### ① <font color="red">movieSelect.addEventListenerメソッドを実行。</font>

・選択したプルダウンメニューのvalue属性の値を、ticketPriceに代入し更新。

・選択したプルダウンメニューのvalue属性の値、インデックス番号を取得する。

##### <font color="red">② updateSelectedCountメソッドを実行。
・selectedクラスを付与されているシートをselectedSeatsに格納。</font>

・selectedクラスを付与したシートのインデックス番号を確認し、JSON文字列にしてsessionStorageに保存。

・selectedSeatsに格納されている、selectedクラスが付与されているシートの数（席数）を確認し、id="count"のHTMLに反映。

・変更したmovieのvalueの値が格納されているticketPriceに席数を掛け、料金を計算し、id="total"のHTMLに反映。

##### ③ <font color="red">setMovieDataメソッドを実行。</font>
. 変更した映画のインデックス番号とvalue属性の値を引数に実行。

・インデックス番号の値に応じて、表示する画像のURLを変更。

・sessionStorageに選択している映画のインデックス番号とvalue属性の値を保存。

---
という流れになります！！
記法の部分が難しいところが多かったので、より解説をして下さっているサイトを下記にまとめております。ぜひご覧ください。

vanillawebprojects.comには他にも色々な簡易的なwebアプリを作りながら学べるので、こちらもよろしければご参考にしてください！

https://vanillawebprojects.com/


## 参考文献
◆スプレッド構文

https://qiita.com/akisx/items/682a4283c13fe336c547

◆ localStorageについて

https://www.granfairs.com/blog/staff/local-storage-01

◆JSON.stringifyについて

https://iwb.jp/javascript-json-stringify-parse-can-use-array/

◆innerText

https://www.javadrive.jp/javascript/dom/index14.html

◆変数の前に　+を書く理由について


https://wp-p.info/tpl_rep.php?cat=js-intermediate&fl=r11