はじめに
インフラ系IT企業を2年でやめ、受託開発会社に勤務して約7ヶ月の3年目のエンジニアです。
今はほとんどインフラはやっておらず、フロントエンドばっかり書いています。勉強のためバリデーションでも作って見よう!と思って作りました。
※バリデーション自体はvuetifyやプラグイン使えばかんたんに実装できますが、自分で作るのって結構大事ですよね、プラグインに依存しないですし。
機能
1.フォームの送信ボタンをクリックするとバリデーションが発火する。
2.入力必須、文字数、形式(stringとかnumber)をチェックする。
3.エラーが発生してる状態だと送信できないようにする。
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頑張っていきたいです。