VueStacks Calendar Series (1/3): Vue.js + Vuetify + Firebase

William Schulte
8 min readSep 6, 2019

--

Hey fellow fanatiks! Hopefully by now you have seen the stuff I have been posting about the VueStacks initiative, which I launched recently in order to help promote full stack Vue.js development. I just want to give special thanks to those who have starred and liked VueStacks on GitHub and Twitter so far! Much appreciated! Hopefully you find these demos useful!

If you haven’t yet heard of the VueStacks initiative, be sure to check it out on GitHub and Twitter. I’ve been uploading new demos to GitHub every few days over the past few weeks, including task managers, image uploaders, and calendars, all built with Firebase and MEVN configs. Stay tuned for more demos over the next little while!

Alright, so today I am going to show you how to build a functional event calendar using Vue.js and Firebase, based on one of the demos that I recently built and uploaded to GitHub. This demo is a modification of a Vuetify calendar component.

A small heads up, this is also my first time writing a tutorial article, so please just bear with me! We’ll get through this together!

There are several calendar examples available on the official Vuetify site. The one that we will be modifying today can be found here. Open up this link to Vuetify in GitHub and set it aside for just a moment. We’ll need to first set up a new Vue.js project. I’ll assume you already have Vue.js installed. If not, go ahead and install it with the following:

npm install -g @vue/cli

Start a new project by adding the following to the CLI:

vue create <your project name>

Select the following features for this project: Babel, Vuex, CSS Pre-processors, Linter.

After the project has been added, CD back into the project and install Vuetify:

vue add vuetify

Great! Onto the next steps!

Setting up the project

Now that we have the project installed, let’s create a new component in the component folder called Calendar.vue.

Navigate back to the GitHub repo containing the calendar. Copy the calendar in its entirety (template and script!) and paste it into Calendar.vue.

Great! Let’s clean up our App.js file a little before we tackle the Calendar component. Basically, we’ll just need to cut out the Vuetify banner, as well as change the component declaration from “HelloWorld” to “Calendar”. The App.js file should now look something like this:

<template>
<v-app>
<v-content>
<Calendar/>
</v-content>
</v-app>
</template>
<script>
import Calendar from './components/Calendar';
export default {
name: 'App',
components: {
Calendar,
},
data: () => ({
//
}),
};
</script>

Next, let’s install a few important dependencies. First , we’ll need to add Firebase:

npm install firebase

…and then vue-textarea-autosize, which will be needed for editing calendar events:

npm install vue-textarea-autosize

Now register those dependencies in the entry point file. Your main.js file should look like the following:

import Vue from 'vue'
import App from './App.vue'
import vuetify from './plugins/vuetify';
import firebase from "firebase";
import VueTextareaAutosize from 'vue-textarea-autosize'
Vue.use(VueTextareaAutosize)Vue.config.productionTip = falsefirebase.initializeApp({
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: ""
});
export const db = firebase.firestore();new Vue({
vuetify,
render: h => h(App)
}).$mount('#app')

As you can see, we’ll also be adding the Firebase SDK scripts inside of main.js, as opposed to create a separate init.js file. One less file to keep track of!

As you’ll recall, we opted to add a store.js file. We’ll use this to add Vuex state management to the calendar in part 2 of this series. But for now, let’s just ignore it!

Update the Template

Alright, now it’s time to tackle the calendar template! Go ahead and fire up the app with the following:

npm run serve

For this app, we’ll be using material design icons, so go ahead and add the following to the index.html file:

<link rel=”stylesheet” href=”https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">

Great! Next we’ll need to add a Vuetify dialog box to our component. In Calendar.vue, add the following inside the toolbar, above the today button:

<v-btn color=”primary” dark @click.stop=”dialog = true”>New Event</v-btn>

Now let’s add the dialog box. Below the first <v-sheet></v-sheet> element, add the following:

<v-dialog v-model="dialog" max-width="500">
<v-card>
<v-container>
<v-form @submit.prevent="addEvent">
<v-text-field
v-model="name"
type="text"
label="event name (required)"
>
</v-text-field>
<v-text-field
v-model="details"
type="text"
label="detail"
>
</v-text-field>
<v-text-field
v-model="start"
type="date"
label="start (required)"
>
</v-text-field>
<v-text-field
v-model="end"
type="date"
label="end (required)"
>
</v-text-field>
<v-text-field
v-model="color"
type="color"
label="color (click to open color menu)"
>
</v-text-field>
<v-btn
type="submit"
color="primary"
class="mr-4"
@click.stop="dialog = false"
>
create event
</v-btn>
</v-form>
</v-container>
</v-card>
</v-dialog>

At this point, you should be able to at least open the dialog box with the input fields. Go ahead and make sure that works!

Now let’s take care of the selected event card that will be triggered after clicking on the newly-created event. We’ll need to add a delete button to the tool bar, with selectedEvent.id as a parameter:

<v-toolbar :color="selectedEvent.color" dark>
<v-btn @click="deleteEvent(selectedEvent.id)" icon>
<v-icon>mdi-delete</v-icon>
</v-btn>
<v-toolbar-title v-html="selectedEvent.name"></v-toolbar-title>
<div class="flex-grow-1"></div>
</v-toolbar>

Next, we need to add textarea boxes to the selected event card with selectedEvent.details bound to it. We’ll need to add this below the toolbar. Later, we’ll add methods that will enable us to toggle the textarea box between editable and readonly. The textarea part of the template should now look like this:

<v-card-text>
<form v-if="currentlyEditing !== selectedEvent.id">
{{ selectedEvent.details }}
</form>
<form v-else>
<textarea-autosize
v-model="selectedEvent.details"
type="text"
style="width: 100%"
:min-height="100"
placeholder="add note">
</textarea-autosize>
</form>
</v-card-text>

Finally, let’s redo the buttons in the selected events to look something like this:

<v-card-actions>
<v-btn text color="secondary" @click="selectedOpen = false">
close
</v-btn>
<v-btn v-if="currentlyEditing !== selectedEvent.id" text @click.prevent="editEvent(selectedEvent)">
edit
</v-btn>
<v-btn text v-else type="submit" @click.prevent="updateEvent(selectedEvent)">
Save
</v-btn>
</v-card-actions>

The edit button triggers the toggling of the textarea from readonly by changing the condition of currentlyEditing. The save button triggers the updateEvent method, which we’ll set up momentarily. At this point, the code for the selected event card in its entirety should look like this:

<v-card color="grey lighten-4" :width="350" flat>
<v-toolbar :color="selectedEvent.color" dark>
<v-btn @click="deleteEvent(selectedEvent.id)" icon>
<v-icon>mdi-delete</v-icon>
</v-btn>
<v-toolbar-title v-html="selectedEvent.name"></v-toolbar-title>
<div class="flex-grow-1"></div>
</v-toolbar>
<v-card-text>
<form v-if="currentlyEditing !== selectedEvent.id">
{{ selectedEvent.details }}
</form>
<form v-else>
<textarea-autosize
v-model="selectedEvent.details"
type="text"
style="width: 100%"
:min-height="100"
placeholder="add note">
</textarea-autosize>
</form>
</v-card-text>
<v-card-actions>
<v-btn text color="secondary" @click="selectedOpen = false">
close
</v-btn>
<v-btn v-if="currentlyEditing !== selectedEvent.id" text @click.prevent="editEvent(selectedEvent)">
edit
</v-btn>
<v-btn text v-else type="submit" @click.prevent="updateEvent(selectedEvent)">
Save
</v-btn>
</v-card-actions>
</v-card>

So that’s pretty much it for the template! Let’s tackle the script tags!

Update the Script

First things first, let’s make sure to import Firebase into the project. To do this, we’ll simply add the following above export default {}

import { db } from ‘@/main’

Now let’s bind our instance to the inputs in the new event dialog box. To do this, we’ll need to add name, details, start, end, and color to data. Set a default hex code for color, possibly ‘#1976D2’ (dark blue), or whatever you want. Add dialog to data and make sure it is set to false. Also, get rid of the hard coded event data. We won’t need that since we’ll be configuring this calendar to enable dynamically inputted events. Data should now look like the following:

data: () => ({
today: new Date().toISOString().substr(0, 10),
focus: new Date().toISOString().substr(0, 10),
type: 'month',
typeToLabel: {
month: 'Month',
week: 'Week',
day: 'Day',
'4day': '4 Days',
},
name: null,
details: null,
start: null,
end: null,
color: '#1976D2', // default event color
currentlyEditing: null,
selectedEvent: {},
selectedElement: null,
selectedOpen: false,
events: [],
dialog: false,
}),

Now onto methods! Let’s start adding some of the core CRUD functions. We’ll start with addEvent () below the next () method:

async addEvent () {
if (this.name && this.start && this.end) {
await db.collection("calEvent").add({
name: this.name,
details: this.details,
start: this.start,
end: this.end,
color: this.color
})
this.getEvents()
this.name = '',
this.details = '',
this.start = '',
this.end = '',
this.color = ''
} else {
alert('You must enter event name, start, and end time')
}
},

The method will add our event name, detail, start and end times, and color to a collection in the Firestore database called ‘calEvent’. Be aware, however, that our events will not render on the calendar without start and end times. If we try to create an event without the times, then we’ll get an error. Therefore, we’ll need to set up a conditional inside the method that checks for a start and end time. Let’s also make the event name part of the conditional, since it doesn’t really make sense to create an event without an event name. You’ll also notice that in the same function, I’ve added this.getEvents(), which will be used to trigger the function that will retrieve inputted events from Firebase. We’ll tackle that method momentarily.

Let’s add some additional methods below addEvent ():

editEvent (ev) {
this.currentlyEditing = ev.id
},
async updateEvent (ev) {
await db.collection('calEvent').doc(this.currentlyEditing).update({
details: ev.details
})
this.selectedOpen = false,
this.currentlyEditing = null
},
async deleteEvent (ev) {
await db.collection("calEvent").doc(ev).delete()
this.selectedOpen = false,
this.getEvents()
},

The editEvent () method enables editing of the targeted event. Take a look back at the selectedEvent element in the calendar. v-if=”currentlyEditing !== selectedEvent.id” renders the event details in the selected event as readonly. “Else”, selectedEvent.detail will be rendered in a textarea element. Clicking “save” will trigger the updateEvent () method, in order to update the targeted event. deleteEvent () will then remove event from Firebase and the calendar.

Now, let’s add getEvents ().

async getEvents () {
let snapshot = await db.collection('calEvent').get()
const events = []
snapshot.forEach(doc => {
let appData = doc.data()
appData.id = doc.id
events.push(appData)
})
this.events = events
},

This method retrieves events from the database, which are then pushed into the events array. Items in the events array are then populated in the DOM ( see :events=”events” in <v-calendar></v-calendar> ).

This method will also be attached to the mounted () life cycle hook, which will then be called by the addEvents (), updateEvents (), and deleteEvents () methods after the instance has fully mounted.

The final component should look like this:

Now fire the calendar up on localhost:8080 and try it out! Add some new events and then trying re-editing them. Double check to make sure your events are saving in Firestore.

Conclusion

Phew, we made it to the end! Great job! Click here to get the full code in GitHub. Finally, click here to check out other demos in the VueStacks GitHub profile!

Up Next: VueStacks Calendar Series (2/3): Adding Vuex

--

--

William Schulte

Mobile App Developer, Coding Educator, LDS, Retro-gamer 🎮