Javascriptで実装するフロントエンドバリデーションまとめ

HTML5に搭載されているフォームバリデーションで事足りる場面も多々ありますが、痒いところには手が届きません。
例えばエラーメッセージをもう少しわかりやすく優しい感じにしたいとか、checkboxは自分で実装しなきゃいけないとか、ブラウザによっては対応してないとか色々。

個人的にはJavascriptを介して実装することが多いので、今回は自分がよく使うバリデーションのパターンをまとめてみました。
フォームを作る誰かの参考になれば幸いです。

## 自分で実装するその前に

世の中にはライブラリという便利なものがあり、npmにもいくつかValidationのPackageが公開されています。

[validator.js](https://www.npmjs.com/package/validator)
[v8n](https://github.com/imbrn/v8n)

jQueryにもバリデーション用のライブラリがあった気がしていますが、今回はVanillaJS対象とします。

## Validation Method

※ validator.jsを使用する場合は、判定条件のところをvalidator.jsのmethodに置き換えれば使用できます。

### 必須入力

``` .js
const REQUIRED_MESSAGE = 'この項目は必須です';
const isEmpty = val => val === '' || val === null || val === undefined;

/**
 * @param  {String|Number|Array} value
 * @return {String}
 */
const checkEmpty = value => {
  // checkboxの必須チェック
  if (Array.isArray(value) && value.length <= 0) {
    return REQUIRED_MESSAGE;
  } else if (isEmpty(value)) {
    return REQUIRED_MESSAGE;
  }
  return '';
};
```

### email

``` .js

const REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

/**
 * @param  {String} value
 * @return {String}
 */
const checkEmail = value => (value.toString().match(REGEX) ? '' : '有効なメールアドレスを入力してください');
```

### URL

``` .js

const REGEX = /^(https?|ftp)(:\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+)$/;

/**
 * @param  {String} value
 * @return {String}
 */
const checkURL = value => (value.toString().match(REGEX) ? '' : '有効なURLを入力してください');
```

### 電話番号

``` .js
const REGEX = /^\d{11}$|^\d{3,4}-\d{3,4}-\d{4}$/;

/**
 * @param  {String|Number} value
 * @return {String}
 */
const checkTel = value => (value.toString().match(REGEX) ? '' : '有効な電話番号を入力してください');
```

### パスワード

全体で8文字以上、数字1文字、大文字アルファベット1文字、小文字アルファベット1文字が含まれている判定と、
確認用パスワードの一致判定です。

```.js
const REGEX = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$/;

const checkPasswordFormat = value =>
  value.toString().match(REGEX)
    ? ''
    : '8文字以上の大文字アルファベット・小文字アルファベット・数字で入力してください';

const checkConfirmPassword = (confirmPassword, password) =>
  confirmPassword === password ? '' : '入力されたパスワードが一致しません';

/**
 * @param  {String} password
 * @param  {String} confirmPassword
 * @return {String}
 */
const checkPassword = (password, confirmPassword) => {
  const formatError = checkPasswordFormat(password);
  if (formatError) return formatError;
  if (confirmPassword) return checkConfirmPassword(confirmPassword, password);
  return '';
};
```

### 郵便番号

```.js
const REGEX = /^\d{3}-?\d{4}$/; ※ "123-4567", "1234567", 1234567

/**
 * @param  {String|Number} value 
 * @return {String}
 */
const checkPostalCode = value => (value.toString().match(PREGEX) ? '' : '7桁の数字で入力してください');
```

### ファイルの容量

```.js
const KB = 1024;
const MEGA = Math.pow(KB, 2);
const MAX_SIZE = MEGA * 2;

/**
 * @param  {String|Number} size ※FileApiのsizeプロパティの値を想定
 * @return {String}
 */
const checkFileSize = size =>
  size > MAX_SIZE ? `容量が${MAX_SIZE / MEGA}MB以下のファイルを選択してください` : '';
```

### ファイルの拡張子

```.JS
const EXT = ['image/jpeg', 'image/png', 'image/jpg'];

/**
 * @param  {String} type ※FileApiのtypeプロパティの値を想定
 * @return {String}
 */
const checkFileExt = type => (!EXT.includes(type) ? `拡張子が${EXT.join(', ')}のファイルを選択してください` : '');
```

### 期間の整合性

開始日が終了日より後の日付や時間になっていないかの判定です。
一から自分で実装するのはめんどくさいので、[moment.js](https://momentjs.com/)を使用します。

```.JS
import moment from 'moment';

/**
 * @param  {String|Number|Array} startDate ※ "2019-01-01", "20190101", 20190101, ["2019", "01", "01"] など
 * @param  {String|Number|Array} endDate ※ startDate同様
 * @return {String}
 */
const checkDatetimePeriod = ({ startDate, endDate }) =>
  moment(startDate).isAfter(moment(endDate)) ? '正しい期間を選択してください' : '';
```

### 最小金額・最大金額

```.js
/**
 * @param  {String|Number} value
 * @param  {String|Number} min
 * @return {String}
 */
const checkMinimumPrice = (value, min) => (value < min ? `${min}円以上で入力してください` : '');

/**
 * @param  {String|Number} value
 * @param  {String|Number} max
 * @return {String}
 */
const checkMaximumPrice = (value, max) => (value > max ? `${max}円以下で入力してください` : '');
```

### 最小文字数・最大文字数

```.js
/**
 * @param  {String|Number} value
 * @param  {String|Number} min
 * @return {String}
 */
const checkMinCount = (value, min) => (value.length < min ? `${min}文字以上で入力してください` : '');

/**
 * @param  {String|Number} value
 * @param  {String|Number} max
 * @return {String}
 */
const checkMaxCount = (value, max) => (value.length > max ? `${max}文字以下で入力してください` : '');
```

### 絵文字の有無

```.js
const RANGES = [
  '\ud83c[\udf00-\udfff]',
  '\ud83d[\udc00-\ude4f]',
  '\ud83d[\ude80-\udeff]',
  '\ud7c9[\ude00-\udeff]',
  '[\u2600-\u27BF]',
];

/**
 * @param  {String} value
 * @return {String}
 */
const checkInclusionPictograph = value => {
  const reg = new RegExp(RANGES.join('|'), 'g');
  return !reg.test(value);
};
```

## 最後に

自身のよく使うバリデーションをまとめてみました。新しいのがパターンを実装したら都度追加していきます。
こんなパターンもあるのではとか間違ってるけど、もっと簡単な書き方があるってご意見などあればコメントいただけますと幸いです。

## 参考

https://momentjs.com/
https://iwb.jp/javascript-remove-form-input-textarea-emoji/