###前置き
カレンダーと一言で言っても、機能も様々なので、
ひとまず機能もシンプルで、あまりごちゃごちゃさせてないものを紹介します。
vueとmomentのみで作っています。
下記が主な機能:
1.<>ボタンで月単位での移動ができる
2.選択状態の概念があること(表示時は現在日付)
3.日付ごとにその日のTodoを表示できる
結構スタイルに依存するので、簡単なcssも記述します。
###完成品
んで、todoがある日付を選択すると、
todoリストが出る仕組み。
###まずはじめに
todoリストを別ファイルで作ってみます。
実際こういうコンポーネントを使うアプリを作る場合は、
だいたいDBから取得するパターンが多いと思います。
今回は簡易的に定数ファイル的なノリで作成。
const TODO_LIST = [
{
id: 1,
title: "起床",
description: "きっとねむい",
date: "2019-07-11",
time: "09:00",
},
{
id: 2,
title: "出勤",
description: "まだねむい",
date: "2019-07-11",
time: "10:00",
},
{
id: 3,
title: "打ち合わせ",
description: "",
date: "2019-07-12",
time: "11:00",
},
{
id: 4,
title: "作業",
description: "なにやろうかななにやろうかな",
date: "2019-07-12",
time: "15:00",
},
{
id: 5,
title: "お風呂",
description: "温度は43度",
date: "2019-07-12",
time: "19:00",
},
{
id: 6,
title: "カレンダー作り",
description: "つくるぞつくるぞ",
date: "2019-07-13",
time: "14:00",
},
];
export default TODO_LIST;
続いて、カレンダーのカレンダーの部分。
<template>
<div class="calender">
<div class="calender-component">
<div class="calender-header">
<div class="arrow-back" v-on:click="changeMonth(0)"><</div>
<div class="current-date">{{ dateLabel }}</div>
<div class="arrow-next" v-on:click="changeMonth(1)">></div>
</div>
<div class="calender-body">
<ul class="calender-panel-list">
<li class="calender-panel_space" v-for="space in spaces"></li>
<li
class="calender-panel"
v-on:click="selectDate"
v-for="date in dates"
:id="date.date"
v-bind:class="selectedDate === date.date ? 'selected' : ''"
>
<div class="calender-date">{{ date.dateNumber }}</div>
<div
class="calender-todo"
v-bind:class="date.todoNumber !== '-' ? 'number' : ''"
>{{ date.todoNumber }}</div>
</li>
</ul>
</div>
<div class="calender-footer">
<div
class="calender-footer_todo"
v-for="todo in todoList"
v-show="todo.date === selectedDate"
>
<div class="calender-footer_todo-time">{{ todo.time }}</div>
<div class="calender-footer_todo-title">{{ todo.title }}</div>
<div class="calender-footer_todo-description">{{ todo.description }}</div>
</div>
</div>
</div>
</div>
</template>
<script>
import moment from "moment";
import TODO_LIST from "../data/todoList";
export default {
mixins: [TODO_LIST],
data() {
return {
todoList: TODO_LIST, //todoリスト
dates: [], //カレンダーの日付
spaces: [], //その月の最初日が始まる場所
dateLabel: "", //フォーマット="2019年7月"
selectedMonth: null, //今選択している月
selectedDate: null //今選択している日付
};
},
methods: {
selectDate(event) {
//日付を選択する
this.selectedDate = event.currentTarget.id;
},
changeMonth(num) {
//月を変更
if (num === 0) {
this.selectedMonth = moment(this.selectedMonth).subtract(1, "months");
} else {
this.selectedMonth = moment(this.selectedMonth).add(1, "months");
}
}
},
created() {
//画面表示時に今日の日付と月を設定。
this.selectedDate = moment().format("YYYY-MM-DD");
this.selectedMonth = moment();
},
watch: {
selectedMonth: function() {
//選択している月の変更時の処理、画面表示時も動きます
this.dateLabel = moment(this.selectedMonth).format("YYYY年MM月"); //月ラベルを更新
this.spaces = []; //スペースを初期化
for (
let i = 0;
i <
moment(this.selectedMonth)
.startOf("month")
.day();
i++
) {
//スペースを更新
this.spaces[i] = i;
}
this.dates = []; //カレンダーパネルを初期化
for (let i = 0; i < moment(this.selectedMonth).daysInMonth(); i++) {
//カレンダーパネルを更新
let todoNumber = "-";
for (let k of Object.keys(this.todoList)) {
//todoListの情報をカレンダーパネルに追加
if (this.dates[i]) {
if (this.todoList[k].date === this.dates[i].date) {
todoNumber++;
}
}
}
this.dates[i] = {
date: moment(this.selectedMonth)
.startOf("month")
.add(i, "day")
.format("YYYY-MM-DD"),
dateNumber: i + 1,
todoNumber: todoNumber
};
}
}
}
};
</script>
ざっくりとした解説をしていきます。
createdにて、まずは今日の日付と、今月の値を取得。
this.selectedDate = moment().format('YYYY-MM-DD');
this.selectedMonth = moment();
methodsは二つ。
1.選択している日付を変更。
selectDate(event) { //日付を選択する
this.selectedDate = event.currentTarget.id
},
2.選択している月を変更。
changeMonth(num) { //月を変更
if(num === 0) {
this.selectedMonth = moment(this.selectedMonth).subtract(1, 'months');
} else {
this.selectedMonth = moment(this.selectedMonth).add(1, 'months');
}
"<"には0、">"では1を返しております。
watchではselectedMonthの状態を監視。
selectedMonth: function() { //選択している月の変更時の処理、画面表示時も動きます
this.dateLabel = moment(this.selectedMonth).format('YYYY年MM月'); //月ラベルを更新
this.spaces = []; //スペースを初期化
for(let i = 0; i < moment(this.selectedMonth).startOf('month').day(); i++) { //スペースを更新
this.spaces[i] = i;
}
this.dates = []; //カレンダーパネルを初期化
for(let i = 0; i < moment(this.selectedMonth).daysInMonth(); i++) { //カレンダーパネルを更新
let todoNumber = '-';
for(let k of Object.keys(this.todoList)) { //todoListの情報をカレンダーパネルに追加
if(this.todoList[k].date === this.dates[i].date) {
todoNumber++;
}
}
this.dates[i] = {
date: moment(this.selectedMonth).startOf('month').add(i, 'day').format('YYYY-MM-DD'),
dateNumber: i + 1,
todoNumber: todoNumber
}
}
}
ここで、選択されてる月(selectedMonth)の日数と、最初の日が何曜日かの計算と、todoListの情報も各日付に入れてます。
spacesとdatesの更新を行なっております。
this.spaces = [0]; //1日が火曜日の場合
this.spaces = [0, 1, 2, 3]; //1日が金曜日の場合
selectedMonthが変更されるたびに、watchの処理が走るので、
それに合わせてカレンダーも更新されます。
スタイルも乗せときます。
<style scoped>
.calender {
width: 336px;
}
.calender-component {
min-height: 320px;
border: solid 1px gray;
padding: 24px;
box-sizing: border-box;
}
.calender-header {
display: flex;
justify-content: space-between;
}
.arrow {
font-size: 16px;
color: gray;
user-select: none;
cursor: pointer;
text-align: center;
width: 24px;
height: 24px;
}
.arrow:hover {
background-color: silver;
border-radius: 4px;
}
.current-date {
color: gray;
user-select: none;
}
.calender-body {
margin-top: 24px;
}
.calender-panel-list {
display: flex;
flex-wrap: wrap;
padding: 0;
justify-content: left;
}
.calender-panel {
padding: 8px 0px;
width: 40px;
text-align: center;
color: gray;
font-size: 14px;
user-select: none;
list-style: none;
}
.calender-panel:hover {
background-color: silver;
border-radius: 4px;
}
.calender-date {
cursor: pointer;
}
.selected {
background-color: silver;
border-radius: 4px;
}
.calender-todo {
cursor: pointer;
text-align:center;
margin: 0px 8px;
line-height: 25px;
}
.calender-panel_space {
width: 40px;
list-style: none;
}
.calender-footer_todo {
border-top: 1px gray solid;
padding-top: 12px;
margin-top: 8px;
}
.calender-footer_todo-time {
font-size: 18px;
color: gray;
word-break: break-word;
text-decoration: underline;
}
.calender-footer_todo-title {
font-size: 20px;
color: gray;
word-break: break-word;
}
.calender-footer_todo-description {
font-size: 14px;
color: gray;
word-break: break-word;
}
.number {
text-decoration: underline;
}
</style>
一行に日付が7日(曜日分)表示させるのは、スタイルで制御しちゃってます。
###まとめと振り返り
実際にこれを使用したプロジェクトでは、1時間ぐらいで作ったので、
ところどころ直して、自分の備忘録として書いてみました。
ほどよくライフサイクルとディレクティブを使ってたりするので、
Vue初心者の学習にちょうどいいかもしれないです。
input type="date"コンポーネントの自作とかも、
これをベースに作れそうですよね。
ただ、スタイル依存の部分が多いという点で、
汎用性に怪しさを感じました。
あとqiitaに初めて投稿しましたが、
わかりやすく書くのってめちゃくちゃむずい。
気づいたところがあれば、都度更新していきたいです。