Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions applications/UIWidgets/Guides/SchedulerPreserveChanges/index.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#scheduler {
height: 600px;
}

/* "Discard Changes" button inside the appointment edit form */
.discard-btn-item {
padding-top: 8px !important;
border-top: 1px solid #eee;
margin-top: 4px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div id="scheduler"></div>
220 changes: 220 additions & 0 deletions applications/UIWidgets/Guides/SchedulerPreserveChanges/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
$(function () {
const appointments = [
{
id: 1,
text: 'Team Stand-up',
startDate: new Date(2026, 4, 8, 9, 0),
endDate: new Date(2026, 4, 8, 9, 30),
description: 'Daily morning sync with the full team.'
},
{
id: 2,
text: 'Design Review',
startDate: new Date(2026, 4, 8, 11, 0),
endDate: new Date(2026, 4, 8, 12, 0),
description: 'Review latest UI mockups and prototypes.'
},
{
id: 3,
text: 'Sprint Planning',
startDate: new Date(2026, 4, 11, 14, 0),
endDate: new Date(2026, 4, 11, 16, 0),
description: 'Plan tasks for the upcoming sprint.'
},
{
id: 4,
text: 'Client Demo',
startDate: new Date(2026, 4, 12, 10, 0),
endDate: new Date(2026, 4, 12, 11, 30),
description: 'Showcase new features to the client.'
},
{
id: 5,
text: 'Retrospective',
startDate: new Date(2026, 4, 13, 15, 0),
endDate: new Date(2026, 4, 13, 16, 0),
description: 'Sprint retrospective — what went well / what to improve.'
}
];
const DRAFT_PREFIX = 'dx-scheduler-draft-';

function getDraftKey(appointmentId) {
return DRAFT_PREFIX + (appointmentId != null ? appointmentId : 'new');
Comment thread
vladaskorohodova marked this conversation as resolved.
Comment thread
vladaskorohodova marked this conversation as resolved.
}

function saveDraft(appointmentId, formData) {
const key = getDraftKey(appointmentId);
const serializable = {
text: formData.text,
description: formData.description,
startDate: formData.startDate instanceof Date
? formData.startDate.toISOString()
: formData.startDate,
endDate: formData.endDate instanceof Date
? formData.endDate.toISOString()
: formData.endDate,
allDay: formData.allDay || false
};
localStorage.setItem(key, JSON.stringify(serializable));
}

function loadDraft(appointmentId) {
const key = getDraftKey(appointmentId);
const raw = localStorage.getItem(key);
if (!raw) return null;
const data = JSON.parse(raw);
if (data.startDate) data.startDate = new Date(data.startDate);
if (data.endDate) data.endDate = new Date(data.endDate);
return data;
Comment on lines +45 to +68
Comment on lines +65 to +68
}

function clearDraft(appointmentId) {
localStorage.removeItem(getDraftKey(appointmentId));
}
let isSaved = false;
let currentAppointmentId = null;
let popupHidingHandler = null;
let currentPopup = null;


function onCanceled(form, originalData) {
if (isSaved) return;

const formData = form.option('formData');
const appointmentId = originalData && originalData.id != null
? originalData.id
: null;

saveDraft(appointmentId, formData);

DevExpress.ui.notify({
message: 'Draft saved. Your unsaved changes will be restored next time you open this appointment.',
type: 'warning',
displayTime: 4500,
position: { my: 'bottom center', at: 'bottom center', of: window }
});
}
$('#scheduler').dxScheduler({
dataSource: appointments,
currentDate: new Date(2026, 4, 8),
currentView: 'week',
views: ['day', 'week', 'month'],
startDayHour: 8,
endDayHour: 19,
height: 600,
editing: {
form: {
labelMode: 'floating',
items: [
{
itemType: 'group',
name: 'mainGroup',
items: [
'subjectGroup',
'dateGroup',
'descriptionGroup',
'repeatGroup',
'resourcesGroup'
]
},
{
itemType: 'group',
name: 'recurrenceGroup'
}
]
}
},
onAppointmentFormOpening: function (e) {
const form = e.form;
const popup = e.popup;
const appointmentData = e.appointmentData || {};

isSaved = false;
currentAppointmentId = appointmentData.id != null ? appointmentData.id : null;
const draft = loadDraft(currentAppointmentId);
if (draft) {
const mergedData = $.extend({}, form.option('formData'), {
text: draft.text,
description: draft.description,
startDate: draft.startDate,
endDate: draft.endDate,
allDay: draft.allDay
});
form.option('formData', mergedData);
const items = form.option('items');
const hasDiscardBtn = items.some(function (item) {
return item.name === 'discardChangesButton';
});
Comment on lines +144 to +147

if (!hasDiscardBtn) {
const originalData = $.extend({}, appointmentData);

items.push({
itemType: 'button',
name: 'discardChangesButton',
horizontalAlignment: 'left',
cssClass: 'discard-btn-item',
buttonOptions: {
text: 'Discard Changes',
type: 'danger',
stylingMode: 'outlined',
icon: 'undo',
onClick: function () {
clearDraft(currentAppointmentId);

form.option('formData', $.extend({}, form.option('formData'), {
text: originalData.text,
Comment thread
vladaskorohodova marked this conversation as resolved.
description: originalData.description,
startDate: originalData.startDate,
endDate: originalData.endDate,
allDay: originalData.allDay || false
}));

form.option('items', form.option('items').filter(function (item) {
return item.name !== 'discardChangesButton';
}));

DevExpress.ui.notify({
message: 'Changes discarded. Form reset to the last saved state.',
type: 'success',
displayTime: 3000
});
}
}
});

form.option('items', items);
}

DevExpress.ui.notify({
message: 'Unsaved draft restored. Use "Discard Changes" to revert to the saved appointment.',
type: 'info',
displayTime: 4000,
position: { my: 'bottom center', at: 'bottom center', of: window }
});
}
if (currentPopup && popupHidingHandler) {
currentPopup.off('hiding', popupHidingHandler);
}

currentPopup = popup;
popupHidingHandler = function () {
if (!isSaved) {
onCanceled(form, appointmentData);
}
};

popup.on('hiding', popupHidingHandler);
},

onAppointmentAdding: function () {
isSaved = true;
clearDraft(null);
},

onAppointmentUpdating: function () {
isSaved = true;
clearDraft(currentAppointmentId);
Comment thread
vladaskorohodova marked this conversation as resolved.
Outdated
Comment thread
vladaskorohodova marked this conversation as resolved.
Outdated
}
});
});
Loading
Loading