##生活の中のリアルとデジタルの小さな融合
フツーの会社員をしながらプログラミングなどなどを勉強している会社員です。
勉強を始めてからそろそろ4か月が経過し、ちょと身の回りのことでもプログラミング関連のことが気になるようになってきました。
そんな関係でこの間、チームラボ ボーダレスに行ってきました。絵を描いてデジタルの海に泳がせるコーナーがあったんですが、老若男女が楽しんでいて、そんな世界観がいいな~とうっとり。生活の中でリアルとデジタルが緩く繋がれる世界観を今年はちょっと試したいな~。
というわけでリアルとデジタルの接点とすべく、またまたカレンダーテクを学んでみました。
##Vuetify
今回はカレンダーの材料もたくさんあったVuetifyをピックしました。VeutifyはVue.jsで使えるマテリアルデザインのフレームワークです。いつもはQiita記事に頼りっぱなしの私ですが今回は直接ホームページからやり方を学びつつ、トライしました。英語という難点はありますが、比較的丁寧に解説がしてありとっつきやすかったです。なんといっても名前からして「美しそうなもの」ができそう。
##目標
書き込めたり、画像を入れたりできるスペースのあるカレンダーを作る~。
##環境
Windows 10
Node.js v12.4.0
Visual Studio Code version 1.41
Vue.js 2X
##方法
1.cmdで自分がプログラムを作成したいファイルを開きます。
2.Quick-Startの手順に従ってVue CLI3をインストール。"sample-app"、"default"、"Mannually select features"がありますが私はdefaultにしました。
3.my-appフォルダに移動して、vuetifyを追加
4.Visual Studio Codeで該当フォルダを開きます。
5.src階下のApp.vueを書き換え、同じくscr階下にapp.jsを追加。さらにcomponentsにCalendar vue.を作成します。
6.yarn serveでローカルの8080に表示されます。
##コード
こちらを参考にさせていただきました。
<template>
<div id="app">
<calendar/>
</div>
</template>
<script>
import calendar from './components/Calendar.vue'
export default {
name: 'app',
components: {
calendar
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
import Vue from 'vue'
import Vuetify from 'vuetify'
import DaySpanVuetify from 'dayspan-vuetify'
import App from './App.vue'
import 'vuetify/dist/vuetify.min.css'
import 'material-design-icons-iconfont/dist/material-design-icons.css'
import 'dayspan-vuetify/dist/lib/dayspan-vuetify.min.css'
Vue.config.productionTip = false
Vue.use(Vuetify);
Vue.use(DaySpanVuetify, {
methods: {
getDefaultEventColor: () => '#1976d2'
}
});
new Vue({
el: '#app',
render: h => h(App)
})
<template>
<v-row class="fill-height">
<v-col>
<v-sheet height="64">
<v-toolbar flat color="white">
<v-btn outlined class="mr-4" color="grey darken-2" @click="setToday">
Today
</v-btn>
<v-btn fab text small color="grey darken-2" @click="prev">
<v-icon small>mdi-chevron-left</v-icon>
</v-btn>
<v-btn fab text small color="grey darken-2" @click="next">
<v-icon small>mdi-chevron-right</v-icon>
</v-btn>
<v-toolbar-title>{{ title }}</v-toolbar-title>
<v-spacer></v-spacer>
<v-menu bottom right>
<template v-slot:activator="{ on }">
<v-btn
outlined
color="grey darken-2"
v-on="on"
>
<span>{{ typeToLabel[type] }}</span>
<v-icon right>mdi-menu-down</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item @click="type = 'day'">
<v-list-item-title>Day</v-list-item-title>
</v-list-item>
<v-list-item @click="type = 'week'">
<v-list-item-title>Week</v-list-item-title>
</v-list-item>
<v-list-item @click="type = 'month'">
<v-list-item-title>Month</v-list-item-title>
</v-list-item>
<v-list-item @click="type = '4day'">
<v-list-item-title>4 days</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
</v-toolbar>
</v-sheet>
<v-sheet height="600">
<v-calendar
ref="calendar"
v-model="focus"
color="primary"
:events="events"
:event-color="getEventColor"
:now="today"
:type="type"
@click:event="showEvent"
@click:more="viewDay"
@click:date="viewDay"
@change="updateRange"
></v-calendar>
<v-menu
v-model="selectedOpen"
:close-on-content-click="false"
:activator="selectedElement"
offset-x
>
<v-card
color="grey lighten-4"
min-width="350px"
flat
>
<v-toolbar
:color="selectedEvent.color"
dark
>
<v-btn icon>
<v-icon>mdi-pencil</v-icon>
</v-btn>
<v-toolbar-title v-html="selectedEvent.name"></v-toolbar-title>
<v-spacer></v-spacer>
<v-btn icon>
<v-icon>mdi-heart</v-icon>
</v-btn>
<v-btn icon>
<v-icon>mdi-dots-vertical</v-icon>
</v-btn>
</v-toolbar>
<v-card-text>
<span v-html="selectedEvent.details"></span>
</v-card-text>
<v-card-actions>
<v-btn
text
color="secondary"
@click="selectedOpen = false"
>
Cancel
</v-btn>
</v-card-actions>
</v-card>
</v-menu>
</v-sheet>
</v-col>
</v-row>
</template>
<script>
export default {
data: () => ({
focus: '',
type: 'month',
typeToLabel: {
month: 'Month',
week: 'Week',
day: 'Day',
'4day': '4 Days',
},
start: null,
end: null,
selectedEvent: {},
selectedElement: null,
selectedOpen: false,
events: [],
colors: ['blue', 'indigo', 'deep-purple', 'cyan', 'green', 'orange', 'grey darken-1'],
names: ['Meeting', 'Holiday', 'PTO', 'Travel', 'Event', 'Birthday', 'Conference', 'Party'],
}),
computed: {
title () {
const { start, end } = this
if (!start || !end) {
return ''
}
const startMonth = this.monthFormatter(start)
const endMonth = this.monthFormatter(end)
const suffixMonth = startMonth === endMonth ? '' : endMonth
const startYear = start.year
const endYear = end.year
const suffixYear = startYear === endYear ? '' : endYear
const startDay = start.day + this.nth(start.day)
const endDay = end.day + this.nth(end.day)
switch (this.type) {
case 'month':
return `${startMonth} ${startYear}`
case 'week':
case '4day':
return `${startMonth} ${startDay} ${startYear} - ${suffixMonth} ${endDay} ${suffixYear}`
case 'day':
return `${startMonth} ${startDay} ${startYear}`
}
return ''
},
monthFormatter () {
return this.$refs.calendar.getFormatter({
timeZone: 'UTC', month: 'long',
})
},
},
mounted () {
this.$refs.calendar.checkChange()
},
methods: {
viewDay ({ date }) {
this.focus = date
this.type = 'day'
},
getEventColor (event) {
return event.color
},
setToday () {
this.focus = this.today
},
prev () {
this.$refs.calendar.prev()
},
next () {
this.$refs.calendar.next()
},
showEvent ({ nativeEvent, event }) {
const open = () => {
this.selectedEvent = event
this.selectedElement = nativeEvent.target
setTimeout(() => this.selectedOpen = true, 10)
}
if (this.selectedOpen) {
this.selectedOpen = false
setTimeout(open, 10)
} else {
open()
}
nativeEvent.stopPropagation()
},
updateRange ({ start, end }) {
const events = []
const min = new Date(`${start.date}T00:00:00`)
const max = new Date(`${end.date}T23:59:59`)
const days = (max.getTime() - min.getTime()) / 86400000
const eventCount = this.rnd(days, days + 20)
for (let i = 0; i < eventCount; i++) {
const allDay = this.rnd(0, 3) === 0
const firstTimestamp = this.rnd(min.getTime(), max.getTime())
const first = new Date(firstTimestamp - (firstTimestamp % 900000))
const secondTimestamp = this.rnd(2, allDay ? 288 : 8) * 900000
const second = new Date(first.getTime() + secondTimestamp)
events.push({
name: this.names[this.rnd(0, this.names.length - 1)],
start: this.formatDate(first, !allDay),
end: this.formatDate(second, !allDay),
color: this.colors[this.rnd(0, this.colors.length - 1)],
})
}
this.start = start
this.end = end
this.events = events
},
nth (d) {
return d > 3 && d < 21
? 'th'
: ['th', 'st', 'nd', 'rd', 'th', 'th', 'th', 'th', 'th', 'th'][d % 10]
},
rnd (a, b) {
return Math.floor((b - a + 1) * Math.random()) + a
},
formatDate (a, withTime) {
return withTime
? `${a.getFullYear()}-${a.getMonth() + 1}-${a.getDate()} ${a.getHours()}:${a.getMinutes()}`
: `${a.getFullYear()}-${a.getMonth() + 1}-${a.getDate()}`
},
},
}
</script>
##コード画面
いつも迷うファイルの位置関係はこんな感じです。
##感想&次の目標
最初はできるかなと不安でしたが構造をよく調べながらなんとかカレンダーは作ることが出来ました。他にも用途に合わせていろんな例がこちらのサイトには載っていて楽しいです。次はこのサイトに色を追加したり、ログイン機能を付けていきたいと思います。