Vue.jsミニハンズオン（TODOリスト作成）

## Vue.jsミニハンズオン（TODOリスト作成）

AngularでもReactでもriot.jsでも満足できなかったひとに、ぴったりフィットなJSフレームワーク「Vue.js」のざっくりハンズオンです。

このハンズオンではnode.jsのパッケージは使わず、Google ChromeとテキストエディタがあればOKです。

Vue.jsミニハンズオンのシリーズは以下を公開しています。

1. [Vue.jsミニハンズオン（TODOリスト作成）](https://qiita.com/moonglows76/items/358ef3cd1566c38ece3a)
1. [Vue.jsミニハンズオン（TODOリストにアニメーションをつける）](https://qiita.com/moonglows76/items/d550f0243319d7111ee3)
1. [Vue.jsミニハンズオン（TODOリストをコンポーネント化する）](https://qiita.com/moonglows76/items/242aaf265672797c6800)


## 今回の目標

目標はシンプルなTODOリストの作成です。
WebStorageに保存してリロードしてもデータを保持できるようにしてます。

## Chromeの拡張機能を入れる

デベロッパーツールでデータを見やすくするためChromeの拡張機能「Vue.js devtools」を入れます。

[Vue.js devtools](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)

Vue.jsを使っているページでデベロッパーツールを表示すると、Elements, Console...の並びにVueが増えているはずです。

[CODEPEN](https://cdpn.io/moonglows76/debug/pWwEgx/mWkoNboQxxEA)

## データを表示してみる

データを単純に表示します。Vue.jsのデータ表示の基本はひとまずこれだけ。

* HTMLの内容にデータを表示するときは `{{ データの名前 }}` を使用します。
* JavaScriptで `new Vue({ el: '#id属性値', data: { データを設定 } })` でVueインスタンスをつくります。

```html:1.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>まずはデータを表示</title>
</head>
<body>

<div id="app">
  <p>{{ message }}</p>
</div>

<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script src="./app1.js"></script>

</body>
</html>
```

```JavaScript:app1.js
const vm = new Vue({
  el: '#app',
  data: {
    message: 'Hellor Vue.js!',
  }
})
```

[CODEPEN](https://codepen.io/moonglows76/pen/pWwEgx)

デベロッパーツールのVueタブをクリックして、`<Root>`と書いてあるところをクリックすると右側にデータが出てきます。
（上記のCODEPENのリンク先を見ている場合は右上の[Change View]-[Debug Mode]をクリックするとデベロッパーツールを表示したときにVueタブが表示されます。お試しあれ。）

`<Root>`の横に `$vm0` とか表示されているのを確認して（番号が違うかもです）、Consoleタブで `$vm0.message` と打つとちゃんと値がとれます。
また、`vm` という変数にVueインスタンスを入れているので `vm.message` と入れても値がとれます。

## データバインディングしてみる

ではテキストボックスに入力したデータとp要素に表示するデータをバインディングしてみましょう。データをバインディングするには対象のHTML要素に `v-model` というディレクティブを追加します。
ディレクティブはVue.jsが利用する `v-` という接頭辞が付いた特別な属性を指します。

```HTML:1.html
<div id="app">
  <input type="text" v-model="message">　<!-- 追加 -->
  <p>{{ message }}</p>
</div>
```

[CODEPEN](https://codepen.io/moonglows76/pen/GMEjrv)

これだけです。テキストボックスの中のテキストを編集するとp要素の中のテキストも同時に変わります。デベロッパーツールのVueタブの`<Root>`をクリックしたときに見えるデータも同時に変わります。

チェックボックスはinput要素に `v-model="true"` であればチェックがつき、 `v-model="false"` であればチェックがはずれます。

```HTML:1.html
<div id="app">
  <input type="text" v-model="message">
  <input type="checkbox" v-model="isChecked">　<!-- 追加 -->
  <p>{{ message }} {{ isChecked }}</p>
</div>
```
```JavaScript:app1.js
const vm = new Vue({
  el: '#app',
  data: {
    message: 'Hellor Vue.js!',
    isChecked: true  // 追加
  },
})
```

[CODEPEN](https://codepen.io/moonglows76/pen/NagRpz)

## リストを表示してみる

シンプルなtodoリストを作るのが今回の目標なのでリスト表示をしてみます。
リスト表示には対象のHTML要素に `v-for` というディレクティブを使います。

`v-for`に指定している `items` は `data` オプション内にある配列です。
`item` は配列内のデータの1セットを示す名前です。この名前はどうつけてもOKです。
配列を複数形でつけて、このデータの1セットは単数形にするのはよくある名前の付け方です。

```HTML:2.html
<div id="app">
  <ul>
    <li v-for="item in items">{{ item.title }}</li>
  </ul>
</div>
```

```JavaScript:app2.js
const vm = new Vue({
  el: '#app',
  data: {
    items: [
        { title: '項目1', id: 1 },
        { title: '項目2', id: 2 },
        { title: '項目3', id: 3 },
    ]
  },
})
```

[CODEPEN](https://codepen.io/moonglows76/pen/gGRwLG)

## クリックイベントをしかける

クリックイベントで何かしら関数が動くようにしてみます。
イベントは対象のHTML要素に `v-on:click` ディレクティブを入れます。`v-on` がイベント、 `click`はクリックイベントを示します。値には関数名やJavaScriptの文を入れることができます。

関数はJavaScriptで `methods` オプションの中に書きます。
dataオプション内のデータは `this.データ名` で参照できます。

```HTML:3.html
<div id="app">
  <p>{{ counter }}</p>
  <button v-on:click="add">追加（関数）</button>
  <button v-on:click="counter++">追加（JavaScriptの文）</button>
</div>
```

```JavaScript:app3.js
const vm = new Vue({
  el: '#app',
  data: {
    counter: 0
  },
  methods: {
    add: function(){
      this.counter++
    },
  },
})
```

[CODEPEN](https://codepen.io/moonglows76/pen/PJjGmE)


## TODOリストを作成する

おおまかに必要なことは学びました。TODOリストを作っていきましょう。
TODOリストには下記の機能があるとします。

- TODOの表示
- TODOの追加
- TODOのチェック
- チェックしたTODOの削除

### TODOの表示

まず「TODOの表示」をしましょう。label要素とチェックボックスも入れておきます。

```html:todo.html
<div id="app">
  <ul>
    <li v-for="item in items">
      <label>
        <input type="checkbox" v-model="item.isChecked"> {{ item.title }}
      </label>
    </li>
  </ul>
</div>
```
TODOリストのデータには下記の2つのプロパティを入れておきます。

* TODOリストの内容
* チェック済み判定フラグ

```JavaScript:todo.js
const vm = new Vue({
  el: '#app',
  data: {
    items: [
        { title: '領収書を準備する', isChecked: true },
        { title: 'Vue.jsハンズオンの資料を作る', isChecked: true },
        { title: '参加者の人数を確認する', isChecked: false },
        { title: 'ピザを注文する', isChecked: false },
        { title: '参加費のお釣りを準備する', isChecked: false },
        { title: '会場設営をする', isChecked: false },
    ]
  },
})
```

[CODEPEN](https://codepen.io/moonglows76/pen/veZXeZ)


テキストをクリックしてもチェックされるのがいい感じです。


### TODOの追加

TODOの追加は「テキストボックスに文字を入力してEnterキーを押したら、配列 `items` に新しく項目を追加する」という処理です。

まずはHTMLの `<div id="app">` 内に下記を追加します。

```html:todo.html
<p>
  <input type="text"
    placeholder="TODOを入力しましょう！"
    v-model="newItemTitle"
    v-on:keyup.enter="addTodo"></p>
```

入力した文字は `newItemTitle` に入ります。
そしてEnterキーを押すと `addTodo` という関数に渡されます。
ではJavaScriptを書きます。

```JavaScript:todo.js
const vm = new Vue({
  el: '#app',
  data: {
    items: [
        { title: '領収書を準備する', isChecked: true },
        { title: 'Vue.jsハンズオンの資料を作る', isChecked: true },
        { title: '参加者の人数を確認する', isChecked: false },
        { title: 'ピザを注文する', isChecked: false },
        { title: '参加費のお釣りを準備する', isChecked: false },
        { title: '会場設営をする', isChecked: false },
    ],
    newItemTitle: '' //追加
  },
  methods: {  //methodsオプションをまるっと追加
    addTodo: function(){
      this.items.push({
        title: this.newItemTitle,
        isChecked: false
      });
    },
  }
})

```

`methods` オプションで `addTodo` 関数を追加しました。配列の `push` メソッドでデータを追加しています。`title` はテキストボックスに入力された内容、`isChecked` は最初からチェックされている項目はないので、`false` を固定で入れておきます。

ブラウザでテキストボックスで文字を入力してEnterキーを押すと、リストに項目が追加されましたね。

ただし、Enterキーを押したあとに、テキストボックス内にまだ文字が残っているので文字を消しましょう。

```JavaScript:todo.js
    addTodo: function(){
      this.items.push({
        title: this.newItemTitle,
        isChecked: false
      });
      this.newItemTitle = ''; //追加
    },
```

[CODEPEN](https://codepen.io/moonglows76/pen/qPjaxa)

だいぶアプリっぽくなりましたね。

### TODOのチェック

項目がチェックされているときはlabel要素にclass属性を設定して、打ち消し線が入るようにしてみましょう。
class属性を設定するかしないかは

* `v-bind:class="done: true"` であれば `class="done"`
* `v-bind:class="done: false"` であれば `class=""`

となります。チェックをしているかどうかは `isChecked` で取れるので ``v-bind:class="done: isChecked"`と設定します。

```html:todo.html
    <li v-for="item in items">
      <label v-bind:class="{ done: item.isChecked }">
        <input type="checkbox" v-model="item.isChecked"> {{ item.title }}
      </label>
    </li>
```

head要素内に打ち消し線を表示するスタイルを追加しましょう。

```html:todo.html
  <style>
    .done { text-decoration: line-through; }
  </style>
```

[CODEPEN](https://codepen.io/moonglows76/pen/qPjawr)


### チェックしたTODOの削除

チェックしたリストがいつまでものこっているのは邪魔っぽいのでまとめて消す機能をつけてみます。

`div#app` 内に下記の `button` 要素をを追加します。

```html:todo.html
  <button v-on:click="deleteTodo()">チェック済みの項目を削除する</button>
```

`methods` オプション内に `deleteTodo` 関数を追加します。
その中で `filter` メソッドを使って配列を更新します。

```JavaScript:todo.js
    deleteTodo: function(){
      this.items = this.items.filter(function (item) {
        return item.isChecked === false; //
      });
    },
```

[CODEPEN](https://codepen.io/moonglows76/pen/NagRZJ)

これでTODOリストの基礎的な表示ができました。


## 再描画でデータがリセットされるのを防ぐ

再描画するとリストがリセットされてしまうので、データをブラウザに保存し、初期表示時に利用してみます。

### データをブラウザに保存する

データをブラウザに保存するときは `localStorage` に `items` という配列をまるごと保存します。methodsオプションに下記を追加します。

```JavaScript:todo.js
saveTodo: function(){
  localStorage.setItem('items', JSON.stringify(this.items));
},
```

`localStorage`に保存するのは `localStorage.setItem('フィールド名': 値)` で簡単に実装できます。ただし、配列のままでは入れることができず、文字列に変換する必要があるので `JSON.stringify` で変換します。

ということで項目を追加したときにブラウザに保存するため、関数を呼び出します。

```JavaScript:todo.js
    addTodo: function(){
      this.items.push({
        title: this.newItemTitle,
        isChecked: false
      });
      this.newItemTitle = '';
      this.saveTodo(); //ブラウザに保存
    },
```

```JavaScript:todo.js
    deleteTodo: function(){
      this.items = this.items.filter(function (item) {
        return item.isChecked === false; //
      });
      this.saveTodo(); //ブラウザに保存
    },
```

チェックボックスをつけたり外したりしたときにも配列の中身が変わるので、関数を呼び出します。

```html:todo.html
    <li v-for="item in items">
      <label v-bind:class="{ done: item.isChecked }">
        <!-- チェックボックスに v-on:change="saveTodo" を追加 -->
        <input type="checkbox" v-model="item.isChecked" v-on:change="saveTodo"> {{ item.title }}
      </label>
    </li>
```

保存出来ているかどうかはデベロッパーツールの `application`タブの左側にある `Storage > localStorage > file://` と探って選択したときに右側に `items` が保存されているのが見えるはずです。 

あとは初期表示時に `localStorage` から配列を読み出すようにします。


### データをブラウザから読み出す

下記をmethodsオプション内に追加します。

```JavaScript:todo.js
    loadTodo: function(){
      this.items = JSON.parse( localStorage.getItem('items') );
      if( !this.items ){
        this.items = [];
      }
    },
```

dataオプション内の配列itemsにブラウザ内のデータを持ってくるだけです。
ブラウザ内のデータは単なる文字列なので、JSON.parseで配列に整えます。
ブラウザにデータがないときは空の配列となるように `[]` を入れ込みます。

初期表示時に動く処理は `mounted` オプションで指定できるので、下記を `methods` オプションの下に追加します。

```JavaScript:todo.js
  mounted: function(){
    this.loadTodo();
  },
```

[CODEPEN](https://codepen.io/moonglows76/full/YrrvVQ)


これでリロードしてもリストがリセットされることはなくなりました。

このハンズオンでVue.jsに興味を持ったらひとまず公式サイトのガイドで手を動かしてみるのをオススメします。ちょっと長いですが、かなりわかりやすいです！

https://jp.vuejs.org/v2/guide/