Vue.jsの基本的な機能だけでTODOリストを作成する


<p class="codepen" data-height="565" data-theme-id="light" data-default-tab="html,result" data-user="tseno" data-slug-hash="MWwdLOw" style="height: 565px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Vue.js practice20200404">
  <span>See the Pen <a href="https://codepen.io/tseno/pen/MWwdLOw">
  Vue.js practice20200404</a> by tseno (<a href="https://codepen.io/tseno">@tseno</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# 解説

## チェックをつけると取り消し線がつく

- チェックをつけると`v-model="item.check"`で、dataの値が変わる。（双方向データバインディング）
- dataの`item.check`が`true`のときに、`class="checked"`が適用される。（HTML クラスのバインディング）

```vuejs:一部のみ
  <style type="text/css">
    <!--
    .checked {
      text-decoration: line-through;
    }
    -->
  </style>
<div v-for="(item, i) in items" :key="item.text">
  <label :class="{checked: item.check}">
    <input type="checkbox" v-model="item.check" />
</div>
```


## 追加ボタンは、必須入力項目を入力しないと有効化されない

- 算出プロパティ`addButtonDisabled`で、テキストボックスと日付入力が空の場合は、`disabled="true"`（追加ボタンを無効）にしている。

```vuejs:一部のみ
    <button @click="onClick" :disabled="addButtonDisabled">追加</button>

  <script>
    var vm = new Vue({
      computed: {
        addButtonDisabled: function() {
          return this.addText.length === 0 || this.addDate == null;
        }
      }
    })
  </script>
```

## 追加ボタンで追加する

- 追加ボタンをクリックすると`onClick`イベントを呼び出す。
- dataの`items`の配列に追加(push)。テキストボックスと日付入力を空にする。

```vuejs:一部のみ
    <button @click="onClick" :disabled="addButtonDisabled">追加</button>

  <script>
    var vm = new Vue({
      methods: {
        onClick: function() {
          this.items.push({
            text: this.addText,
            check: false,
            dueDate: this.addDate,
          });
          this.addText = "";
          this.addDate = null;
        }
    })
  </script>
```

## [X]ボタンで削除する

- [X]ボタンをクリックすると`onDelete(i)`イベントを呼び出す。（何番目かを引数で渡す）
- dataの`items`の配列からi番目の要素を削除(splice)。

```vuejs:一部のみ
    <button @click="onDelete(i)">X</button>

  <script>
    var vm = new Vue({
      methods: {
        onDelete: function(i) {
          this.items.splice(i, 1);
        }
    })
  </script>
```

## 追加・削除時にアニメーションする

- リスト表示部分を`transition-group`タグで囲む
- `.v-enter-active` `.v-enter-active` ゆっくり表示／非表示する時間（500ms）
- `.v-move` ゆっくり動く時間（1s）
- `.v-leave-active { position: absolute; }` これを入れないと、削除時にアニメーションしない
- `.v-enter` `.v-leave-to` 表示し始めと、消え終わりのとき、透明にする（opacity: 0.0）
- `.v-enter-to` `.v-leave` 表示し終わりと、消え始めのとき、透明にしない（opacity: 1.0）

```vuejs
<style type="text/css">
    <!--
    .v-enter-active,
    .v-leave-active {
      transition: opacity 500ms;
    }
    .v-move {
      transition: transform 1s;
    }
    .v-leave-active {
      position: absolute;
    }
    .v-enter,
    .v-leave-to {
      opacity: 0.0;
    }
    .v-enter-to,
    .v-leave {
      opacity: 1.0;
    }
    -->
  </style>
    <transition-group tag="div">
      <div v-for="(item, i) in items" :key="item.text">
        <label :class="{checked: item.check}">
          <input type="checkbox" v-model="item.check" />
          {{ item.text + '（' + item.dueDate.toLocaleDateString() + '）' }}
        </label>
        <button @click="onDelete(i)">X</button>
      </div>
    </transition-group>
```
## Element UIのカレンダーを表示させる

- Element UIをCDNから読み込む
- `ELEMENT.locale(ELEMENT.lang.ja);` でカレンダーの曜日表示が日本語になる
- `el-date-picker` タグで表示させる。`type="date"`を指定しているが、`year/month/date/dates/datetime/ week/datetimerange/daterange/monthrange`のいずれかを指定できる。

```vue

    <el-date-picker v-model="addDate" type="date" placeholder="期限">
    </el-date-picker>

 <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" />
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  <script src="http://unpkg.com/element-ui/lib/umd/locale/ja.js"></script>
  <script>
    ELEMENT.locale(ELEMENT.lang.ja);
  </script>
```


# 全体のソース

```html
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8" />
  <title>TODO List 20200404</title>
  <style type="text/css">
    <!--
    .checked {
      text-decoration: line-through;
    }

    .v-enter-active,
    .v-leave-active {
      transition: opacity 500ms;
    }

    .v-move {
      transition: transform 1s;
    }

    .v-leave-active {
      position: absolute;
    }

    .v-enter,
    .v-leave-to {
      opacity: 0.0;
    }

    .v-enter-to,
    .v-leave {
      opacity: 1.0;
    }
    -->
  </style>
</head>

<body>
  <div id="app">
    <transition-group tag="div">
      <div v-for="(item, i) in items" :key="item.text">
        <label :class="{checked: item.check}">
          <input type="checkbox" v-model="item.check" />
          {{ item.text + '（' + item.dueDate.toLocaleDateString() + '）' }}
        </label>
        <button @click="onDelete(i)">X</button>
      </div>
    </transition-group>
    <input type="text" v-model="addText" />
    <el-date-picker v-model="addDate" type="date" placeholder="期限">
    </el-date-picker>
    <button @click="onClick" :disabled="addButtonDisabled">追加</button>
  </div>
  <script src="http://unpkg.com/vue/dist/vue.js"></script>
  <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css" />
  <script src="https://unpkg.com/element-ui/lib/index.js"></script>
  <script src="http://unpkg.com/element-ui/lib/umd/locale/ja.js"></script>
  <script>
    ELEMENT.locale(ELEMENT.lang.ja);
    var vm = new Vue({
      el: "#app",
      data: {
        addText: "",
        addDate: "",
        items: [{
            text: "旅行の予約をする",
            check: true,
            dueDate: new Date(),
          },
          {
            text: "領収書をまとめる",
            check: false,
            dueDate: new Date(),
          },
          {
            text: "レンタカーの手配",
            check: false,
            dueDate: new Date(),
          },
          {
            text: "天気予報チェック",
            check: false,
            dueDate: new Date(),
          },
        ],
      },
      computed: {
        addButtonDisabled: function() {
          return this.addText.length === 0 || this.addDate == null;
        },
      },
      methods: {
        onClick: function() {
          this.items.push({
            text: this.addText,
            check: false,
            dueDate: this.addDate,
          });
          this.addText = "";
          this.addDate = null;
        },
        onDelete: function(i) {
          this.items.splice(i, 1);
        },
      },
    });
  </script>
</body>

</html>
```