
| Demo | |
|---|---|
| Downloads | |
| Repository | |
| Languages | JavaScript |
| Technologies | Vue.js |
Overview
In this Vue Scheduler tutorial, you will learn how to work with holidays:
-
How to define global and per-resource holidays.
-
How to display the holidays using custom Scheduler cell background color.
-
How to reject drag-and-drop operations that overlap holidays when the mouse is released.
The attached Vue 3 project uses the open-source DayPilot Lite for JavaScript Scheduler.
For an introduction to using the Vue Scheduler component, please see the following tutorial:
License
The sample uses DayPilot Lite, licensed under Apache License 2.0. You can use it in commercial projects. See the licensing information included in the download.
How to display global holidays in Vue Scheduler?
First, you need to define the global holidays. We will use a globalHolidays array which holds holiday dates. Each item specifies a date range, defined using start and end dates. The end date specifies the last day of the holiday (we need to take it into account later when checking the date range, see below).
The following example defines a holiday for August 9, 2027:
const globalHolidays = [
{ start: "2027-08-09", end: "2027-08-09", backColor: "#b6d7a8", headerColor: "#6aa84f" }
]
Now you need to configure the Vue Scheduler component to highlight the grid cells that overlap any of the holiday date ranges.
You can customize the Vue Scheduler cells using onBeforeCellRender event handler. This event handler is called once for each of the Scheduler cells. You can use it to set custom background color and other properties.
In the event handler, we will check if the cell date (args.cell.start and args.cell.end) overlaps with the global holidays.
const findHoliday = (holidays, start, end) => {
return holidays?.find(holiday => {
const holidayStart = new DayPilot.Date(holiday.start)
const holidayEnd = new DayPilot.Date(holiday.end).addDays(1)
return DayPilot.Util.overlaps(holidayStart, holidayEnd, start, end)
})
}
Note that we have added 1 day to the end date. The DayPilot.Util.overlaps() method works with exact time points. Our end date ("2027-08-09") is interpreted as the start of August 9, 2027 (ie. "2027-08-09T00:00:00"). In order to treat it as the end of August 9, 2027, you need to add one day using addDays(1). That will result in "2027-08-10T00:00:00", which is the time point we need.
If an overlap is detected, we set a custom background color using args.cell.properties.backColor property.
The shared findHoliday() helper above returns the first overlapping holiday. Bind the cell-render handler using @beforeCellRender="onBeforeCellRender". It calls highlightGlobalHolidays() to apply the green background. In src/components/Scheduler.vue, connect the listener in <template> and define the holiday data and handlers in <script setup>. The handler uses findHoliday() from above. Ellipses omit unrelated component settings and previously shown code:
<template>
<!-- ... -->
<DayPilotScheduler
...
@beforeCellRender="onBeforeCellRender"
...
/>
</template>
<script setup>
// ...
import { DayPilot, DayPilotScheduler } from '@daypilot/daypilot-lite-vue'
// ...
const globalHolidays = [
{ start: "2027-08-09", end: "2027-08-09", backColor: "#b6d7a8", headerColor: "#6aa84f" }
]
// ...
const onBeforeCellRender = (args) => {
highlightGlobalHolidays(args)
// ...
}
const highlightGlobalHolidays = (args) => {
const holiday = findHoliday(globalHolidays, args.cell.start, args.cell.end)
if (holiday) {
args.cell.properties.backColor = holiday.backColor
}
}
// ...
</script>
How to highlight global holidays in the Vue Scheduler time header?
You can also highlight the global holidays in the Vue Scheduler time header. Note the green bar displayed in the time header cell for August 9, 2027:
To highlight the holidays in the time header, you can use the onBeforeTimeHeaderRender event handler, which lets you customize the appearance of the time headers cells.
The logic is very similar - you need to check for overlaps of the time header cell start and end with the global holiday dates.
This time, we will not highlight the whole cell using the backColor property but add a small bar at the bottom using active areas. The day-level header has level === 1. Because each cell represents one whole day, left: 0 and right: 0 make the bar span that cell:
const onBeforeTimeHeaderRender = (args) => {
if (args.header.level !== 1) {
return
}
const holiday = findHoliday(globalHolidays, args.header.start, args.header.end)
if (holiday) {
args.header.areas = [
{ left: 0, right: 0, bottom: 0, height: 5, backColor: holiday.headerColor }
]
}
}
How to disable drag and drop operations for holidays?
In the previous example, we have changed the background color of the Scheduler cells but we didn’t change the cell behavior.
In DayPilot Lite, use onTimeRangeSelect to check the selected range when the mouse is released. Bind it using @timeRangeSelect="onTimeRangeSelect". When the checkbox is checked, overlapsHoliday() checks both global and resource-specific dates. Call args.preventDefault() before awaiting the alert so that the normal event-creation handler does not run. Clear the selection after the alert closes. The excerpt shows the checkbox binding, selection listeners and validation logic; the normal onTimeRangeSelected event-creation handler is omitted here and included in the full source below.
DayPilot Pro for JavaScript supports built-in disabled cells with real-time feedback during dragging. Lite does not provide that disabled-cell behavior: its drag preview can cross holidays, and this sample rejects the completed operation on release.
<template>
<div class="space">
<label><input type="checkbox" v-model="holidaysDisabled">Block drag and drop on holidays</label>
</div>
<DayPilotScheduler
ref="schedulerRef"
...
timeRangeSelectedHandling="Enabled"
...
@timeRangeSelect="onTimeRangeSelect"
@timeRangeSelected="onTimeRangeSelected"
...
/>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { DayPilot, DayPilotScheduler } from '@daypilot/daypilot-lite-vue'
const schedulerRef = ref(null)
const holidaysDisabled = ref(false)
// ...
const overlapsHoliday = (start, end, resource) => {
if (!holidaysDisabled.value) {
return false
}
const row = schedulerRef.value.control.rows.find(resource)
return findHoliday(globalHolidays, start, end) || findHoliday(row?.data.holidays, start, end)
}
const onTimeRangeSelect = async (args) => {
if (overlapsHoliday(args.start, args.end, args.resource)) {
args.preventDefault()
await DayPilot.Modal.alert("This range overlaps a holiday.")
args.control.clearSelection()
}
}
const onTimeRangeSelected = async (args) => {
// ...
}
// ...
</script>
How to display resource-specific holidays in the Vue Scheduler?
You can also define holidays for specific resources. In this example, we define the holidays using a custom holidays property of the resources[] array:
const resources = [
{ name: "Resource A", id: "A" },
{ name: "Resource B", id: "B", holidays: [
{ start: "2027-08-04", end: "2027-08-06", backColor: "#dd7e6b" }
] },
{ name: "Resource C", id: "C" },
{ name: "Resource D", id: "D", holidays: [
{ start: "2027-08-02", end: "2027-08-06", backColor: "#ffe599" }
] },
// ...
]
Now, it’s necessary to check the overlaps with the resource holidays in the onBeforeCellRender event handler. The cell’s args.cell.row.data.holidays provides the data directly; highlightResourceHolidays() passes it to the same findHoliday() helper. Call this function after highlightGlobalHolidays() so a resource-specific color takes precedence when the dates overlap. Resources without holidays need no special handling because the helper returns undefined for a missing array.
For selection and event changes, find the row by resource ID using rows.find(). This is how overlapsHoliday() retrieves the resource’s holiday list:
const row = schedulerRef.value.control.rows.find(resource)
The same check protects moving and resizing existing events. Bind @eventMove="onEventMove" and @eventResize="onEventResize". For a move, check the target resource (newResource); for a resize, keep the event’s resource. Both handlers cancel synchronously before the default update and display an alert. Like selection validation, these checks run on release, not continuously during dragging. The excerpt below connects the resources array shown above to the component and shows the rendering and move/resize listeners with their handlers. It reuses the earlier findHoliday() and overlapsHoliday() helpers; their definitions and unrelated settings are omitted.
<template>
<!-- ... -->
<DayPilotScheduler
...
:resources="resources"
...
eventMoveHandling="Update"
eventResizeHandling="Update"
@beforeCellRender="onBeforeCellRender"
...
@eventMove="onEventMove"
@eventResize="onEventResize"
/>
</template>
<script setup>
// ...
import { DayPilot, DayPilotScheduler } from '@daypilot/daypilot-lite-vue'
// ...
const resources = [
// ...
]
// ...
const onBeforeCellRender = (args) => {
highlightGlobalHolidays(args)
highlightResourceHolidays(args)
}
// ...
const highlightResourceHolidays = (args) => {
const holiday = findHoliday(args.cell.row.data.holidays, args.cell.start, args.cell.end)
if (holiday) {
args.cell.properties.backColor = holiday.backColor
}
}
// ...
const onEventMove = (args) => {
if (overlapsHoliday(args.newStart, args.newEnd, args.newResource)) {
args.preventDefault()
DayPilot.Modal.alert("This position overlaps a holiday.")
}
}
const onEventResize = (args) => {
if (overlapsHoliday(args.newStart, args.newEnd, args.e.resource())) {
args.preventDefault()
DayPilot.Modal.alert("This position overlaps a holiday.")
}
}
</script>
Full Source Code
And here is the full source code for the Vue Scheduler example which highlights global and resource-specific holidays in the Scheduler grid, with an option to reject selections, moves, and resizes that overlap holidays. Direct Vue props configure the Scheduler, and Vue event listeners connect the handlers. The checkbox is read when each operation finishes, so toggling it does not require a Scheduler redraw. Sample events load in onMounted().
<template>
<div class="space">
<label><input type="checkbox" v-model="holidaysDisabled">Block drag and drop on holidays</label>
</div>
<DayPilotScheduler
ref="schedulerRef"
startDate="2027-08-01"
:days="31"
:rowHeaderWidth="118"
scale="Day"
:timeHeaders="timeHeaders"
:resources="resources"
:events="events"
:durationBarVisible="false"
timeRangeSelectedHandling="Enabled"
eventMoveHandling="Update"
eventResizeHandling="Update"
@beforeCellRender="onBeforeCellRender"
@beforeTimeHeaderRender="onBeforeTimeHeaderRender"
@timeRangeSelect="onTimeRangeSelect"
@timeRangeSelected="onTimeRangeSelected"
@eventMove="onEventMove"
@eventResize="onEventResize"
/>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { DayPilot, DayPilotScheduler } from '@daypilot/daypilot-lite-vue'
const schedulerRef = ref(null)
const holidaysDisabled = ref(false)
const timeHeaders = [{ groupBy: "Month" }, { groupBy: "Day", format: "d" }]
const globalHolidays = [
{ start: "2027-08-09", end: "2027-08-09", backColor: "#b6d7a8", headerColor: "#6aa84f" }
]
const resources = [
{ name: "Resource A", id: "A" },
{ name: "Resource B", id: "B", holidays: [
{ start: "2027-08-04", end: "2027-08-06", backColor: "#dd7e6b" }
] },
{ name: "Resource C", id: "C" },
{ name: "Resource D", id: "D", holidays: [
{ start: "2027-08-02", end: "2027-08-06", backColor: "#ffe599" }
] },
{ name: "Resource E", id: "E" },
{ name: "Resource F", id: "F" },
{ name: "Resource G", id: "G" },
{ name: "Resource H", id: "H" },
{ name: "Resource I", id: "I" },
{ name: "Resource J", id: "J" }
]
const events = ref([])
onMounted(() => {
events.value = [
{ id: 1, start: "2027-08-02", end: "2027-08-04", text: "Event 1", resource: "A" },
{ id: 2, start: "2027-08-11", end: "2027-08-14", text: "Event 2", resource: "F" }
]
})
const findHoliday = (holidays, start, end) => {
return holidays?.find(holiday => {
const holidayStart = new DayPilot.Date(holiday.start)
const holidayEnd = new DayPilot.Date(holiday.end).addDays(1)
return DayPilot.Util.overlaps(holidayStart, holidayEnd, start, end)
})
}
const onBeforeCellRender = (args) => {
highlightGlobalHolidays(args)
highlightResourceHolidays(args)
}
const highlightGlobalHolidays = (args) => {
const holiday = findHoliday(globalHolidays, args.cell.start, args.cell.end)
if (holiday) {
args.cell.properties.backColor = holiday.backColor
}
}
const highlightResourceHolidays = (args) => {
const holiday = findHoliday(args.cell.row.data.holidays, args.cell.start, args.cell.end)
if (holiday) {
args.cell.properties.backColor = holiday.backColor
}
}
const onBeforeTimeHeaderRender = (args) => {
if (args.header.level !== 1) {
return
}
const holiday = findHoliday(globalHolidays, args.header.start, args.header.end)
if (holiday) {
args.header.areas = [
{ left: 0, right: 0, bottom: 0, height: 5, backColor: holiday.headerColor }
]
}
}
const overlapsHoliday = (start, end, resource) => {
if (!holidaysDisabled.value) {
return false
}
const row = schedulerRef.value.control.rows.find(resource)
return findHoliday(globalHolidays, start, end) || findHoliday(row?.data.holidays, start, end)
}
const onTimeRangeSelect = async (args) => {
if (overlapsHoliday(args.start, args.end, args.resource)) {
args.preventDefault()
await DayPilot.Modal.alert("This range overlaps a holiday.")
args.control.clearSelection()
}
}
const onTimeRangeSelected = async (args) => {
const modal = await DayPilot.Modal.prompt("Create a new event:", "Event 3")
args.control.clearSelection()
if (modal.canceled) {
return
}
args.control.events.add({
id: DayPilot.guid(),
start: args.start,
end: args.end,
resource: args.resource,
text: modal.result
})
}
const onEventMove = (args) => {
if (overlapsHoliday(args.newStart, args.newEnd, args.newResource)) {
args.preventDefault()
DayPilot.Modal.alert("This position overlaps a holiday.")
}
}
const onEventResize = (args) => {
if (overlapsHoliday(args.newStart, args.newEnd, args.e.resource())) {
args.preventDefault()
DayPilot.Modal.alert("This position overlaps a holiday.")
}
}
</script>
<style>
body .scheduler_default_main {
--dp-scheduler-event-background: #42a5f5;
--dp-scheduler-event-background-hover: #42a5f5;
}
.scheduler_default_event,
.scheduler_default_event_inner {
border-radius: 25px;
}
body .scheduler_default_event_inner {
color: #ffffff;
border: 1px solid #1e88e5;
padding-left: 10px;
}
</style>
You can download a complete Vue 3 project at the top of the article.
DayPilot




