
| Demo | |
|---|---|
| Downloads | |
| Repository | |
| Languages | JavaScript |
| Technologies | Vue.js |
| Code License | Apache License 2.0 (Open-Source) |
Overview
-
How to add a calendar UI with day, week, and month views to your Vue application.
-
The toolbar lets users switch between a single day, a full week, and a month.
-
All views share the same data source and are updated automatically.
-
The appearance of calendar events is defined using a custom Vue component, and applied using Vue templates.
-
The Vue project uses the open-source DayPilot Lite for JavaScript scheduling library.
Creating Day, Week, and Month Calendar Views in Vue
To add a calendar UI to our Vue application, we will use the open-source DayPilot Lite for JavaScript library:
npm install @daypilot/daypilot-lite-vue
First, we need to create the calendar views. Each view will be defined using a standalone calendar component, and the components will be dynamically hidden or displayed when switching between views.
The day view is created using the Vue Calendar component from DayPilot:
<DayPilotCalendar
id="day"
:viewType="'Day'"
:startDate="startDate"
:visible="viewType === 'Day'"
:events="events"
...
>
...
</DayPilotCalendar>
The week view is created using the Vue Calendar component as well, this time with viewType set to "Week".
<DayPilotCalendar
id="week"
:viewType="'Week'"
:startDate="startDate"
:visible="viewType === 'Week'"
:events="events"
...
>
...
</DayPilotCalendar>
The month view is created using the Vue Monthly Calendar component:
<DayPilotMonth
id="month"
:startDate="startDate"
:visible="viewType === 'Month'"
:events="events"
...
>
...
</DayPilotMonth>
You can see that all components use the same values of :startDate and :events props. The :visible value is set depending on the currently selected view.
The :startDate prop (Calendar API docs, Month API docs) points to the startDate reactive variable which stores the currently-selected date:
const startDate = ref(DayPilot.Date.today());
Changing this value will automatically change the current date for all calendar views. All calendar components will automatically calculate the correct start and end dates to ensure that they display the correct date range that includes the specified day.
The :events prop (Calendar API docs, Month API docs) points to an events variable that stores the event data. Since this is shared for all views, it is not necessary to reload events from the server when the user changes a view.
const events = ref([]);
When loading data from a server, include the selected month and the adjoining week on each side so all dates displayed by these views are covered. Request that range again when the selected date moves outside the loaded range.
In our application, we don’t load the data from the server but we use a static array for simplicity:
const loadEvents = () => {
const firstDay = DayPilot.Date.today().firstDayOfWeek().addDays(1);
events.value = [
{ id: 1, start: firstDay.addHours(9), end: firstDay.addHours(10), text: "Event 1", color: "#93c47d"},
{ id: 2, start: firstDay.addDays(1).addHours(11), end: firstDay.addDays(1).addHours(12), text: "Event 2", color: "#cccccc"},
{ id: 3, start: firstDay.addDays(1).addHours(13), end: firstDay.addDays(1).addHours(15), text: "Event 3", color: "#6fa8dc"},
{ id: 4, start: firstDay.addHours(15), end: firstDay.addHours(16), text: "Event 4", color: "#f6b26b"},
{ id: 5, start: firstDay.addHours(11), end: firstDay.addHours(14), text: "Event 5", color: "#ffd966"},
];
};
The :visible prop sets the component visibility (Calendar API docs, Month API docs), depending on the current view which is stored in the viewType variable.
const viewType = ref("Week");
The initial value is set to "Week" which shows the week view on the initial load.
Keep event changes in the shared events array as well. When a user selects an empty time range, the handler clears the selection and, unless the prompt is canceled, appends a new event to the array. Vue passes the new list to all three views:
const onTimeRangeSelected = async (args) => {
const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
const calendar = args.control;
calendar.clearSelection();
if (modal.canceled) { return; }
events.value = [...events.value, {
start: args.start,
end: args.end,
id: DayPilot.guid(),
text: modal.result
}];
};
Editing an event replaces its text and color in the same array; deleting it filters the array by ID. The move and resize handlers prevent the control's default update and replace the event's start and end values in shared state. This keeps changes made in one view visible after switching to another. The sample stores data in memory, so reloading the page restores the example events.
Toolbar with Buttons for Switching the Calendar Views
To allow switching between the calendar views, we add a toolbar with three buttons above the current calendar view:
<div class="buttons">
<button @click="viewType='Day'" :class="{ selected: viewType === 'Day' }">Day</button>
<button @click="viewType='Week'" :class="{ selected: viewType === 'Week' }">Week</button>
<button @click="viewType='Month'" :class="{ selected: viewType === 'Month' }">Month</button>
</div>
The @click event simply sets the value of the viewType variable. This variable is reactive and Vue automatically updates visibility of the calendar views whenever the value changes.
Changing the Calendar Date using a Date Picker in Vue
To let users change the current date, we add a date picker using the DayPilot Navigator component.
<DayPilotNavigator
id="navigator"
:selectMode="viewType"
:showMonths="3"
:skipMonths="3"
@timeRangeSelected="args => startDate = args.day">
</DayPilotNavigator>
The date picker can be configured to display one or more months (we use :showMonths="3" to display three months, see also the API docs).
The :selectMode prop sets the selection size (API docs). We will point it to the viewType variable to reflect the current view ("Day", "Week", or "Month").
Using Vue Templates to Define Calendar Event Content
The Vue calendar component supports named slots that you can use to define templates for event boxes.
This is helpful if you want to style the text or add buttons, icons and other interactive content.
To use a Calendar event slot, add a nested template marked with the #event attribute. The Vue Calendar component passes an instance of a DayPilot.Event object as event to the template.
This first example renders the event text and start time directly in the slot, without a custom component. The startDate and events props use the shared values introduced above:
<DayPilotCalendar viewType="Week" :startDate="startDate" :events="events">
<template #event="{event}">
{{event.text()}} {{event.start().toString("h:mm tt")}}
</template>
</DayPilotCalendar>
You can also use custom Vue components in the template.
We will use this approach in our Vue application as well - it will help us encapsulate the logic and reuse the template in all calendar views.
<DayPilotCalendar
id="week"
:viewType="'Week'"
:startDate="startDate"
:visible="viewType === 'Week'"
:events="events"
:eventBorderRadius="5"
:durationBarVisible="false"
@beforeEventRender="onBeforeEventRender"
@timeRangeSelected="onTimeRangeSelected"
@eventMove="onEventMove"
@eventResize="onEventResize"
>
<template #event="{event}">
<CalendarEvent
:event="event"
@edit="onEventEdit"
@delete="onEventDelete"
/>
</template>
</DayPilotCalendar>
Here, we have used a custom CalendarEvent Vue component that defines more complex HTML and custom icons.
Vue.js Calendar Event Component with Custom Content
This is our CalendarEvent component that specifies the content of calendar events.
It shows a header with the event text and two icons on the right side:
-
“delete”
-
“edit”
Clicking the icons calls the onDelete and onEdit callback props, respectively. The parent supplies these callbacks using @delete and @edit. Click propagation is stopped so an icon click does not also trigger the calendar event click action.
The icons are defined as SVG symbols in the daypilot.svg file.
<template>
<div :data-event-id="event.id()" :class="['event-header', { 'use-header': useHeader }]">
{{ event.text() }}
<span>
<svg @click.stop="handleDelete" aria-label="Delete event" role="button" tabindex="0" @keydown.enter.stop="handleDelete">
<use href="/icons/daypilot.svg#x-2"></use>
</svg>
<svg @click.stop="handleEdit" aria-label="Edit event" role="button" tabindex="0" @keydown.enter.stop="handleEdit">
<use href="/icons/daypilot.svg#edit"></use>
</svg>
</span>
</div>
</template>
<script setup>
const {event, useHeader, onEdit, onDelete} = defineProps({
event: {type: Object, required: true},
useHeader: {type: Boolean, required: false, default: true},
onEdit: {type: Function, required: false},
onDelete: {type: Function, required: false},
});
const handleEdit = () => {
if (onEdit) {
onEdit(event);
}
};
const handleDelete = () => {
if (onDelete) {
onDelete(event);
}
};
</script>
<style scoped>
.event-header {
padding: 4px;
color: #333333;
border-radius: 5px;
}
.use-header.event-header {
background-color: #ffffff66;
margin-top: 2px;
margin-left: 2px;
margin-right: 2px;
}
.event-header span {
float: right;
}
.event-header svg {
width: 16px;
height: 16px;
margin-left: 4px;
cursor: pointer;
color: #33333366;
}
.event-header svg:hover {
color: #333333;
}
</style>
The CalendarEvent component can be used in templates for <DayPilotCalendar> and <DayPilotMonth> components. Reusing the same component for all calendar views ensures that the events have the same appearance.
In case of <DayPilotMonth>, we use a slightly different styling because the event box is much smaller in the monthly calendar. This custom styling mode that doesn’t use a highlighted header can be activated using :useHeader="false":
<DayPilotMonth
id="month"
:startDate="startDate"
:visible="viewType === 'Month'"
:events="events"
@beforeEventRender="onBeforeEventRender"
@timeRangeSelected="onTimeRangeSelected"
@eventMove="onEventMove"
@eventResize="onEventResize"
>
<template #event="{event}">
<CalendarEvent
:event="event"
:use-header="false"
@edit="onEventEdit"
@delete="onEventDelete"
/>
</template>
</DayPilotMonth>
Full Source Code
Here is the full source code of Calendar.vue, which integrates three calendar views (daily, weekly, and monthly calendar), a date picker for changing the visible date and buttons for changing the calendar view.
<template>
<div style="display: flex">
<div style="margin-right: 10px">
<DayPilotNavigator
id="navigator"
:selectMode="viewType"
:showMonths="3"
:skipMonths="3"
@timeRangeSelected="args => startDate = args.day">
</DayPilotNavigator>
</div>
<div style="flex-grow: 1;">
<div class="buttons">
<button @click="viewType='Day'" :class="{ selected: viewType === 'Day' }">Day</button>
<button @click="viewType='Week'" :class="{ selected: viewType === 'Week' }">Week</button>
<button @click="viewType='Month'" :class="{ selected: viewType === 'Month' }">Month</button>
</div>
<DayPilotCalendar
id="day"
:viewType="'Day'"
:startDate="startDate"
:visible="viewType === 'Day'"
:events="events"
@beforeEventRender="onBeforeEventRender"
@timeRangeSelected="onTimeRangeSelected"
@eventMove="onEventMove"
@eventResize="onEventResize"
>
<template #event="{event}">
<CalendarEvent
:event="event"
@edit="onEventEdit"
@delete="onEventDelete"
/>
</template>
</DayPilotCalendar>
<DayPilotCalendar
id="week"
:viewType="'Week'"
:startDate="startDate"
:visible="viewType === 'Week'"
:events="events"
:eventBorderRadius="5"
:durationBarVisible="false"
@beforeEventRender="onBeforeEventRender"
@timeRangeSelected="onTimeRangeSelected"
@eventMove="onEventMove"
@eventResize="onEventResize"
>
<template #event="{event}">
<CalendarEvent
:event="event"
@edit="onEventEdit"
@delete="onEventDelete"
/>
</template>
</DayPilotCalendar>
<DayPilotMonth
id="month"
:startDate="startDate"
:visible="viewType === 'Month'"
:events="events"
@beforeEventRender="onBeforeEventRender"
@timeRangeSelected="onTimeRangeSelected"
@eventMove="onEventMove"
@eventResize="onEventResize"
>
<template #event="{event}">
<CalendarEvent
:event="event"
:use-header="false"
@edit="onEventEdit"
@delete="onEventDelete"
/>
</template>
</DayPilotMonth>
</div>
</div>
</template>
<script setup>
import {DayPilot, DayPilotCalendar, DayPilotMonth, DayPilotNavigator} from '@daypilot/daypilot-lite-vue';
import {ref, onMounted} from 'vue';
import CalendarEvent from "@/components/CalendarEvent.vue";
const events = ref([]);
const viewType = ref("Week");
const startDate = ref(DayPilot.Date.today());
const onTimeRangeSelected = async (args) => {
const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 1");
const calendar = args.control;
calendar.clearSelection();
if (modal.canceled) { return; }
events.value = [...events.value, {
start: args.start,
end: args.end,
id: DayPilot.guid(),
text: modal.result
}];
};
const onBeforeEventRender = (args) => {
args.data.barHidden = true;
args.data.borderRadius = "5px";
args.data.backColor = (args.data.color || "#cccccc") + "cc";
args.data.borderColor = "darker";
};
const colors = [
{name: "Green", id: "#93c47d"},
{name: "Blue", id: "#6fa8dc"},
{name: "Orange", id: "#f6b26b"},
{name: "Yellow", id: "#ffd966"},
{name: "Gray", id: "#cccccc"},
];
const onEventEdit = async (event) => {
const form = [
{name: "Text", id: "text", type: "text"},
{name: "Color", id: "color", type: "select", options: colors},
];
const modal = await DayPilot.Modal.form(form, event.data);
if (modal.canceled) {
return;
}
events.value = events.value.map(e => e.id === event.id()
? {...e, text: modal.result.text, color: modal.result.color}
: e);
};
const onEventDelete = (event) => {
const data = event.data;
events.value = events.value.filter(e => e.id !== data.id);
}
const onEventMove = (args) => {
args.preventDefault();
events.value = events.value.map(e => e.id === args.e.id()
? {...e, start: args.newStart, end: args.newEnd}
: e);
};
const onEventResize = (args) => {
args.preventDefault();
events.value = events.value.map(e => e.id === args.e.id()
? {...e, start: args.newStart, end: args.newEnd}
: e);
};
const loadEvents = () => {
const firstDay = DayPilot.Date.today().firstDayOfWeek().addDays(1);
events.value = [
{ id: 1, start: firstDay.addHours(9), end: firstDay.addHours(10), text: "Event 1", color: "#93c47d"},
{ id: 2, start: firstDay.addDays(1).addHours(11), end: firstDay.addDays(1).addHours(12), text: "Event 2", color: "#cccccc"},
{ id: 3, start: firstDay.addDays(1).addHours(13), end: firstDay.addDays(1).addHours(15), text: "Event 3", color: "#6fa8dc"},
{ id: 4, start: firstDay.addHours(15), end: firstDay.addHours(16), text: "Event 4", color: "#f6b26b"},
{ id: 5, start: firstDay.addHours(11), end: firstDay.addHours(14), text: "Event 5", color: "#ffd966"},
];
};
onMounted(() => {
loadEvents();
});
</script>
<style>
/* override the built-in theme, must not be scoped */
body .calendar_default_event_inner {
padding: 0px;
}
</style>
You can download the full Vue project using the link at the top of the tutorial.
DayPilot




