【Vue × Firebase】Tollero風タスク管理アプリの作成

#Header と Home コンポーネントの作成
まずは見た目を整えるために、HeaderとBordコンポーネントの作成、
App.vueを編集していきます。

Header コンポーネントは、src/components/にHeader.vueとして作成しましょう。 
以下のものをコピペしてもらって大丈夫です。


```vuejs:Header.vue
<template>
  <header class="header">
    <h1 class="header__ttl">タスク管理アプリ</h1>
    <div class="header__menu">
      <router-link to="/" class="header__link">Home</router-link>
      <router-link to="/about" class="header__link">About</router-link>
    </div>
  </header>
</template>

<style lang="scss" scoped>
.header {
  position: sticky;
  top: 0;
  background-color: #1876d1;
  padding: 1rem 3rem;
  z-index: 999;
  display: flex;
  align-items: center;
  &__ttl {
    color: #fff;
  }
  &__menu {
    margin-left: auto;
  }
  &__link {
    color: #fff;
    text-decoration: none;
    background-color: transparent;
    border: none;
    outline: none;
    font-size: 1rem;
    &:not(:first-child) {
      margin-left: 2rem;
    }
    &:hover {
      color: #bbb;
    }
  }
}
</style>
```

続いて、Bordコンポーネントはsrc/views/にBord.vueとして作成しましょう。


```vuejs:Bord.vue
<template>
  <div class="bord">
    <h2 class="bord__ttl">カード一覧</h2>
  </div>
</template>

<style lang="scss" scoped>
  .bord {
    padding-top: 2rem;
    background-image: url("../assets/bg.jpg");
    background-size: cover;
    background-position: center;
    &__wrap {
      overflow-x: scroll;
      height: calc(100vh - 11rem);
      padding: 0 2rem;
      margin-top: 2rem;
    }
    &__cards {
      display: inline-flex;
      align-items: flex-start;
    }
    &__ttl {
      margin-left: 2rem;
      color: #ffffff;
    }
  }
</style>
```

背景画像として使うので、src/assets/に、以下の画像を保存してください。
ファイル名は、**bg.jpg**としておきます。

App.vueは以下のように作成してください。

```vuejs:App.vue
<template>
  <div id="app">
    <Header />
    <router-view />
  </div>
</template>

<script>
import Header from "@/components/Header";

export default {
  components: {
    Header,
  },
};
</script>

<style lang="scss">
*,
*::before,
*::after {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
</style>
```

続いてrouter を編集していきます。

プロジェクトを作成したままだと以下のようになっているかと思います。

```javascript:router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Home",
    component: Home,
  },
  {
    path: "/about",
    name: "About",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue"),
  },
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes,
});

export default router;
```
Home.vueが呼ばれているところを、Bord.vueが呼び出されるように変更しましょう。

```javascript:router/index.js
import Vue from "vue";
import VueRouter from "vue-router";
import Bord from "../views/Bord.vue"; // 変更

Vue.use(VueRouter);

const routes = [
  {
    path: "/",
    name: "Bord", // 変更
    component: Bord, // 変更
  },
  {
    path: "/about",
    name: "About",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/About.vue"),
  },
];

const router = new VueRouter({
  mode: "history",
  base: process.env.BASE_URL,
  routes,
});

export default router;
```

ここまでできたら、一度ページを確認してみましょう。

以下のようになっていれば大丈夫です。

![Gha56G.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/c4f323c3-6f45-0650-4ff8-201942ef613d.png)

#List コンポーネントの作成
続いて、List コンポーネントを作成していきます。

List コンポーネントは以下のように作成してください。

```vuejs:List.vue
<template>
  <div class="list">
    <div class="list__header">
      <h3 class="list__ttl">{{ list.title }}</h3>
      <button class="list__delete">×</button>
    </div>
    <div class="list__content"></div>
  </div>
</template>

<script>
  export default {
    props: {
      list: {
        type: Object,
        required: true,
      },
    },
  };
</script>

<style lang="scss" scoped>
  .list {
    position: relative;
    border: 1px solid;
    border-color: #aab4c4;
    background-color: #ebecf0;
    width: 24rem;
    padding: 1rem;
    border-radius: 0.5rem;
    &:not(:first-child) {
      margin-left: 2rem;
    }
    &__ttl {
      font-size: 1.5rem;
    }
    &__delete {
      position: absolute;
      right: 10px;
      top: 16px;
      border: none;
      background-color: red;
      border-radius: 50%;
      color: #fff;
      width: 1rem;
      height: 1rem;
    }
    &__content {
      margin-top: 2rem;
    }
  }
</style>
```

props で、オブジェクト型の list という変数を受け取っています。

typeは props で受け取る型を指定しています。また、requiredでは、必須かどうかを指定しています。

```javascript:List.vue
props: {
  list: {
    type: Object,
    required: true,
  }
},
```

次に、コンポーネントを呼び出す前に、表示を確認するために、store に仮でデータを入れておきましょう。

Card はまだ表示できませんが、今後使うことになるので、入れておきます。

```javascript:store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    lists: [
      {
        title: "リスト1",
        cards: ["カード１", "カード２", "カード３"],
      },
      {
        title: "リスト2",
        cards: ["カード１", "カード２"],
      },
      {
        title: "リスト3",
        cards: [],
      },
    ],
  },
  mutations: {},
  actions: {},
  modules: {},
});
```

次に、List コンポーネントを Bord コンポーネントから呼び出してみます。 コンポーネントの呼び出しは以下のようになります。

```vuejs:Bord.vue
(省略)

<script>
  import List from "@/components/List";
  export default {
    components: {
      List,
    },
  };
</script>

(省略)
```

また、store からのデータを受け取るために、computedを以下のように定義しましょう。

```vuejs:
Bord.vue
(省略)

<script>
  import List from "@/components/List";
  export default {
    components: {
      List,
    },
    // 追加
    computed: {
      lists() {
        return this.$store.state.lists;
      }
    },
  }
</script>

(省略)
```
store から受け取った配列を、v-forで表示させていきます。

```
<template>
  <div class="bord">
    <h2 class="bord__ttl">カード一覧</h2>
    <div class="bord__wrap">
      <div class="bord__cards">
        <List v-for="(list, index) in lists" :list="list" :key="list.id" />
      <div>
    </div>
  </div>
</template>

(省略)
```

:list="list"で、list コンポーネントに list を受け渡しています。

:key="list.id"で、キーを指定しています。

ここまでできたら表示を確認してみましょう。

以下のようになっていれば大丈夫です。
![s49HaA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/7f9c20a1-3efe-b683-990f-afaeb7eeb19c.png)

#ListCreate コンポーネントの作成
次に、リストを作成する ListCreate コンポーネントを作成していきます。

まずは、ListCreate.vueとして、以下のように作成してください。

```vuejs:ListCreate.vue
<template>
  <form class="list-create">
    <input type="text" class="list-create__input" />
    <button type="submit" class="list-create__button">
      リストを追加
    </button>
  </form>
</template>

<style lang="scss" scoped>
  .list-create {
    $this: &;
    margin-left: 2rem;
    width: 19rem;
    border-radius: 0.5rem;
    padding: 0.5rem 0.5rem 0;
    background-color: #ebecf0;
    padding-bottom: 0.5rem;
    &__input {
      border-radius: 0.5rem;
      width: 18rem;
      backdrop-filter: blur(10px);
      border: none;
      font-size: 1rem;
      padding: 0.75rem 1rem;
      outline: none;
      background-color: #fff;
      color: #333;
      font-weight: bold;
      &::placeholder {
        color: #5e6d83;
      }
    }
    &__button {
      border: none;
      background-color: #59ac44;
      padding: 0.5rem 1rem;
      border-radius: 0.5rem;
      margin-top: 0.5rem;
      color: #fff;
    }
  }
</style>
```

これを Bord コンポーネントから呼び出します。

```vuejs:Bord.vue
<template>
  <div class="bord">
    <h2 class="bord__ttl">カード一覧</h2>
    <div class="bord__wrap">
      <div class="bord__cards">
        <List
          v-for="(list, index) in lists"
          :list="list"
          :listIndex="index"
          :key="list.id"
        />
        <ListCreate />!-- 追加 -->
      </div>
    </div>
  </div>
</template>

<script>
  import List from "@/components/List";
  import ListCreate from "@/components/ListCreate"; // 追加

  export default {
    components: {
      List,
      ListCreate, // 追加
    },
    computed: {
      lists() {
        return this.$store.state.lists;
      },
    },
  };
</script>
```

これを表示すると以下のようになるかと思います。
![alsdn.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/24b26490-ce01-0d96-2fe0-80ec61697d0e.png)


#リストの追加機能
続いて、リストの追加機能を実装していきます。

まずは、新しく追加するタスクの名前を格納するために、titleを用意しましょう。

```vuejs:ListCreate.vue
<template>
  <form class="list-create">
    <input type="text" class="list-create__input" v-model="title" />
    <button type="submit" class="list-create__button">
      リストを追加
    </button>
  </form>
</template>

<script>
  export default {
    // 追加
    data() {
      return {
        title: "",
      };
    },
  };
</script>

(省略)
```
次に、フォームと紐付けるために、v-modelで、titleを指定します。

```vuejs:ListCreate.vue
<template>
  <form class="list-create">
    <input type="text" class="list-create__input" v-model="title" /> !-- 変更
    -->
    <button type="submit" class="list-create__button">
      リストを追加
    </button>
  </form>
</template>

<script>
  export default {
    data() {
      return {
        title: "",
      };
    },
  };
</script>
```

次に、リストを追加するアクションを用意しましょう。

```vuejs:ListCreate.vue
<template>
  <form class="list-create" @submit.prevent="addList"><!-- 変更 -->
    <input type="text" class="list-create__input" v-model="title" />
    <button type="submit" class="list-create__button">
      リストを追加
    </button>
  </form>
</template>

<script>
  export default {
    data() {
      return {
        title: "",
      };
    },
    // 以下追加
    methods: {
      addList() {
        this.$store.dispatch("addList", this.title);
        this.title = "";
      },
    },
  };
</script>

(省略)
```

次に、リストを追加するアクションとミューテーションを、定義していきます。

```javascript:store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    lists: [
      {
        title: "リスト1",
        cards: ["カード１", "カード２", "カード３"],
      },
      {
        title: "リスト2",
        cards: ["カード１", "カード２"],
      },
      {
        title: "リスト3",
        cards: [],
      },
    ],
  },
  mutations: {
    // 追加
    addList(state, title) {
      state.lists.push({ title: title });
    },
  },
  actions: {
    // 追加
    addList({ commit }, title) {
      commit("addList", title);
    },
  },
  modules: {},
});
```
ここまでできたら、以下のようにカードを追加できるようになっているかと思います。

![s49HaA.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/173a9afb-34b1-67da-bf18-c9c9cde396d4.gif)


続いて、フォームの状態に応じて、見た目が変わるようにしましょう。

- 何もしていない状態
- フォームを入力中の場合
- フォームに文字が入っている場合

以下のようにフォームの見た目を変化させていきます。

![fK88YU.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/c0e85233-32c8-8fda-9ab9-a8db8063630c.gif)

CSSを少し修正するので、ListCreate.vueのstyleタグの中身を以下のもので上書きしてください。

```css
(省略)

<style lang="scss" scoped>
.list-create {
  $this: &;
  margin-left: 2rem;
  width: 19rem;
  padding: 0 0.5rem;
  border-radius: 0.5rem;
  &__input {
    border-radius: 0.5rem;
    width: 18rem;
    backdrop-filter: blur(10px);
    background-color: rgba(#1876d0, 0.5);
    border: none;
    font-size: 1rem;
    padding: 0.75rem 1rem;
    outline: none;
    &::placeholder {
      color: #043979;
    }
  }
  &__button {
    border: none;
    background-color: #59ac44;
    padding: 0.5rem 1rem;
    border-radius: 0.5rem;
    margin-top: 0.5rem;
    color: #fff;
  }
  &.active {
    background-color: #ebecf0;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
    #{$this}__input {
      color: #333;
      background-color: #fff;
      font-weight: bold;
      &::placeholder {
        color: #5e6d83;
      }
    }
  }
}
</style>
```

まずは、フォームの状態を管理するための、isActiveという変数を追加します。

```vuejs:ListCreate.vue
<template>
  <form class="list-create" @submit.prevent="addList">
    <input type="text" class="list-create__input" v-model="title" />
    <button type="submit" class="list-create__button">リストを追加</button>
  </form>
</template>

<script>
  export default {
    data() {
      return {
        title: "",
        isActive: false, // 追加
      };
    },
    methods: {
      addList() {
        this.$store.dispatch("addList", this.title);
        this.title = "";
      },
    },
  };
</script>
```

次に、要素がフォーカスした時、フォーカスが外れた時のイベントを追加しましょう。

フォーカスした時、フォーカスが外れた時のイベントはそれぞれ、focusin、focusoutで取得できます。

なので、それぞれに対応するメソッドを追加していきます。

```vuejs:ListCreate.vue
<template>
  <form class="list-create" @submit.prevent="addList">
    <input
      type="text"
      class="list-create__input"
      v-model="title"
      @focusin="focusIn"
      @focusout="focusOut"
    />
    <button type="submit" class="list-create__button">リストを追加</button>
  </form>
</template>

(省略)
```

それぞれのメソッドを定義していきます。

```vuejs:ListCreate.vue
<script>
  export default {
    data() {
      return {
        title: "",
        isActive: false,
      };
    },
    methods: {
      addList() {
        this.$store.dispatch("addList", this.title);
        this.title = "";
      },
      // 追加
      focusIn() {
        this.isActive = true;
      },
      // 追加
      focusOut() {
        this.isActive = false;
      },
    },
  };
</script>
```

みて分かる通り、focusInでは、isActive を true にし、focusOutでは、isActive を false にしています。

この isActive に応じて、クラスのつけ外しを実装しましょう。

```vuejs:ListCreate.vue
<template>
  <form
    class="list-create"
    @submit.prevent="addList"
    :class="isActive ? 'active' : ''"
  >
    <input
      type="text"
      class="list-create__input"
      v-model="title"
      @focusin="focusIn"
      @focusout="focusOut"
    />
    <button v-if="isActive" type="submit" class="list-create__button">
      リストを追加
    </button>
  </form>
</template>

(省略)
```

button 要素に関しては、v-ifで、表示を分けています。

form タグに、:class="isActive ? 'active' : ''"というのを追加しました。これは、三項演算子といい、条件に応じて、二つの値を出し分けることが出来ます。

activeが true なら、active というクラスを追加し、false なら何も追加しません。

ここまで出来たら以下のように動作するはずです。
![asdOwq9.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/dfc27716-5db3-9734-e8f7-d6980d6e3c5c.gif)


次に、状態に応じて、placeholder が変化するようにしましょう。

フォーカスしていない時は、「リストを追加」と表示し、フォーカスしている時は、「リストのタイトルを入力」と変化します。

まずは、placeholderという変数を用意します。初期値は、新しいリストを追加としましょう。

```javascript:ListCreate.vue
(省略)

  data() {
    return {
      title: "",
      isActive: false,
      placeholder: "新しいリストを追加" // 追加
    };
  },

(省略)
```

次に、それぞれのメソッドを以下のように変更しましょう。

``````javascript:ListCreate.vue
export default {
  data() {
    return {
      title: "",
      isActive: false,
      placeholder: "新しいリストを追加",
    };
  },
  methods: {
    addList() {
      this.$store.dispatch("addList", this.title);
      this.title = "";
    },
    focusIn() {
      this.isActive = true;
      this.placeholder = "リストのタイトルを入力..."; // 追加
    },
    focusOut() {
      this.isActive = false;
      this.placeholder = "新しいリストを追加"; // 追加
    },
  },
};
```

これを表示するように修正します。

```vuejs:ListCreate.vue
<template>
  <form
    class="list-create"
    @submit.prevent="addList"
    :class="isActive ? 'active' : ''"
  >
    <input
      type="text"
      class="list-create__input"
      v-model="title"
      @focusin="focusIn"
      @focusout="focusOut"
      :placeholder="placeholder"
    />
    <button v-if="isActive" type="submit" class="list-create__button">
      リストを追加
    </button>
  </form>
</template>

(省略)
```

ここまで出来たら、以下のように動作します。

![HJHJJ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/3142f9de-6a87-74e6-78d3-17223898a9c1.gif)

ただこのままだと文字の入力中にフォーカスが外れると、以下のようになってしまいます。

![Lissst.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/3f6aaf05-187e-4369-d763-dde1ad78b936.gif)

文字が入力中かどうかを判定するメソッドも追加しましょう。

```vuejs:ListCreate.vue
(省略)

<script>
export default {
  data() {
    return {
      title: "",
      isActive: false,
      placeholder: "新しいリストを追加"
    };
  },
  methods: {
    addList() {
      this.$store.dispatch("addList", this.title);
      this.title = "";
    },
    focusIn() {
      this.isActive = true;
      this.placeholder = "リストのタイトルを入力...";
    },
    focusOut() {
      this.isActive = false;
      this.placeholder = "新しいリストを追加";
    }
  },
  // 追加
  computed: {
    bodyExists() {
      return this.title.length > 0;
    }
  }
};

(省略)
```

次にそれぞれ条件式を以下のように書き換えましょう。

```vuejs:ListCreate.vue
<template>
  <form
    class="list-create"
    @submit.prevent="addList"
    :class="isActive || bodyExists ? 'active' : ''"
  >
    <input
      type="text"
      class="list-create__input"
      v-model="title"
      @focusin="focusIn"
      @focusout="focusOut"
      :placeholder="placeholder"
    />
    <button
      v-if="isActive || bodyExists"
      type="submit"
      class="list-create__button"
    >
      リストを追加
    </button>
  </form>
</template>
```

これで、以下のように動作するはずです。

![ghghasd.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/b5a51d43-8b28-c529-8403-a41cb392f1ee.gif)

#List の削除機能の作成
続いて、リストの削除機能を作成していきます。

List コンポーネントに、削除用のメソッドを用意します。

また、削除ボタンに、メソッドを呼び出すイベントを定義しましょう。

```vuejs:List.vue
<template>
  <div class="list">
    <div class="list__header">
      <h3 class="list__ttl">{{ list.title }}</h3>
      <button class="list__delete" @click="removeList">×</button>!-- 追加
    </div>
    <div class="list__content"></div>
  </div>
</template>

<script>
  export default {
    props: {
      list: {
        type: Object,
        required: true,
      },
      listIndex: {
        type: Number,
        required: true,
      },
    },
    // 以下追加
    methods: {
      removeList() {
        if (confirm("リストを削除しますか？")) {
          this.$store.dispatch("removeList", {
            listIndex: this.listIndex,
          });
        }
      },
    },
  };
</script>
```

confirm で「はい」「いいえ」の選択肢があるダイアログを出します。

confirmはダイアログで、「はい」が選択されてた時は、trueを、「いいえ」が選択された時は、falseを返します。

「はい」が選択された時に、removeListアクションが呼ばれます。 引数として、List の index 番号を渡しています。

次に、リストを削除するアクションとミューテーションを、定義していきます。

```javascript:store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    lists: [
      {
        title: "リスト1",
        cards: ["カード１", "カード２", "カード３"],
      },
      {
        title: "リスト2",
        cards: ["カード１", "カード２"],
      },
      {
        title: "リスト3",
        cards: [],
      },
    ],
  },
  mutations: {
    addList(state, title) {
      state.lists.push({ title: title });
    },
    // 追加
    removeList(state, payload) {
      state.lists.splice(payload.listIndex, 1);
    },
  },
  actions: {
    addList({ commit }, title) {
      commit("addList", title);
    },
    // 追加
    removeList({ commit }, payload) {
      commit("removeList", payload);
    },
  },
  modules: {},
});
```

アクションでは、受け取った index 番号をpayloadとして受け取り、ミューテーションに渡しています。

ミューテーションでは、受け取った index 番号を元に、splice メソッドで、対象の配列を削除しています。

ここまでできたら、リストの削除ができるようになっているかと思います。

![Hfb56i.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/8645671f-0c19-23e6-d5b8-11a09ede85aa.gif)

Card コンポーネントの作成
続いて、Card コンポーネントを作成していきます。

まずは以下のように、Card.vueを作成してください。

```vuejs:Card.vue
<template>
  <div class="card">
    <p class="card__body">{{ title }}</p>
    <button class="card__delete">×</button>
  </div>
</template>

<script>
  export default {
    props: {
      title: {
        type: String,
        required: true,
      },
    },
  };
</script>

<style lang="scss" scoped>
  .card {
    position: relative;
    &:not(:first-child) {
      margin-top: 1rem;
    }
    &__body {
      background-color: #fff;
      border: 1px solid;
      border-color: #aab4c4;
      font-weight: bold;
      padding: 0.75rem 1rem;
      border-radius: 0.5rem;
    }
    &__delete {
      position: absolute;
      right: 10px;
      top: 50%;
      transform: translateY(-50%);
      border: none;
      background-color: #fff;
      border-radius: 50%;
      color: red;
      width: 1rem;
      height: 1rem;
    }
  }
</style>
```

続いて、List.vue で、この Card コンポーネントを呼び出します。

```vuejs:List.vue
<template>
  <div class="list">
    <div class="list__header">
      <h3 class="list__ttl">{{ list.title }}</h3>
      <button class="list__delete" @click="removeList">×</button>
    </div>
    <div class="list__content">
      <!-- 追加ここから -->
      <Card v-for="card in list.cards" :title="card" :key="card.id" />
      <!-- 追加ここまで -->
    </div>
  </div>
</template>

<script>
  import Card from "@/components/Card"; // 追加

  export default {
    components: {
      Card, // 追加
    },
    props: {
      list: {
        type: Object,
        required: true,
      },
      listIndex: {
        type: Number,
        required: true,
      },
    },
    methods: {
      removeList() {
        if (confirm("リストを削除しますか？")) {
          this.$store.dispatch("removeList", {
            listIndex: this.listIndex,
          });
        }
      },
    },
  };
</script>
```
これで以下のようにカードが表示されるかと思います。

![Radio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/f8dc97c0-d405-3c83-6037-383a4e7b52fd.png)

#CardCreate コンポーネントの作成
続いて、カードを作成する CardCreate コンポーネントを作成します。

まずは以下のように、CardCreate.vueを作成してください。

```vuejs:CardCreate.vue
<template>
  <form class="card-create">
    <input type="text" class="card-create__input" v-model="title" />
    <button type="submit" class="card-create__button">カードを追加</button>
  </form>
</template>

<style lang="scss" scoped>
  .card-create {
    $this: &;
    margin-top: 0.5rem;
    border-radius: 0.5rem;
    background-color: #ebecf0;
    padding-top: 0.5rem;
    padding-bottom: 0.5rem;
    &__input {
      border-radius: 0.5rem;
      width: 100%;
      background-color: inherit;
      border: none;
      font-size: 1rem;
      padding: 0.75rem 1rem;
      outline: none;
      border: 1px solid #aab4c4;
      color: #333;
      background-color: #fff;
      font-weight: bold;
      &::placeholder {
        color: #043979;
      }
    }
    &__button {
      border: none;
      background-color: #59ac44;
      padding: 0.5rem 1rem;
      border-radius: 0.5rem;
      margin-top: 0.5rem;
      color: #fff;
    }
    &.active {
      {$this}__input {
        &::placeholder {
          color: #5e6d83;
        }
      }
    }
  }
</style>
```
これをList.vueから呼び出します。

```vuejs:List.vue
<template>
  <div class="list">
    <div class="list__header">
      <h3 class="list__ttl">{{ list.title }}</h3>
      <button class="list__delete" @click="removeList">×</button>
    </div>
    <div class="list__content">
      <Card v-for="card in list.cards" :title="card" :key="card.id" />
      <CardCreate />
      <!-- 追加 -->
    </div>
  </div>
</template>

<script>
  import Card from "@/components/Card";
  import CardCreate from "@/components/CardCreate"; // 追加

  export default {
    components: {
      Card,
      CardCreate, // 追加
    },
    props: {
      list: {
        type: Object,
        required: true,
      },
      listIndex: {
        type: Number,
        required: true,
      },
    },
    methods: {
      removeList() {
        if (confirm("リストを削除しますか？")) {
          this.$store.dispatch("removeList", {
            listIndex: this.listIndex,
          });
        }
      },
    },
  };
</script>
```

続いて、カードを追加する機能を作成していきます。

まずは、どのリストのカードなのかを判別するために、リストの index 番号が必要になるので、それを受け取るようにします。

Bord.vueで、list の index 番号を List コンポーネントに受け渡します。

```vuejs:Bord.vue
<template>
  <div class="bord">
    <h2 class="bord__ttl">カード一覧</h2>
    <div class="bord__wrap">
      <div class="bord__cards">
        <!-- 変更 -->
        <List
          v-for="(list, index) in lists"
          :list="list"
          :listIndex="index"
          :key="list.id"
        />
        <ListCreate />
      </div>
    </div>
  </div>
</template>
```

List コンポーネントでは、props で値を受け取り、CardCreate コンポーネントへ受け渡します。

```vuejs:List.vue
<template>
  <div class="list">
    <div class="list__header">
      <h3 class="list__ttl">{{ list.title }}</h3>
      <button class="list__delete" @click="removeList">×</button>
    </div>
    <div class="list__content">
      <Card v-for="card in list.cards" :title="card" :key="card.id" />
      <CardCreate :listIndex="listIndex" />
      <!-- 追加 -->
    </div>
  </div>
</template>

<script>
  import Card from "@/components/Card";
  import CardCreate from "@/components/CardCreate";

  export default {
    components: {
      Card,
      CardCreate,
    },
    props: {
      list: {
        type: Object,
        required: true,
      },
      // 追加
      listIndex: {
        type: Number,
        required: true,
      },
    },
    methods: {
      removeList() {
        if (confirm("リストを削除しますか？")) {
          this.$store.dispatch("removeList", {
            listIndex: this.listIndex,
          });
        }
      },
    },
  };
</script>

(省略)
```

CardCreate コンポーネントで props で受け取ります。

```vuejs:CardCreate.vue
<template>
  <form class="card-create">
    <input type="text" class="card-create__input" v-model="title" />
    <button type="submit" class="card-create__button">カードを追加</button>
  </form>
</template>

<script>
  export default {
    data() {
      return {
        title: "",
      };
    },
    // 追加
    props: {
      listIndex: {
        type: Number,
        required: true,
      },
    },
  };
</script>

(省略)
```

続いて、Card を追加するメソッドを作成しましょう。

```vuejs:CardCreate.vue
<template>
  <form class="card-create" @submit.prevent="addCard">
    <!-- 変更 -->
    <input type="text" class="card-create__input" v-model="title" />
    <button type="submit" class="card-create__button">カードを追加</button>
  </form>
</template>

<script>
  export default {
    data() {
      return {
        title: "",
      };
    },
    props: {
      listIndex: {
        type: Number,
        required: true,
      },
    },
    // 追加
    methods: {
      addCard() {
        this.$store.dispatch("addCard", {
          listIndex: this.listIndex,
          title: this.title,
        });
        this.title = "";
      },
    },
  };
</script>
```

form には、submit イベントでメソッドが実行されるようにしています。

addCard メソッドでは、addCard アクションを呼び出しています。 引数には、リストの index 番号と、入力した title を入れてあります。

続いて、カードを追加するアクション、ミューテーションを作成します。


```javascript:store/index.js
(省略)

  mutations: {
    addList(state, title) {
      state.lists.push({title: title})
    },
    removeList(state, payload) {
      state.lists.splice(payload.listIndex, 1)
    },
    // 追加
    addCard(state, payload) {
      state.lists[payload.listIndex].cards.push(
        payload.title
      )
    }
  },
  actions: {
    addList({ commit }, title) {
      commit('addList', title)
    },
    removeList({ commit }, payload) {
      commit('removeList', payload)
    },
    // 追加
    addCard({ commit }, payload) {
      commit('addCard', payload)
    }
  },

(省略)
```

これで以下のように、カードが追加できるようになったかと思います。

![1201479e6fb4259a2355613859a8641f.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/ec7c287b-a5ff-f47d-153b-d102d586946d.gif)

次に、カードの状態に応じて、見た目を変えていきます。

ListCreate コンポーネントで実装したように以下の状態に応じて変化させます。

- 何もしていない状態
- フォームを入力中の場合
- フォームに文字が入っている場合

以上、見た目を変化させます。

実装の方法は、ListCreate コンポーネントとあまりかわりませんので、説明は省略します。

以下のように変更してください。

```vuejs:CardCreate.vue
<template>
  <form
    @submit.prevent="addCard"
    :class="[active || bodyExists ? 'active' : '']"
    class="card-create"
  >
    <input
      type="text"
      class="card-create__input"
      v-model="title"
      :placeholder="placeholder"
      @focusin="focusIn"
      @focusout="focusOut"
    />
    <button
      type="submit"
      class="card-create__button"
      v-if="active || bodyExists"
    >
      カードを追加
    </button>
  </form>
</template>

<script>
  export default {
    props: {
      listIndex: {
        type: Number,
        required: true,
      },
    },
    data() {
      return {
        title: "",
        placeholder: "新しいカードを追加",
        active: false,
      };
    },
    methods: {
      addCard() {
        this.$store.dispatch("addCard", {
          listIndex: this.listIndex,
          title: this.title,
        });
        this.title = "";
      },
      focusIn() {
        this.active = true;
        this.placeholder = "カードのタイトルを入力...";
      },
      focusOut() {
        this.active = false;
        this.placeholder = "新しいカードを追加";
      },
    },
    computed: {
      bodyExists() {
        return this.title.length > 0;
      },
    },
  };
</script>

<style lang="scss" scoped>
  .card-create {
    $this: &;
    margin-top: 0.5rem;
    border-radius: 0.5rem;
    &__input {
      border-radius: 0.5rem;
      width: 100%;
      background-color: inherit;
      border: none;
      font-size: 1rem;
      padding: 0.75rem 1rem;
      outline: none;
      &::placeholder {
        color: #043979;
      }
    }
    &__button {
      border: none;
      background-color: #59ac44;
      padding: 0.5rem 1rem;
      border-radius: 0.5rem;
      margin-top: 0.5rem;
      color: #fff;
    }
    &.active {
      background-color: #ebecf0;
      padding-top: 0.5rem;
      padding-bottom: 0.5rem;
      #{$this}__input {
        border: 1px solid #aab4c4;
        color: #333;
        background-color: #fff;
        font-weight: bold;
        &::placeholder {
          color: #5e6d83;
        }
      }
    }
  }
</style>
```

変更したら、以下のようになっているかと思います。
![c86e089cd762544926a241b1851c48ad.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/f7ff1ad7-74ca-ab94-d3bf-eacfa6fbb17c.gif)

#Card の削除機能の作成
続いて、Card の削除機能を作成していきます。

カードを削除するには、カードがどのリストにいるのか、何番目のカードなのか、を判別する必要があります。

そのため、リストの index 番号、カードの index 番号が必要になるので、List コンポーネントから props で受け取りましょう。

List コンポーネントからリストの index 番号の listIndex、カードの index 番号の cardIndex を受け渡します。

```vuejs:List.vue
<template>
  <div class="list">
    <div class="list__header">
      <h3 class="list__ttl">{{ list.title }}</h3>
      <button class="list__delete" @click="removeList">×</button>
    </div>
    <div class="list__content">
      <!-- 以下変更 -->
      <Card
        v-for="(card, index) in list.cards"
        :title="card"
        :key="card.id"
        :listIndex="listIndex"
        :cardIndex="index"
      />
      <!-- 変更ここまで -->
      <CardCreate :listIndex="listIndex" />
    </div>
  </div>
</template>
```
変更したら、Card コンポーネントで、props で受け取ります。

```vuejs:Card.vue
(省略)

<script>
  export default {
    props: {
      title: {
        type: String,
        required: true,
      },
      // 追加
      listIndex: {
        type: Number,
        required: true,
      },
      // 追加
      cardIndex: {
        type: Number,
        required: true,
      },
    },
  };
</script>

(省略)
```

できらた、削除するためのイベントとメソッドを追加しましょう。

```vuejs:Card.vue
<template>
  <div class="card">
    <p class="card__body">{{ title }}</p>
    <!-- 変更 -->
    <button class="card__delete" @click="removeCard">×</button>
  </div>
</template>

<script>
  export default {
    props: {
      title: {
        type: String,
        required: true,
      },
      listIndex: {
        type: Number,
        required: true,
      },
      cardIndex: {
        type: Number,
        required: true,
      },
    },
    // 追加
    methods: {
      removeCard() {
        if (confirm("カードを削除しますか？")) {
          this.$store.dispatch("removeCard", {
            listIndex: this.listIndex,
            cardIndex: this.cardIndex,
          });
        }
      },
    },
  };
</script>
```

removeCard メソッドでは、リストの削除時と同じように、confirm で削除を制御しています。

続いて、リストを削除するアクションと、ミューテーションを作成していきます。 

```javascript:store/index.js
(省略)

mutations: {
  addList(state, title) {
    state.lists.push({title: title})
  },
  removeList(state, payload) {
    state.lists.splice(payload.listIndex, 1)
  },
  addCard(state, payload) {
    state.lists[payload.listIndex].cards.push(
      payload.title
    )
  },
  // 追加
  removeCard(state, payload) {
    state.lists[payload.listIndex].cards.splice(payload.cardIndex, 1)
  }
},
actions: {
  addList({ commit }, title) {
    commit('addList', title)
  },
  removeList({ commit }, payload) {
    commit('removeList', payload)
  },
  addCard({ commit }, payload) {
    commit('addCard', payload)
  },
  // 追加
  removeCard({ commit }, payload) {
    commit('removeCard', payload)
  }
},

(省略)
```

ミューテーションのremoveCardでは、listIndex を使い、削除するカードがある list を呼び出しています。また、splice メソッドで、カードを削除しています。

ここまでできれば、以下のようにカードが削除できるようになっているかと思います。
![49cbca9f94b2f3f520efb02af1c7782e.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/c97b7e0b-2205-50b5-34e2-25f136f72d55.gif)

#ドラッグ&ドロップ機能を実装する
ここまでできたら、ドラッグ&ドロップで、リストとカードを移動できるように実装していきます。

ここでは、ドラッグ&ドロップを簡単に実装できるVue-draggableを使っていきます。

以下のコマンドでdraggableを追加しましょう。

```
$ npm install --save vuedraggable
```

まずは、List の移動を実装します。Bord.vueで、vuedraggable を、コンポーネントとして登録します。

```javascript:Bord.vue
(省略)

<script>
  import List from "@/components/List";
  import ListCreate from "@/components/ListCreate";
  import Draggable from "vuedraggable"; // 追加

  export default {
    components: {
      List,
      ListCreate,
      Draggable, // 追加
    },
    computed: {
      lists() {
        return this.$store.state.lists;
      },
    },
  };
</script>

(省略)
```

続いて、読み込んだコンポーネントを適応させていきます。

html を以下のように変更してください。


```vuejs:Bord.vue
<template>
  <div class="bord">
    <h2 class="bord__ttl">カード一覧</h2>
    <div class="bord__wrap">
      <!-- 変更 -->
      <Draggable class="bord__cards">
        <List
          v-for="(list, index) in lists"
          :list="list"
          :listIndex="index"
          :key="list.id"
        />
        <ListCreate />
        <!-- 変更 -->
      </Draggable>
    </div>
  </div>
</template>

(省略)
```

実はこれだけでドラッグ&ドロップを実装できます。

ページを確認してみましょう。以下のようになっていれば大丈夫です。

![f73d19a9e770cce7b1d32d8a6fff6b3f.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/211d5634-4ddb-5c6e-dd71-4d70ba031501.gif)

しかし、これだけだと見た目が変わっているだけで、store のデータは更新されていません。

なのでドラッグ&ドロップが完了するたびに、store が更新されるように変更しましょう。

ドラッグ&ドロップが完了したイベントは、endで取得できます。

なので、以下のようにイベントを追加しましょう。

```vuejs:Bord.vue
<template>
  <div class="bord">
    <h2 class="bord__ttl">カード一覧</h2>
    <div class="bord__wrap">
      <!-- 変更 -->
      <Draggable class="bord__cards" @end="updateList" :list="lists">
        <List
          v-for="(list, index) in lists"
          :list="list"
          :listIndex="index"
          :key="list.id"
        />
        <ListCreate />
      </Draggable>
    </div>
  </div>
</template>

<script>
  import List from "@/components/List";
  import ListCreate from "@/components/ListCreate";
  import Draggable from "vuedraggable";

  export default {
    components: {
      List,
      ListCreate,
      Draggable,
    },
    // 追加
    methods: {
      updateList() {
        this.$store.dispatch("updateList", { lists: this.lists });
      },
    },
    computed: {
      lists() {
        return this.$store.state.lists;
      },
    },
  };
</script>

(省略)
```

vuedraggableは、props の一つとして、listを受けとります、:list="lists"として、lists を渡しておくと、ドラッグ&ドロップが完了するたびにdateを更新することができます。

updateList メソッドでは、アクションの updateList を呼び出しており、{ lists: this.lists }でリスト全体を引数に渡しています。

dataにある lists は、ドラッグ&ドロップが完了するたびに更新されているので、更新された lists が渡されます。

続いて、リストを更新するアクションとミューテーションを作成していきます。

```javascript:store/index.js
(省略)

mutations: {
  addList(state, title) {
    state.lists.push({title: title})
  },
  removeList(state, payload) {
    state.lists.splice(payload.listIndex, 1)
  },
  addCard(state, payload) {
    state.lists[payload.listIndex].cards.push(
      payload.title
    )
  },
  removeCard(state, payload) {
    state.lists[payload.listIndex].cards.splice(payload.cardIndex, 1)
  },
  // 追加
  updateList(state, payload) {
    state.list = payload.lists
  }
},
actions: {
  addList({ commit }, title) {
    commit('addList', title)
  },
  removeList({ commit }, payload) {
    commit('removeList', payload)
  },
  addCard({ commit }, payload) {
    commit('addCard', payload)
  },
  removeCard({ commit }, payload) {
    commit('removeCard', payload)
  },
  // 追加
  updateList({ commit }, payload) {
    commit('updateList', payload)
  }
},

(省略)
```

ミューテーションのupdateListでは、state 内のリストを丸々上書きしています。

これで、ドラッグ&ドロップが完了するたびにリストを更新できるようになりました。

ついでに、リストがスムーズに移動するように変更しましょう。

```vuejs:Bord.vue
<template>
  <div class="bord">
    <h2 class="bord__ttl">カード一覧</h2>
    <div class="bord__wrap">
      <!-- 変更 -->
      <Draggable class="bord__cards" animation="150" @end="updateList" :list="lists">
        <List
          v-for="(list, index) in lists"
          :list="list"
          :listIndex="index"
          :key="list.id"
        />
        <ListCreate />
      </Draggable>
    </div>
  </div>
</template>
```

animation="150"を propr として渡すと、スムーズに移動させることができます。

ここまでできたら以下のようになっています。

![fd59ed43c1b67184d4aa2cce14ee9008.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/855d3ff6-de71-fd65-62ee-057a7209df59.gif)

続いて、カードをドラッグ&ドロップできるようにしましょう。

カードのドラッグ&ドロップは大きく分けて二種類の動きがあります。

リスト内でドラッグ&ドロップする

他のリスト間へドラッグ&ドロップする
それぞれ分けて実装しましょう。

まずはリスト内でのドラッグ&ドロップを実装します。

```vuejs:List.vue
<template>
  <div class="list">
    <div class="list__header">
      <h3 class="list__ttl">{{ list.title }}</h3>
      <button class="list__delete" @click="removeList">×</button>
    </div>
    <div class="list__content">
      <!-- 追加 -->
      <Draggable :list="list.cards" animation="150">
        <Card
          v-for="(card, index) in list.cards"
          :title="card"
          :key="card.id"
          :listIndex="listIndex"
          :cardIndex="index"
        />
      </Draggable>
    </div>
    <CardCreate :listIndex="listIndex" />
  </div>
</template>

<script>
import Card from "@/components/Card";
import Draggable from "vuedraggable";
// 追加
import CardCreate from "@/components/CardCreate";

export default {
  components: {
    Card,
    CardCreate,
    Draggable, // 追加
  },
  props: {
    list: {
      type: Object,
      required: true,
    },
    listIndex: {
      type: Number,
      required: true,
    },
  },
  methods: {
    removeList() {
      if (confirm("リストを削除しますか？")) {
        this.$store.dispatch("removeList", {
          listIndex: this.listIndex,
          listId: this.list.id
        });
      }
    },
  },
};
</script>

(省略)
```

まずは移動だけ実装しました。以下のようになっているかとお思います。

![f0a39c525b13efed8a26d8b713cb6ca7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/64c65e47-e14e-7866-6030-88062cd349d7.gif)

続いて、他のリスト間へのドラッグ&ドロップを実装していきます。

```vuejs:List.vue
<template>
  <div class="list">
    <div class="list__header">
      <h3 class="list__ttl">{{ list.title }}</h3>
      <button class="list__delete" @click="removeList">×</button>
    </div>
    <div class="list__content">
      <!-- 変更 -->
      <Draggable :list="list.cards" animation="150" group="cards">
        <Card
          v-for="(card, index) in list.cards"
          :title="card"
          :key="card.id"
          :listIndex="listIndex"
          :cardIndex="index"
        />
      </Draggable>
    </div>
    <CardCreate :listIndex="listIndex" />
  </div>
</template>

(省略)
```
group="cards"と記述することで、同じグループ内でのドラッグ&ドロップを可能にします。

実際に動作を確認してみましょう。以下のようにカード間を移動できるようになっているかと思います。
![573b05b06f9779d71565c9e1ea210d0e.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1246814/009294d4-b532-7601-4270-33b53bc14467.gif)

続いて、カードの更新を store に保存できるように機能を追加しましょう。

```vuejs:List.vue
<template>
  <div class="list">
    <div class="list__header">
      <h3 class="list__ttl">{{ list.title }}</h3>
      <button class="list__delete" @click="removeList">×</button>
    </div>
    <div class="list__content">
      <Draggable
        :list="list.cards"
        animation="150"
        group="cards"
        @end="$emit('update')"
      >
        <Card
          v-for="(card, index) in list.cards"
          :title="card"
          :key="card.id"
          :listIndex="listIndex"
          :cardIndex="index"
        />
      </Draggable>
    </div>
    <CardCreate :listIndex="listIndex" />
  </div>
</template>

(省略)
```

@end="$emit('update')"で、親コンポーネントへイベントを渡すことができます。

```vuejs:Bord.vue
<template>
  <div class="bord">
    <h2 class="bord__ttl">カード一覧</h2>
    <div class="bord__wrap">
      <Draggable
        class="bord__cards"
        animation="150"
        @end="updateList"
        :list="lists"
      >
        <!-- 変更 -->
        <List
          v-for="(list, index) in lists"
          :list="list"
          :listIndex="index"
          :key="list.id"
          @update="updateList"
        />
        <ListCreate />
      </Draggable>
    </div>
  </div>
</template>

(省略)
```

小コンポーネントから受け取ったupdateイベントでupdateListメソッドを動かしています。

updateListメソッドではリスト全体を上書きしているので、カードも一緒に更新できます。

これでドラッグ&ドロップ機能の実装は完了しました。