CSSやJavascriptでカレンダーを作るの、難しくないですか?
Vuetifyのカレンダーコンポーネントを使うと簡単にカレンダーを作ることができたので、作り方を紹介したいと思います
ゴール
↓のようなカレンダーを作ります
https://vuetify-calendar-sample.netlify.app/
Vuetify
VuetifyはVueで使えるUIライブラリの一つです
自分でCSSを記述することなく美しいUIを作ることができます
今回はVue CLIを使って簡単なカレンダーのデモを作成していきます
環境
- Node.js v14.8.0
- VueCLI 4.5.3
nodeのインストール
nodenvを使ってnodeをインストールしました。nodenvのインストールはこちらを参考にしてください
$ nodenv install 14.8.0
$ nodenv global 14.8.0
$ nodenv local 14.8.0
$ node -v
v14.8.0
VueCLIのインストール
公式ガイドを参考にインストールします
$ npm install -g @vue/cli@4.5.3
$ vue --version
@vue/cli 4.5.3
プロジェクトの作成
VueCLIコマンドでプロジェクトを作成します
$ vue creare calendar-app
プリセットはDefault(Vue2)を選択しました (Vue3で試してみましたがVuetifyのインストール時にエラーが出ました)
Vuetifyのインストール
プロジェクトディレクトリに移動してVuetifyをインストールします
$ cd calendar-app
$ vue add vuetify
プリセットはDefaultを選択しました
インストールが終わるとsrc/main.js
にVuetifyの設定が自動で記述されます
import Vue from 'vue'
import App from './App.vue'
+import vuetify from './plugins/vuetify';
Vue.config.productionTip = false
new Vue({
- render: h => h(App),
+ vuetify,
+ render: h => h(App)
}).$mount('#app')
ここまでで表示してみると下の画像のようにVuetifyのウェルカム画面が表示されます
npm run serve
# http://localhost:8080/ を開く
カレンダーを表示する
まず、src/App.vue
を次のように書き換えます
<template>
<v-app>
<v-main>
<Calendar />
</v-main>
</v-app>
</template>
<script>
import Calendar from './components/Calendar';
export default {
name: 'App',
components: {
Calendar,
},
};
</script>
v-app
コンポーネントで囲うことを忘れてしまうとVuetifyのスタイルが適用されないので注意です 参考
次に、src/components/Calendar.vue
を作成します
公式ドキュメントのサンプルを見つつ、最低限表示できるところまでコードを削ぎ落としました
<template>
<div>
<v-sheet tile height="6vh" color="grey lighten-3" class="d-flex align-center">
<v-btn icon @click="$refs.calendar.prev()">
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-btn icon @click="$refs.calendar.next()">
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
</v-sheet>
<v-sheet height="94vh">
<v-calendar
ref="calendar"
:events="events"
:event-color="getEventColor"
@change="getEvents"
></v-calendar>
</v-sheet>
</div>
</template>
<script>
export default {
data: () => ({
events: [],
}),
methods: {
getEvents() {
const events = [
{
name: '会議',
start: new Date('2020-08-03T01:00:00'), // 開始時刻
end: new Date('2020-08-03T02:00:00'), // 終了時刻
color: 'blue',
timed: true, // 終日ならfalse
},
];
this.events = events;
},
getEventColor(event) {
return event.color;
},
},
};
</script>
今回はデモ用なので直接イベントデータを記述していますが、APIからデータを取得する処理に書き換えることもできます
このようなカレンダーが表示されます
<v-calendar
ref="calendar"
:events="events"
:event-color="getEventColor"
@change="getEvents"
></v-calendar>
:events="events"
でカレンダーに表示するイベントデータを渡しています
events
変数は最初は空配列ですが、@change="getEvents"
で値を代入しています
const events = [
{
name: '会議',
start: new Date('2020-08-03T01:00:00'),
end: new Date('2020-08-03T02:00:00'),
color: 'blue',
timed: true,
},
];
これがカレンダーに表示されるイベントの中身です
start
, end
は開始時刻と終了時刻であり、Dateオブジェクトを指定します
timed
には時刻を表示する時はtrue
、表示しない(終日)の時はfalse
を指定します
配列にイベントオブジェクトを追加していくことでカレンダーに表示されるイベントが増えていきます
ここから機能を追加していきます
年月を表示する
表示しているカレンダーの年月を表示するようにします
Moment.jsを使うので先にインストールしておきます
$ npm install moment --save
<template>
<div>
<v-sheet tile height="6vh" color="grey lighten-3" class="d-flex align-center">
<v-btn icon @click="$refs.calendar.prev()">
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-btn icon @click="$refs.calendar.next()">
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
+ <v-toolbar-title>{{ title }}</v-toolbar-title>
</v-sheet>
</div>
</template>
<script>
+ import moment from 'moment';
export default {
data: () => ({
events: [],
+ value: moment().format('yyyy-MM-DD'), // 現在日時
}),
+ computed: {
+ title() {
+ return moment(this.value).format('yyyy年 M月'); // 表示用文字列を返す
+ }
+ },
methods: {
...
},
};
</script>
value
変数には2020-08-01
のような形式の日付文字列が入ります
初期値は現在の日付です
title()
で表示形式を変換して表示しています
月を戻す/進める
v-calendar
にv-model="value"
を設定します
これだけで、ボタンを押すと戻る/進むできるようになります
<v-calendar
ref="calendar"
+ v-model="value"
:events="events"
:event-color="getEventColor"
@change="getEvents"
></v-calendar>
例えば戻るボタンを押すと$refs.calendar.prev()
が実行されます
これが実行されるとvalue
に先月の末日が代入されます
元が2020-08-15
だったら2020-07-31
が入ります
一方で$refs.calendar.next()
は次月の初日である2020-08-01
が入ります
従ってvalue
に表示させたい月の日付を代入することでカレンダーを切り替えることができます
今日ボタンを追加
<template>
<div>
<v-sheet tile height="6vh" color="grey lighten-3" class="d-flex align-center">
+ <v-btn outlined small class="ma-4" @click="setToday">
+ 今日
+ </v-btn>
<v-btn icon @click="$refs.calendar.prev()">
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
...
</template>
<script>
...
methods: {
+ setToday () {
+ this.value = moment().format('yyyy-MM-DD')
+ },
...
</script>
今月のカレンダーに戻りたい時はvalue
に今日の日付を代入すればできます👍
日本語表記
曜日の表示がSUNやMONのように英語表記になっているので、日本語表記に変えます
<v-calendar
ref="calendar"
v-model="value"
:events="events"
:event-color="getEventColor"
+ locale="ja-jp"
+ :day-format="(timestamp) => new Date(timestamp.date).getDate()"
+ :month-format="(timestamp) => (new Date(timestamp.date).getMonth() + 1) + ' /'"
@change="getEvents"
></v-calendar>
イベントのクリック処理、日付のクリック処理
イベントをクリックした時、日付をクリックした時の処理の書き方です
ここでは簡単のためalert
を実行しています
<template>
...
<v-calendar
ref="calendar"
v-model="value"
:events="events"
:event-color="getEventColor"
locale="ja-jp"
:day-format="(timestamp) => new Date(timestamp.date).getDate()"
:month-format="(timestamp) => new Date(timestamp.date).getMonth() + 1 + ' /'"
@change="getEvents"
+ @click:event="showEvent"
+ @click:date="viewDay"
></v-calendar>
...
</template>
<script>
...
methods: {
setToday() {
this.value = moment().format('yyyy-MM-DD');
},
+ showEvent({ event }) {
+ alert(`clicked ${event.name}`);
+ },
+ viewDay({ date }) {
+ alert(`date: ${date}`);
+ },
...
</script>
@click:event
, @click:date
にそれぞれ実行させるメソッドを指定します
メソッドの引数に色々なデータを持つオブジェクトが渡されるので、それを使ってイベントの詳細やデイリーカレンダーの表示などを実行できそうです
最終的なコード
<template>
<div>
<v-sheet tile height="6vh" color="grey lighten-3" class="d-flex align-center">
<v-btn outlined small class="ma-4" @click="setToday">
今日
</v-btn>
<v-btn icon @click="$refs.calendar.prev()">
<v-icon>mdi-chevron-left</v-icon>
</v-btn>
<v-btn icon @click="$refs.calendar.next()">
<v-icon>mdi-chevron-right</v-icon>
</v-btn>
<v-toolbar-title>{{ title }}</v-toolbar-title>
</v-sheet>
<v-sheet height="94vh">
<v-calendar
ref="calendar"
v-model="value"
:events="events"
:event-color="getEventColor"
locale="ja-jp"
:day-format="(timestamp) => new Date(timestamp.date).getDate()"
:month-format="(timestamp) => new Date(timestamp.date).getMonth() + 1 + ' /'"
@change="getEvents"
@click:event="showEvent"
@click:date="viewDay"
></v-calendar>
</v-sheet>
</div>
</template>
<script>
import moment from 'moment';
export default {
data: () => ({
events: [],
value: moment().format('yyyy-MM-DD'),
}),
computed: {
title() {
return moment(this.value).format('yyyy年 M月');
},
},
methods: {
setToday() {
this.value = moment().format('yyyy-MM-DD');
},
showEvent({ event }) {
alert(`clicked ${event.name}`);
},
viewDay({ date }) {
alert(`date: ${date}`);
},
getEvents() {
const events = [
// new Dateからmomentに変更
{
name: '会議',
start: moment('2020-08-03 10:00:00').toDate(),
end: moment('2020-08-03 11:00:00').toDate(),
color: 'blue',
timed: true,
},
// イベントを追加
{
name: '休暇',
start: moment('2020-08-04').toDate(),
end: moment('2020-08-04').toDate(),
color: 'green',
timed: false,
},
{
name: '出張',
start: moment('2020-08-05').toDate(),
end: moment('2020-08-07').toDate(),
color: 'cyan',
timed: false,
},
{
name: '飲み会',
start: moment('2020-08-06').toDate(),
end: moment('2020-08-06').toDate(),
color: 'orange',
timed: false,
},
{
name: '打ち合わせ',
start: moment('2020-08-07 10:00').toDate(),
end: moment('2020-08-07 11:00').toDate(),
color: 'cyan',
timed: true,
},
{
name: '振り返り',
start: moment('2020-08-07 11:00:00').toDate(),
end: moment('2020-08-07 12:00').toDate(),
color: 'cyan',
timed: true,
},
{
name: '休暇',
start: moment('2020-09-07').toDate(),
end: moment('2020-09-11').toDate(),
color: 'green',
timed: false,
},
];
this.events = events;
},
getEventColor(event) {
return event.color;
},
},
};
</script>
まとめ
Vuetifyを使ってカレンダーを表示する方法を紹介しました
自分でCSSやJavascriptを書くよりもずっと少ないコードで実装することができました!
もっとたくさんイベントを追加してみるとわかるのですが、連日のイベントだと横に伸びたり、イベントが多いとmore表示になったりして気が利いています。便利