はじめに
Recursionというサービスのプロジェクト課題の1つであるタスク管理アプリを作成した際に
詰まった箇所が多かったので、頭の整理のためにまとめた。
今回は、デザインを良い感じにしてくれるVuetifyとドラッグ&ドロップで要素を動かせるVue.Draggableを使用した。
完成品
live demo
https://takasu-y.github.io/task_management_app/
開発環境
- Node.js v14.4.0
- npm 6.14.5
- vue/cli 4.5.13
バージョン確認方法
node -v
npm -v
vue --version
環境構築
プロジェクト作成
vue create "プロジェクト名"
Vue2を選択する。
※Vue2を選択する理由は、現在(2021年10月時点)VuetifyがVue3をサポートしていないため。
//Vue2を選択
Please pick a preset: (Use arrow keys)
> Default ([Vue 2] babel, eslint) ← こちらを選択
Default (Vue 3) ([Vue 3] babel, eslint)
Manually select features
下記のように表示されたらプロジェクト作成完了。
🎉 Successfully created project "プロジェクト名".
👉 Get started with the following commands:
$ cd "プロジェクト名"
$ npm run serve
ディレクトリ移動
起動するためにプロジェクトのディレクトリへ移動する。
cd "プロジェクト名"
プロジェクト起動
npm run serve
下記のLocal URLをcmd+クリック
DONE Compiled successfully in 2720ms 12:35:28
App running at:
- Local: http://localhost:8080/
- Network: http://192.168.1.39:8080/
Note that the development build is not optimized.
To create a production build, run npm run build.
下記の画面が表示されれば成功。
画面が確認出来たら
^ + cで起動停止する。
Vuetifyのインストール
vue add vuetify
Still proceed -> Yes を選択する。
WARN There are uncommitted changes in the current repository, it's recommended to commit or stash them first.
? Still proceed? Yes
Choose a preset -> Defaultを選択する。
? Choose a preset: (Use arrow keys)
Configure (advanced)
❯ Default (recommended)
Vite Preview (Vuetify 3 + Vite)
Prototype (rapid development)
Vuetify 3 Preview (Vuetify 3)
下記のように表示されたらインストール完了。
✔ Successfully invoked generator for plugin: vue-cli-plugin-vuetify
vuetify Discord community: https://community.vuetifyjs.com
vuetify Github: https://github.com/vuetifyjs/vuetify
vuetify Support Vuetify: https://github.com/sponsors/johnleider
Vuetifyが反映されていることを確認
先程と同様にプロジェクトを起動して、
下記のように画面にVuetifyが表示されていれば成功。
Material Design Iconsのインストール
Vuetify font Iconを追加を参考にインストールする。
npm install @mdi/font -D
CDNの場合は
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@6.x/css/materialdesignicons.min.css" rel="stylesheet">
Vue.Draggableのインストール
npm i -S vuedraggable
使い方
draggableタグで囲んだ要素を動かせるようになる。
ポイントとしては、
- 動かしたい要素に共通のセレクターを指定する
- draggableタグ内のdraggable属性に動かしたい要素のセレクターを指定する
- draggableを複数使う場合はgroup属性を指定する
開発
いきなりコーディングに入っても上手くいかないので、
以下の①〜③を明確にしてからコーディングを行った。
コードを書く前に考えたこと
①要件を整理する
②何のコンポーネントを作成するか決める
③どこに状態を持たせるか決める
要件
task
- タスクカードはタスクのタイトルと内容を記入できる。
- 編集:🖋アイコンまたはタスクカードをダブルクリック
- お気に入り:★アイコンをクリック
- チェック:✔️アイコンをクリック
- 削除:🗑アイコンをクリック
Section
- セクションの右側+ボタンで新たにセクションを追加。
- セクション名はダブルクリックすることで編集できる。
- 各セクション下部の+ボタンで新たにタスクを追加。
- タスクカードを並べる。
- 各セクションを横並びにする。
作成するコンポーネント
- task.vue
- taskSection.vue
- taskManager.vue
どこに状態を持たせるか
ワイ「何で子コンポーネントに状態を持たせたらあかんの?」を参考に。
"ハスケル子「子コンポーネントは、親から受け取った状態を映し出す単なるディスプレイとして作りましょう」"
ということなので、タスクの状態はtaskSectionコンポーネントに持たせ、
taskコンポーネントはpropsで状態を受け取って、反映させるだけにした。
<script>
export default {
name: "task",
props: {
task: {
type: Object, //データ型
required: true, //データ渡し必須
},
favorite: {
type: Boolean,
required: true,
},
check: {
type: Boolean,
required: true,
},
},
};
</script>
タスクカードの各アイコンがクリックされたら
$emit(任意のイベント名, タスクカードのオブジェクト)で親コンポーネントへ送る。
<template>
(略)
<v-card-actions class="d-flex justify-end">
<v-icon @click="$emit('edit', task)">mdi-pencil</v-icon>
<v-icon @click="$emit('favorite', task)" :class="{ 'lime--text': task.favorite}">mdi-star</v-icon>
<v-icon @click="$emit('check', task)" :class="{ 'teal--text': task.check}">mdi-check-bold</v-icon>
<v-icon @click="$emit('delete', task)">mdi-delete</v-icon>
</v-card-actions>
(略)
</template>
子コンポーネントから送ったイベントを
親コンポーネントのtemplateへ
<template>
(略)
<draggable
v-model="tasks"
draggable=".item"
group="items"
>
<task
v-for="task in tasks"
:key="task.id"
:task="task"
class="item"
@edit="editTask" //子コンポーネントから送られてきたeditイベント
@favorite="favoriteTask" //子コンポーネントから送られてきたfavoriteイベント
@check="checkTask" //子コンポーネントから送られてきたcheckイベント
@delete="deleteTask" //子コンポーネントから送られてきたdeleteイベント
></task>
</draggable>
(略)
</template>
タスクの状態や変更を加えるメソッドは全てTaskSectionコンポーネントへ記述。
<script>
export default {
(略)
data() {
return {
section: {
title: "",
editable: true,
},
tasks: [
{
id: todoId++,
title: "朝ごはんを食べる",
body: "プロテインも飲む",
editable: false,
favorite: false,
check: false,
},
{
id: todoId++,
title: "昼ごはんを食べる",
body: "中華かラーメンを食べる",
editable: false,
favorite: false,
check: false,
},
{
id: todoId++,
title: "夜ごはんを食べる",
body: "サラダのみにする",
editable: false,
favorite: false,
check: false,
},
]
}
},
methods: {
addTask(){
this.tasks.push({
id: todoId++,
title: "",
body: "",
editable: true,
favorite: false,
check: false,
})
},
editTask(taskObject){
taskObject.editable = !taskObject.editable;
},
favoriteTask(taskObject){
taskObject.favorite = !taskObject.favorite;
},
checkTask(taskObject){
taskObject.check = !taskObject.check;
},
deleteTask(taskObject){
this.tasks = this.tasks.filter(task => task.id !== taskObject.id);
},
},
};
</script>
最後にtaskManager.vueでセクションを並べたり、追加ボタンを設置して完成です。
おまけ
GitHub Pagesに公開する場合は、/docs配下にコンパイルしなければいけないので
vue.config.jsにoutputDir: 'docs/',を記述して npm run build
を行う。