Vue.js 編 - 初心者こそ、Docker を使おう！　

## 簡単なTodo アプリを作りましょう
* オリジナルはdotinstall さんの vue.js のtodoアプリをアレンジしたものですので詳しくはそちらをみてください。

## 今回は、Vue.js を使って、Todoに必要な処理の練習をします。

### Todo タスクを追加してみましょう。
`lang`指定で`ts`,`stylus` を指定していますが、`typescript`, `stylus css` 用ですが、実際つかっているのは、`javascript`, `css` です。　`stylus`にすると、混在できますので`stylus` を使っています。　`typescript` の混在できますので、型つけたければつければいいかと思います。

Vue.js は、input周りは、簡単に処理できるのがいいですね。

`src/MyApp.vue`

```ts
<template lang="pug">
  div.container
    Todo
</template>
<script lang="ts">
import Vue from 'vue';
import  Hello  from './Hello.vue';
import Todo from './Todo.vue';
export default Vue.extend({
  components: {
    Todo,
  }
})
</script>
<style lang="stylus">
body {
  font-size: 16px;
  font-family: Verdana, Geneva, Tahoma, sans-serif;
}
.container {
  width: 300px;
  margin: auto;
}
h1 {
  font-size: 16px;
  border-bottom: 1px solid #ddd;
  padding: 16px 0;
}
li {
  line-height: 1.5;
}
input[type="text"] {
  padding: 2px;
}

.done
  color gray
  text-decoration line-through


</style>

```

`src/Todo.vue`

```ts
<template lang="pug">
  div
    ul
      li(v-for="todo in todos")
        | {{ todo }}
    form( @submit.prevent="addItem" )
      input(type="text" v-model="newItem")
      input(type="submit" value="Add")

</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  data() {
    return({
      newItem: '',
      todos: [
        "task 0",
        "task 1",
        "task 2",
      ]
    })
  },
  methods: {
    addItem() {
      this.todos.push(this.newItem);
      this.newItem = '';
    }
  }
})
</script>
```


* コンテナ(uname  linux でしたね)

```
uname
cd src
npx parcel --hmr-port 1235 --hmr-hostname localhost index.pug
```


## delete / checkbox
削除もチェックボックスも実装が楽ですね。
`id` 追加しましたが、使っていません(笑) index指定で削除していますが、まずければ　`id` に変えようと思いましたが、問題なさそうなので、使っていません。

`src/Todo.vue`

```ts
<template lang="pug">
  div
    ul( v-if="todos.length" )
      li(v-for="todo, index in todos")
        input(type="checkbox" v-model="todo.isDone")
        span( :class="{done: todo.isDone }" )  {{ todo.title }} : {{ todo.isDone }}
        button( @click="deleteItem(index)" ) Delete
    ul( v-else )
      | Nothing todo
    form( @submit.prevent="addItem" )
      input(type="text" v-model="newItem")
      input(type="submit" value="Add")

</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  data() {
    return({
      newItem: '',
      todos: [
        { id: 0, title: 'Task 0', isDone: false },
        { id: 1, title: 'Task 1', isDone: false },
        { id: 2, title: 'Task 2', isDone: true },
      ]
    })
  },
  methods: {
    addItem() {
      const newItem = {
        id: new Date().getTime().toString(36),
        title: this.newItem,
        isDone: false,
      }
      this.todos.push(newItem);
      this.newItem = '';
      console.log(newItem);

    },
    deleteItem(index) {
      this.todos.splice(index, 1)
    }
  }
})
</script>

```

## 複数削除
完了していないタスクは、computed で常に集計できるので、それを新しい配列にすれば、結果的に完了タスクを削除したことになりますね。

`Todo.vue`

```ts
<template lang="pug">
  div
    h1
      | todo
      span ( {{ remaining.length }} : {{ todos.length }} )
      button( @click="doneItemsPurge" ) Purge
    ul( v-if="todos.length" )
      li(v-for="todo, index in todos")
        input(type="checkbox" v-model="todo.isDone")
        span( :class="{done: todo.isDone }" )  {{ todo.title }} : {{ todo.isDone }}
        button( @click="deleteItem(index)" ) Delete
    ul( v-else )
      | Nothing todo
    form( @submit.prevent="addItem" )
      input(type="text" v-model="newItem")
      input(type="submit" value="Add")

</template>
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
  data() {
    return({
      newItem: '',
      todos: [
        { id: 0, title: 'Task 0', isDone: false },
        { id: 1, title: 'Task 1', isDone: false },
        { id: 2, title: 'Task 2', isDone: true },
      ]
    })
  },
  methods: {
    doneItemsPurge() {
      this.todos = this.remaining
    },
    addItem() {
      const newItem = {
        id: new Date().getTime().toString(36),
        title: this.newItem,
        isDone: false,
      }
      this.todos.push(newItem);
      this.newItem = '';
      console.log(newItem);
    },
    deleteItem(index) {
      this.todos.splice(index, 1)
    }
  },
  computed: {
    remaining() {
      return this.todos.filter( todo => {
        return !todo.isDone
      })
    }
  }

})
</script>
```

## データの永続化
初心者むけでは大体 `localStorage` を使う例が多いので、 ここでは、`Firebase` の DB `FireStore` を使ってみようかと思います。

`FireStore db` は、`googole` のアカウントがあれば、利用できますので、`firebase console` で検索してみてください。　ここでは、プロジェクトの作成や`fireStore`の有効にする方法は説明しませんので、利用出来る前提で始めていきます。
fireStore の　読み書きルール　はすべて true の　テストモードで行っています。

* ファイル構成です。

```

├── Dockerfile
├── docker-compose.yml
├── firebase.js
└── src
    ├── Hello.vue
    ├── MyApp.vue
    ├── Todo.vue
    ├── dist
    ├── index.js
    ├── index.pug
    ├── package.json
    └── yarn.lock
```

ルードディレクトリに `firebase.js` を作っていますので、そこに`firebase` の　`config`をコピペしてください。

`firebase.js`

```js
export const config = {
  apiKey: "",
  authDomain: "",
  databaseURL: "",
  projectId: "",
  storageBucket: "",
  messagingSenderId: "",
  appId: "",
  measurementId: ""
};
```

MyApp.vue はほとんど変わっていませんが一応記載します。

`src/MyApp.vue`

```ts
<template lang="pug">
  div.container
    //- Hello
    Todo
</template>
<script lang="ts">

import Vue from 'vue';
import  Hello  from './Hello.vue';
import Todo from './Todo.vue';
export default Vue.extend({
  components: {
    Todo,
    Hello,
  }
})
</script>
<style lang="stylus">
body {
  font-size: 16px;
  font-family: Verdana, Geneva, Tahoma, sans-serif;
}
.container {
  width: 700px;
  margin: auto;
}
h1 {
  font-size: 16px;
  border-bottom: 1px solid #ddd;
  padding: 16px 0;
}
li {
  line-height: 1.5;
}
input[type="text"] {
  padding: 2px;
}

.done
  color gray
  text-decoration line-through


</style>
```

Todo.vue は、`fireStore`対応で大幅に変わっています。
リファクタリングもしていないので、見苦しい感じが残っていますが、実装の苦労の跡が見えていいのではないでしょうか(笑)
なるべく、fireStoreの機能を使って実装しようとしているので、データのやり取りは、fireStoreを毎回介して行っています。
マウントとアンマウントの時に`fireStore` にデータを渡したほうが動作が軽く楽かもしれませんが
`fireStore`の練習には、こちらのほうがいいかと思います.

ラグが気になりますが、追加、削除、完了、未完了、一括削除ができているのOK でしょう。(笑)

### async / await に慣れていない人へ

DB を使うと処理が非同期になるため、async / await を使っていますが、asyce/await に慣れていない人に一言いうと、処理に時間がかかりそうだと思うものの前に,すべて`await` を　付けていけばいいです。
今回で言えば、データの読み書きで `firestore` に対してのと命令を書いた場合にあたります。
付けていないと、ほしいデータが入る前に次の処理が走ってしまって、空処理になるのを防いでいます。
ですから、時間がかかると思ったら、`await` を処理の前につけてください。
`await`　を使うとき、親要素に `async` という呪文が必要なので、これを関数式に付けます。

`await`をつけたけれど、親要素がない場合は、そのあたりの処理一体を、関数にまとめてしまえばいいです。
そうすれば、非同期処理はできているはずです。



`src/Todo.vue`

```ts
<template lang="pug">
  div
    h1
      | todo
      span ( {{ remaining.length }} : {{ todos.length }} )
      button( @click="doneItemsPurge" ) Purge

    form( @submit.prevent="addItem" )
      input(type="text" v-model="newItem")
      input(type="submit" value="Add")

    ul( v-if="todos.length" )
      li(v-for="todo, index in todos")
        //- input(type="checkbox" v-model="todo.isDone")
        input(type="checkbox" @click="checkboxChange(index)" :checked="todo.isDone")
        span( :class="{done: todo.isDone }" )  {{ todo.title }} : {{ todo.isDone }} : {{ todo.date }}
        button( @click="deleteItem(index)" ) Delete
    ul( v-else )
      | Nothing todo

</template>
<script lang="ts">

import firebase from 'firebase/app';
import 'firebase/firestore';
import { config } from '../firebase';

firebase.initializeApp(config);
const db = firebase.firestore()
const collection = db.collection('todos')

import Vue from 'vue'
export default Vue.extend({
  data() {
    return({
      newItem: '',
      todos: [],
      itemsId: [],
      storeItem: {},
      state: [],
    })
  },
  watch: {
   state: { // 更新チェック されたら、実行
     handler: async function(){
        const queryItems = await collection.orderBy("created_at", "desc").get()
        const qureyItemsData = queryItems.docs.map( item => item.data())
        const qureyItemsId = queryItems.docs.map( item => item.id )

        this.todos = qureyItemsData;
        this.itemsId = qureyItemsId;
    }, deep: true
   },
   todos: {
     handler: async function(){
    }, deep: true
   }
  },
  methods: {
    async checkboxChange(index) {
      let isDoneState = Boolean;
      const item = await collection.doc(this.itemsId[index])
      const isDone = await item.get()
        .then(function(doc) {
          isDoneState = doc.data().isDone
        })
        .catch( err => {
          console.log(err);
        })
      const changeItem = await item.update({
        isDone: !isDoneState,
      })
      this.state = ["checkboxUpdate"]

    },
    async deleteItem(index) {
      const item = await collection.doc(this.itemsId[index]).delete()
      this.state = ["delete"]
    },
    addItem() {
      this.storeItem = {
        id: new Date().getTime(),
        date: new Date().toLocaleString("ja"),
        title: this.newItem,
        isDone: false,
        created_at: firebase.firestore.FieldValue.serverTimestamp(),
      }
      const item = collection.add(this.storeItem);
      this.newItem = '';
      this.state = ["addItem"] // 状態更新通知用
    },
    async doneItemsPurge() {
      // this.todos = this.remaining

      const itemsQuery = await collection.where("isDone", "==" , true)
        .get()
        .then(function(querySnapshot) {
          querySnapshot.forEach(function(doc) {
            const item = collection.doc(doc.id).delete();
            console.log(item);
          });
        })
        .catch(function(error) {
          console.log("Error getting documents: ", error);
        })

      this.state = ["purge"] // 状態更新通知用
    },
  },
  computed: {
    remaining() {
      return this.todos.filter( todo => {
        return !todo.isDone
      })
    }
  },
  async mounted() {
    const query =  await collection.get()
    const items = query.docs.map( item => item.data())
    const itemsId = query.docs.map( item => item.id)
    this.todos = items;
    this.itemsId = itemsId;
    this.state =  items;
  }

})
</script>
```

これで、Vue.js での練習は、終了です。
お疲れ様でした。

次回は, この環境で、React を使ってみたいと思います