1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Vue.js】Formバリデーションを作ってみた【Javascript】

Last updated at Posted at 2020-02-27

はじめに

インフラ系IT企業を2年でやめ、受託開発会社に勤務して約7ヶ月の3年目のエンジニアです。
今はほとんどインフラはやっておらず、フロントエンドばっかり書いています。勉強のためバリデーションでも作って見よう!と思って作りました。
※バリデーション自体はvuetifyやプラグイン使えばかんたんに実装できますが、自分で作るのって結構大事ですよね、プラグインに依存しないですし。

機能

1.フォームの送信ボタンをクリックするとバリデーションが発火する。
2.入力必須、文字数、形式(stringとかnumber)をチェックする。
3.エラーが発生してる状態だと送信できないようにする。

###見た目
スクリーンショット 2020-02-27 13.45.20.png

formの見た目は適宜調整してください。

まずは、vueのformのソースからです。
formの部分は結構見ずらいので、コンポーネントに分けた方が良いと思います。

コード

Form

<template>
  <div class="form">
      <div class="form-item">
        <div class="form-item__label">
          {{ input.name.name }}
          <span class="form-item__require" v-if="input.name.require">必須</span>
        </div>
        <div class="form-item__input">
          <div class="underline">
            <input v-model="input.name.value" :id="input.name.name" type="text" placeholder />
            <label :for="input.name.name">{{ input.name.name }}</label>
          </div>
          <div class="validation-message">{{input.name.errorMessage[0]}}</div>
        </div>
      </div>
      <div class="form-item">
        <div class="form-item__label">
          {{ input.name_kana.name }}
          <span class="form-item__require" v-if="input.name_kana.require">必須</span>
        </div>
        <div class="form-item__input">
          <div class="underline">
            <input v-model="input.name_kana.value" :id="input.name_kana.name" type="text" placeholder />
            <label :for="input.name_kana.name">{{ input.name_kana.name }}</label>
          </div>
          <div class="validation-message">{{input.name_kana.errorMessage[0]}}</div>
        </div>
      </div>
      <div class="form-item">
        <div class="form-item__label">
          {{ input.mail.name }}
          <span class="form-item__require" v-if="input.mail.require">必須</span>
        </div>
        <div class="form-item__input">
          <div class="underline">
            <input v-model="input.mail.value" :id="input.mail.name" type="text" placeholder />
            <label :for="input.mail.name">{{ input.mail.name }}</label>
          </div>
          <div class="validation-message">{{input.mail.errorMessage[0]}}</div>
        </div>
      </div>
        <button type="button" class="submit-btn" @click="submit">登録</button>
  </div>
</template>

<script>
import {validation} from '@/utilities/validation/validation.js';
const form = {
  name: 'aaaaaaa',
  name_kana: 'a',
  mail: 'testes@gmail.com',
}

export default {
  data() {
    return {
      form,
      input: {
        name: {
          name: '名前',
          value: '',
          length: [1, 28],
          require: true,
          errorMessage: []
        },
        name_kana: {
          name: '名前(かな)',
          value: '',
          length: [1, 28],
          format: 'Hiragana',
          require: true,
          errorMessage: []
        },
        mail: {
          name: 'メールアドレス',
          value: '',
          format: 'Mail',
          length: [1, 28],
          require: false,
          errorMessage: []
        },
      }
    };
  },
  watch: {
    form: {
      handler(newValue) {
        for(let key in this.input) {
            this.input[key].value = newValue[key];
        }
      },
      immediate: true
    }
  },
  methods: {
    handleValidate(result){
      return validation(this.input);
    },
    submit() {
      const result = this.handleValidate(this.input);
      if (result.validStatus) {
        console.log('送信')
      }
    }
  }
};
</script>

<style lang="scss">
.form{
  padding: 24px 120px;
}
.form-item {
  min-height: 72px;
  padding: 16px 0;
  display: flex;
  &__label {
    font-size: 13px;
    color: black;
    text-align: left;
    font-weight: 600;
    text-align: right;
    min-width: 140px;
    max-width: 140px;
    margin-right: 12px;
  }
  &__input{
    width: 100%;
  }
  &__require{
    background-color: red;
    font-size: 10px;
    color: white;
    border-radius: 4px;
    display: inline-block;
    padding: 1px 4px;
    vertical-align: middle;
  }
}

.validation-message {
  font-size: 12px;
  color: red;
  margin-top: 8px;
  text-align: left;
}
.underline {
  border-bottom: 2px solid #bbb;
  padding: 0 8px 2px 8px;
  position: relative;
  &::before {
    left: 0;
    content: "";
    height: 5px;
    width: 2px;
    position: absolute;
    bottom: 0;
    background-color: #bbb;
  }
  &::after {
    content: "";
    height: 5px;
    width: 2px;
    position: absolute;
    right: 0;
    bottom: 0;
    background-color: #bbb;
  }
  input {
    position: relative;
    border: none;
    border-image-width: 0;
    outline: none;
    margin: 0;
    font-size: 12px;
    padding: 0;
    z-index: 1;
    -webkit-appearance: none;
    border-radius: 0;
    background-color: transparent;
    width: 100%;
    display: block;
    box-sizing: border-box;
    color: #111;
    font-family: "Roboto", sans-serif;
    font-weight: bold;
  }
  label {
    width: 100%;
    padding-bottom: 2px;
    cursor: default;
    font-weight: bold;
    position: absolute;
    font-size: 12px;
    font-family: "Roboto", sans-serif;
    top: -14px;
    left: 8px;
    opacity: 0;
    color: #777;
  }
}
.placeholder {
  user-select: none;
  position: absolute;
  left: 8px;
  bottom: 8px;
  color: #777;
  font-family: "Roboto", sans-serif;
  font-weight: normal;
  font-size: 18px;
  z-index: 0;
  opacity: 0.99;
}
.submit-btn {
  margin: 0 auto;
	display: block;
	position: relative;
	width: 160px;
	padding: 0.8em;
	text-align: center;
	text-decoration: none;
	color: #fff;
	background: #3A8BA8;
  &:hover {
	cursor: pointer;
	text-decoration: none;
	background:#fff;
  color:  #3A8BA8;
  border:solid 1px #3A8BA8;
  }
}

</style>

まず data() オプションのinputオブジェクトにv-modelやバリデーションで使う値を定義しています。
名前のフォームを例に上げて説明すると、、、

 name: {
    name: '名前', // フォームの名前を定義します。エラーメッセージやラベルで使用されます。
    value: '', // v-modelで使用します。
    length: [1, 28], // 入力値の長さを確認に使用します。1つ目の数字が最小文字数、2つ目は最大文字数です。定義しない場合はチェックを行いません。
    require: true, // 入力必須の確認に使用します。定義しない場合は入力必須チェックを行いません。
    errorMessage: [] // エラーメッセージの表示に使用します。配列なのでv-forで回せば全てのエラーを表示できます。
 },

これらを定義したあと、「登録」ボタンが押されたタイミングでバリデーションのメソッドが発火します。エラーがあればメッセージが表示されます。
エラーが無ければ任意のメソッドを実行させることができます。

※watchでform変数をみて、inputに代入していますが、これは編集画面等でバリデーションしたい場合に使用します。
vuex等でvalueをstateに入れてそれをinputのvalueにのみ入れます。

バリデーション本体

かなりコードが汚いとおもいますが、一応動きます。

// formatチェックで使用する正規表現です。必要に応じてファイルを分けたり、追加が可能です。
const ValidateSchema = {
  Number: {
    schema: /^[0-9]*$/,
    errorMessage: "は数字で入力してください"
  },
  NumberHyphen: {
    // eslint-disable-next-line no-useless-escape
    schema: /^[0-9\-]+$/,
    errorMessage: "に数字とハイフン以外が入力されています"
  },
  Hiragana: {
    schema: /^[ぁ-んー]+$/,
    errorMessage: "はひらがなで入力してください"
  },
  Mail: {
    // eslint-disable-next-line no-useless-escape
    schema: /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/,
    errorMessage: "は正しい形式で入力してください"
  }
};

// 必須チェック
function requireCheck(input) {
  let result;
  let errorMessage;
    if (input.value.length <= 0 || input.value === null) {
      errorMessage = input.name + "は必須です。";
      result = { valid: false };
    } else {
      result = { valid: true };
      errorMessage = '';
    }
  return { errorMessage, ...result };
};

// 長さチェック
function lengthCheck(input) {
  const minLength = input.length[0];
  const maxLength = input.length[1];
  let errorMessage;
  let result;
  if (input.value.length >= minLength && input.value.length <= maxLength) {
    result = { valid: true };
    errorMessage = '';
  } else {
    errorMessage = input.name + "" + minLength + "文字以上" + maxLength + "文字以下で入力してください";
    result = { valid: false };
  }
  return { ...result, errorMessage };
};

// フォーマットチェック
function formatCheck(input) {
  const formatType = input.format;
  let errorMessage;
  let result;
  if (formatType && !input.value == '' || !input.value == null) {
    const validate = ValidateSchema[formatType].schema.test(input.value);
    if (validate) {
      result = { valid: true };
      errorMessage = '';
    } else {
      errorMessage = input.name + ValidateSchema[formatType].errorMessage;
      result = { valid: false };
    }
  } else {
    result = { valid: true };
    errorMessage = "";
  }
  return { ...result, errorMessage };
};

// エラーを初期化するメソッド
function clearErrorMessage(input) {
  return input.errorMessage = [];
}

// バリデーションメソッド
export const validation = (input) => {
  // 最初はエラーは0
  let errors = 0;
  const result = input;
  for (let key in input) {
    //エラーを初期化
    clearErrorMessage(result[key], errors);
    // 入力必須が設定されていた場合のみ実行
    if (input[key].require) {
      const requireCheckResult = requireCheck(result[key]);
      if (requireCheckResult.valid != true) {
        result[key].errorMessage.push(requireCheckResult.errorMessage);
        errors++;
      }
    }
    // 入力必須が設定されていて、長さチェックが設定されていた場合のみ実行
    if (input[key].length && input[key].require) {
      const lengthCheckResult = lengthCheck(result[key]);
      if (lengthCheckResult.valid != true) {
        result[key].errorMessage.push(lengthCheckResult.errorMessage);
        errors++;
      }
    }
    // フォーマットが設定されていた場合のみ実行
    if (input[key].format) {
      const formatCheckResult = formatCheck(result[key]);
      if (formatCheckResult.valid != true) {
        result[key].errorMessage.push(formatCheckResult.errorMessage);
        errors++;
      }
    }
    if (errors == 0) {
      clearErrorMessage(result[key], errors);
    }
  }
  if (errors === 0) {
    return { validStatus: true };
  }
  return { validStatus: false, input: { ...result } };
};

コンポーネントの方で定義したinputのオブジェクトを単純にforでまわして一つひとつバリデーションしています。
最終的にerrorsが0になった場合のみvalidStatusをtrueにして返します。

まとめ

かなり遠回りなコードだと思いますが、一応バリデーションを実行することができます。
改善することとして、バリデーションのスキーマをファイルに分けたり、formをコンポーネント化したりすることですね。
何か不具合等あればコメントいただければと思います。

今後もjavascript頑張っていきたいです。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?