MDL-81559 calendar: adjust end date automatically

This commit is contained in:
David Woloszyn
2026-03-06 14:06:14 +11:00
parent dcf942624d
commit ac2313b3fd
5 changed files with 129 additions and 8 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+108
View File
@@ -34,6 +34,11 @@ import * as Repository from 'core_calendar/repository';
const SELECTORS = {
SAVE_BUTTON: '[data-action="save"]',
LOADING_ICON_CONTAINER: '[data-region="loading-icon-container"]',
START_DATE_FIELDS: 'select[name^="timestart"]',
END_DATE_FIELDS: 'select[name^="timedurationuntil"]',
START_DATE_DATE_PICKER: '#id_timestart_calendar',
END_DATE_DATE_PICKER: '#id_timedurationuntil_calendar',
DURATION_FIELD: '#id_timedurationminutes',
};
export default class ModalEventForm extends Modal {
@@ -56,6 +61,8 @@ export default class ModalEventForm extends Modal {
this.reloadingBody = false;
this.reloadingTitle = false;
this.saveButton = this.getFooter().find(SELECTORS.SAVE_BUTTON);
this.eventTimeDuration = 0;
this.lastUsedDatePickerId = null;
}
configure(modalConfig) {
@@ -231,6 +238,79 @@ export default class ModalEventForm extends Modal {
this.saveButton.prop('disabled', false);
}
/**
* Create a date object from the fields in the form.
*
* @method getDateFromFields
* @param {string} selector The selector that is used to build the id of the start/end date fields.
* @return {Date} The date object created from the fields
*/
getDateFromFields(selector) {
const identifier = selector.match(/"([^"]+)"/)[1];
const d = new Date();
d.setFullYear(document.getElementById(`id_${identifier}_year`).value);
d.setMonth(document.getElementById(`id_${identifier}_month`).value - 1);
d.setDate(document.getElementById(`id_${identifier}_day`).value);
d.setHours(document.getElementById(`id_${identifier}_hour`).value);
d.setMinutes(document.getElementById(`id_${identifier}_minute`).value);
return d;
}
/**
* Update the end time value based on the duration of the event.
*
* If the event ends in 1 hour and 15 minutes, then updating the
* start time will respect this duration and push the end time forward
* and retain the 1 hour and 15 minute duration. This behaviour matches
* those of Google and iOS calendars in 2026.
*
* @method updateEndTime
*/
updateEndTime() {
const startDate = this.getDateFromFields(SELECTORS.START_DATE_FIELDS);
const endDate = new Date(startDate.getTime() + this.eventTimeDuration);
const identifier = SELECTORS.END_DATE_FIELDS.match(/"([^"]+)"/)[1];
document.getElementById(`id_${identifier}_year`).value = endDate.getFullYear();
document.getElementById(`id_${identifier}_month`).value = endDate.getMonth() + 1;
document.getElementById(`id_${identifier}_day`).value = endDate.getDate();
document.getElementById(`id_${identifier}_hour`).value = endDate.getHours();
document.getElementById(`id_${identifier}_minute`).value = endDate.getMinutes();
}
/**
* Calculate the duration of the event by comparing the start and end dates.
*
* @method calculateDuration
* @return {Number} Millisecond difference.
*/
calculateDuration() {
const startDate = this.getDateFromFields(SELECTORS.START_DATE_FIELDS);
const endDate = this.getDateFromFields(SELECTORS.END_DATE_FIELDS);
return (endDate - startDate);
}
/**
* Register the YUI calendar events.
*
* These events need to be registered after the elements have been rendered.
*
* @method registerCalendarEvents
*/
registerCalendarEvents() {
const calendar = M?.form?.dateselector?.calendar;
if (calendar) {
calendar.on('dateClick', () => {
// The YUI date pickers share the same elements.
// Determine which ones was last used and perform the required method.
if (this.lastUsedDatePickerId === SELECTORS.START_DATE_DATE_PICKER) {
this.updateEndTime();
} else if (this.lastUsedDatePickerId === SELECTORS.END_DATE_DATE_PICKER) {
this.eventTimeDuration = this.calculateDuration();
}
}, this);
}
}
/**
* Reload the title for the modal to the appropriate value
* depending on whether we are creating a new event or
@@ -314,6 +394,9 @@ export default class ModalEventForm extends Modal {
this.bodyPromise.then(() => {
this.enableButtons();
this.updateEndTime();
this.eventTimeDuration = this.calculateDuration();
this.registerCalendarEvents();
return;
})
.catch(Notification.exception)
@@ -478,6 +561,31 @@ export default class ModalEventForm extends Modal {
e.preventDefault();
e.stopPropagation();
});
this.getModal().on('change', SELECTORS.START_DATE_FIELDS, () => {
this.updateEndTime();
});
this.getModal().on('change', SELECTORS.END_DATE_FIELDS, () => {
this.eventTimeDuration = this.calculateDuration();
});
this.getModal().on('click', SELECTORS.START_DATE_DATE_PICKER, () => {
this.lastUsedDatePickerId = SELECTORS.START_DATE_DATE_PICKER;
});
this.getModal().on('click', SELECTORS.END_DATE_DATE_PICKER, () => {
this.lastUsedDatePickerId = SELECTORS.END_DATE_DATE_PICKER;
});
this.getModal().on('focusout', SELECTORS.DURATION_FIELD, (e) => {
const duration = parseInt(e.target.value);
if (isNaN(duration)) {
return;
}
this.eventTimeDuration = duration * 60 * 1000;
this.updateEndTime();
});
}
}
@@ -87,11 +87,14 @@ class create extends \moodleform {
$mform->addRule('name', get_string('required'), 'required', null, 'client');
$mform->setType('name', PARAM_TEXT);
// Add the select elements for the available event types.
$this->add_event_type_elements($mform, $eventtypes);
// Event time start field.
$mform->addElement('date_time_selector', 'timestart', get_string('date'), ['defaulttime' => $starttime]);
// Add the select elements for the available event types.
$this->add_event_type_elements($mform, $eventtypes);
// Add the variety of elements allowed for selecting event duration.
$this->add_event_duration_elements($mform);
// Start of advanced elements.
// Advanced elements are not visible to the user by default.
@@ -104,9 +107,6 @@ class create extends \moodleform {
$mform->setType('location', PARAM_RAW_TRIMMED);
$mform->setAdvanced('location');
// Add the variety of elements allowed for selecting event duration.
$this->add_event_duration_elements($mform);
// Add the form elements for repeating events.
$this->add_event_repeat_elements($mform);
@@ -217,7 +217,6 @@ class create extends \moodleform {
get_string('durationminutes', 'calendar'), 'maxlength="7" size="7"');
$mform->addGroup($group, 'durationgroup', get_string('eventduration', 'calendar'), '<br />', false);
$mform->setAdvanced('durationgroup');
$mform->disabledIf('timedurationuntil', 'duration', 'noteq', 1);
$mform->disabledIf('timedurationuntil[day]', 'duration', 'noteq', 1);
@@ -468,3 +468,17 @@ Feature: Perform basic calendar functionality
And I press "Save"
And I click on "type change test event" "link"
And I should see "Category event"
@javascript
Scenario: Move a date in the event form should update the event end date
Given I log in as "admin"
And the following "events" exist:
| name | eventtype | timestart | timeduration |
| Change date test event | user | ##today midnight +1 seconds## | 86400 |
When I am on "Course 1" course homepage
And I follow "Course calendar"
And I click on "Change date test event" "link"
And I click on "Edit" "button" in the "Change date test event" "dialogue"
And I set the following fields to these values:
| Date | ##today midnight +2 days## |
Then the field "Until" matches value "##today midnight +3 days##"