Calendar event scheduling in Vue
You will need Node 6+ and npm installed on your machine.
Today, we’ll be creating a realtime event scheduling application. Using our application, users can easily schedule calendar events and receive realtime updates for new calendar events. We’ll be using Pusher’s pub/sub pattern to get realtime updates and Vue.js for creating the user interface.
Prerequisites
To follow this tutorial a basic understanding of Vue and Node.js is required. Please ensure that you have at least Node version 6>= installed before you begin.
We’ll be using these tools to build our application:
Here’s a demo of the final product:
Initializing the application and installing project dependencies
To get started, we will use the vue-cli to bootstrap our application. First, we’ll install the CLI by running npm install -g @vue/cli
in a terminal.
To create a Vuejs project using the CLI, we’ll run the following command:
vue create vue-eventapp
After running this command, you will be asked by the CLI to pick a preset. Please select the default preset.
Note: the @vue/cli 3.0 is still in beta and should not be used in production.
Next, run the following commands in the root folder of the project to install dependencies.
// install depencies required to build the server
npm install express body-parser dotenv pusher
// front-end dependencies
npm install pusher-js vue-fullcalendar@latest date-fns vuejs-datepicker
Start the app dev server by running npm run serve
in a terminal in the root folder of your project.
A browser tab should open on http://localhost:8080. The screenshot below should be similar to what you see in your browser:
Building the server
We’ll build our server using Express. Express is a fast, unopinionated, minimalist web framework for Node.js.
Create a file called server.js
in the root of the project and update it with the code snippet below
// server.js
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const Pusher = require('pusher');
const app = express();
const port = process.env.PORT || 4000;
const pusher = new Pusher({
appId: process.env.PUSHER_APP_ID,
key: process.env.PUSHER_KEY,
secret: process.env.PUSHER_SECRET,
cluster: process.env.PUSHER_CLUSTER,
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
res.header(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
next();
});
app.listen(port, () => {
console.log(`Server started on port ${port}`);
});
The calls to our endpoint will be coming in from a different origin. Therefore, we need to make sure we include the CORS headers (Access-Control-Allow-Origin
). If you are unfamiliar with the concept of CORS headers, you can find more information here.
Create a Pusher account and a new Pusher Channels app if you haven’t done so yet and get your appId
, key
and secret
.
Create a file in the root folder of the project and name it .env
. Copy the following snippet into the .env
file and ensure to replace the placeholder values with your Pusher credentials.
// .env
// Replace the placeholder values with your actual pusher credentials
PUSHER_APP_ID=PUSHER_APP_ID
PUSHER_KEY=PUSHER_KEY
PUSHER_SECRET=PUSHER_SECRET
PUSHER_CLUSTER=PUSHER_CLUSTER
We’ll make use of the dotenv
library to load the variables contained in the .env
file into the Node environment. The dotenv
library should be initialized as early as possible in the application.
Draw route
Let’s create a post route named schedule
, our application will send requests to this route when a user attempts to schedule events.
// server.js
require('dotenv').config();
...
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
...
});
app.post('/schedule', (req, res) => {
const {body} = req;
const data = {
...body,
};
pusher.trigger('schedule', 'new-event', data);
res.json(data);
});
...
- The request body will be sent as the data for the triggered Pusher event. The same object will be sent as a response to the user.
- The trigger is achieved using the
trigger
method which takes the trigger identifier(schedule
), an event name (new-event
), and a payload.
Start the server by running node server
in a terminal in the root folder of your project.
Calendar component
We’ll be creating a component(Calendar
), this component will display our calendar with the events scheduled by a user.
Create a file called Calendar.vue
in the src/components
folder of your project. Open the file and copy the code below into it:
// src/components/Calendar.vue
<template>
<full-calendar :events="events" />
</template>
<script>
import FullCalendar from 'vue-fullcalendar';
export default {
name: 'Calendar',
props: ['events'],
components: {
FullCalendar
}
}
</script>
<style>
.red {
background: rgb(235, 77, 77) !important;
color: whitesmoke !important;
}
.blue {
background: rgb(59, 59, 163) !important;
color: whitesmoke !important;
}
.orange {
background: orange !important;
color: white !important;
}
.green {
background: rgb(49, 155, 49) !important;
color: white !important;
}
.blue,
.orange,
.red,
.green {
font-size: 13px;
font-weight: 500;
text-transform: capitalize;
}
.event-item {
padding: 2px 0 2px 4px !important;
}
</style>
Our component will make use of the Vue-fullcalendar library. The library provides a full-calendar
component. Our component will receive an events
prop, a list of events to be bound to the full-calendar
component.
Also, we’ve added a few styles to the style
section of the component. These styles will help theme the events that will be bound to the calendar. Later in the tutorial, we’ll make use of these styles.
Event form component
Now that the we’ve set up the calendar component, let’s create an EventForm
component that will be used for scheduling new events.
Create a file EventForm.vue
in the src/components
folder. Copy the following into the file. We’ll break it down into three snippets, the template
snippet, followed by the script
and finally the style
snippet.
Template
The template will hold the form element that will handle creation of new events.
// /src/components/EventForm.vue
<template>
<form @submit.prevent="handleSubmit">
<div class="input-holder">
<input type="text" placeholder="Event title" v-model="event.title"/>
</div>
<div class="input-holder">
<date-picker :placeholder="'Start date'" v-model="event.start" />
</div>
<div class="input-holder">
<date-picker :placeholder="'End date'" v-model="event.end"/>
</div>
<div class="input-holder">
<textarea placeholder="Event description" rows="4" v-model="event.data.description" ></textarea>
</div>
<div class="input-holder">
<color-picker @colorPicked="selectColor" :color="event.cssClass" />
</div>
<div class="input-holder">
<button type="submit">Schedule</button>
</div>
</form>
</template>
In the template, we made use of the date-picker
component, Vuejs-datepicker. This component will handle start
and stop
date selection for our events. Also, we’ll be able to theme our events using a color-picker
. We haven’t gone about creating the color-picker
component but that’s coming soon.
Script
We’ll handle all the functionality of our component in the script section. Update the EventForm.vue
file to include the script section.
// src/components/EventForm.vue
<template>
...
</template>
<script>
import DatePicker from 'vuejs-datepicker';
import format from 'date-fns/format';
import ColorPicker from './ColorPicker';
export default {
name: 'EventForm',
data(){
return {
event: {
title: '',
start: '',
end: '',
cssClass: '',
data: {
description: ''
}
}
}
},
methods: {
async handleSubmit(){
const start = format(this.event.start, 'YYYY-MM-DD');
const end = format(this.event.end, 'YYYY-MM-DD');
const event = {
...this.event,
start,
end
}
const req = await fetch('http://localhost:4000/schedule', {
method: 'POST',
body: JSON.stringify(event),
headers: {
'content-type': 'application/json'
}
});
const res = await req.json();
this.resetValues();
},
selectColor(color){
this.event = {
...this.event,
cssClass: color
}
},
resetValues(){
this.event = {
title: '',
start: '',
end: '',
cssClass: '',
data: {
description: ''
}
}
}
},
components: {
DatePicker,
ColorPicker
}
}
</script>
In our scripts section, we have one data
property, event
, this will hold all the data needed to schedule an event.
The methods
property has three methods. The handleSubmit
method uses the date-fns library to format the start
and end
dates and then sends the data to the server to schedule an event. When a response is returned, the data in the response is emitted to the parent component. The resetvalues
method resets the values to their initial state.
The selectColor
method will be bound to the colorPicked
event emitted by the color-picker
component. This method is triggered whenever a color is selected.
Styles
Update the component with the following styles:
// src/components/EventForm.vue
<template>
...
</template>
<script>
...
</script>
<style>
form {
display: flex;
flex-direction: column;
margin-left: 30px;
}
.input-holder {
margin: 10px 0;
display: flex;
justify-content: flex-start;
}
.vdp-datepicker {
width: 100%;
}
.vdp-datepicker > div > input {
width: 77%;
}
.input-holder > button {
justify-self: center;
padding: 12px 25px;
border-radius: 0;
text-transform: uppercase;
font-weight: 600;
background: orangered;
color: white;
border: none;
font-size: 14px;
letter-spacing: -0.1px;
cursor: pointer;
}
input,
textarea {
padding: 12px 15px;
border: 2px solid rgba(0, 0, 0, 0.2);
border-radius: 0;
width: 70%;
opacity: 0.8;
font-size: 15px;
font-weight: normal;
}
input:focus,
textarea:focus,
button:focus {
border: 2px solid orangered;
outline: none;
box-shadow: 0 2px 3px 1px rgba(0, 0, 0, 0.2);
}
</style>
Next, let’s create the color-picker
component.
Color picker component
The color component will help us theme our event by letting us select a color that suits the event.
Create a file named ColorPicker.vue
in the src/components/
directory and update it with the code below:
// src/components/ColorPicker.vue
<template>
<div class="picker-main">
<h4 class="header">Select event theme</h4>
<div class="color-picker">
<div class="color" v-for="(theme, index) in colors" :key="index" @click="selectColor(theme)" :class="{selected: color === theme, [theme]: theme}"></div>
</div>
</div>
</template>
<script>
export default {
name: 'ColorPicker',
props: ['color'],
data(){
return {
colors: ['red', 'green', 'blue', 'orange']
}
},
methods: {
selectColor(color){
this.$emit('colorPicked', color);
}
}
}
</script>
<style scoped>
.picker-main {
width: 55%;
}
.header {
font-size: 14px;
text-transform: uppercase;
color: orangered;
letter-spacing: 0.5px;
margin: 0 0 6px;
text-align: left;
}
.color-picker {
display: flex;
justify-content: space-around;
}
.color-picker > .color {
width: 40px;
height: 40px;
border-radius: 50%;
border: 1.5px solid whitesmoke;
cursor: pointer;
}
.color.selected{
box-shadow: 0 2px 3px 1px rgba(0, 0, 0, 0.2);
border: 3px solid rgba(0, 0, 0, 0.4);
}
.color.red {
background: rgb(235, 77, 77);
}
.color.blue {
background: rgb(59, 59, 163);
}
.color.orange {
background: orange;
}
.color.green {
background: rgb(49, 155, 49);
}
</style>
In the template section, we loop through an array of colors
, creating a clickable element that emits a color
when clicked. The component takes a prop color
from the parent component.
App component
Now that we’ve built out the components to be used for our application, let’s render them in the App
component to create a usable application. Open the App.vue
file and update it to look like the snippet below:
// src/App.vue
<template>
<div id="app">
<div class="main">
<div class="calendar-holder">
<calendar :events="events" />
</div>
<div class="form-holder">
<h3>Schedule an event</h3>
<event-form />
</div>
</div>
</div>
</template>
<script>
import Calendar from './components/Calendar.vue'
import EventForm from './components/EventForm.vue'
import Pusher from 'pusher-js';
export default {
name: 'app',
components: {
Calendar,
EventForm
},
data(){
return {
events: [{
title : 'event1',
start : '2018-07-09',
cssClass : 'blue',
YOUR_DATA : {}
},
{
title : 'event2',
start : '2018-07-10',
end : '2018-07-13',
cssClass : ['orange']
}]
}
}
}
</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;
}
.main {
display: flex;
align-items: center;
}
.calendar-holder {
width: 65%;
}
.form-holder {
width: 35%;
}
.form-holder > h3 {
color: orangered;
text-transform: uppercase;
font-size: 16px;
text-align: left;
margin-left: 30px;
margin-bottom: 10px;
}
</style>
We’ve populated the data
property with a list of events. There is a method handleNewEvent
, this method is bound to the event-form
component. It appends the new event emitted from the event-form
component to the list of events.
You can now check out the current look of the application by visiting http://localhost:8080. Make sure both the vue dev server (yarn serve
) and the server (node server
) are running in separate terminals in the root folder of your project.
Introducing Pusher and realtime updates
Our application will update in realtime whenever there’s a new event added. We’ll be using Pusher’s pub/sub pattern to enable this functionality in our application.
We’ll update the App
component to include the created
lifecycle. It’s in this lifecycle we’ll initialise Pusher and listen for new events. Open the App.vue
file and update it to match the snippet below:
// /src/App.vue
<template>
...
</template>
<script>
import Calendar from './components/Calendar.vue'
import EventForm from './components/EventForm.vue'
import Pusher from 'pusher-js';
export default {
name: 'app',
components: {
...
},
data(){
...
},
created(){
const pusher = new Pusher('PUSHER_KEY', {
cluster: 'PUSHER_CLUSTER',
encrypted: true,
});
const channel = pusher.subscribe('schedule');
channel.bind('new-event', (data) => {
this.events = [
...this.events,
data
];
})
}
}
</script>
<style>
...
</style>
Note: ensure you replace the
PUSHER_KEY
andPUSHER_CLUSTER
placeholder strings with your actual credentials.
In the created
lifecycle, we initialized Pusher, subscribed to the schedule
channel and listened for the new-event
event. In the callback, we appended the data returned from the event to the list of events.
Test application
Open two browsers side by side to observe the realtime functionality of the application. Events scheduled on one browser are picked up by the other browser. Here’s a screenshot of two browsers side by side using the application:
Note: Ensure both the server and the dev server are up by running
npm run serve
andnode server
on separate terminal sessions.
Conclusion
We’ve created an event scheduling application using Vue.js, using Pusher to provide realtime functionality. You can think up new ideas to extend the application. It’ll be fun to see what you come up with. The source code for this tutorial is available on GitHub here.
12 July 2018
by Christian Nwamba