DevExtreme v26.1 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

JavaScript/jQuery Scheduler - Resolve Time Conflicts

This example addresses appointment time conflicts when using the DevExtreme JavaScript Scheduler. Use the Allow Overlapping Appointments select-box to select a desired time conflict resolution mode.

Backend API
$(() => { let popup; let form; let showConflictError = false; let overlappingRule = 'sameResource'; const scheduler = $('#scheduler').dxScheduler({ dataSource: data, views: ['day', 'week', 'workWeek', 'month'], currentView: 'week', currentDate: new Date(2026, 1, 10), startDayHour: 9, endDayHour: 19, height: 600, showAllDayPanel: false, allDayPanelMode: 'hidden', resources: [{ fieldExpr: 'assigneeId', dataSource: assignees, valueExpr: 'id', colorExpr: 'color', icon: 'user', allowMultiple: true, }], editing: { popup: { onInitialized: (e) => { popup = e.component; }, onHidden: () => { setConflictError(false); }, }, form: { labelMode: 'hidden', elementAttr: { class: 'hide-informer', id: 'form' }, onInitialized: (e) => { form = e.component; form.on('fieldDataChanged', (e) => { if (showConflictError && ['startDate', 'endDate', 'assigneeId', 'recurrenceRule'].includes(e.dataField)) { setConflictError(false); form.validate(); } }); }, items: [ { name: 'conflictInformer', template: () => $('<div>') .addClass('conflict-informer') .text('This time slot conflicts with another appointment.'), }, { name: 'mainGroup', items: [ 'subjectGroup', 'dateGroup', 'repeatGroup', { name: 'assigneeIdGroup', items: [ 'assigneeIdIcon', { name: 'assigneeIdEditor', isRequired: true, editorOptions: { onValueChanged: (e) => { if (e.value.length > 1) { e.component.option('value', [e.value[e.value.length - 1]]); } }, tagTemplate: (tagData) => $('<div />') .css('background-color', tagData.color) .css('border-color', tagData.color) .addClass('dx-tag-content') .append( $('<span />').text(tagData.text), $('<div />').addClass('dx-tag-remove-button'), ), }, }, ], }, ], }, 'recurrenceGroup', ], customizeItem: (item) => { if (item.name === 'allDayEditor' || item.name === 'recurrenceEndEditor') { item.label.visible = true; } else if (item.name === 'subjectEditor') { item.editorOptions.placeholder = 'Add title'; } if (item.name === 'startTimeEditor' || item.name === 'endTimeEditor') { item.validationRules = [ { type: 'required' }, { type: 'custom', message: 'Time conflict', ignoreEmptyValue: true, reevaluate: true, validationCallback: () => !showConflictError, }, ]; } }, }, }, onAppointmentAdding(e) { handleConflict(e, e.appointmentData); }, onAppointmentUpdating(e) { handleConflict(e, e.newData); }, }).dxScheduler('instance'); function setConflictError(show) { showConflictError = show; form?.option('elementAttr.class', show ? '' : 'hide-informer'); } function handleConflict(e, appointmentData) { if (!detectConflict(appointmentData)) { setConflictError(false); return; } e.cancel = true; if (popup?.option('visible')) { setConflictError(true); form.validate(); } else { const dialog = DevExpress.ui.dialog.custom({ showTitle: false, messageHtml: '<p id="conflict-dialog">This time slot conflicts with another appointment.</p>', buttons: [{ type: 'default', text: 'Close', stylingMode: 'contained', onClick: () => { dialog.hide(); }, }], }); dialog.show(); } } function getNextDay(date) { const next = new Date(date); next.setDate(next.getDate() + 1); return next; } function getEndDate(occurrence) { return occurrence.appointmentData.allDay ? getNextDay(occurrence.startDate) : occurrence.endDate; } function isOverlapping(a, b) { const aEnd = getEndDate(a); const bEnd = getEndDate(b); if (a.startDate >= bEnd || b.startDate >= aEnd) return false; if (overlappingRule === 'sameResource') { return a.appointmentData.assigneeId[0] === b.appointmentData.assigneeId[0]; } return true; } function detectConflict(newAppointment) { const allAppointments = scheduler.getDataSource().items(); const startDate = new Date(newAppointment.startDate); let endDate; if (newAppointment.recurrenceRule) { endDate = scheduler.getEndViewDate(); } else if (newAppointment.allDay) { endDate = getNextDay(startDate); } else { endDate = new Date(newAppointment.endDate); } const existingOccurrences = scheduler .getOccurrences(startDate, endDate, allAppointments) .filter((occurrence) => occurrence.appointmentData.id !== newAppointment.id); const newOccurrences = scheduler.getOccurrences(startDate, endDate, [newAppointment]); return newOccurrences.some((newOccurrence) => existingOccurrences.some((existingOccurrence) => isOverlapping(newOccurrence, existingOccurrence), ), ); } $('#overlapping-rule').dxSelectBox({ items: [ { value: 'sameResource', text: 'Different Resources' }, { value: 'allResources', text: 'Never' }, ], valueExpr: 'value', displayExpr: 'text', value: 'sameResource', onValueChanged(e) { overlappingRule = e.value; }, }); });
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script> <script>window.jQuery || document.write(decodeURIComponent('%3Cscript src="js/jquery.min.js"%3E%3C/script%3E'))</script> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/26.1.3/css/dx.light.css" /> <script src="js/dx.all.js?v=26.1.3"></script> <script src="data.js"></script> <link rel="stylesheet" type="text/css" href="styles.css" /> <script src="index.js"></script> </head> <body class="dx-viewport"> <div class="demo-container"> <div id="scheduler"></div> <div class="options"> <div class="option"> <span>Allow Overlapping Appointments</span> <div id="overlapping-rule"></div> </div> </div> </div> </body> </html>
.dx-scheduler-appointment { color: #242424; } .options { padding: 20px; background-color: rgba(191, 191, 191, 0.15); margin-top: 20px; } .option { margin-top: 10px; display: flex; align-items: center; gap: 10px; } .hide-informer .dx-item:has(.conflict-informer) { display: none !important; } .conflict-informer { background-color: #FCEAE8; color: #C50F1F; font-size: 12px; padding: 0 12px; height: 36px; line-height: 36px; box-sizing: border-box; margin-bottom: 8px; } .dx-dialog:has(#conflict-dialog) .dx-overlay-content { width: 280px; } .dx-dialog:has(#conflict-dialog) .dx-dialog-content { padding-bottom: 16px; } .dx-dialog:has(#conflict-dialog) .dx-dialog-buttons { padding-top: 0; padding-bottom: 16px; } .dx-dialog:has(#conflict-dialog) .dx-toolbar-center, .dx-dialog:has(#conflict-dialog) .dx-button { width: 100%; }
const assignees = [ { id: 1, text: 'Samantha Bright', color: '#A7E3A5' }, { id: 2, text: 'John Heart', color: '#CFE4FA' }, { id: 3, text: 'Todd Hoffman', color: '#F9E2AE' }, { id: 4, text: 'Sandra Johnson', color: '#F1BBBC' }, ]; const data = [ { id: 1, text: 'Website Re-Design Plan', startDate: new Date(2026, 1, 9, 9, 30), endDate: new Date(2026, 1, 9, 11, 30), assigneeId: [2], }, { id: 2, text: 'Install New Router in Dev Room', startDate: new Date(2026, 1, 9, 14, 30), endDate: new Date(2026, 1, 9, 15, 30), assigneeId: [3], }, { id: 3, text: 'Approve Personal Computer Upgrade Plan', startDate: new Date(2026, 1, 10, 10, 0), endDate: new Date(2026, 1, 10, 11, 0), assigneeId: [1], }, { id: 4, text: 'Final Budget Review', startDate: new Date(2026, 1, 10, 12, 0), endDate: new Date(2026, 1, 10, 13, 35), assigneeId: [1], }, { id: 5, text: 'Install New Database', startDate: new Date(2026, 1, 11, 9, 45), endDate: new Date(2026, 1, 11, 11, 15), assigneeId: [4], }, { id: 6, text: 'Approve New Online Marketing Strategy', startDate: new Date(2026, 1, 11, 12, 0), endDate: new Date(2026, 1, 11, 14, 0), assigneeId: [2], }, { id: 7, text: 'Prepare 2021 Marketing Plan', startDate: new Date(2026, 1, 12, 11, 0), endDate: new Date(2026, 1, 12, 13, 30), assigneeId: [3], }, { id: 8, text: 'Brochure Design Review', startDate: new Date(2026, 1, 12, 14, 0), endDate: new Date(2026, 1, 12, 15, 30), assigneeId: [2], }, { id: 9, text: 'Create Icons for Website', startDate: new Date(2026, 1, 13, 10, 0), endDate: new Date(2026, 1, 13, 11, 30), assigneeId: [1], }, { id: 10, text: 'Launch New Website', startDate: new Date(2026, 1, 13, 12, 20), endDate: new Date(2026, 1, 13, 14, 0), assigneeId: [4], }, { id: 11, text: 'Upgrade Server Hardware', startDate: new Date(2026, 1, 13, 14, 30), endDate: new Date(2026, 1, 13, 16, 0), assigneeId: [2], }, ];

Detect Conflicts

Handle the onAppointmentAdding and onAppointmentUpdating events to check if a new or updated appointment creates a time conflict. Set e.cancel = true to block the operation when necessary.

Call getOccurrences to expand recurring appointments into individual occurrences within the target range. Check for overlapping time range values.

Conflict Detection Modes

The demo implements the following detection modes:

  • Different Resources: appointments assigned to different resources (assignees) can overlap.
  • Never: overlapping appointments are not allowed, regardless of resource assignment.

To implement resource-aware checks, access appointments and compare their assigneeId field values.

Display Errors

When a conflict is detected, the demo displays the error as follows:

  • A message box.
  • An inline validation message (if an appointment edit form is active).

To display inline validation, configure a custom form item inside editing.form and use the customizeItem function to attach custom validationRules to time editors.