kuzira cafe フォームの入力内容表示機能を完全に理解する

## 1. はじめに
 SBクリエイティブ 「スラスラわかるHTML&CSSのきほん 第3版」のサンプルコードではお問い合わページで入力した内容を表で表示することができる．その仕組みについて理解することを目標にする．

サンプルコードは以下からダウンロードできる．
https://www.sbcr.jp/product/4815611651/

## 2. 対象ページの挙動

![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644068/9a537f47-be9a-5425-10e3-63f046551d3e.png)
1. フォームに各項目を入力し，送信ボタンを押す．

![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3644068/5997a2ce-51bc-f5df-fb5e-a42f222582ab.png)
2. すると，入力項目についての表が表示される．



## 3. この挙動を実現する手順
1. フォームに入力する
1. ボタンを押す
1. 入力内容をURLに埋め込む
1. ページを遷移させる
1. URLから入力内容を取り出す
1. 取り出した入力内容をtableに追加する

### 3-1. [1~4]フォームに入力からページ遷移
`contact.html`で実現する．
`contact.html`には`form`タグで囲まれたフォームが定義されている．

```html
<form action="result.html">
    <p>
        <label for="kind">お問い合わせの種類</label><br>
        <select name="kind" id="kind">
            <option value="1">ご予約</option>
            <option value="2">イベントに関するお問い合わせ</option>
            <option value="3">その他</option>
        </select>
    </p>
    <p>
        はじめてのご来店ですか？<br>
        <label><input type="radio" name="first" value="yes" checked>はい</label><br>
        <label><input type="radio" name="first" value="no">いいえ</label>
    </p>
    <p>
        当カフェをお知りになったきっかけは？<br>
        <label><input type="checkbox" name="how" value="friend">知り合いの紹介で</label><br>
        <label><input type="checkbox" name="how" value="magazine">SNS、Webサイト、雑誌などを見て</label>
    </p>
    <p>
        <label for="subject">お問い合わせの件名</label><br>
        <input type="text" name="subject" placeholder="タイトル" id="subject">
    </p>
    <p>
        <label for="message">お問い合わせの具体的な内容</label><br>
        <textarea name="message" id="message"></textarea>
    </p>
    <p class="submit">
        <input type="submit" value="送信">
    </p>
</form>
```

`form`タグの中では`input`を以下のように定義したとき，そこで取得した情報はURLに次のように埋め込まれる．
```html
<input name="名前" value="値" />
```
```text
<url部分>?名前=値
```
例
```text
http://127.0.0.1:5500/result.html?kind=2&first=yes
```

`form`タグの中では`input`を複数定義できる．
```html
<input name="名前1" value="値1" />
<input name="名前2" value="値2" />
<input name="名前3" value="値3" />
```
```text
<url部分>?名前1=値1&名前2=値2&名前3=値3
```

`form`タグ内で定義した`input`の値は，`type="submit"`とした`input`でURLに反映させることができる．
```html
<input type="submit" value="送信">
```

送信ボタンを押した後のページ遷移先は，`form`タグ中で`action`を指定する．ドキュメントには以下のように書かれている．
> フォーム経由で送信された情報を処理するプログラムの URL（https://developer.mozilla.org/ja/docs/Web/HTML/Element/form）
```html
<form action="result.html">
```

### 3-2. [5] URLから入力内容を取り出す
`result.html`中で定義されている以下のスクリプトを使う．
```html
<script>
    'use strict';

    const render = function(q) {
        const parent = document.getElementById('result');
        for (const item of q) {
            parent.insertAdjacentHTML('beforeend', `<tr><td>${item[0]}</td><td>${item[1]}</td></tr>`);
        }
    }

    window.addEventListener('DOMContentLoaded', (e) => {
        try {
            const urlParam = decodeURIComponent(location.search.replace(/\+/g, ' ')).slice(1);
            const qList = urlParam.split('&');
            const queries = [];
            for (const item of qList) {
                const split = item.split('=');
                queries.push([split[0], split[1].split('<').join('&lt;').split('>').join('&gt;')]);
            }
            render(queries);
        } catch(err) {
            console.log(err);
        }
    });
</script>
```

#### 3-2-1. スクリプトの概要
このスクリプトは大きく以下の3つに分けられる．
1. `'use strict';`
1. `const render = function(q) {…`
1. `window.addEventListener('DOMContentLoaded', (e) => {…`

実行順は以下の通り．
1. `'use strict';`
1. `window.addEventListener('DOMContentLoaded', (e) => {…`
1. `const render = function(q) {…`

#### 3-2-2. 各命令・関数について

##### 3-2-2-1. `use strict`
JavaScriptのコードが厳格モード（Strict mode）で実行されるようになる．今回はあまり気にしなくて良い．

##### 3-2-2-2. `window.addEventListener('DOMContentLoaded', (e) => {…`
###### 3-2-2-2-1. 関数の概要
`window.addEventListener`はターゲットに特定のイベントが配信されるたびに呼び出される関数を設定する．以下のようにして使う．
```
addEventListener(type, listener)
```

- type： 対象とするイベントの種類を表す文字列
- listener： typeで指定したイベントが発生するときに通知を受け取るオブジェクト（handleEvent()メソッドを持つオブジェクトまたは関数）

今回は`type`に'DOMContentLoaded'を指定している．
> DOMContentLoaded イベントは、HTML の文書が完全に読み込まれ構文解析され、すべての遅延スクリプト（`<script defer src="…">` および `<script type="module">`）がダウンロードされ、実行されたときに発生します。画像、サブフレーム、非同期スクリプトの読み込みの完了は待ちません。
（https://developer.mozilla.org/ja/docs/Web/API/Document/DOMContentLoaded_event）

また，`listener`には以下の関数を指定している．

```javascript
(e) => {
    try {
        const urlParam = decodeURIComponent(location.search.replace(/\+/g, ' ')).slice(1);
        const qList = urlParam.split('&');
        const queries = [];
        for (const item of qList) {
            const split = item.split('=');
            queries.push([split[0], split[1].split('<').join('&lt;').split('>').join('&gt;')]);
        }
        render(queries);
    } catch(err) {
        console.log(err);
    }
}
```

###### 3-2-2-2-2. コールバック関数の詳細
- 引数
    - 引数としてeを受け取る．これはイベントオブジェクトであり，今回は使用しない．
- `try-catch`
    - 関数の全体を構成する．最初に`try`ブロック内のコードが実行され，そこで例外が発生すると`catch`ブロック内のコードが実行される．
- `const urlParam = decodeURIComponent(location.search.replace(/\+/g, ' ')).slice(1);`
    - URLのクエリパラメータ文字列内の`+`を` `に置換してデコードし，先頭の`?`削除する．
    - 次の4文に分割できる．
    ```javascript
    const tmp1 = location.search;
    const tmp2 = tmp1.replace(/\+/g, ' ');
    const tmp3 = decodeURIComponent(tmp2);
    const urlParam = tmp3.slice(1);
    ```

    - `tmp1`
        - `location.search`ではクエリ文字列を取得する．クエリ文字列とはURLの?以降の部分．この命令で取得できる文字列には?も含まれる．
    - `tmp2`
        - `tmp1`内の`+`を` `に置換する．
            - URLでは` `が`+`にエンコードされる．そのため，`+`をもとに戻す必要がある．なお，`+`は`%2B`にエンコードされており，`decodeURIComponent`で`+`にデコードされる．
        - `replace(/\+/g, ' ')`で文字列を置換する．`replace`関数は，通常最初に一致した第1引数のパターン（今回は文字列）を第2引数の文字列に置換した文字列を返す．
        - 第1引数`/\+/g`は正規表現．`/`，`\+`，`/g`に分けられる．`/`は正規表現の始まりを表す．`\+`は`+`をエスケープした文字列を表す．`\g`は通常最初に一致した第1引数のパターンを置換するところ，一致した第1引数のパターン全てを置換するように指定する．
    - `tmp3`
        - `decodeURIComponent`関数でエンコードされたURIをデコードする．
    - `urlParam`
        - 文字列戦闘の`?`を取り除く
        - `slice`関数で文字列インデックスが1の文字から先を取得する．つまり2文字目以降を取得する．

    分割したときの各変数の値は，例えば以下のようになる．
    ```javascript
    tmp1     = ?kind=1&first=yes&subject=%E4%BB%B6%E5%90%8D&message=%E5%86%85%E5%AE%B9+%E3%81%A7%E3%81%99
    tmp2     = ?kind=1&first=yes&subject=%E4%BB%B6%E5%90%8D&message=%E5%86%85%E5%AE%B9 %E3%81%A7%E3%81%99
    tmp3     = ?kind=1&first=yes&subject=件名&message=内容 です
    urlParam = kind=1&first=yes&subject=件名&message=内容 です
    ```

- `const qList = urlParam.split('&');`
    - `urlParam`の文字列を`&`で分割して配列にする．配列には`&`は含まれない．
    - 例えば以下のように処理される．
        ```javascript
        urlParam = kind=1&first=yes&subject=件名&message=内容 です
        qList= [ "kind=1", "first=yes", "subject=件名", "message=内容 です" ]
        ```
- `const queries = [];`
    - 空の配列を作る．
- `for (const item of qList) {…`
    - `queries`を2次元配列として，i番目のクエリについて`[i][0]`成分に左辺を，`[i][1]`成分には右辺を格納する．
    - 範囲for文で処理する．
    - `queries.push([split[0], split[1].split('<').join('&lt;').split('>').join('&gt;')]);`
        - splitの`<`を`&lt;`に，`>`を`&gt;`に置換する．この文は以下の6文に分割できる．
            ```javascript
            const tmpList1 = split[1].split('<');
            const tmpStr2 = tmpList1.join('&lt;');

            const tmpList3 = tmpStr2.split('>');
            const tmpStr4 = tmpList3.join('&gt;');

            const tmpList5 = [split[0], tmpStr4];
            queries.push(tmpList5);
            ```
            `split`で分割された配列は`join`関数により引数で指定した文字列で結合される．例えば`item`が`"message=a<b>c"`であるときは以下のように処理される．

            ```
            item = "message=a<b>c"
            split = [ "message", "a<b>c" ]
            tmpList1 = [ "a", "b>c" ]
            tmpStr2 = a&lt;b>c
            tmpList3 = [ "a&lt;b", "c" ]
            tmpStr4 = a&lt;b&gt;c
            tmpList5 = [ "message", "a&lt;b&gt;c" ]
            ```

            `<`と`>`を`&lt;`と`&gt;`に置換するのは，クロスサイトスクリプティング（XSS）攻撃を防止するためだ．ここで処理する文字列は後でDOM操作によりhtmlコードとして挿入される．このときに`<>`が残っていたとき，これがタグとして解釈される場合がある．攻撃の意思が無いとしても，誤動作の原因になるため，それを防止する．

        このfor文で`queries`は以下のようになる．
        ```javascript
        qList = [ "kind=1", "first=yes", "subject=件名", "message=内容 です" ]
        queries = [ [ "kind", "1" ],
            [ "first", "yes" ],
            [ "subject", "件名" ],
            [ "message", "内容 です" ] ]
        ```


##### 3-2-2-3. `const render = function(q) {…`
関数`render`は以下の通り．
```javascript
const render = function(q) {
    const parent = document.getElementById('result');
    for (const item of q) {
        parent.insertAdjacentHTML('beforeend', `<tr><td>${item[0]}</td><td>${item[1]}</td></tr>`);
    }
}
```

- `const parent = document.getElementById('result');`
    - idがresultである要素を取得する
- `for (const item of q) {…`
    - `parent`に対して`q`の内容を表の行として追加する
        ```javascript
        for (const item of q) {
            parent.insertAdjacentHTML('beforeend', `<tr><td>${item[0]}</td><td>${item[1]}</td></tr>`);
        }
        ```
        - <code>parent.insertAdjacentHTML('beforeend', `<tr><td>${item[0]}</td><td>${item[1]}</td></tr>`);</code>
            - `insertAdjacentHTML`関数で1列目が`item[0]`，2列目が`item[1]`表の行を追加する
            - `insertAdjacentHTML`関数の使い方は以下の通り
                ```javascript
                insertAdjacentHTML(position, text)
                ```
                - position：要素の相対的な位置を表す文字列
                - text：HTMLまたはXMLとして解釈して挿入される文字列
            - 今回は`'beforeend'`により要素のすぐ内側の最後の子として挿入される
            - 挿入内容は以下のとおり
                ```html
                <tr>
                    <td>${item[0]}</td>
                    <td>${item[1]}</td>
                </tr>
                ```

# 4. おわりに
kujiraじゃなくてkuziraであってるらしい（超余談）．