Vue.jsで入力フォームの数を動的に制御する方法

###前置き
備忘録程度の投稿です。

```javascript
[
  {
    id: 1,
    title: "起床",
    description: "きっとねむい",
    date: "2019-07-11",
    time: "09:00",
  },
  {
    id: 2,
    title: "出勤",
    description: "まだねむい",
    date: "2019-07-11",
    time: "10:00",
  },
......
]
```

こんな感じの形式のオブジェクトデータを複数登録する、
なんてのはWebアプリだとよくありますよね。

参画しているプロジェクトでまさにそんな処理を書く機会があったので紹介します。

機能は下記の通り。
**1.サーバーから取得してきたデータをフォームに表示。(今回は定数ファイルで再現)**
**2.フォームを増やす、減らす**
**3.一部フォームの文字数を表示させる**

ひとまずフロント側での挙動はざっくり上記の通りです。
サーバーサイドの更新処理は、一度データを消してから送られてきたもので上書きする方法であれば、
フロント側から送信データはシンプルで済みます。

###やってみる

今回のデータは下記。

```javascript:todoList.js
const TODO_LIST = [
  {
    id: 1,
    title: "起床",
    description: "きっとねむい",
    date: "2019-07-11",
    time: "09:00",
  },
  {
    id: 2,
    title: "出勤",
    description: "まだねむい",
    date: "2019-07-11",
    time: "10:00",
  }
];

export default TODO_LIST;
```
初期表示は二つフォームが出てればいいわけですね。

###完成品
出来上がったものがこちらです。
![FireShot Capture 001 - form_handling - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469924/5b1db747-345b-de2b-3c15-bd117e22e5eb.png)

各フォームに削除ボタン、
一番下に追加ボタン、
タイトルと説明には文字数カウントを表示しております。

コードはこちら

```vue:FormHandling.vue
<template>
  <div class="body">
    <div v-for="(todo, index) in todoList" class="form">
      <div class="form-component">
        <div class="form-component_title">
          日付
        </div>
        <div class="form-component_content">
          <input type="date" v-model="todo.date">
        </div>
      </div>
      <div class="form-component">
        <div class="form-component_title">
          時間
        </div>
        <div class="form-component_content">
          <input type="time" v-model="todo.time">
        </div>
      </div>
      <div class="form-component">
        <div class="form-component_title">
          タイトル
        </div>
        <div class="form-component_content">
          <input type="text" v-model="todo.title">
          {{ todo.title.length }} / 30
        </div>
      </div>
      <div class="form-component">
        <div class="form-component_title">
          説明
        </div>
        <div class="form-component_content">
          <textarea v-model="todo.description"></textarea>
          {{ todo.description.length }} / 30
        </div>
      </div>
      <div class="commands">
        <button v-on:click="deleteForm(index)">削除</button>
      </div>
    </div>
    <div class="commands">
      <button v-on:click="addForm">追加</button>
    </div>
  </div>
</template>

<script>
import TODO_LIST from '../data/todoList';

export default {
  mixins: [TODO_LIST],
  data () {
    return {
      todoList: [], //todoリスト
    }
  },
  created() {
    this.todoList = TODO_LIST;
  },
  methods: {
    addForm() {
      const additionalForm = {
        title: "",
        description: "",
        date: "",
        time: "",
      }
      this.todoList.push(additionalForm);
    },
    deleteForm(id) {
      this.todoList.splice(id, 1);
    }
  }
}
</script>
```

見栄え上、適当なスタイルを当てるためにクラス名を振ってますが悪しからず。
やってることは主に下記の通り

**1.フォームデータを宣言**

```javascript
  created() {
    this.todoList = TODO_LIST;
  },
```
created内で、importしているTODO_LISTをdataに入れ込んでます。
今回はファイルからですが、だいたいここはapiとかでデータ取得してくると思います。
apiを使用する際も、mountedとかじゃなく、createdに書くのが正しいっぽいです。

**2.追加ボタン**

```javascript
    addForm() {
      const additionalForm = {
        title: "",
        description: "",
        date: "",
        time: "",
      }
      this.todoList.push(additionalForm);
    },
```
データから取得してきた形式と同じものを生成します。
idは、画面的に不要だったので除外。

押してみると...

![FireShot Capture 003 - form_handling - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469924/38439481-3626-7b09-ea07-4a90cb04688d.png)

ちゃんと空のフォームが一個追加されましたね。


**3.削除ボタン**

```javascript
    deleteForm(id) {
      this.todoList.splice(id, 1);
    }
```
削除はv-forで生成したindexを返して、その値でspliceを行っています。
v-forで値を監視しているので、即座に画面上でも反映、これ以外の処理は不要です。

**4.文字数カウント**

```vue
{{ todo.title.length }} / 30
```
これは非常にシンプル。
lengthとか使えるのは非常に便利ですね、さすがVue。

**注意事項**：今回は、定数ファイルや、新しく生成されるフォームデータは基本的に**空文字**ですが、
サーバーから取得する値が**NULL**の場合、**エラーになります。**

```vue
{{ todo.description ? todo.description.length : '0' }} / 30
```
用心のため、こんな感じの記述のほうが安心ですね


あとは全部DOMで処理。
v-modelで値を入れてるので、書き換える都度更新されてます。

###まとめと振り返り
当初、こういう処理をVueで書くとなってから
ggってもなかなか出てこなかったので、
自分でいろいろやってみたら、意外と簡単にできたので書いてみました。

ほかにはどういう方法があるんですかね、データを登録するところまでを考えると
コード量も大して多くないので悪くない気もします。

実際のプロジェクトだと、日付のフォーマットがYYYY/MM/DDだったりで、変換をはさんだりして
手間がかかりました。
(時間系のフォームってフォーマット指定したいですよね、そもそも表示はYYYY/MM/DDなのに、valueはYYYY-MM-DDなのがちょっとよくわからない...)