フロントエンド開発2

この記事は、「[架空プロジェクトを通してシステム開発とドキュメント作成を体験してみる（2022 Late）](https://qiita.com/minimumskills/items/aedf6a93658db3f74747)」の記事の一部です。

# 概要

ここでは簡単なお問合せフォーム（コンタクトページ）を作成します。
まだお問合せ内容を受け取るサーバ側の仕組み（API）は存在しないので送信する手前（送信内容の確認）までの機能を作ってみます。

*フロントエンド開発全行程完了時のイメージ*
![000001.jpg](https://drive.google.com/uc?export=view&id=1sMhXiQh7CKqbmU6tXL9c371fTwifvsSf)

# 完成イメージ

通常のときの見た目。
お名前、email、お問合せ内容の３つ。送信ボタンを押すとサーバに送信する想定。

![000010.jpg](https://drive.google.com/uc?export=view&id=1hl9chei2bPN_p8TAQKqrZJTq9vKY6idq)


エラーメッセージ表示時。
各値を検証（バリデーション）してエラーがあれば表示し、送信はしない。

![000020.jpg](https://drive.google.com/uc?export=view&id=1if_VlM4qle5j5S2h9samzlTfiCU_wSjW)

送信内容の確認。
各値を検証（バリデーション）して問題がなければサーバ側に送信。
ただ、現状ではサーバ側で受け取る仕組み（API）ができていないので、送信する値をアラート画面で表示させる実装をしてみます。

![000030.jpg](https://drive.google.com/uc?export=view&id=1FGOvN9o6pJNQsb8G58e43ltb30dfK8-0)


# コンタクトページ作成

## contact.html作成

websiteフォルダ内にcontact.htmlを作成します。

![000040.jpg](https://drive.google.com/uc?export=view&id=1cFdkm0wT0SiXbJ16m7QL8rtuFbbnI3ie)

## 基本構造

ヘッダ・フッタ部分はindex.htmlと同じ内容なのでindex.htmlで作成したコードをコピーしてcontact.htmlに貼り付けて再利用します。

コピーしたら、headerタグとmainタグの中の情報はお問合せページに不要なので削除して下記の状態にします。
titleタグを「WebSite|お問合せ」としておきます。

>mainタグは再度使いますが。一旦削除。

>実際の開発ではヘッダ、フッタ等の共通部は共通部品として再利用できるように作成しますが、ここでは「無駄な記述の体験」も含め、同じ記述を個別のページで行う方法で記述します。

```contact.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSite|お問合せ</title>
    <link rel="stylesheet" href="./style.css" />
</head>

<body>
    <nav class="nav">
        <ul>
            <li><a href="./index.html">WebSite</a></li>
            <li><a href="./index.html">TOP</a></li>
            <li><a href="./contact.html">CONTACT</a></li>
        </ul>
    </nav>


    <footer class="footer">
        &copy; 2022 bluecode, inc. All Rights Reserved.
    </footer>
</body>

</html>
```

## フォーム作成

### contact.htmlにフォームを作成

bodyタグの中にフォームを作成します。
お問合せフォームの項目は下記の3つにします。 

| 項目 | id名 | タグの種類 | 検証（バリデーション）ルール |
|:--|:--|:--|:--|
| お名前 | name | input | 必須 |
| メールアドレス | email | input | 必須、aaa@aaa.comの形式 |
| 内容 | body | textarea | 必須、1文字以上10文字以下 |

仕様するタグの說明は以下の通り。

| キーワード | 解説 |
|--|--|
| form | 入力・送信フォームを作成する際に使用するタグ。 タグ内に項目を設定する。 |
| label | キャプションタグ |
| input | formタグ内で構成する要素を設定するタグ。属性（textやemail)を指定してその要素の詳細を設定する |
| textarea | formタグ内で構成する要素で複数行のプレーンテキストを編集する項目に使うタグ |
| button | ボタンを構成するタグ |

3つの項目をそれぞれ、div（クラス名form_group)で囲んでグループにします。 
その中に、labelとinputタグまたはtextareaタグを指定します。 

![000050.jpg](https://drive.google.com/uc?export=view&id=1t1HsdeOqcX3nde7ubSBWalvmhdh7LDoI)

>グループ化することが必須ではないですが、スタイルの適用などが楽になるためにそうしています。

こんな感じ。

```html:各グループの例
<div class="form_group">
    <label>お名前</label>
    <input type="text" name="name" />    
</div>
``` 

formをと各要素（フループ）を挿入したコード。

```diff_html:contact.html
    <!DOCTYPE html>
    <html lang="ja">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>WebSite|お問合せ</title>
        <link rel="stylesheet" href="./style.css" />
    </head>

    <body>
        <nav class="nav">
            <ul>
                <li><a href="./index.html">WebSite</a></li>
                <li><a href="./index.html">TOP</a></li>
                <li><a href="#">CONTACT</a></li>
            </ul>
        </nav>
+       <main class="contact_form">
+           <h2>お問合せ</h2>
+           <form>
+               <div class="form_group">
+                   <label>お名前</label>
+                   <input type="text" id="name" />
+               </div>
+               <div class="form_group">
+                   <label>email</label>
+                   <input type="email" id="email" />
+               </div>
+               <div class="form_group">
+                   <label>お問合せ内容</label>
+                   <textarea id="body" rows=4></textarea>
+               </div>
+               <button class="submit_btn" id="submit_btn">送信</button>
+           </form>
+       </main>
        <footer class="footer">
            &copy; 2022 bluecode, inc. All Rights Reserved.
        </footer>
    </body>

    </html>
```
```html:コピペ用
<main class="contact_form">
    <h2>お問合せ</h2>
    <form>
        <div class="form_group">
            <label>お名前</label>
            <input type="text" id="name" />
        </div>
        <div class="form_group">
            <label>email</label>
            <input type="email" id="email" />
        </div>
        <div class="form_group">
            <label>お問合せ内容</label>
            <textarea id="body" rows=4></textarea>
        </div>
        <button class="submit_btn" id="submit_btn">送信</button>
    </form>
</main>
```
保存してcontact.htmlをブラウザで開いて確認してみます。
スタイルの調整をしていないのでスタイルは崩れていますが、必要なフォームの内容は確認できました。

![000060.jpg](https://drive.google.com/uc?export=view&id=1CcKYa4GnwH4C7FQoC21q16zKTiIQVvgY)


### style.cssでレイアウト調整
contact.htmlはindex.htmlのコピーをベースにしているのでstyle.cssはすでに読み込み済みです。
style.cssに記述すると反映されるので、style.cssにお問合せフォームで追加した要素のスタイルも追加していきます。

```style.css
/* Contact */
.contact_form {
    padding: 40px 20px;
    text-align: center;
}

.form_group {
    border-top: 1px solid #ddd;
    margin-top: 20px;
    padding: 20px 0px;
    width: 100%;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
}

.form_group label {
    width: 30%;
    font-weight: bold;
    font-size: 18px;
}

.form_group input,
.form_group textarea {
    border: 1px solid #ddd;
    border-radius: 6px;
    width: 60%;
    font-size: 18px;
}

.form_group input {
    height: 40px;
}

.submit_btn {
    padding: 5px 15px;
    background-color: #049dbf;
    color: #fff;
    border-radius: 5px;
    border: 0;
    font-size: 16px;
}

.submit_btn:hover {
    background-color: #0183a0;
}
```
保存して確認します。
![000070.jpg](https://drive.google.com/uc?export=view&id=1GoDpDba8u5No7afmuxjc4GNhjRYnE7fW)


## JavaScriptで値を取得
見た目ができたので、送信ボタンをクリックしたら各入力項目の値をJavaScriptで取得してアラートで表示してみます。

### index.js作成とcontact.htmlで読み込み
contact.htmlやindex.htmlと同様にindex.jsを作成します。

![000080.jpg](https://drive.google.com/uc?export=view&id=1M27d_bhOeboro8n6GI0-IZHqnlfC7rV8)

JavaScript側でidを使ってsubmit_btn（送信ボタン）をコントロールできるようにします。
submit_btnを取得して、ボタンが押されたらアラートを出すプログラムを追加してみます。

```index.js
const submit_btn = document.getElementById("submit_btn");
```

submit_btnがクリックされたら実行する動作を記述します。
`e.preventDefault();`はformタグ内のボタンが押されると、formタグで指定されている先（指定なしの場合は自分自身）にPOSTするというデフォルトで設定されている動作をクリアするものです。クリアしなければ、この中でなにを記述しても先にPOST実行されてしまい、記述内容が実行されることはありません。

```diff_javascript:index.js
    const submit_btn = document.getElementById("submit_btn");

+   submit_btn.addEventListener("click", (e) => {
+       e.preventDefault();
+       alert('ボタンが押された');
+   });
```

```javascript:コピペ用
submit_btn.addEventListener("click", (e) => {
    e.preventDefault();
    alert('ボタンが押された');
});
```
jsファイル側が準備できたら、contact.htmlでindex.jsを読み込みます。
javaScriptはbodyの閉じタグの直前で読み込みます。

```diff_html:contact.html
    <!DOCTYPE html>
    <html lang="ja">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>WebSite|お問合せ</title>
        <link rel="stylesheet" href="./style.css" />
    </head>

    <body>
        <nav class="nav">
            <ul>
                <li><a href="./index.html">WebSite</a></li>
                <li><a href="./index.html">TOP</a></li>
                <li><a href="#">CONTACT</a></li>
            </ul>
        </nav>
        <main class="contact_form">
            <h2>お問合せ</h2>
            <form>
                <div class="form_group">
                    <label>お名前</label>
                    <input type="text" id="name" />
                </div>
                <div class="form_group">
                    <label>email</label>
                    <input type="email" id="email" />
                </div>
                <div class="form_group">
                    <label>お問合せ内容</label>
                    <textarea id="body" rows=4></textarea>
                </div>
                <button class="submit_btn" id="submit_btn">送信</button>
            </form>
        </main>
        <footer class="footer">
            &copy; 2022 bluecode, inc. All Rights Reserved.
        </footer>

+       <script src="./index.js"></script>
    </body>

    </html>
```

### 動作確認
保存したらブラウザを更新して送信ボタンをクリックしてみます。
「ボタンが押された」というアラートが表示されれば動作確認完了です。
![000090.jpg](https://drive.google.com/uc?export=view&id=1IjiJtGCnoCBl98xJ3p9gvbKP8iaFPwi4)

>うまく動かない場合は、右クリック＞「検証」で「Console」を確認してみてください。
><img width="500" alt="000100.jpg" src="https://drive.google.com/uc?export=view&id=17lXnDhBo8eY-DSo8Ir74D2H1M0XC439l">
エラーが表示されている場合は、メッセージを確認します。
エラー文言の右側にエラーが発生されている箇所が表示され、クリックすると、「source」でおかしい部分を教えてくれます。
上記のエラー例の場合、「nullのプロパティを読み取ることができません。」とあり、index.jsを確認すると、取得しようとしているボタンのIDが間違っているため（正しくはsubmit_btnだがbuttonになっている）、ボタンを取得することができずエラーになっていたことがわかります。
><img width="500" alt="000110.jpg" src="https://drive.google.com/uc?export=view&id=1c-QPiuI9x3aW40HUH41QHPRV8VBbMkHf">
index.jsを確認して、取得しようとしているもののId名を修正することでエラーはなくなります。

### 値の取得

サーバにデータを送るためには、まず、お名前・email・お問合せ内容を取得する必要があります。
JavaScriptを利用すると各form要素の値を取得することができます。では、取得してみましょう。

>なお、nameの変数名をnameではなくcontact_nameとしたのは「name」がjavascriptで非推奨となっているためです。

```diff_javascript:index.js
    const submit_btn = document.getElementById("submit_btn");

+   const contact_name = document.getElementById("name");
+   const email = document.getElementById("email");
+   const body = document.getElementById("body");

    submit_btn.addEventListener("click", (e) => {
        e.preventDefault();
        alert('ボタンが押された');
    });
```

```javascript:コピペ用
const contact_name = document.getElementById("name");
const email = document.getElementById("email");
const body = document.getElementById("body");
```
alertで「ボタンが押された」と表示していた部分に取得した値を入れます。
> ここではテンプレートリテラルの記述方法を使います。
alertで表示される部分を、シングルクォート（'）ではなくバッククォート（`）で囲むとテンプレートリテラルとなり、改行文字（\n）は改行され、変数（${xxx}）は展開して表示してくれます。（シングルクオートで囲んだ場合は改行文字はそのまま表示、変数も変数名がそのまま表示されます。）

```diff_javascript:index.js
    const submit_btn = document.getElementById("submit_btn");

    const contact_name = document.getElementById("name");
    const email = document.getElementById("email");
    const body = document.getElementById("body");

    submit_btn.addEventListener("click", (e) => {
        e.preventDefault();
-       alert('ボタンが押された');
+       alert(`お名前：${contact_name.value}\nemail：${email.value}\nお問合せ内容：${body.value}`);
    });
```
### 動作確認

保存して各入力項目に適当な値を入力して「送信」をクリックします。
入力した値がアラートで表示されていれば確認完了です。

![000120.jpg](https://drive.google.com/uc?export=view&id=1QpWpXW_imOqVu2bJJ5b7pI_j5wCvsdH_)


# 値の検証（バリデーション）

送信ボタンをクリックしてAPIに値を送信する前に、バリデーションを追加して入力項目の値が適切かどうかをチェックするプログラムを追加します。

## エラーメッセージをhtmlに追加
入力エラーがあった場合、エラーメッセージをフォームに表示したいので、htmlとcssを使ってメッセージを追加します。

```diff_html:contact.html
    <!DOCTYPE html>
    <html lang="ja">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>WebSite|お問合せ</title>
        <link rel="stylesheet" href="./style.css" />
    </head>

    <body>
        <nav class="nav">
            <ul>
                <li><a href="./index.html">WebSite</a></li>
                <li><a href="./index.html">TOP</a></li>
                <li><a href="#">CONTACT</a></li>
            </ul>
        </nav>
        <main class="contact_form">
            <h2>お問合せ</h2>
            <form>
                <div class="form_group">
                    <label>お名前</label>
                    <input type="text" id="name" />
+                   <span class="form_error" id="name_error">お名前は必須です。</span>
                </div>
                <div class="form_group">
                    <label>email</label>
                    <input type="email" id="email" />
+                   <span class="form_error" id="email_error">emailは必須かつemailの形式で入力してください。</span>
                </div>
                <div class="form_group">
                    <label>お問合せ内容</label>
                    <textarea id="body" rows=4></textarea>
+                   <span class="form_error" id="body_error">お問合せ内容は必須かつ1文字以上10文字以下で入力してください。</span>
                </div>
                <button class="submit_btn" id="submit_btn">送信</button>
            </form>
        </main>
        <footer class="footer">
            &copy; 2022 bluecode, inc. All Rights Reserved.
        </footer>

        <script src="./index.js"></script>
    </body>

    </html>
```

form_errorのスタイルを追加します。
```style.css
.form_error {
    padding-left: 30%;
    width: 60%;
    font-size: small;
    color: red;
    text-align: start;
    padding-top: 10px;
}
```
保存して確認します。
![000130.jpg](https://drive.google.com/uc?export=view&id=1WNtkI_i2ViGZNxYi5ib6Ngn2Avff5m9D)

デザインはできました。
ただ、エラー文言は通常は非表示としておきたいので「hidden」というクラスを追加します。

```diff_html:contact.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSite|お問合せ</title>
    <link rel="stylesheet" href="./style.css" />
</head>

<body>
    <nav class="nav">
        <ul>
            <li><a href="./index.html">WebSite</a></li>
            <li><a href="./index.html">TOP</a></li>
            <li><a href="#">CONTACT</a></li>
        </ul>
    </nav>
    <main class="contact_form">
        <h2>お問合せ</h2>
        <form>
            <div class="form_group">
                <label>お名前</label>
                <input type="text" id="name" />
-               <span class="form_error" id="name_error">お名前は必須です。</span>
+               <span class="form_error hidden" id="name_error">お名前は必須です。</span>
            </div>
            <div class="form_group">
                <label>email</label>
                <input type="email" id="email" />
-               <span class="form_error" id="email_error">emailは必須かつemailの形式で入力してください。</span>
+               <span class="form_error hidden" id="email_error">emailは必須かつemailの形式で入力してください。</span>
            </div>
            <div class="form_group">
                <label>お問合せ内容</label>
                <textarea id="body" rows=4></textarea>
-               <span class="form_error" id="body_error">お問合せ内容は必須かつ1文字以上10文字以下で入力してください。</span>
+               <span class="form_error hidden" id="body_error">お問合せ内容は必須かつ1文字以上10文字以下で入力してください。</span>
            </div>
            <button class="submit_btn" id="submit_btn">送信</button>
        </form>
    </main>

    <footer class="footer">
        &copy; 2022 bluecode, inc. All Rights Reserved.
    </footer>

    <script src="./index.js"></script>
</body>

</html>
```

```html:コピペ用
<form>
    <div class="form_group">
        <label>お名前</label>
        <input type="text" id="name" />
        <span class="form_error hidden" id="name_error">お名前は必須です。</span>
    </div>
    <div class="form_group">
        <label>email</label>
        <input type="email" id="email" />
        <span class="form_error hidden" id="email_error">emailは必須かつemailの形式で入力してください。</span>
    </div>
    <div class="form_group">
        <label>お問合せ内容</label>
        <textarea id="body" rows=4></textarea>
        <span class="form_error hidden" id="body_error">お問合せ内容は必須かつ1文字以上10文字以下で入力してください。</span>
    </div>
    <button class="submit_btn" id="submit_btn">送信</button>
</form>
```
スタイルシートで「hidden」クラスは非表示の設定をしておきます。

```style.css
.hidden {
    display: none;
}
```
保存して確認します。
一見、メッセージ文言がない状態に戻ってしまったように見えますが、検証画面でElementsを確認すると、スタイルが効いて非表示になっているだけで、きちんとあることがわかります。
![000140.jpg](https://drive.google.com/uc?export=view&id=14ZX4Dmt5ilb5f7a7CkYawRlFjdfbDuXg)



## バリデーションを実装

## お名前のバリデーション

お名前は必須項目なので、値が入っていない状態はエラーとします。（値はなんでもいい）

まず、名前のエラーを制御するために名前のエラーメッセージをindex.jsで取得します。

```diff_javascript:index.js
    const submit_btn = document.getElementById("submit_btn");

    const contact_name = document.getElementById("name");
    const email = document.getElementById("email");
    const body = document.getElementById("body");

+   const name_error = document.getElementById("name_error");

    submit_btn.addEventListener("click", (e) => {
        e.preventDefault();
        alert(`お名前：${contact_name.value}\nemail：${email.value}\nお問合せ内容：${body.value}`);
    });
```

ボタンをクリックして、お名前になにも入力がなければ、スタイルシートで非表示をコントロールしている「hidden」クラスを削除してエラー文言を表示します。

```diff_javascript:index.js
    const submit_btn = document.getElementById("submit_btn");

    const contact_name = document.getElementById("name");
    const email = document.getElementById("email");
    const body = document.getElementById("body");

    const name_error = document.getElementById("name_error");

    submit_btn.addEventListener("click", (e) => {
        e.preventDefault();

+       if (contact_name.value == "") {
+           name_error.classList.remove("hidden");
+       }
       
        alert(`お名前：${contact_name.value}\nemail：${email.value}\nお問合せ内容：${body.value}`);
    });
```

```javascript:コピペ用
if (contact_name.value == "") {
    name_error.classList.remove("hidden");
}
```

保存して確認します。
お名前に何も入力せず送信ボタンをクリックすると、アラートが出たあとにお名前のエラーメッセージが表示されます。

![000150.jpg](https://drive.google.com/uc?export=view&id=1vD__aV432N033WDckfDYj1v4N7AWpmQg)

>alertコマンドを利用すると一度処理が止まってしまうのでアラートダイアログボックスのOKを押すとエラーが表示されます。

![000160.jpg](https://drive.google.com/uc?export=view&id=1SKYkoaP5Vga1LQ2fyiZPFcR6TYtym3-b)

エラー表示は正しく動作しているようですが、アラートダイアログはエラーが出てない場合のみに表示させたいのでコードを改変していきます。
name_errorの持っているクラスの中にhiddenがある場合はアラートを実行します（hiddenがある = エラー文言が表示されていない = 入力にエラーはない）。


```diff_javascript:index.js
    const submit_btn = document.getElementById("submit_btn");

    const contact_name = document.getElementById("name");
    const email = document.getElementById("email");
    const body = document.getElementById("body");

    const name_error = document.getElementById("name_error");

    submit_btn.addEventListener("click", (e) => {
        e.preventDefault();

        if (contact_name.value == "") {
            name_error.classList.remove("hidden");
        }

+       if (name_error.classList.contains("hidden")) {
            alert(`お名前：${contact_name.value}\nemail：${email.value}\nお問合せ内容：${body.value}`);
+       }

    });
```

### 動作確認
お名前に値を入れず送信ボタンをします。
アラートは出ず、エラーメッセージが表示されました。
![000170.jpg](https://drive.google.com/uc?export=view&id=1nfIgKY9qPW2G6heXW8Rs9cK7JDEf7BU6)
ページを更新して値を入力して送信ボタンをクリックします。（*現時点では、削除されたhiddenクラスを再度追加するコードがないため、ページを更新をしないとうまく動作しません*）
アラートが表示されました。
![000180.jpg](https://drive.google.com/uc?export=view&id=1ggKuVjbIfg3pm8glO8dBxFocftot3SFm)

### キーボート入力時にバリデーションチェック
送信ボタンを使ったお名前に対するバリデーションチェックはうまくいきました。
ただこれでは送信ボタンを押すまでバリデーションチェックがされないため、ユーザーは送信ボタンを押すまで入力に誤りがあることに気付きづらい実装となっています。
そこで次は入力時に常にバリデーションチェックが実行されるようにしていきたいと思います。

keyupを使って記述します。
> keyupはJavaScripのElementでキーが押されて離されたときに発火されます。

contact_nameでキーボードがアップされるたびにバリデーションチェックが走り、hiddenクラスを追加するか削除するかの動作が実行されます。

```diff_javascript:index.js
    const submit_btn = document.getElementById("submit_btn");

    const contact_name = document.getElementById("name");
    const email = document.getElementById("email");
    const body = document.getElementById("body");

    const name_error = document.getElementById("name_error");

    submit_btn.addEventListener("click", (e) => {
        e.preventDefault();

        if (contact_name.value == "") {
            name_error.classList.remove("hidden");
        }

        if (name_error.classList.contains("hidden")) {
            alert(`お名前：${contact_name.value}\nemail：${email.value}\nお問合せ内容：${body.value}`);
        }

    });

+   contact_name.addEventListener("keyup", (e) => {
+       if (contact_name.value == "") {
+           name_error.classList.remove("hidden");
+       } else {
+           name_error.classList.add("hidden");
+       }
+   });
```

```javascript:コピペ用
contact_name.addEventListener("keyup", (e) => {
    if (contact_name.value == "") {
        name_error.classList.remove("hidden");
    } else {
        name_error.classList.add("hidden");
    }
});
```

### 動作確認
入力のたびにバリデーションチェックが正しく入っていることを確認します。

## emailのバリデーション
お名前と行うことはほとんど同じですが、emailのバリデーションはemailアドレスの形式かどうかを正規表現を使ってチェックします。
> 正規表現とは、簡単に言うと文字のパターンを表すものです。
例えば、「半角数字の後にアットマーク」というルールを適用したいとき「1@」「22@」はOKだけど、「あ@」「1あ」はダメというのをプログラムでどう表現したらいいか、となったとき正規表現を使うことで簡単に表現できます。（この場合`/^[0-9]{1,}@$`と表すことができる）

今回は正規表現を複雑にせず[**`半角英数字またはドット（1文字以上）+@+半角英数字またはドット（1文字以上）+.+半角英字（1文字以上）`**]をemailアドレスの形式とします。

```diff_javascript:index.js
    const submit_btn = document.getElementById("submit_btn");

    const contact_name = document.getElementById("name");
    const email = document.getElementById("email");
    const body = document.getElementById("body");

    const name_error = document.getElementById("name_error");
+   const email_error = document.getElementById("email_error");

+   const email_exp = /^[a-z0-9.]+@[a-z0-9.]+\.[a-z]+$/;

    submit_btn.addEventListener("click", (e) => {
        e.preventDefault();

        if (contact_name.value == "") {
            name_error.classList.remove("hidden");
        }

        if (name_error.classList.contains("hidden")) {
            alert(`お名前：${contact_name.value}\nemail：${email.value}\nお問合せ内容：${body.value}`);
        }

    });

    contact_name.addEventListener("keyup", (e) => {
        if (contact_name.value == "") {
            name_error.classList.remove("hidden");
        } else {
            name_error.classList.add("hidden");
        }
    });
```
送信ボタンをクリックしたら、チェックを実行し、比較して一致しない場合はエラーを表示し、hiddenクラスを削除するようにします。
alertにもお名前と同じロジックを＆でつないで追加します。

```diff_javascript:index.js
    const submit_btn = document.getElementById("submit_btn");

    const contact_name = document.getElementById("name");
    const email = document.getElementById("email");
    const body = document.getElementById("body");

    const name_error = document.getElementById("name_error");
    const email_error = document.getElementById("email_error");

    const email_exp = /^[a-z0-9.]+@[a-z0-9.]+\.[a-z]+$/;

    submit_btn.addEventListener("click", (e) => {
        e.preventDefault();

        if (contact_name.value == "") {
            name_error.classList.remove("hidden");
        }

+       if (!email_exp.test(email.value)) {
+           email_error.classList.remove("hidden");
+       }

-       if (name_error.classList.contains("hidden")) {
+       if (name_error.classList.contains("hidden") && email_error.classList.contains("hidden")) {
            alert(`お名前：${contact_name.value}\nemail：${email.value}\nお問合せ内容：${body.value}`);
        }

    });

    contact_name.addEventListener("keyup", (e) => {
        if (contact_name.value == "") {
            name_error.classList.remove("hidden");
        } else {
            name_error.classList.add("hidden");
        }
    });
```

```javascript:コピペ用
if (!email_exp.test(email.value)) {
    email_error.classList.remove("hidden");
}

if (name_error.classList.contains("hidden") && email_error.classList.contains("hidden")) {
    alert(`お名前：${contact_name.value}\nemail：${email.value}\nお問合せ内容：${body.value}`);
}
```
### 動作確認
保存して確認します。
emailにはまだkeyup（キーボート入力時）によるバリデーションチェックの実装をしていないため、送信ボタン押して確認をします。
値が空っぽの状態と形式が間違っている状態でエラーメッセージが表示され、正しく入力しているとアラートが出ることを確認します。**（都度ブラウザを更新して実行）**
![000190.jpg](https://drive.google.com/uc?export=view&id=1KzwD9QJHe-XQTsLRIyo6hPcrWbaBdsSF)
![000200.jpg](https://drive.google.com/uc?export=view&id=1jPWhk1ZbjtbHXHdCz-GirVU8Dx0-pz6S)


### キーボート入力中にバリデーションチェック
keyupを利用して常にバリデーションチェックをするようにします。

```diff_javascript:index.js
    submit_btn.addEventListener("click", (e) => {
        e.preventDefault();

        if (contact_name.value == "") {
            name_error.classList.remove("hidden");
        }

        if (!email_exp.test(email.value)) {
            email_error.classList.remove("hidden");
        }

        if (name_error.classList.contains("hidden") && email_error.classList.contains("hidden")) {
            alert(`お名前：${contact_name.value}\nemail：${email.value}\nお問合せ内容：${body.value}`);
        }

    });

    contact_name.addEventListener("keyup", (e) => {
        if (contact_name.value == "") {
            name_error.classList.remove("hidden");
        } else {
            name_error.classList.add("hidden");
        }
    });

+   email.addEventListener("keyup", (e) => {
+       if (!email_exp.test(email.value)) {
+           email_error.classList.remove("hidden");
+       } else {
+           email_error.classList.add("hidden");
+       }
+   });
```
```javascript:コピペ用
email.addEventListener("keyup", (e) => {
    if (!email_exp.test(email.value)) {
        email_error.classList.remove("hidden");
    } else {
        email_error.classList.add("hidden");
    }
});
```
### 動作確認
動作確認して、入力中にも正しくバリデーションチェックが走っていること、入力が正しい場合はアラートが出ることを確認して終了します。

## お問合せ内容バリデーション
手順は同じですが、お問合せ内容は入力文字数制限をかけていきます。
1文字以上10文字以内をルールとします。

```diff_javascript:index.js
    const submit_btn = document.getElementById("submit_btn");

    const contact_name = document.getElementById("name");
    const email = document.getElementById("email");
    const body = document.getElementById("body");

    const name_error = document.getElementById("name_error");
    const email_error = document.getElementById("email_error");
+   const body_error = document.getElementById("body_error");

    const email_exp = /^[a-z0-9.]+@[a-z0-9.]+\.[a-z]+$/;
+   const body_exp = /^.{1,10}$/;
```
送信ボタンクリック時の制御を追加します。

```diff_javascript:index.js
    submit_btn.addEventListener("click", (e) => {
        e.preventDefault();

        if (contact_name.value == "") {
            name_error.classList.remove("hidden");
        }

        if (!email_exp.test(email.value)) {
            email_error.classList.remove("hidden");
        }

+       if (!body_exp.test(body.value)) {
+           body_error.classList.remove("hidden");
+       }

-       if(name_error.classList.contains("hidden")&&email_error.classList.contains("hidden")){
+       if (name_error.classList.contains("hidden") && email_error.classList.contains("hidden") && body_error.classList.contains("hidden")) {
            alert(`お名前：${contact_name.value}\nemail：${email.value}\nお問合せ内容：${body.value}`);
        }

    });

```

```javascript:コピペ用
if (!body_exp.test(body.value)) {
    body_error.classList.remove("hidden");
}

if (name_error.classList.contains("hidden") && email_error.classList.contains("hidden") && body_error.classList.contains("hidden")) {
    alert(`お名前：${contact_name.value}\nemail：${email.value}\nお問合せ内容：${body.value}`);
}
```

### キーボート入力中にバリデーションチェック
keyupを利用して常にバリデーションチェックをするようにします。

```diff_javascript:index.js
    contact_name.addEventListener("keyup", (e) => {
        if (contact_name.value == "") {
            name_error.classList.remove("hidden");
        } else {
            name_error.classList.add("hidden");
        }
    });

    email.addEventListener("keyup", (e) => {
        if (!email_exp.test(email.value)) {
            email_error.classList.remove("hidden");
        } else {
            email_error.classList.add("hidden");
        }
    });

+   body.addEventListener("keyup", (e) => {
+       if (!body_exp.test(body.value)) {
+           body_error.classList.remove("hidden");
+       } else {
+           body_error.classList.add("hidden");
+       }
+   });
```
```javascript:コピペ用
body.addEventListener("keyup", (e) => {
    if (!body_exp.test(body.value)) {
        body_error.classList.remove("hidden");
    } else {
        body_error.classList.add("hidden");
    }
})
```

### 動作確認
動作確認して、入力中にも正しくバリデーションチェックが走っていること、入力が正しい場合はアラートが出ることを確認してバリデーションの実装も完了です。

# 最終的なコード

```contact.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSite|お問合せ</title>
    <link rel="stylesheet" href="./style.css" />
</head>

<body>
    <nav class="nav">
        <ul>
            <li><a href="./index.html">WebSite</a></li>
            <li><a href="./index.html">TOP</a></li>
            <li><a href="./contact.html">CONTACT</a></li>
        </ul>
    </nav>
    <main class="contact_form">
        <h2>お問合せ</h2>
        <form>
            <div class="form_group">
                <label>お名前</label>
                <input type="text" id="name" />
                <span class="form_error hidden" id="name_error">お名前は必須です。</span>
            </div>
            <div class="form_group">
                <label>email</label>
                <input type="email" id="email" />
                <span class="form_error hidden" id="email_error">emailは必須かつemailの形式で入力してください。</span>
            </div>
            <div class="form_group">
                <label>お問合せ内容</label>
                <textarea id="body" rows=4></textarea>
                <span class="form_error hidden" id="body_error">お問合せ内容は必須かつ1文字以上10文字以下で入力してください。</span>
            </div>
            <button class="submit_btn" id="submit_btn">送信</button>
        </form>
    </main>
    <footer class="footer">
        &copy; 2022 bluecode, inc. All Rights Reserved.
    </footer>

    <script src="./index.js"></script>
</body>

</html>
```

```style.css
html {
    height: 100%;
}

body {
    margin: 0;
    padding: 0;
    font-family: sans-serif;
    height: 100%;
}

.nav {
    /* 背景色設定 */
    background-color: #666;
}

.nav ul {
    /* 表示幅を90%に */
    width: 90%;
    /* フレックスボックス化 */
    display: flex;
    /* 先頭のポチをなくす */
    list-style: none;
    /* 余白の自動調整 （上下の余白は0、左右は自動調整）*/
    margin: 0 auto;
    /* 高さを50pxに指定 */
    height: 50px;
}

.nav a {
    /* アンダーライン削除 */
    text-decoration: none;
    /* 文字色設定 */
    color: #fff;
    /* 内側余白を上右下左の順で設定 */
    padding: 10px 20px 10px 0px;
    /*ブロックにする */
    display: block;
    /* 文字を大き目に設定 */
    font-size: large;
}

.nav a:hover {
    /* マウスオーバーした時の文字色を設定 */
    color: #aaa;
}

/* トップ */
.top {
    /* 背景にpicsum.photosから取得した画像を設定 */
    background-image: url("https://picsum.photos/1000/300");
    /* 画像をいっぱいに表示 */
    background-size: cover;
    /* 画像を表示している四隅の角に少し丸みをつける */
    /* border-radius: 5px; */
    /* 表示する高さを画面の40％に */
    height: 40vh;
    /* 表示幅を画面の90%に */
    width: 100%;
    /* フレックスボックスを指定*/
    display: flex;
    /* フレックスアイテムを使って両端から中央に配置*/
    justify-content: center;
    /* フレックスアイテムを使って上下から中央に配置 */
    align-items: center;
    /* テキストの配置を中央揃えに */
    text-align: center;
}

.top_msg {
    /* 文字色を白に */
    color: #fff;
}

.content {
    width: 90%;
    margin: 0 auto;
    text-align: center;
}

.footer {
    background-color: #666;
    height: 60px;
    color: #fff;
    display: flex;
    justify-content: center;
    align-items: center;
    position: sticky;
    top: 100vh;
}


/* お問合せフォーム */
/* Contact */
.contact_form {
    padding: 40px 20px;
    text-align: center;
}

.form_group {
    border-top: 1px solid #ddd;
    margin-top: 20px;
    padding: 20px 0px;
    width: 100%;
    display: flex;
    align-items: center;
    flex-wrap: wrap;
}

.form_group label {
    width: 30%;
    font-weight: bold;
    font-size: 18px;
}

.form_group input,
.form_group textarea {
    border: 1px solid #ddd;
    border-radius: 6px;
    width: 60%;
    font-size: 18px;
}

.form_group input {
    height: 40px;
}

.submit_btn {
    padding: 5px 15px;
    background-color: #049dbf;
    color: #fff;
    border-radius: 5px;
    border: 0;
    font-size: 16px;
}

.submit_btn:hover {
    background-color: #0183a0;
}

.form_error {
    padding-left: 30%;
    width: 60%;
    font-size: small;
    color: red;
    text-align: start;
    padding-top: 10px;
}

.hidden {
    display: none;
}
```

```index.js
const submit_btn = document.getElementById("submit_btn");

const contact_name = document.getElementById("name");
const email = document.getElementById("email");
const body = document.getElementById("body");

const name_error = document.getElementById("name_error");
const email_error = document.getElementById("email_error");
const body_error = document.getElementById("body_error");

const email_exp = /^[a-z0-9.]+@[a-z0-9.]+\.[a-z]+$/;
const body_exp = /^.{1,10}$/;

submit_btn.addEventListener("click", (e) => {
    e.preventDefault();

    if (contact_name.value == "") {
        name_error.classList.remove("hidden");
    }

    if (!email_exp.test(email.value)) {
        email_error.classList.remove("hidden");
    }

    if (!body_exp.test(body.value)) {
        body_error.classList.remove("hidden");
    }

    if (name_error.classList.contains("hidden") && email_error.classList.contains("hidden") && body_error.classList.contains("hidden")) {
        alert(`お名前：${contact_name.value}\nemail：${email.value}\nお問合せ内容：${body.value}`);
    }

});

contact_name.addEventListener("keyup", (e) => {
    if (contact_name.value == "") {
        name_error.classList.remove("hidden");
    } else {
        name_error.classList.add("hidden");
    }
});

email.addEventListener("keyup", (e) => {
    if (!email_exp.test(email.value)) {
        email_error.classList.remove("hidden");
    } else {
        email_error.classList.add("hidden");
    }
});

body.addEventListener("keyup", (e) => {
    if (!body_exp.test(body.value)) {
        body_error.classList.remove("hidden");
    } else {
        body_error.classList.add("hidden");
    }
});
```
# まとめ

* Webでユーザーの入力はformと関連タグ群で処理する
* 値の取得や送信はJavaScriptで行う（CSRの場合）
* 単純な機能でもそれなりの記述量になる
  * 特にバリデーションはどこまでも複雑にすることができ、実装だけで数日を要する仕様にする（なる）ことも可能
  * 不用意に「えーと、Googleとかと同じバリデーションルールで。当然でしょ！」とか気軽に言わない（苦笑）
* クライアント側で実行されるJavaScriptのコードは閲覧可能なので、ここに機微情報(ID,PWとか）を含めない
* ライセンスを無視して（他社の）JavaScriptコードを勝手に利用しない（たまに炎上してます）
  * 外注が勝手に・・・が通用しない（明日は我が身）

# ドキュメント作成視点での考察

* データ入力をformで処理することをどこにどう記述すべき？
* 各入力項目の条件をどこにどう記述すべき？
* ボタン押下時の処理をどこにどう記述すべき？

# 関連コンテンツ

* 次：[フロントエンド開発３](https://qiita.com/minimumskills/items/f74b14e04285f52b999d)
* 前：[フロントエンド開発１](https://qiita.com/minimumskills/items/7fa35754f1434d40470c)