
| Demo | |
|---|---|
| Downloads | |
| Languages | JavaScript |
| Technologies | Vue.js |
Overview
Build a small equipment-reservation application with the Vue Scheduler from DayPilot Pro for JavaScript. The sample uses Vue 3, the Composition API and <script setup>.
-
Display a monthly timeline with daily cells and equipment locations.
-
Select dates to create a reservation with a title and status.
-
Move reservations between dates and resources, and resize their duration.
-
Edit or delete reservations using an event menu.
-
Navigate between months without losing changes.
-
Render titles and Tentative/Confirmed labels using a Vue event template.
All changes stay in memory for the current page session. Reloading restores the demo data. This example does not prevent overlapping bookings or provide production booking guarantees.
License
Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript.
Run the Vue project
Download and extract the sample. Use Node.js 20.19 or later in the 20.x line, or Node.js 22.12 or later. The project was tested with Node.js 24.21.0 and npm 11.19.0. In the project directory, run npm install, then npm run dev, and open the local URL printed by Vite. Run npm run build to create the production files in dist; npm run preview serves that build locally.
The manifest includes Vue, Vite and daypilot-pro-vue. The download pins the stable DayPilot Pro trial package; additional installation options are listed at npm.daypilot.org. Keep the included .npmrc when installing. You can also use the DayPilot UI Builder as an optional starting point.
src/App.vue displays src/components/Scheduler.vue, which contains the Scheduler configuration, data and interaction handlers. The local menu icons live in public/icons/daypilot.svg.
Display the Scheduler and configure its timeline
Import DayPilotScheduler to render the component and DayPilot for dates, IDs, dialogs and menus. Imports in <script setup> are available directly to the template. Mount the component like this:
src/App.vue
<template>
<Scheduler />
</template>
<script setup>
import Scheduler from './components/Scheduler.vue'
</script>
The following excerpts belong to Scheduler.vue; unrelated template properties and script declarations are omitted. Direct props supply Scheduler properties, and Vue listeners connect its event handlers.
<DayPilotScheduler
ref="schedulerRef"
:startDate="startDate"
:days="days"
scale="Day"
:timeHeaders="timeHeaders"
<!-- ... -->
>
import { DayPilot, DayPilotScheduler } from 'daypilot-pro-vue'
import { computed, nextTick, onMounted, ref } from 'vue'
const schedulerRef = ref(null)
const initialMonth = DayPilot.Date.today().firstDayOfMonth()
const startDate = ref(initialMonth)
const days = computed(() => startDate.value.daysInMonth())
const monthLabel = computed(() => startDate.value.toString('MMMM yyyy'))
const timeHeaders = [
{ groupBy: 'Month' },
{ groupBy: 'Day', format: 'd' }
]
scale="Day" gives each cell one day. The two header levels group those cells by month and label each day. The selected month starts on its first day, and daysInMonth() supplies its actual length. At article width, horizontal scrolling reveals the rest of the month; the sample does not squeeze reservation text into thirty narrow columns.
Load resources and reservations
Each resource needs a unique id. An event’s resource must match one of these IDs to place the reservation in the correct row. The rowHeaderColumns configuration reads name and location from each resource:
const rowHeaderColumns = [
{ title: 'Name', display: 'name', width: 150 },
{ title: 'Location', display: 'location', width: 100 }
]
const resources = ref([
{ id: 'camera', name: 'Camera kit', location: 'Studio A' },
{ id: 'lighting', name: 'Lighting kit', location: 'Studio A' },
{ id: 'audio', name: 'Audio kit', location: 'Studio B' },
{ id: 'mobile', name: 'Mobile kit', location: 'Store' },
{ id: 'projector', name: 'Projector', location: 'Studio B' }
])
Bind the resource rows using :resources="resources". Load reservations once after mounting with schedulerRef.value.control.update({ events: initialEvents }). The Scheduler then owns the event collection for the page session. Here is the first initial reservation; the remaining demo records are omitted:
// Load once: the Scheduler owns subsequent reservation changes.
const initialEvents = [
{
id: 'r1',
text: 'Product shoot',
start: initialMonth.addDays(2),
end: initialMonth.addDays(7),
resource: 'camera',
tags: { status: 'Confirmed' }
},
// ...
]
onMounted(() => {
schedulerRef.value.control.update({ events: initialEvents })
})
id identifies a reservation for later updates; text is its title. start and end specify the time interval, and resource chooses the equipment. Application-specific status is stored in tags.status. The sample generates this dataset once from initialMonth. There is no bound :events prop to reload an older array during later Vue updates. Navigation changes the visible dates while retaining the Scheduler’s current reservations.
End times are exclusive. A reservation occupying one complete day ends at midnight of the following day. The example above occupies the 3rd through the 7th: its end is midnight at the start of the 8th. Keep this convention when sending dates to a backend.
Create reservations by selecting dates
Drag across empty cells in an equipment row. With timeRangeSelectedHandling="Enabled" and @timeRangeSelected="onTimeRangeSelected", releasing the mouse opens the shared reservation editor. The selection supplies its dates and resource; DayPilot.guid() supplies a unique ID.
async function onTimeRangeSelected(args) {
try {
const reservation = await openEditor({
id: DayPilot.guid(),
text: '',
start: args.start,
end: args.end,
resource: args.resource,
tags: { status: 'Tentative' }
})
if (reservation) {
args.control.events.add(reservation)
}
} finally {
args.control.clearSelection()
}
}
Saving calls events.add() to add the reservation to the Scheduler and display it. Cancel returns no reservation and leaves the data unchanged. The finally block clears the selection after either result. args.control is the underlying Scheduler instance. You can also access it after mounting through schedulerRef.value.control; its methods include clearSelection().
The editor uses DayPilot.Modal.form(). Its Title validator rejects whitespace-only input and displays a message without changing the data. Status is a select field that writes to tags.status.
const form = [
{
name: 'Title',
id: 'text',
onValidate: args => {
if (!String(args.value || '').trim()) {
args.valid = false
args.message = 'Enter a reservation title.'
}
}
},
{
name: 'Status',
id: 'tags.status',
type: 'select',
options: [
{ id: 'Tentative', name: 'Tentative' },
{ id: 'Confirmed', name: 'Confirmed' }
]
}
]
async function openEditor(reservation) {
const draft = { ...reservation, tags: { ...reservation.tags } }
const modal = await DayPilot.Modal.form(form, draft, { width: 360 })
if (modal.canceled) {
return null
}
const text = String(modal.result.text || '').trim()
if (!text) {
return null
}
return { ...reservation, text, tags: { ...reservation.tags, status: modal.result.tags.status } }
}
The editor receives a copy, including a separate tags object, so typing or choosing a status cannot alter the original event before OK. The saved title is trimmed. The same editor will handle existing reservations later in the tutorial.
Move and resize reservations
Drag a reservation body to another date or equipment row. The Scheduler previews the proposed range while dragging. With eventMoveHandling="Update", it applies the new dates and resource to its event collection automatically when you release the reservation:
<DayPilotScheduler
...
eventMoveHandling="Update"
eventResizeHandling="Update"
...
>
...
</DayPilotScheduler>
Drag either event edge to change its duration. With eventResizeHandling="Update", the Scheduler applies the new dates automatically; the resource stays unchanged. Moving and resizing retain the reservation’s ID, title, status and other custom data.
No custom move or resize callback is needed for these local updates. Creation, editing and deletion use the same Scheduler event collection, so subsequent edits read the completed move or resize values. The move and resize handlers are available if you need to validate an operation before it is applied.
Edit and delete reservations
Each reservation has a small arrow that opens a native DayPilot context menu. Edit uses the same editor as an event click. Delete removes the reservation from the Scheduler using events.remove():
const contextMenu = new DayPilot.Menu({
items: [
{ text: 'Edit', onClick: args => editReservation(args.source.id()) },
{ text: 'Delete', onClick: args => deleteReservation(args.source.id()) }
]
})
async function editReservation(id) {
const scheduler = schedulerRef.value.control
const current = scheduler.events.find(id)
if (!current) {
return
}
const updated = await openEditor(current.data)
if (updated) {
scheduler.events.update(updated)
}
}
function onEventClick(args) {
editReservation(args.e.id())
}
function deleteReservation(id) {
const scheduler = schedulerRef.value.control
const event = scheduler.events.find(id)
if (event) {
scheduler.events.remove(event)
}
}
events.find(id) reads the current Scheduler event, including any dates or resource changed by dragging. The shared editor receives current.data and edits a copy. Only OK commits the result with events.update(); Cancel keeps the existing title and status. The saved result retains the ID, dates, resource and other custom fields. Selecting Confirmed changes its label and color; it does not lock the reservation or start an approval workflow.
The render hook adds an active area with action: 'ContextMenu'. The icon opens the menu without opening the event editor. It references the bundled SVG using a relative path, so no external icon request is required. The complete render hook appears in the customization section below.
Navigate between months
Previous and Next move by one month; Today returns to the current month. The label and day count are computed from startDate, so February and December/January transitions need no special cases. An adjacent month can be empty because the current reservations may all fall in another month. Navigation retains the Scheduler event collection, including additions, edits and deletions.
<div class="toolbar">
<button @click="changeMonth(-1)">Previous</button>
<button @click="showToday">Today</button>
<button @click="changeMonth(1)">Next</button>
<strong>{{ monthLabel }}</strong>
</div>
async function changeMonth(offset) {
startDate.value = startDate.value.addMonths(offset)
await nextTick()
schedulerRef.value.control.scrollTo(startDate.value)
}
async function showToday() {
startDate.value = DayPilot.Date.today().firstDayOfMonth()
await nextTick()
schedulerRef.value.control.scrollTo(startDate.value)
}
The handlers change the displayed month without regenerating reservations or resetting edits. After updating the date ref, they wait for nextTick() before requesting a scroll with scrollTo(). The horizontal scrollbar lets you reach dates outside the current viewport.
Customize events with a Vue template
The #event slot replaces event content with two short lines. Normal Vue interpolation displays the title as text, and the label makes the status understandable without relying on color. The following slot sits inside DayPilotScheduler:
<template #event="{ event }">
<div class="reservation">
<div class="reservation-title">{{ event.text() }}</div>
<div class="reservation-status">{{ event.data.tags?.status || 'Tentative' }}</div>
</div>
</template>
The .reservation class reserves 28 pixels on the right for the menu arrow. A 48-pixel event height and six-pixel row margins leave room for both lines. Long titles are truncated with an ellipsis instead of painting under the menu.
Use onBeforeEventRender to derive presentation from status. Bind the hook with :onBeforeEventRender="onBeforeEventRender". It also creates the menu active area:
function onBeforeEventRender(args) {
const confirmed = args.data.tags?.status === 'Confirmed'
args.data.backColor = confirmed ? '#dcebdc' : '#fff0cc'
args.data.borderColor = confirmed ? '#719471' : '#b79a50'
args.data.fontColor = '#243324'
args.data.areas = [
{
right: 5,
top: 13,
width: 20,
height: 20,
symbol: 'icons/daypilot.svg#minichevron-down-4',
fontColor: '#354535',
action: 'ContextMenu',
visibility: 'Visible',
style: 'cursor: pointer;',
toolTip: 'Reservation menu'
}
]
}
Complete component and next steps
The download contains the complete src/components/Scheduler.vue, including the scoped event/toolbar styles. src/App.vue mounts it without additional application plumbing. For more options, see resource header columns, the Vue reservation application tutorial and changing the visible date.
This application keeps reservations in memory; browser reload restores demo data. To integrate a backend, load resources and events from your API and persist create, edit, delete, move and resize operations. Validate permissions, dates and booking conflicts on the server, and handle failed saves before treating a booking as committed. The client-side Tentative/Confirmed field alone provides no booking guarantee.
History
September 29, 2026: Rebuilt the sample with Vue 3 Composition API and Vite. Added equipment locations, reservation creation/editing/deletion, automatic Scheduler move/resize updates, month navigation and a title/status event template. Updated the screenshots and setup instructions.
DayPilot




