ライブラリなしで Form Validation を実装してみる

# やりたいこと
ライブラリを使わず、生の JavaScript と HTML のみで Form Validation を実現する。

こんな感じ ↓
![スクリーンショット 2019-04-15 14.02.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114828/60113ee0-b19f-0a22-1de2-2de23a5ecc43.png)


# なんで？
React.js や Vue.js を導入途中のプロジェクトでは、jQuery などのライブラリを使う意味が薄れていく。
そのときに生JSだけで書くこともあるかなーと思い、サンプルコードを書いてみた。
（実用性があるかは不明）

# 完成品
Form Validation Sample - CodePen
https://codepen.io/ezawa800/pen/EJVwQL?editors=1010

# やり方
## 1. HTML標準の Data Form Validation を利用する
詳細は [フォームデータの検証 - Web 開発を学ぶ | MDN](https://developer.mozilla.org/ja/docs/Learn/HTML/Forms/Data_form_validation) を参照してください。

```sample1.html
<div class="form-area">
  <h1 class="form-title">1. Use default validMessage</h1>
  <form id="sample-form1" name="sample-form1" method="POST" action="">
    <label>Your ID</label>
    <input type="text"
      id="sample-text-input1"
      pattern="^[a-zA-Z0-9-_]+$"
      title="半角英数字とハイフン(-)、アンダーライン(_)で入力してください。"
      maxlength="20" required 
    />
    <input type="submit" />

    <div class="input-note">半角英数字とハイフン(-)、アンダーライン(_)を使って、20文字以内で入力してください。</div>
  </form>
</div>
```

### 必須入力
`required` 属性を利用する。

```html
<input type="text" required />
```

送信ボタンをクリックすると以下のようなエラーになって、送信を中止する。
![スクリーンショット 2019-04-15 13.22.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114828/97db0a78-a520-4580-b0ea-1a1a21c188dc.png)


### パターンの制限
`pattern` 属性を利用する。

```html
<input type="text" pattern="^[a-zA-Z0-9-_]+$" title="半角英数字とハイフン(-)、アンダーライン(_)で入力してください。" />
```

送信ボタンをクリックすると以下のようなエラーになって、送信を中止する。
![スクリーンショット 2019-04-15 13.22.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114828/683f8d46-aa09-2009-1ded-3e2cc9e9127d.png)

そのままだと「指定されている形式で入力してください。」とだけ表示されるが、`title` 属性を追加すると任意のメッセージを追加できる。


### 文字数制限
`maxlength` 属性を利用する。

```html
<input type="text" maxlength="20" />
```

指定した文字数以上は入力できなくなる。

## 2. エラーメッセージをカスタマイズする
ここまでだと HTML のみで実現できているので JS の出番がないが、バリデーションエラーがある場合は「送信」ボタンを押す前の教えてほしいケースを想定して、以下の2パターンでエラーメッセージを表示させてみた。

###　２−１． 標準のエラーメッセージを任意の場所に表示する

```html
<div class="form-area">
  <h1 class="form-title">1. Use default validMessage</h1>
  <form id="sample-form1" name="sample-form1" method="POST" action="">
    <label>Your ID</label>
    <input type="text"
      id="sample-text-input1"
      pattern="^[a-zA-Z0-9-_]+$"
      title="半角英数字とハイフン(-)、アンダーライン(_)で入力してください。"
      maxlength="20" required 
    />
    <input type="submit" />

    <div id="sample-input-error1" class="error-text"></div>
    <div class="input-note">半角英数字とハイフン(-)、アンダーライン(_)を使って、20文字以内で入力してください。</div>
  </form>
</div>
```

```js
document.addEventListener("DOMContentLoaded", function(evt) {
  // 1. Use default validMessage
  document.querySelector('#sample-text-input1').addEventListener('input', function(event) {
    var validMessage = this.validationMessage;
    if (validMessage) {
      document.querySelector('#sample-input-error1').innerHTML = validMessage;
    } else {
      document.querySelector('#sample-input-error1').innerHTML = '';
    }
  });
});
```

バリデーションエラーがある場合は、対象の`input` に `validationMessage` の値がセットされる。
それをそのまま使ってエラーメッセージを表示したいのが上記のサンプル。

![スクリーンショット 2019-04-15 13.42.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114828/74fbc107-8050-3a17-ab61-94aefb25ea8c.png)

コード量が少なくて済む一方で、最低限の情報しか表示されないのが適さないケースもあるかもしれない。

### ２−１． カスタムエラーメッセージを任意の場所に表示する

```html
<div class="form-area">
  <h1 class="form-title">2. Use custom validation message</h1>
  <form id="sample-form2" name="sample-form2" method="POST" action="">
    <label>Your ID</label>
    <input type="text"
      id="sample-text-input2"
      pattern="^[a-zA-Z0-9-_]+$"
      title="半角英数字とハイフン(-)、アンダーライン(_)で入力してください。"
      maxlength="20" required 
    />
    <input type="submit" />
    <div id="sample-input-error2" class="error-text"></div>
    <div class="input-note">
      半角英数字とハイフン(-)、アンダーライン(_)を使って、20文字以内で入力してください。
    </div>
  </form>
</div>
```

```js
document.addEventListener("DOMContentLoaded", function(evt) {
  // 2. Use custom validation message
  document.querySelector('#sample-text-input2').addEventListener('input', function(event) {
    var sampleInput = event.target,
        errorBox = document.querySelector('#sample-input-error2');
    if (sampleInput.checkValidity()) {
      errorBox.innerHTML = '';
      return;
    }
    
    var regex = new RegExp(sampleInput.getAttribute('pattern'));
    if (sampleInput.value === "") {
      errorBox.innerHTML = 'IDは必須です。';
    } else if (!regex.test(sampleInput.value)) {
      errorBox.innerHTML = '使用できない文字が含まれています。';
    } else {
      errorBox.innerHTML = '何かが間違っています。';
    }
  });
});
```

`checkValidity` メソッドを使って、バリデーションの結果を取得し、その結果に応じて自分でエラーメッセージ表示処理を実装したパターン。

細やかなメッセージや挙動の調整ができる一方で、ちょっとコードが多いので共通化などの工夫が必要。

![スクリーンショット 2019-04-15 13.47.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114828/c0568980-1e9c-3620-982c-f458fa2e8b53.png)

## 3. エラーになっている入力欄のスタイル変更

```scss
input {
  &:invalid {
    border: 2px solid red;
    
    &:focus {
      outline-color: rgba(255, 0, 0, .5);
    }
  }
}
```

`:invalid` を使うと、バリデーションエラーが発生している入力欄のスタイルを調整できる。

![スクリーンショット 2019-04-15 13.49.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114828/6bf11b7c-8e30-e232-3d98-24e6d5e19144.png)

※上部の入力欄はエラー、下部はエラーでかつフォーカスが当たっている。


# まとめ
基本的にはライブラリを使った方が便利なことが多く、React.js や Vue.js を使う場合も生JSで書くことは少ないです。
ただ、移行期間中など何かしらの理由で生JSで書くケースもなくはないと思ったので、試しにちょっとしたサンプルを書いてみました。