Vue2 b-form-input + ValidationProviderの使った同期的バリデーションフォームの作り方

## 概要

本記事は**Vue2 Options Api**を使用しています。バージョンの違いにご注意ください。 

vue.jsを使って、同期的にバリデーションを行う入力フォームを作る方法について解説していきます。 

**b-form-input**とは、BootstrapVueを導入することで使える入力用のタグです。  

https://bootstrap-vue.org/docs/components/form-input

**ValidationProvider**とは、VeeValidateを導入することで使えるバリデーターです。  

https://vee-validate.logaretm.com/v2/guide/components/validation-provider.html

以下のような入力フォームを作ることを目的とします。  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814957/6d031300-7cad-5768-9121-1543479635ca.png)  

入力イベントに同期してバリデーションを行います。  

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814957/9ff20277-7268-df70-5bef-af104bab840f.png)  

## 実行環境

**vue@2.7.16** 
bootstrap-vue@2.23.1
vee-validate@3.4.15

## 目次
- [vue.jsを導入する](#vue.jsを導入する)
- [フォーム作成手順](#フォーム作成手順)

## vue.jsを導入する

### 1. Vue CLIのインストール

今回は**Vue2**を選んでください。

``` bash
npm install -g @vue/cli
```

### 2. 新規プロジェクトの作成

``` bash
vue create my-project
```

もし以下のようなエラーが出た場合については、こちらの記事に対処法が載っています。

` vue : このシステムではスクリプトの実行が無効になっているため、ファイル C:\Program Files\nodejs\を読み込むことができません。 `
<br>

**PowerShell vue : このシステムではスクリプトの実行が無効になっているため～ 
エラーの対処法**

https://qiita.com/kadotaoru/items/0fac22195b925a1b0a83

### 3. プロジェクトディレクトリへ移動

``` bash
cd my-project
```

### 4. BootstrapVueのインストール

``` bash
npm install bootstrap bootstrap-vue
```

### 5. ValidationProviderのインストール

``` bash
npm install vee-validate@3
```

Vue2を選択した場合、**vee-validate@3**としてバージョンを合わせます。  

### 6. 初期設定

ひと通りインストールが終わると、main.jsというディレクトリが作成されているので、以下のように設定を行います。  

``` main.js
import Vue from 'vue';
import App from './App.vue';
import { BootstrapVue, IconsPlugin } from 'bootstrap-vue';
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap-vue/dist/bootstrap-vue.css';
import { ValidationProvider } from 'vee-validate';

// BootstrapVueのプラグインを使用
Vue.use(BootstrapVue);
Vue.use(IconsPlugin);

// VeeValidateのValidationProviderを登録
Vue.component('ValidationProvider', ValidationProvider);

new Vue({
  render: h => h(App),
}).$mount('#app');
```

### 7. サーバーを立ち上げる

`http://localhost:8080/`
などで立ち上がるようになります。

``` bash
npm run serve
```

<br>
これらの手順を経て、以下の画面が出力されれば成功です。<br>
これからVueを使えるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814957/e2798fe4-f374-ba8a-9aa2-88e933e261cb.png)

## フォーム作成手順

目標は、以下の要件を達成することです。順番に行っていきましょう。 
1. **b-form-input**を出力する
2. スタイルの調整
3. **ValidationProvider**コンポーネントをグローバルに使う
4. 同期的なバリデーション
5. 日本語エラーメッセージの表示
<br>

この部分にフォームを実装していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814957/92f5e8b8-1531-6b85-f840-6d79f79f4be1.png)

### 1. b-form-inputを出力する

``` HelloWorld.vue
<template>
　<b-form-input v-model="price" placeholder="例）123など"></b-form-input>
</template>

<script>
export default {
  data() {
    return {
      price: 0
    }
  }
}
</script>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814957/be1e4e2a-98c8-b3ff-9cf1-25f4ebe32fd9.png)

**b-form-input**の使い方は簡単です。タグを置くだけ。  
v-modelを使って、入力データを動的に監視します。  

すると、大きな入力エリアができたと思います。  
次はスタイルを調整していきます。  

### 2. スタイルの調整

``` HelloWorld.vue
<template>
　<div style="padding-top: 50px;">
    <b-row class="justify-content-center">
    　<b-col cols="3">
        <b-form-input v-model="price" placeholder="例）123など"></b-form-input>
    　</b-col>
    </b-row>
　</div>
</template>

<script>
export default {
  data() {
    return {
      price: 0
    }
  }
}
</script>
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814957/75dc9c09-a22c-13bf-ebb3-73c4c6439260.png)


スタイルの調節には`b-row`と`b-col`を使います。  
colsは"12"を最大として大きさを表します。  
今回は1/4の大きさにしたかったので`cols="3"`にしました。  

また、その他に自由にスタイルを埋め込みたい場合は、**b-form-inputにclassを付けることも可能です**。  

### 3. ValidationProviderコンポーネントをグローバルに使う

グローバル（すべてのファイル）でValidationProviderが使えるように`main.js`を編集します。  

``` main.js
import { extend, ValidationProvider } from 'vee-validate';
import * as rules from 'vee-validate/dist/rules';

Object.keys(rules).forEach((rule) => {
  extend(rule, rules[rule]);
});
```

これにより、各ファイルでインポートしなくてもValidationProviderが使用できるようになります。

### 4. 同期的なバリデーション

``` HelloWorld.vue
<template>
    <div style="padding-top: 50px;">
      <p>金額入力フォーム</p>
      <ValidationProvider
        v-slot="validationContext"
        rules="required|numeric"
        :immediate="true"
      >
        <b-row class="justify-content-center">
          <b-col cols="3">
            <b-form-input 
              v-model="price" 
              placeholder="例）123など"
              :state="getValidationState(validationContext)"
            >
            </b-form-input>
          </b-col>
        </b-row>
      </ValidationProvider>
    </div>
</template>

<script>
export default {
  data() {
    return {
      price: 0
    }
  },

  methods: {
    getValidationState({ validated, valid }) {
      return validated ? valid : null;
    },
  },
}
</script>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814957/9b146361-2514-35a4-93c2-d76b9b463374.png)

同期的なバリデーションに必要な要素は以下の通りです。
- **ValidationProvider**の`v-slot`により、validationContext（バリデーションの状態）を定義する
- `rules`によってバリデーションルールを定義する。定義できるものはvee-validateに準拠している
- **b-form-input**の`state`属性を切り替えることで、エラーかそうでないかを切り替えることができる
- `immediate`属性により、フォーム送信時ではなく、入力時に即時バリデーションをするようになる

最後に、エラーメッセージを出力していきます。  

### 5. 日本語エラーメッセージの表示

``` main.js
import { extend, ValidationProvider, localize } from 'vee-validate';
import ja from 'vee-validate/dist/locale/ja';

localize('ja', ja);

localize({
  ja: {
    messages: {
      ...ja.messages,
      numeric: '{_field_}は半角数字のみ使用できます',
    }
  }
});
```

`localize`と`ja`をインポートして、日本語メッセージで表示するように設定します。  
また、messageの変更も可能で、本来「数字のみ使用できます」のところを「半角数字のみ使用できます」に変更しています。  

``` HelloWorld.vue
<template>
    <div style="padding-top: 50px;">
      <p>金額入力フォーム</p>
      <ValidationProvider
        v-slot="validationContext"
        rules="required|numeric"
        :immediate="true"
      >
        <b-row class="justify-content-center">
          <b-col cols="3">
            <b-form-input 
              v-model="price" 
              placeholder="例）123など"
              :state="getValidationState(validationContext)"
            >
            </b-form-input>
          </b-col>
        </b-row>
      </ValidationProvider>
    </div>
</template>

<script>
export default {
  data() {
    return {
      price: 0
    }
  },

  methods: {
    getValidationState({ validated, valid }) {
      return validated ? valid : null;
    },
  },
}
</script>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3814957/e9a9c06c-bc67-0599-23f9-4d6744f19342.png)

エラーメッセージの表示に必要な要素は以下の通りです。  
- **ValidationProvider**に`name`属性を付けて、フィールド名をメッセージに渡す
- **b-form-invalid-feedback**を使い、コンテキストから受け取ったエラーメッセージを表示する

以上により、同期的なバリデーションを定義することができました。  

## まとめ

個人的に「何がどこと繋がっているかわからない」という状態で非常に苦労したため、順を追って詳しくまとめてみました。  

長くなってしまいましたが、同じくVueのバリデーションにつまずいている方にとって、何か発見につながれば幸いです。