first commit

This commit is contained in:
CHIEFSOFT\ameye
2024-09-30 18:11:26 -04:00
commit e592ca6823
27270 changed files with 5002257 additions and 0 deletions
+284
View File
@@ -0,0 +1,284 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module is the highest level module for the calendar. It is
* responsible for initialising all of the components required for
* the calendar to run. It also coordinates the interaction between
* components by listening for and responding to different events
* triggered within the calendar UI.
*
* @module core_calendar/calendar
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/templates',
'core/notification',
'core_calendar/repository',
'core_calendar/events',
'core_calendar/view_manager',
'core_calendar/crud',
'core_calendar/selectors',
'core/url',
'core/str',
],
function(
$,
Templates,
Notification,
CalendarRepository,
CalendarEvents,
CalendarViewManager,
CalendarCrud,
CalendarSelectors,
Url,
Str,
) {
var SELECTORS = {
ROOT: "[data-region='calendar']",
DAY: "[data-region='day']",
NEW_EVENT_BUTTON: "[data-action='new-event-button']",
DAY_CONTENT: "[data-region='day-content']",
LOADING_ICON: '.loading-icon',
VIEW_DAY_LINK: "[data-action='view-day-link']",
CALENDAR_MONTH_WRAPPER: ".calendarwrapper",
TODAY: '.today',
DAY_NUMBER_CIRCLE: '.day-number-circle',
DAY_NUMBER: '.day-number',
SCREEN_READER_ANNOUNCEMENTS: '.calendar-announcements',
CURRENT_MONTH: '.calendar-controls .current'
};
/**
* Handler for the drag and drop move event. Provides a loading indicator
* while the request is sent to the server to update the event start date.
*
* Triggers a eventMoved calendar javascript event if the event was successfully
* updated.
*
* @param {event} e The calendar move event
* @param {int} eventId The event id being moved
* @param {object|null} originElement The jQuery element for where the event is moving from
* @param {object} destinationElement The jQuery element for where the event is moving to
*/
var handleMoveEvent = function(e, eventId, originElement, destinationElement) {
var originTimestamp = null;
var destinationTimestamp = destinationElement.attr('data-day-timestamp');
if (originElement) {
originTimestamp = originElement.attr('data-day-timestamp');
}
// If the event has actually changed day.
if (!originElement || originTimestamp != destinationTimestamp) {
Templates.render('core/loading', {})
.then(function(html, js) {
// First we show some loading icons in each of the days being affected.
destinationElement.find(SELECTORS.DAY_CONTENT).addClass('hidden');
Templates.appendNodeContents(destinationElement, html, js);
if (originElement) {
originElement.find(SELECTORS.DAY_CONTENT).addClass('hidden');
Templates.appendNodeContents(originElement, html, js);
}
return;
})
.then(function() {
// Send a request to the server to make the change.
return CalendarRepository.updateEventStartDay(eventId, destinationTimestamp);
})
.then(function() {
// If the update was successful then broadcast an event letting the calendar
// know that an event has been moved.
$('body').trigger(CalendarEvents.eventMoved, [eventId, originElement, destinationElement]);
return;
})
.always(function() {
// Always remove the loading icons regardless of whether the update
// request was successful or not.
var destinationLoadingElement = destinationElement.find(SELECTORS.LOADING_ICON);
destinationElement.find(SELECTORS.DAY_CONTENT).removeClass('hidden');
Templates.replaceNode(destinationLoadingElement, '', '');
if (originElement) {
var originLoadingElement = originElement.find(SELECTORS.LOADING_ICON);
originElement.find(SELECTORS.DAY_CONTENT).removeClass('hidden');
Templates.replaceNode(originLoadingElement, '', '');
}
return;
})
.catch(Notification.exception);
}
};
/**
* Listen to and handle any calendar events fired by the calendar UI.
*
* @method registerCalendarEventListeners
* @param {object} root The calendar root element
* @param {object} eventFormModalPromise A promise reolved with the event form modal
*/
var registerCalendarEventListeners = function(root, eventFormModalPromise) {
var body = $('body');
body.on(CalendarEvents.created, function() {
CalendarViewManager.reloadCurrentMonth(root);
});
body.on(CalendarEvents.deleted, function() {
CalendarViewManager.reloadCurrentMonth(root);
});
body.on(CalendarEvents.updated, function() {
CalendarViewManager.reloadCurrentMonth(root);
});
body.on(CalendarEvents.editActionEvent, function(e, url) {
// Action events needs to be edit directly on the course module.
window.location.assign(url);
});
// Handle the event fired by the drag and drop code.
body.on(CalendarEvents.moveEvent, handleMoveEvent);
// When an event is successfully moved we should updated the UI.
body.on(CalendarEvents.eventMoved, function() {
CalendarViewManager.reloadCurrentMonth(root);
});
// Announce the newly loaded month to screen readers.
body.on(CalendarEvents.monthChanged, root, async function() {
const monthName = body.find(SELECTORS.CURRENT_MONTH).text();
const monthAnnoucement = await Str.get_string('newmonthannouncement', 'calendar', monthName);
body.find(SELECTORS.SCREEN_READER_ANNOUNCEMENTS).html(monthAnnoucement);
});
CalendarCrud.registerEditListeners(root, eventFormModalPromise);
};
/**
* Register event listeners for the module.
*
* @param {object} root The calendar root element
* @param {boolean} isCalendarBlock - A flag indicating whether this is a calendar block.
*/
var registerEventListeners = function(root, isCalendarBlock) {
const viewingFullCalendar = document.getElementById(CalendarSelectors.fullCalendarView);
// Listen the click on the day link to render the day view.
root.on('click', SELECTORS.VIEW_DAY_LINK, function(e) {
var dayLink = $(e.target).closest(SELECTORS.VIEW_DAY_LINK);
var year = dayLink.data('year'),
month = dayLink.data('month'),
day = dayLink.data('day'),
courseId = dayLink.data('courseid'),
categoryId = dayLink.data('categoryid');
const urlParams = {
view: 'day',
time: dayLink.data('timestamp'),
course: courseId,
};
if (viewingFullCalendar) {
// Construct the URL parameter string from the urlParams object.
const urlParamString = Object.entries(urlParams)
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
.join('&');
CalendarViewManager.refreshDayContent(root, year, month, day, courseId, categoryId, root,
'core_calendar/calendar_day', isCalendarBlock).then(function() {
e.preventDefault();
// Update the URL if it's not calendar block.
if (!isCalendarBlock) {
CalendarViewManager.updateUrl('?' + urlParamString);
}
return;
}).catch(Notification.exception);
} else {
window.location.assign(Url.relativeUrl('calendar/view.php', urlParams));
}
});
root.on('change', CalendarSelectors.elements.courseSelector, function() {
var selectElement = $(this);
var courseId = selectElement.val();
const courseName = $("option:selected", selectElement).text();
CalendarViewManager.reloadCurrentMonth(root, courseId, null)
.then(function() {
// We need to get the selector again because the content has changed.
return root.find(CalendarSelectors.elements.courseSelector).val(courseId);
})
.then(function() {
CalendarViewManager.updateUrl('?view=month&course=' + courseId);
CalendarViewManager.handleCourseChange(Number(courseId), courseName);
return;
})
.catch(Notification.exception);
});
var eventFormPromise = CalendarCrud.registerEventFormModal(root),
contextId = $(SELECTORS.CALENDAR_MONTH_WRAPPER).data('context-id');
registerCalendarEventListeners(root, eventFormPromise);
if (contextId) {
// Bind click events to calendar days.
root.on('click', SELECTORS.DAY, function(e) {
var target = $(e.target);
const displayingSmallBlockCalendar = root.parents('aside').data('blockregion') === 'side-pre';
if (!viewingFullCalendar && displayingSmallBlockCalendar) {
const dateContainer = target.closest(SELECTORS.DAY);
const wrapper = target.closest(CalendarSelectors.wrapper);
const courseId = wrapper.data('courseid');
const params = {
view: 'day',
time: dateContainer.data('day-timestamp'),
course: courseId,
};
window.location.assign(Url.relativeUrl('calendar/view.php', params));
} else {
const hasViewDayLink = target.closest(SELECTORS.VIEW_DAY_LINK).length;
const shouldShowNewEventModal = !hasViewDayLink;
if (shouldShowNewEventModal) {
var startTime = $(this).attr('data-new-event-timestamp');
eventFormPromise.then(function(modal) {
var wrapper = target.closest(CalendarSelectors.wrapper);
modal.setCourseId(wrapper.data('courseid'));
var categoryId = wrapper.data('categoryid');
if (typeof categoryId !== 'undefined') {
modal.setCategoryId(categoryId);
}
modal.setContextId(wrapper.data('contextId'));
modal.setStartTime(startTime);
modal.show();
return;
}).catch(Notification.exception);
}
}
e.preventDefault();
});
}
};
return {
/**
* Initializes the calendar view manager and registers event listeners.
*
* @param {HTMLElement} root - The root element where the calendar view manager and event listeners will be attached.
* @param {boolean} [isCalendarBlock=false] - A flag indicating whether this is a calendar block.
*/
init: function(root, isCalendarBlock = false) {
root = $(root);
CalendarViewManager.init(root, 'month', isCalendarBlock);
registerEventListeners(root, isCalendarBlock);
}
};
});
+123
View File
@@ -0,0 +1,123 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module is responsible for the calendar filter.
*
* @module core_calendar/calendar_filter
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core_calendar/selectors',
'core_calendar/events',
'core/str',
'core/templates',
],
function(
$,
CalendarSelectors,
CalendarEvents,
Str,
Templates
) {
var registerEventListeners = function(root) {
root.on('click', CalendarSelectors.eventFilterItem, function(e) {
var target = $(e.currentTarget);
toggleFilter(target);
e.preventDefault();
});
$('body').on(CalendarEvents.viewUpdated, function() {
var filters = root.find(CalendarSelectors.eventFilterItem);
filters.each(function(i, filter) {
filter = $(filter);
if (filter.data('eventtype-hidden')) {
var data = getFilterData(filter);
fireFilterChangedEvent(data);
}
});
});
};
var toggleFilter = function(target) {
var data = getFilterData(target);
// Toggle the hidden. We need to render the template before we change the value.
data.hidden = !data.hidden;
M.util.js_pending("core_calendar/calendar_filter:toggleFilter");
return Str.get_string('eventtype' + data.eventtype, 'calendar')
.then(function(nameStr) {
data.name = nameStr;
data.icon = true;
data.key = 'i/' + data.eventtype + 'event';
data.component = 'core';
return data;
})
.then(function(context) {
return Templates.render('core_calendar/event_filter_key', context);
})
.then(function(html, js) {
return Templates.replaceNode(target, html, js);
})
.then(function() {
fireFilterChangedEvent(data);
M.util.js_complete("core_calendar/calendar_filter:toggleFilter");
return;
});
};
/**
* Fire the filterChanged event for the specified data.
*
* @param {object} data The data to include
*/
var fireFilterChangedEvent = function(data) {
M.util.js_pending("month-mini-filterChanged");
$('body').trigger(CalendarEvents.filterChanged, {
type: data.eventtype,
hidden: data.hidden,
});
M.util.js_complete("month-mini-filterChanged");
};
/**
* Get the filter data for the specified target.
*
* @param {jQuery} target The target node
* @return {Object}
*/
var getFilterData = function(target) {
return {
eventtype: target.data('eventtype'),
hidden: target.data('eventtype-hidden'),
};
};
return {
init: function(root) {
root = $(root);
registerEventListeners(root);
}
};
});
+116
View File
@@ -0,0 +1,116 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module is the highest level module for the calendar. It is
* responsible for initialising all of the components required for
* the calendar to run. It also coordinates the interaction between
* components by listening for and responding to different events
* triggered within the calendar UI.
*
* @module core_calendar/calendar_mini
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core_calendar/selectors',
'core_calendar/events',
'core_calendar/view_manager',
],
function(
$,
CalendarSelectors,
CalendarEvents,
CalendarViewManager
) {
/**
* Listen to and handle any calendar events fired by the calendar UI.
*
* @method registerCalendarEventListeners
* @param {object} root The calendar root element
*/
var registerCalendarEventListeners = function(root) {
var body = $('body');
var namespace = '.' + root.attr('id');
body.on(CalendarEvents.created + namespace, root, reloadMonth);
body.on(CalendarEvents.deleted + namespace, root, reloadMonth);
body.on(CalendarEvents.updated + namespace, root, reloadMonth);
body.on(CalendarEvents.eventMoved + namespace, root, reloadMonth);
};
/**
* Reload the month view in this month.
*
* @param {EventFacade} e
*/
var reloadMonth = function(e) {
var root = e.data;
var body = $('body');
var namespace = '.' + root.attr('id');
if (root.is(':visible')) {
CalendarViewManager.reloadCurrentMonth(root);
} else {
// The root has been removed.
// Remove all events in the namespace.
body.off(CalendarEvents.created + namespace);
body.off(CalendarEvents.deleted + namespace);
body.off(CalendarEvents.updated + namespace);
body.off(CalendarEvents.eventMoved + namespace);
}
};
var registerEventListeners = function(root) {
$('body').on(CalendarEvents.filterChanged, function(e, data) {
var daysWithEvent = root.find(CalendarSelectors.eventType[data.type]);
daysWithEvent.toggleClass('calendar_event_' + data.type, !data.hidden);
});
var namespace = '.' + root.attr('id');
$('body').on('change' + namespace, CalendarSelectors.elements.courseSelector, function() {
if (root.is(':visible')) {
var selectElement = $(this);
var courseId = selectElement.val();
var categoryId = null;
CalendarViewManager.reloadCurrentMonth(root, courseId, categoryId);
} else {
$('body').off('change' + namespace);
}
});
};
return {
init: function(root, loadOnInit) {
root = $(root);
CalendarViewManager.init(root);
registerEventListeners(root);
registerCalendarEventListeners(root);
if (loadOnInit) {
// The calendar hasn't yet loaded it's events so we
// should load them as soon as we've initialised.
CalendarViewManager.reloadCurrentMonth(root);
}
}
};
});
+96
View File
@@ -0,0 +1,96 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* This module is responsible for handle calendar day and upcoming view.
*
* @module core_calendar/calendar_view
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/notification',
'core_calendar/selectors',
'core_calendar/events',
'core_calendar/view_manager',
'core_calendar/crud'
],
function(
$,
Notification,
CalendarSelectors,
CalendarEvents,
CalendarViewManager,
CalendarCrud
) {
var registerEventListeners = function(root, type) {
var body = $('body');
CalendarCrud.registerRemove(root);
var reloadFunction = 'reloadCurrent' + type.charAt(0).toUpperCase() + type.slice(1);
body.on(CalendarEvents.created, function() {
CalendarViewManager[reloadFunction](root);
});
body.on(CalendarEvents.deleted, function() {
CalendarViewManager[reloadFunction](root);
});
body.on(CalendarEvents.updated, function() {
CalendarViewManager[reloadFunction](root);
});
root.on('change', CalendarSelectors.courseSelector, function() {
var selectElement = $(this);
var courseId = selectElement.val();
const courseName = $("option:selected", selectElement).text();
CalendarViewManager[reloadFunction](root, courseId, null)
.then(function() {
// We need to get the selector again because the content has changed.
return root.find(CalendarSelectors.courseSelector).val(courseId);
})
.then(function() {
CalendarViewManager.updateUrl('?view=' + type + '&course=' + courseId);
CalendarViewManager.handleCourseChange(Number(courseId), courseName);
return;
})
.fail(Notification.exception);
});
body.on(CalendarEvents.filterChanged, function(e, data) {
var daysWithEvent = root.find(CalendarSelectors.eventType[data.type]);
if (data.hidden == true) {
daysWithEvent.addClass('hidden');
} else {
daysWithEvent.removeClass('hidden');
}
CalendarViewManager.foldDayEvents(root);
});
var eventFormPromise = CalendarCrud.registerEventFormModal(root);
CalendarCrud.registerEditListeners(root, eventFormPromise);
};
return {
init: function(root, type, isCalendarBlock = false) {
root = $(root);
CalendarViewManager.init(root, type, isCalendarBlock);
registerEventListeners(root, type, isCalendarBlock);
}
};
});
+262
View File
@@ -0,0 +1,262 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A module to handle CRUD operations within the UI.
*
* @module core_calendar/crud
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/str',
'core/notification',
'core/modal_events',
'core_calendar/modal_event_form',
'core_calendar/repository',
'core_calendar/events',
'core_calendar/modal_delete',
'core_calendar/selectors',
'core/pending',
'core/modal_save_cancel',
'core/config',
],
function(
$,
Str,
Notification,
ModalEvents,
ModalEventForm,
CalendarRepository,
CalendarEvents,
CalendarModalDelete,
CalendarSelectors,
Pending,
ModalSaveCancel,
Config,
) {
/**
* Prepares the action for the summary modal's delete action.
*
* @param {Number} eventId The ID of the event.
* @param {string} eventTitle The event title.
* @param {Number} eventCount The number of events in the series.
* @return {Promise}
*/
function confirmDeletion(eventId, eventTitle, eventCount) {
var pendingPromise = new Pending('core_calendar/crud:confirmDeletion');
var deleteStrings = [
{
key: 'deleteevent',
component: 'calendar'
},
];
eventCount = parseInt(eventCount, 10);
var deletePromise;
var isRepeatedEvent = eventCount > 1;
if (isRepeatedEvent) {
deleteStrings.push({
key: 'confirmeventseriesdelete',
component: 'calendar',
param: {
name: eventTitle,
count: eventCount,
},
});
deletePromise = CalendarModalDelete.create();
} else {
deleteStrings.push({
key: 'confirmeventdelete',
component: 'calendar',
param: eventTitle
});
deletePromise = ModalSaveCancel.create();
}
var stringsPromise = Str.get_strings(deleteStrings);
var finalPromise = $.when(stringsPromise, deletePromise)
.then(function(strings, deleteModal) {
deleteModal.setRemoveOnClose(true);
deleteModal.setTitle(strings[0]);
deleteModal.setBody(strings[1]);
if (!isRepeatedEvent) {
deleteModal.setSaveButtonText(strings[0]);
}
deleteModal.show();
deleteModal.getRoot().on(ModalEvents.save, function() {
var pendingPromise = new Pending('calendar/crud:initModal:deletedevent');
CalendarRepository.deleteEvent(eventId, false)
.then(function() {
$('body').trigger(CalendarEvents.deleted, [eventId, false]);
return;
})
.then(pendingPromise.resolve)
.catch(Notification.exception);
});
deleteModal.getRoot().on(CalendarEvents.deleteAll, function() {
var pendingPromise = new Pending('calendar/crud:initModal:deletedallevent');
CalendarRepository.deleteEvent(eventId, true)
.then(function() {
$('body').trigger(CalendarEvents.deleted, [eventId, true]);
return;
})
.then(pendingPromise.resolve)
.catch(Notification.exception);
});
return deleteModal;
})
.then(function(modal) {
pendingPromise.resolve();
return modal;
})
.catch(Notification.exception);
return finalPromise;
}
/**
* Create the event form modal for creating new events and
* editing existing events.
*
* @method registerEventFormModal
* @param {object} root The calendar root element
* @return {object} The create modal promise
*/
var registerEventFormModal = function(root) {
var eventFormPromise = ModalEventForm.create();
// Bind click event on the new event button.
root.on('click', CalendarSelectors.actions.create, function(e) {
eventFormPromise.then(function(modal) {
var wrapper = root.find(CalendarSelectors.wrapper);
var categoryId = wrapper.data('categoryid');
const courseId = wrapper.data('courseid');
if (typeof categoryId !== 'undefined' && courseId != Config.siteId) {
modal.setCategoryId(categoryId);
}
// Attempt to find the cell for today.
// If it can't be found, then use the start time of the first day on the calendar.
var today = root.find(CalendarSelectors.today);
var firstDay = root.find(CalendarSelectors.day);
if (!today.length && firstDay.length) {
modal.setStartTime(firstDay.data('newEventTimestamp'));
}
modal.setContextId(wrapper.data('contextId'));
modal.setCourseId(wrapper.data('courseid'));
modal.show();
return;
})
.catch(Notification.exception);
e.preventDefault();
});
root.on('click', CalendarSelectors.actions.edit, function(e) {
e.preventDefault();
var target = $(e.currentTarget),
calendarWrapper = target.closest(CalendarSelectors.wrapper),
eventWrapper = target.closest(CalendarSelectors.eventItem);
eventFormPromise.then(function(modal) {
// When something within the calendar tells us the user wants
// to edit an event then show the event form modal.
modal.setEventId(eventWrapper.data('eventId'));
modal.setContextId(calendarWrapper.data('contextId'));
modal.setCourseId(eventWrapper.data('courseId'));
modal.show();
e.stopImmediatePropagation();
return;
}).catch(Notification.exception);
});
return eventFormPromise;
};
/**
* Register the listeners required to remove the event.
*
* @param {jQuery} root
*/
function registerRemove(root) {
root.on('click', CalendarSelectors.actions.remove, function(e) {
// Fetch the event title, count, and pass them into the new dialogue.
var eventSource = $(this).closest(CalendarSelectors.eventItem);
var eventId = eventSource.data('eventId'),
eventTitle = eventSource.data('eventTitle'),
eventCount = eventSource.data('eventCount');
confirmDeletion(eventId, eventTitle, eventCount);
e.preventDefault();
});
}
/**
* Register the listeners required to edit the event.
*
* @param {jQuery} root
* @param {Promise} eventFormModalPromise
* @returns {Promise}
*/
function registerEditListeners(root, eventFormModalPromise) {
var pendingPromise = new Pending('core_calendar/crud:registerEditListeners');
return eventFormModalPromise
.then(function(modal) {
// When something within the calendar tells us the user wants
// to edit an event then show the event form modal.
$('body').on(CalendarEvents.editEvent, function(e, eventId) {
var target = root.find(`[data-event-id=${eventId}]`),
calendarWrapper = root.find(CalendarSelectors.wrapper);
modal.setEventId(eventId);
modal.setContextId(calendarWrapper.data('contextId'));
modal.setReturnElement(target);
modal.show();
e.stopImmediatePropagation();
});
return modal;
})
.then(function(modal) {
pendingPromise.resolve();
return modal;
})
.catch(Notification.exception);
}
return {
registerRemove: registerRemove,
registerEditListeners: registerEditListeners,
registerEventFormModal: registerEventFormModal
};
});
+208
View File
@@ -0,0 +1,208 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A javascript module to store calendar drag and drop data.
*
* This module is unfortunately required because of the limitations
* of the HTML5 drag and drop API and it's ability to provide data
* between the different stages of the drag/drop lifecycle.
*
* @module core_calendar/drag_drop_data_store
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
/* @var {int|null} eventId The id of the event being dragged */
var eventId = null;
/* @var {int|null} durationDays How many days the event spans */
var durationDays = null;
/* @var {int|null} minTimestart The earliest valid timestart */
var minTimestart = null;
/* @var {int|null} maxTimestart The latest valid tiemstart */
var maxTimestart = null;
/* @var {string|null} minError Error message for min timestamp violation */
var minError = null;
/* @var {string|null} maxError Error message for max timestamp violation */
var maxError = null;
/**
* Store the id of the event being dragged.
*
* @param {int} id The event id
*/
var setEventId = function(id) {
eventId = id;
};
/**
* Get the stored event id.
*
* @return {int|null}
*/
var getEventId = function() {
return eventId;
};
/**
* Check if the store has an event id.
*
* @return {bool}
*/
var hasEventId = function() {
return eventId !== null;
};
/**
* Store the duration (in days) of the event being dragged.
*
* @param {int} days Number of days the event spans
*/
var setDurationDays = function(days) {
durationDays = days;
};
/**
* Get the stored number of days.
*
* @return {int|null}
*/
var getDurationDays = function() {
return durationDays;
};
/**
* Store the minimum timestart valid for an event being dragged.
*
* @param {int} timestamp The unix timstamp
*/
var setMinTimestart = function(timestamp) {
minTimestart = timestamp;
};
/**
* Get the minimum valid timestart.
*
* @return {int|null}
*/
var getMinTimestart = function() {
return minTimestart;
};
/**
* Check if a minimum timestamp is set.
*
* @return {bool}
*/
var hasMinTimestart = function() {
return minTimestart !== null;
};
/**
* Store the maximum timestart valid for an event being dragged.
*
* @param {int} timestamp The unix timstamp
*/
var setMaxTimestart = function(timestamp) {
maxTimestart = timestamp;
};
/**
* Get the maximum valid timestart.
*
* @return {int|null}
*/
var getMaxTimestart = function() {
return maxTimestart;
};
/**
* Check if a maximum timestamp is set.
*
* @return {bool}
*/
var hasMaxTimestart = function() {
return maxTimestart !== null;
};
/**
* Store the error string to display if trying to drag an event
* earlier than the minimum allowed date.
*
* @param {string} message The error message
*/
var setMinError = function(message) {
minError = message;
};
/**
* Get the error message for a minimum time start violation.
*
* @return {string|null}
*/
var getMinError = function() {
return minError;
};
/**
* Store the error string to display if trying to drag an event
* later than the maximum allowed date.
*
* @param {string} message The error message
*/
var setMaxError = function(message) {
maxError = message;
};
/**
* Get the error message for a maximum time start violation.
*
* @return {string|null}
*/
var getMaxError = function() {
return maxError;
};
/**
* Reset all of the stored values.
*/
var clearAll = function() {
setEventId(null);
setDurationDays(null);
setMinTimestart(null);
setMaxTimestart(null);
setMinError(null);
setMaxError(null);
};
return {
setEventId: setEventId,
getEventId: getEventId,
hasEventId: hasEventId,
setDurationDays: setDurationDays,
getDurationDays: getDurationDays,
setMinTimestart: setMinTimestart,
getMinTimestart: getMinTimestart,
hasMinTimestart: hasMinTimestart,
setMaxTimestart: setMaxTimestart,
getMaxTimestart: getMaxTimestart,
hasMaxTimestart: hasMaxTimestart,
setMinError: setMinError,
getMinError: getMinError,
setMaxError: setMaxError,
getMaxError: getMaxError,
clearAll: clearAll
};
});
+84
View File
@@ -0,0 +1,84 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A javascript module to enhance the event form.
*
* @module core_calendar/event_form
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core_calendar/repository', 'core/notification'], function($, CalendarRepository, Notification) {
var SELECTORS = {
EVENT_GROUP_COURSE_ID: '[name="groupcourseid"]',
EVENT_GROUP_ID: '[name="groupid"]',
SELECT_OPTION: 'option'
};
/**
* Listen for when the user changes the group course when configuring
* a group event and filter the options in the group select to only
* show the groups available within the course the user has selected.
*
* @method addCourseGroupSelectListeners
* @param {object} formElement The root form element
*/
var addCourseGroupSelectListeners = function(formElement) {
var courseGroupSelect = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID);
var loadGroupSelectOptions = function(groups) {
var groupSelect = formElement.find(SELECTORS.EVENT_GROUP_ID),
groupSelectOptions = groupSelect.find(SELECTORS.SELECT_OPTION),
courseGroups = $(groups);
// Let's clear all options first.
groupSelectOptions.remove();
groupSelect.prop("disabled", false);
courseGroups.each(function(id, group) {
$(groupSelect).append($("<option></option>").attr("value", group.id).text(group.name));
});
};
// If the user choose a course in the selector do a WS request to get groups.
courseGroupSelect.on('change', function() {
var courseId = formElement.find(SELECTORS.EVENT_GROUP_COURSE_ID).val();
if (isNaN(courseId) || courseId <= 0) {
loadGroupSelectOptions([]);
} else {
CalendarRepository.getCourseGroupsData(courseId)
.then(function(groups) {
return loadGroupSelectOptions(groups);
})
.catch(Notification.exception);
}
});
};
/**
* Initialise all of the form enhancements.
*
* @method init
* @param {string} formId The value of the form's id attribute
*/
var init = function(formId) {
var formElement = $('#' + formId);
addCourseGroupSelectListeners(formElement);
};
return {
init: init,
};
});
+37
View File
@@ -0,0 +1,37 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contain the events the calendar component can fire.
*
* @module core_calendar/events
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
export default {
created: 'calendar-events:created',
deleted: 'calendar-events:deleted',
deleteAll: 'calendar-events:delete_all',
updated: 'calendar-events:updated',
editEvent: 'calendar-events:edit_event',
editActionEvent: 'calendar-events:edit_action_event',
eventMoved: 'calendar-events:event_moved',
dayChanged: 'calendar-events:day_changed',
monthChanged: 'calendar-events:month_changed',
moveEvent: 'calendar-events:move_event',
filterChanged: 'calendar-events:filter_changed',
courseChanged: 'calendar-events:course_changed',
viewUpdated: 'calendar-events:view_updated',
};
+45
View File
@@ -0,0 +1,45 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A javascript module to enhance the calendar export form.
*
* @module core_calendar/export
* @copyright 2021 Jun Pataleta
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import 'core/copy_to_clipboard';
/**
* Selectors for the calendar export page.
*
* @property {string} copyUrlId The element ID of the Copy URL button.
*/
const selectors = {
copyUrlId: 'copyexporturl',
};
/**
* Initialises the calendar export JS module.
*
* @method init
*/
export const init = () => {
// Enable the copy URL button and focus on it.
const copyUrl = document.getElementById(selectors.copyUrlId);
copyUrl.removeAttribute('disabled');
copyUrl.focus();
};
+144
View File
@@ -0,0 +1,144 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A module to handle Delete/Update operations of the manage subscription page.
*
* @module core_calendar/manage_subscriptions
* @copyright 2021 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.0
*/
import * as CalendarSelectors from 'core_calendar/selectors';
import * as CalendarRepository from 'core_calendar/repository';
import ModalSaveCancel from 'core/modal_save_cancel';
import * as ModalEvents from 'core/modal_events';
import {exception as displayException, addNotification, fetchNotifications} from 'core/notification';
import Prefetch from 'core/prefetch';
import {getString} from 'core/str';
import {eventTypes} from 'core/local/inplace_editable/events';
/**
* Get subscription id for given element.
*
* @param {HTMLElement} element update/delete link
* @return {Number}
*/
const getSubscriptionId = element => {
return parseInt(element.closest('tr').dataset.subid);
};
/**
* Get subscription name for given element.
*
* @param {HTMLElement} element update/delete link
* @return {String}
*/
const getSubscriptionName = element => {
return element.closest('tr').dataset.subname;
};
/**
* Get subscription table row for subscription id.
*
* @param {string} subscriptionId Subscription id
* @return {Element}
*/
const getSubscriptionRow = subscriptionId => {
return document.querySelector(`tr[data-subid="${subscriptionId}"]`);
};
/**
* Create modal.
*
* @param {HTMLElement} element
* @param {string} messageCode Message code.
* @return {promise} Promise for modal
*/
const createModal = (element, messageCode) => {
const subscriptionName = getSubscriptionName(element);
return ModalSaveCancel.create({
title: getString('confirmation', 'admin'),
body: getString(messageCode, 'calendar', subscriptionName),
buttons: {
save: getString('yes')
},
}).then(modal => {
modal.getRoot().on(ModalEvents.hidden, () => {
element.focus();
});
modal.show();
return modal;
});
};
/**
* Response handler for delete action.
*
* @param {HTMLElement} element
* @param {Object} data
* @return {Promise}
*/
const responseHandlerForDelete = async(element, data) => {
const subscriptionName = getSubscriptionName(element);
const message = data.status ? await getString('subscriptionremoved', 'calendar', subscriptionName) : data.warnings[0].message;
const type = data.status ? 'info' : 'error';
return addNotification({message, type});
};
/**
* Register events for update/delete links.
*/
const registerEventListeners = () => {
document.addEventListener('click', e => {
const deleteAction = e.target.closest(CalendarSelectors.actions.deleteSubscription);
if (deleteAction) {
e.preventDefault();
const modalPromise = createModal(deleteAction, 'confirmsubscriptiondelete');
modalPromise.then(modal => {
modal.getRoot().on(ModalEvents.save, () => {
const subscriptionId = getSubscriptionId(deleteAction);
CalendarRepository.deleteSubscription(subscriptionId).then(data => {
const response = responseHandlerForDelete(deleteAction, data);
return response.then(() => {
const subscriptionRow = getSubscriptionRow(subscriptionId);
return subscriptionRow.remove();
});
}).catch(displayException);
});
return modal;
}).catch(displayException);
}
});
document.addEventListener(eventTypes.elementUpdated, e => {
const inplaceEditable = e.target;
if (inplaceEditable.getAttribute('data-component') == 'core_calendar') {
fetchNotifications();
}
});
};
/**
* Initialises.
*/
export const init = () => {
Prefetch.prefetchStrings('moodle', ['yes']);
Prefetch.prefetchStrings('core_admin', ['confirmation']);
Prefetch.prefetchStrings('core_calendar', ['confirmsubscriptiondelete', 'subscriptionremoved']);
registerEventListeners();
};
+92
View File
@@ -0,0 +1,92 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contain the logic for the delete modal.
*
* @module core_calendar/modal_delete
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import * as CustomEvents from 'core/custom_interaction_events';
import Modal from 'core/modal';
import ModalEvents from 'core/modal_events';
import CalendarEvents from './events';
const SELECTORS = {
DELETE_ONE_BUTTON: '[data-action="deleteone"]',
DELETE_ALL_BUTTON: '[data-action="deleteall"]',
CANCEL_BUTTON: '[data-action="cancel"]',
};
/**
* Constructor for the Modal.
*
* @class
* @param {object} root The root jQuery element for the modal
*/
export default class ModalDelete extends Modal {
static TYPE = 'core_calendar-modal_delete';
static TEMPLATE = 'calendar/event_delete_modal';
constructor(root) {
super(root);
this.setRemoveOnClose(true);
}
/**
* Set up all of the event handling for the modal.
*
* @method registerEventListeners
*/
registerEventListeners() {
// Apply parent event listeners.
super.registerEventListeners(this);
this.getModal().on(CustomEvents.events.activate, SELECTORS.DELETE_ONE_BUTTON, (e, data) => {
const saveEvent = $.Event(ModalEvents.save);
this.getRoot().trigger(saveEvent, this);
if (!saveEvent.isDefaultPrevented()) {
this.hide();
data.originalEvent.preventDefault();
}
});
this.getModal().on(CustomEvents.events.activate, SELECTORS.DELETE_ALL_BUTTON, (e, data) => {
const saveEvent = $.Event(CalendarEvents.deleteAll);
this.getRoot().trigger(saveEvent, this);
if (!saveEvent.isDefaultPrevented()) {
this.hide();
data.originalEvent.preventDefault();
}
});
this.getModal().on(CustomEvents.events.activate, SELECTORS.CANCEL_BUTTON, (e, data) => {
const cancelEvent = $.Event(ModalEvents.cancel);
this.getRoot().trigger(cancelEvent, this);
if (!cancelEvent.isDefaultPrevented()) {
this.hide();
data.originalEvent.preventDefault();
}
});
}
}
ModalDelete.registerModalType();
+484
View File
@@ -0,0 +1,484 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Contain the logic for the quick add or update event modal.
*
* @module core_calendar/modal_event_form
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import * as CustomEvents from 'core/custom_interaction_events';
import Modal from 'core/modal';
import * as FormEvents from 'core_form/events';
import CalendarEvents from './events';
import * as Str from 'core/str';
import * as Notification from 'core/notification';
import * as Fragment from 'core/fragment';
import * as Repository from 'core_calendar/repository';
const SELECTORS = {
SAVE_BUTTON: '[data-action="save"]',
LOADING_ICON_CONTAINER: '[data-region="loading-icon-container"]',
};
export default class ModalEventForm extends Modal {
static TYPE = 'core_calendar-modal_event_form';
static TEMPLATE = 'calendar/modal_event_form';
/**
* Constructor for the Modal.
*
* @param {object} root The root jQuery element for the modal
*/
constructor(root) {
super(root);
this.eventId = null;
this.startTime = null;
this.courseId = null;
this.categoryId = null;
this.contextId = null;
this.reloadingBody = false;
this.reloadingTitle = false;
this.saveButton = this.getFooter().find(SELECTORS.SAVE_BUTTON);
}
configure(modalConfig) {
modalConfig.large = true;
super.configure(modalConfig);
}
/**
* Set the context id to the given value.
*
* @method setContextId
* @param {Number} id The event id
*/
setContextId(id) {
this.contextId = id;
}
/**
* Retrieve the current context id, if any.
*
* @method getContextId
* @return {Number|null} The event id
*/
getContextId() {
return this.contextId;
}
/**
* Set the course id to the given value.
*
* @method setCourseId
* @param {Number} id The event id
*/
setCourseId(id) {
this.courseId = id;
}
/**
* Retrieve the current course id, if any.
*
* @method getCourseId
* @return {Number|null} The event id
*/
getCourseId() {
return this.courseId;
}
/**
* Set the category id to the given value.
*
* @method setCategoryId
* @param {Number} id The event id
*/
setCategoryId(id) {
this.categoryId = id;
}
/**
* Retrieve the current category id, if any.
*
* @method getCategoryId
* @return {Number|null} The event id
*/
getCategoryId() {
return this.categoryId;
}
/**
* Check if the modal has an course id.
*
* @method hasCourseId
* @return {bool}
*/
hasCourseId() {
return this.courseId !== null;
}
/**
* Check if the modal has an category id.
*
* @method hasCategoryId
* @return {bool}
*/
hasCategoryId() {
return this.categoryId !== null;
}
/**
* Set the event id to the given value.
*
* @method setEventId
* @param {Number} id The event id
*/
setEventId(id) {
this.eventId = id;
}
/**
* Retrieve the current event id, if any.
*
* @method getEventId
* @return {Number|null} The event id
*/
getEventId() {
return this.eventId;
}
/**
* Check if the modal has an event id.
*
* @method hasEventId
* @return {bool}
*/
hasEventId() {
return this.eventId !== null;
}
/**
* Set the start time to the given value.
*
* @method setStartTime
* @param {Number} time The start time
*/
setStartTime(time) {
this.startTime = time;
}
/**
* Retrieve the current start time, if any.
*
* @method getStartTime
* @return {Number|null} The start time
*/
getStartTime() {
return this.startTime;
}
/**
* Check if the modal has start time.
*
* @method hasStartTime
* @return {bool}
*/
hasStartTime() {
return this.startTime !== null;
}
/**
* Get the form element from the modal.
*
* @method getForm
* @return {object}
*/
getForm() {
return this.getBody().find('form');
}
/**
* Disable the buttons in the footer.
*
* @method disableButtons
*/
disableButtons() {
this.saveButton.prop('disabled', true);
}
/**
* Enable the buttons in the footer.
*
* @method enableButtons
*/
enableButtons() {
this.saveButton.prop('disabled', false);
}
/**
* Reload the title for the modal to the appropriate value
* depending on whether we are creating a new event or
* editing an existing event.
*
* @method reloadTitleContent
* @return {object} A promise resolved with the new title text
*/
reloadTitleContent() {
if (this.reloadingTitle) {
return this.titlePromise;
}
this.reloadingTitle = true;
if (this.hasEventId()) {
this.titlePromise = Str.get_string('editevent', 'calendar');
} else {
this.titlePromise = Str.get_string('newevent', 'calendar');
}
this.titlePromise.then((string) => {
this.setTitle(string);
return string;
})
.catch(Notification.exception)
.always(() => {
this.reloadingTitle = false;
return;
});
return this.titlePromise;
}
/**
* Send a request to the server to get the event_form in a fragment
* and render the result in the body of the modal.
*
* If serialised form data is provided then it will be sent in the
* request to the server to have the form rendered with the data. This
* is used when the form had a server side error and we need the server
* to re-render it for us to display the error to the user.
*
* @method reloadBodyContent
* @param {string} formData The serialised form data
* @return {object} A promise resolved with the fragment html and js from
*/
reloadBodyContent(formData) {
if (this.reloadingBody) {
return this.bodyPromise;
}
this.reloadingBody = true;
this.disableButtons();
const args = {};
if (this.hasEventId()) {
args.eventid = this.getEventId();
}
if (this.hasStartTime()) {
args.starttime = this.getStartTime();
}
if (this.hasCourseId()) {
args.courseid = this.getCourseId();
}
if (this.hasCategoryId()) {
args.categoryid = this.getCategoryId();
}
if (typeof formData !== 'undefined') {
args.formdata = formData;
}
this.bodyPromise = Fragment.loadFragment('calendar', 'event_form', this.getContextId(), args);
this.setBody(this.bodyPromise);
this.bodyPromise.then(() => {
this.enableButtons();
return;
})
.catch(Notification.exception)
.always(() => {
this.reloadingBody = false;
return;
});
return this.bodyPromise;
}
/**
* Reload both the title and body content.
*
* @method reloadAllContent
* @return {object} promise
*/
reloadAllContent() {
return $.when(this.reloadTitleContent(), this.reloadBodyContent());
}
/**
* Kick off a reload the modal content before showing it. This
* is to allow us to re-use the same modal for creating and
* editing different events within the page.
*
* We do the reload when showing the modal rather than hiding it
* to save a request to the server if the user closes the modal
* and never re-opens it.
*
* @method show
*/
show() {
this.reloadAllContent();
super.show(this);
}
/**
* Clear the event id from the modal when it's closed so
* that it is loaded fresh next time it's displayed.
*
* The event id will be set by the calling code if it wants
* to edit a specific event.
*
* @method hide
*/
hide() {
super.hide(this);
this.setEventId(null);
this.setStartTime(null);
this.setCourseId(null);
this.setCategoryId(null);
}
/**
* Get the serialised form data.
*
* @method getFormData
* @return {string} serialised form data
*/
getFormData() {
return this.getForm().serialize();
}
/**
* Send the form data to the server to create or update
* an event.
*
* If there is a server side validation error then we re-request the
* rendered form (with the data) from the server in order to get the
* server side errors to display.
*
* On success the modal is hidden and the page is reloaded so that the
* new event will display.
*
* @method save
* @return {object} A promise
*/
save() {
const loadingContainer = this.saveButton.find(SELECTORS.LOADING_ICON_CONTAINER);
// Now the change events have run, see if there are any "invalid" form fields.
const invalid = this.getForm().find('[aria-invalid="true"]');
// If we found invalid fields, focus on the first one and do not submit via ajax.
if (invalid.length) {
invalid.first().focus();
return Promise.resolve();
}
loadingContainer.removeClass('hidden');
this.disableButtons();
const formData = this.getFormData();
// Send the form data to the server for processing.
return Repository.submitCreateUpdateForm(formData)
.then((response) => {
if (response.validationerror) {
// If there was a server side validation error then
// we need to re-request the rendered form from the server
// in order to display the error for the user.
this.reloadBodyContent(formData);
return;
} else {
// Check whether this was a new event or not.
// The hide function unsets the form data so grab this before the hide.
const isExisting = this.hasEventId();
// No problemo! Our work here is done.
this.hide();
// Trigger the appropriate calendar event so that the view can be updated.
if (isExisting) {
$('body').trigger(CalendarEvents.updated, [response.event]);
} else {
$('body').trigger(CalendarEvents.created, [response.event]);
}
}
return;
})
.catch(Notification.exception)
.always(() => {
// Regardless of success or error we should always stop
// the loading icon and re-enable the buttons.
loadingContainer.addClass('hidden');
this.enableButtons();
return;
});
}
/**
* Set up all of the event handling for the modal.
*
* @method registerEventListeners
* @fires event:uploadStarted
* @fires event:formSubmittedByJavascript
*/
registerEventListeners() {
// Apply parent event listeners.
super.registerEventListeners(this);
// When the user clicks the save button we trigger the form submission. We need to
// trigger an actual submission because there is some JS code in the form that is
// listening for this event and doing some stuff (e.g. saving draft areas etc).
this.getModal().on(CustomEvents.events.activate, SELECTORS.SAVE_BUTTON, (e, data) => {
this.getForm().submit();
data.originalEvent.preventDefault();
e.stopPropagation();
});
// Catch the submit event before it is actually processed by the browser and
// prevent the submission. We'll take it from here.
this.getModal().on('submit', (e) => {
FormEvents.notifyFormSubmittedByJavascript(this.getForm()[0]);
this.save();
// Stop the form from actually submitting and prevent it's
// propagation because we have already handled the event.
e.preventDefault();
e.stopPropagation();
});
}
}
ModalEventForm.registerModalType();
@@ -0,0 +1,236 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A javascript module to handle calendar drag and drop in the calendar
* month view navigation.
*
* This code is run each time the calendar month view is re-rendered. We
* only register the event handlers once per page load so that the in place
* DOM updates that happen on month change don't continue to register handlers.
*
* @module core_calendar/month_navigation_drag_drop
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core_calendar/drag_drop_data_store',
],
function(
$,
DataStore
) {
var SELECTORS = {
DRAGGABLE: '[draggable="true"][data-region="event-item"]',
DROP_ZONE: '[data-drop-zone="nav-link"]',
};
var HOVER_CLASS = 'bg-primary text-white';
var TARGET_CLASS = 'drop-target';
var HOVER_TIME = 1000; // 1 second hover to change month.
// We store some static variables at the module level because this
// module is called each time the calendar month view is reloaded but
// we want some actions to only occur ones.
/* @var {bool} registered If the event listeners have been added */
var registered = false;
/* @var {int} hoverTimer The timeout id of any timeout waiting for hover */
var hoverTimer = null;
/* @var {object} root The root nav element we're operating on */
var root = null;
/**
* Add or remove the appropriate styling to indicate whether
* the drop target is being hovered over.
*
* @param {object} target The target drop zone element
* @param {bool} hovered If the element is hovered over ot not
*/
var updateHoverState = function(target, hovered) {
if (hovered) {
target.addClass(HOVER_CLASS);
} else {
target.removeClass(HOVER_CLASS);
}
};
/**
* Add some styling to the UI to indicate that the nav links
* are an acceptable drop target.
*/
var addDropZoneIndicator = function() {
root.find(SELECTORS.DROP_ZONE).addClass(TARGET_CLASS);
};
/**
* Remove the styling from the nav links.
*/
var removeDropZoneIndicator = function() {
root.find(SELECTORS.DROP_ZONE).removeClass(TARGET_CLASS);
};
/**
* Get the drop zone target from the event, if one is found.
*
* @param {event} e Javascript event
* @return {object|null}
*/
var getTargetFromEvent = function(e) {
var target = $(e.target).closest(SELECTORS.DROP_ZONE);
return (target.length) ? target : null;
};
/**
* This will add a visual indicator to the calendar UI to
* indicate which nav link is a valid drop zone.
*
* @param {Event} e
*/
var dragstartHandler = function(e) {
// Make sure the drag event is for a calendar event.
var eventElement = $(e.target).closest(SELECTORS.DRAGGABLE);
if (eventElement.length) {
addDropZoneIndicator();
}
};
/**
* Update the hover state of the target nav element when
* the user is dragging an event over it.
*
* This will add a visual indicator to the calendar UI to
* indicate which nav link is being hovered.
*
* @param {event} e The dragover event
*/
var dragoverHandler = function(e) {
// Ignore dragging of non calendar events.
if (!DataStore.hasEventId()) {
return;
}
e.preventDefault();
var target = getTargetFromEvent(e);
if (!target) {
return;
}
// If we're not draggin a calendar event then
// ignore it.
if (!DataStore.hasEventId()) {
return;
}
if (!hoverTimer) {
hoverTimer = setTimeout(function() {
target.click();
hoverTimer = null;
}, HOVER_TIME);
}
updateHoverState(target, true);
removeDropZoneIndicator();
};
/**
* Update the hover state of the target nav element that was
* previously dragged over but has is no longer a drag target.
*
* This will remove the visual indicator from the calendar UI
* that was added by the dragoverHandler.
*
* @param {event} e The dragstart event
*/
var dragleaveHandler = function(e) {
// Ignore dragging of non calendar events.
if (!DataStore.hasEventId()) {
return;
}
var target = getTargetFromEvent(e);
if (!target) {
return;
}
if (hoverTimer) {
clearTimeout(hoverTimer);
hoverTimer = null;
}
updateHoverState(target, false);
addDropZoneIndicator();
e.preventDefault();
};
/**
* Remove the visual indicator from the calendar UI that was
* added by the dragoverHandler.
*
* @param {event} e The drop event
*/
var dropHandler = function(e) {
// Ignore dragging of non calendar events.
if (!DataStore.hasEventId()) {
return;
}
removeDropZoneIndicator();
var target = getTargetFromEvent(e);
if (!target) {
return;
}
updateHoverState(target, false);
e.preventDefault();
};
return {
/**
* Initialise the event handlers for the drag events.
*
* @param {object} rootElement The element containing calendar nav links
*/
init: function(rootElement) {
// Only register the handlers once on the first load.
if (!registered) {
// These handlers are only added the first time the module
// is loaded because we don't want to have a new listener
// added each time the "init" function is called otherwise we'll
// end up with lots of stale handlers.
document.addEventListener('dragstart', dragstartHandler, false);
document.addEventListener('dragover', dragoverHandler, false);
document.addEventListener('dragleave', dragleaveHandler, false);
document.addEventListener('drop', dropHandler, false);
document.addEventListener('dragend', removeDropZoneIndicator, false);
registered = true;
}
// Update the module variable to operate on the given
// root element.
root = $(rootElement);
// If we're currently dragging then add the indicators.
if (DataStore.hasEventId()) {
addDropZoneIndicator();
}
},
};
});
+403
View File
@@ -0,0 +1,403 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A javascript module to handle calendar drag and drop in the calendar
* month view.
*
* @module core_calendar/month_view_drag_drop
* @copyright 2017 Ryan Wyllie <ryan@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([
'jquery',
'core/notification',
'core/str',
'core_calendar/events',
'core_calendar/drag_drop_data_store'
],
function(
$,
Notification,
Str,
CalendarEvents,
DataStore
) {
var SELECTORS = {
ROOT: "[data-region='calendar']",
DRAGGABLE: '[draggable="true"][data-region="event-item"]',
DROP_ZONE: '[data-drop-zone="month-view-day"]',
WEEK: '[data-region="month-view-week"]',
};
var INVALID_DROP_ZONE_CLASS = 'bg-faded';
var INVALID_HOVER_CLASS = 'bg-danger text-white';
var VALID_HOVER_CLASS = 'bg-primary text-white';
var ALL_CLASSES = INVALID_DROP_ZONE_CLASS + ' ' + INVALID_HOVER_CLASS + ' ' + VALID_HOVER_CLASS;
/* @var {bool} registered If the event listeners have been added */
var registered = false;
/**
* Get the correct drop zone element from the given javascript
* event.
*
* @param {event} e The javascript event
* @return {object|null}
*/
var getDropZoneFromEvent = function(e) {
var dropZone = $(e.target).closest(SELECTORS.DROP_ZONE);
return (dropZone.length) ? dropZone : null;
};
/**
* Determine if the given dropzone element is within the acceptable
* time range.
*
* The drop zone timestamp is midnight on that day so we should check
* that the event's acceptable timestart value
*
* @param {object} dropZone The drop zone day from the calendar
* @return {bool}
*/
var isValidDropZone = function(dropZone) {
var dropTimestamp = dropZone.attr('data-day-timestamp');
var minTimestart = DataStore.getMinTimestart();
var maxTimestart = DataStore.getMaxTimestart();
if (minTimestart && minTimestart > dropTimestamp) {
return false;
}
if (maxTimestart && maxTimestart < dropTimestamp) {
return false;
}
return true;
};
/**
* Get the error string to display for a given drop zone element
* if it is invalid.
*
* @param {object} dropZone The drop zone day from the calendar
* @return {string}
*/
var getDropZoneError = function(dropZone) {
var dropTimestamp = dropZone.attr('data-day-timestamp');
var minTimestart = DataStore.getMinTimestart();
var maxTimestart = DataStore.getMaxTimestart();
if (minTimestart && minTimestart > dropTimestamp) {
return DataStore.getMinError();
}
if (maxTimestart && maxTimestart < dropTimestamp) {
return DataStore.getMaxError();
}
return null;
};
/**
* Remove all of the styling from each of the drop zones in the calendar.
*/
var clearAllDropZonesState = function() {
$(SELECTORS.ROOT).find(SELECTORS.DROP_ZONE).each(function(index, dropZone) {
dropZone = $(dropZone);
dropZone.removeClass(ALL_CLASSES);
});
};
/**
* Update the hover state for the event in the calendar to reflect
* which days the event will be moved to.
*
* If the drop zone is not being hovered then it will apply some
* styling to reflect whether the drop zone is a valid or invalid
* drop place for the current dragging event.
*
* This funciton supports events spanning multiple days and will
* recurse to highlight (or remove highlight) each of the days
* that the event will be moved to.
*
* For example: An event with a duration of 3 days will have
* 3 days highlighted when it's dragged elsewhere in the calendar.
* The current drag target and the 2 days following it (including
* wrapping to the next week if necessary).
*
* @param {string|object} dropZone The drag target element
* @param {bool} hovered If the target is hovered or not
* @param {Number} count How many days to highlight (default to duration)
*/
var updateHoverState = function(dropZone, hovered, count) {
if (typeof count === 'undefined') {
// This is how many days we need to highlight.
count = DataStore.getDurationDays();
}
var valid = isValidDropZone(dropZone);
dropZone.removeClass(ALL_CLASSES);
if (hovered) {
if (valid) {
dropZone.addClass(VALID_HOVER_CLASS);
} else {
dropZone.addClass(INVALID_HOVER_CLASS);
}
} else {
dropZone.removeClass(VALID_HOVER_CLASS + ' ' + INVALID_HOVER_CLASS);
if (!valid) {
dropZone.addClass(INVALID_DROP_ZONE_CLASS);
}
}
count--;
// If we've still got days to highlight then we should
// find the next day.
if (count > 0) {
var nextDropZone = dropZone.next();
// If there are no more days in this week then we
// need to move down to the next week in the calendar.
if (!nextDropZone.length) {
var nextWeek = dropZone.closest(SELECTORS.WEEK).next();
if (nextWeek.length) {
nextDropZone = nextWeek.children(SELECTORS.DROP_ZONE).first();
}
}
// If we found another day then let's recursively
// update it's hover state.
if (nextDropZone.length) {
updateHoverState(nextDropZone, hovered, count);
}
}
};
/**
* Find all of the calendar event drop zones in the calendar and update the display
* for the user to indicate which zones are valid and invalid.
*/
var updateAllDropZonesState = function() {
$(SELECTORS.ROOT).find(SELECTORS.DROP_ZONE).each(function(index, dropZone) {
dropZone = $(dropZone);
if (!isValidDropZone(dropZone)) {
updateHoverState(dropZone, false);
}
});
};
/**
* Set up the module level variables to track which event is being
* dragged and how many days it spans.
*
* @param {event} e The dragstart event
*/
var dragstartHandler = function(e) {
var target = $(e.target);
var draggableElement = target.closest(SELECTORS.DRAGGABLE);
if (!draggableElement.length) {
return;
}
var eventElement = draggableElement.find('[data-event-id]');
var eventId = eventElement.attr('data-event-id');
var minTimestart = draggableElement.attr('data-min-day-timestamp');
var maxTimestart = draggableElement.attr('data-max-day-timestamp');
var minError = draggableElement.attr('data-min-day-error');
var maxError = draggableElement.attr('data-max-day-error');
var eventsSelector = SELECTORS.ROOT + ' [data-event-id="' + eventId + '"]';
var duration = $(eventsSelector).length;
DataStore.setEventId(eventId);
DataStore.setDurationDays(duration);
if (minTimestart) {
DataStore.setMinTimestart(minTimestart);
}
if (maxTimestart) {
DataStore.setMaxTimestart(maxTimestart);
}
if (minError) {
DataStore.setMinError(minError);
}
if (maxError) {
DataStore.setMaxError(maxError);
}
e.dataTransfer.effectAllowed = "move";
e.dataTransfer.dropEffect = "move";
// Firefox requires a value to be set here or the drag won't
// work and the dragover handler won't fire.
e.dataTransfer.setData('text/plain', eventId);
e.dropEffect = "move";
updateAllDropZonesState();
};
/**
* Update the hover state of the target day element when
* the user is dragging an event over it.
*
* This will add a visual indicator to the calendar UI to
* indicate which day(s) the event will be moved to.
*
* @param {event} e The dragstart event
*/
var dragoverHandler = function(e) {
// Ignore dragging of non calendar events.
if (!DataStore.hasEventId()) {
return;
}
e.preventDefault();
var dropZone = getDropZoneFromEvent(e);
if (!dropZone) {
return;
}
updateHoverState(dropZone, true);
};
/**
* Update the hover state of the target day element that was
* previously dragged over but has is no longer a drag target.
*
* This will remove the visual indicator from the calendar UI
* that was added by the dragoverHandler.
*
* @param {event} e The dragstart event
*/
var dragleaveHandler = function(e) {
// Ignore dragging of non calendar events.
if (!DataStore.hasEventId()) {
return;
}
var dropZone = getDropZoneFromEvent(e);
if (!dropZone) {
return;
}
updateHoverState(dropZone, false);
e.preventDefault();
};
/**
* Determines the event element, origin day, and destination day
* once the user drops the calendar event. These three bits of data
* are provided as the payload to the "moveEvent" calendar javascript
* event that is fired.
*
* This will remove the visual indicator from the calendar UI
* that was added by the dragoverHandler.
*
* @param {event} e The dragstart event
*/
var dropHandler = function(e) {
// Ignore dragging of non calendar events.
if (!DataStore.hasEventId()) {
return;
}
var dropZone = getDropZoneFromEvent(e);
if (!dropZone) {
DataStore.clearAll();
clearAllDropZonesState();
return;
}
if (isValidDropZone(dropZone)) {
var eventId = DataStore.getEventId();
var eventElementSelector = SELECTORS.ROOT + ' [data-event-id="' + eventId + '"]';
var eventElement = $(eventElementSelector);
var origin = null;
if (eventElement.length) {
origin = eventElement.closest(SELECTORS.DROP_ZONE);
}
$('body').trigger(CalendarEvents.moveEvent, [eventId, origin, dropZone]);
} else {
// If the drop zone is not valid then there is not need for us to
// try to process it. Instead we can just show an error to the user.
var message = getDropZoneError(dropZone);
Str.get_string('errorinvaliddate', 'calendar').then(function(string) {
Notification.exception({
name: string,
message: message || string
});
});
}
DataStore.clearAll();
clearAllDropZonesState();
e.preventDefault();
};
/**
* Clear the data store and remove the drag indicators from the UI
* when the drag event has finished.
*/
var dragendHandler = function() {
DataStore.clearAll();
clearAllDropZonesState();
};
/**
* Re-render the drop zones in the new month to highlight
* which areas are or aren't acceptable to drop the calendar
* event.
*/
var calendarMonthChangedHandler = function() {
updateAllDropZonesState();
};
return {
/**
* Initialise the event handlers for the drag events.
*/
init: function() {
if (!registered) {
// These handlers are only added the first time the module
// is loaded because we don't want to have a new listener
// added each time the "init" function is called otherwise we'll
// end up with lots of stale handlers.
document.addEventListener('dragstart', dragstartHandler, false);
document.addEventListener('dragover', dragoverHandler, false);
document.addEventListener('dragleave', dragleaveHandler, false);
document.addEventListener('drop', dropHandler, false);
document.addEventListener('dragend', dragendHandler, false);
$('body').on(CalendarEvents.monthChanged, calendarMonthChangedHandler);
registered = true;
}
},
};
});
+125
View File
@@ -0,0 +1,125 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Javascript popover for the `core_calendar` subsystem.
*
* @module core_calendar/popover
* @copyright 2021 Huong Nguyen <huongnv13@gmail.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 4.0
*/
import 'theme_boost/popover';
import jQuery from 'jquery';
import * as CalendarSelectors from 'core_calendar/selectors';
/**
* Check if we are allowing to enable the popover or not.
* @param {Element} dateContainer
* @returns {boolean}
*/
const isPopoverAvailable = (dateContainer) => {
return window.getComputedStyle(dateContainer.querySelector(CalendarSelectors.elements.dateContent)).display === 'none';
};
const isPopoverConfigured = new Map();
const showPopover = target => {
const dateContainer = target.closest(CalendarSelectors.elements.dateContainer);
if (!isPopoverConfigured.has(dateContainer)) {
const dateEle = jQuery(target);
dateEle.popover({
trigger: 'manual',
placement: 'top',
html: true,
title: dateContainer.dataset.title,
content: () => {
const source = jQuery(dateContainer).find(CalendarSelectors.elements.dateContent);
const content = jQuery('<div>');
if (source.length) {
const temptContent = source.find('.hidden').clone(false);
content.html(temptContent.html());
}
return content.html();
},
'animation': false,
});
isPopoverConfigured.set(dateContainer, true);
}
if (isPopoverAvailable(dateContainer)) {
jQuery(target).popover('show');
target.addEventListener('mouseleave', hidePopover);
target.addEventListener('focusout', hidePopover);
// Set up the hide function to the click event type.
target.addEventListener('click', hidePopover);
}
};
const hidePopover = e => {
const target = e.target;
const dateContainer = e.target.closest(CalendarSelectors.elements.dateContainer);
if (!dateContainer) {
return;
}
if (isPopoverConfigured.has(dateContainer)) {
const isTargetActive = target.contains(document.activeElement);
const isTargetHover = target.matches(':hover');
// Checks if a target element is clicked or pressed.
const isTargetClicked = document.activeElement.contains(target);
let removeListener = true;
if (!isTargetActive && !isTargetHover) {
jQuery(target).popover('hide');
} else if (isTargetClicked) {
jQuery(document.activeElement).popover('hide');
} else {
removeListener = false;
}
if (removeListener) {
target.removeEventListener('mouseleave', hidePopover);
target.removeEventListener('focusout', hidePopover);
target.removeEventListener('click', hidePopover);
}
}
};
/**
* Register events for date container.
*/
const registerEventListeners = () => {
const showPopoverHandler = (e) => {
const dayLink = e.target.closest(CalendarSelectors.links.dayLink);
if (!dayLink) {
return;
}
e.preventDefault();
showPopover(dayLink);
};
document.addEventListener('mouseover', showPopoverHandler);
document.addEventListener('focusin', showPopoverHandler);
};
let listenersRegistered = false;
if (!listenersRegistered) {
registerEventListeners();
listenersRegistered = true;
}
+215
View File
@@ -0,0 +1,215 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A javascript module to handle calendar ajax actions.
*
* @module core_calendar/repository
* @copyright 2017 Simey Lameze <lameze@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import Ajax from 'core/ajax';
/**
* Delete a calendar event.
*
* @method deleteEvent
* @param {number} eventId The event id.
* @param {boolean} deleteSeries Whether to delete all events in the series
* @return {promise} Resolved with requested calendar event
*/
export const deleteEvent = (eventId, deleteSeries = false) => {
const request = {
methodname: 'core_calendar_delete_calendar_events',
args: {
events: [{
eventid: eventId,
repeat: deleteSeries,
}]
}
};
return Ajax.call([request])[0];
};
/**
* Get a calendar event by id.
*
* @method getEventById
* @param {number} eventId The event id.
* @return {promise} Resolved with requested calendar event
*/
export const getEventById = (eventId) => {
const request = {
methodname: 'core_calendar_get_calendar_event_by_id',
args: {
eventid: eventId
}
};
return Ajax.call([request])[0];
};
/**
* Submit the form data for the event form.
*
* @method submitCreateUpdateForm
* @param {string} formData The URL encoded values from the form
* @return {promise} Resolved with the new or edited event
*/
export const submitCreateUpdateForm = (formData) => {
const request = {
methodname: 'core_calendar_submit_create_update_form',
args: {
formdata: formData
}
};
return Ajax.call([request])[0];
};
/**
* Get calendar data for the month view.
*
* @method getCalendarMonthData
* @param {number} year Year
* @param {number} month Month
* @param {number} courseId The course id.
* @param {number} categoryId The category id.
* @param {boolean} includeNavigation Whether to include navigation.
* @param {boolean} mini Whether the month is in mini view.
* @param {number} day Day (optional)
* @param {string} view The calendar view mode.
* @return {promise} Resolved with the month view data.
*/
export const getCalendarMonthData = (year, month, courseId, categoryId, includeNavigation, mini, day = 1, view = 'month') => {
const request = {
methodname: 'core_calendar_get_calendar_monthly_view',
args: {
year,
month,
courseid: courseId,
categoryid: categoryId,
includenavigation: includeNavigation,
mini,
day,
view,
}
};
return Ajax.call([request])[0];
};
/**
* Get calendar data for the day view.
*
* @method getCalendarDayData
* @param {number} year Year
* @param {number} month Month
* @param {number} day Day
* @param {number} courseId The course id.
* @param {number} categoryId The id of the category whose events are shown
* @return {promise} Resolved with the day view data.
*/
export const getCalendarDayData = (year, month, day, courseId, categoryId) => {
const request = {
methodname: 'core_calendar_get_calendar_day_view',
args: {
year,
month,
day,
courseid: courseId,
categoryid: categoryId,
}
};
return Ajax.call([request])[0];
};
/**
* Change the start day for the given event id. The day timestamp
* only has to be any time during the target day because only the
* date information is extracted, the time of the day is ignored.
*
* @param {int} eventId The id of the event to update
* @param {int} dayTimestamp A timestamp for some time during the target day
* @return {promise}
*/
export const updateEventStartDay = (eventId, dayTimestamp) => {
const request = {
methodname: 'core_calendar_update_event_start_day',
args: {
eventid: eventId,
daytimestamp: dayTimestamp
}
};
return Ajax.call([request])[0];
};
/**
* Get calendar upcoming data.
*
* @method getCalendarUpcomingData
* @param {number} courseId The course id.
* @param {number} categoryId The category id.
* @return {promise} Resolved with the month view data.
*/
export const getCalendarUpcomingData = (courseId, categoryId) => {
const request = {
methodname: 'core_calendar_get_calendar_upcoming_view',
args: {
courseid: courseId,
categoryid: categoryId,
}
};
return Ajax.call([request])[0];
};
/**
* Get the groups by course id.
*
* @param {Number} courseId The course id to fetch the groups from.
* @return {promise} Resolved with the course groups.
*/
export const getCourseGroupsData = (courseId) => {
const request = {
methodname: 'core_group_get_course_groups',
args: {
courseid: courseId
}
};
return Ajax.call([request])[0];
};
/**
* Delete calendar subscription by id.
*
* @param {Number} subscriptionId The subscription id
* @return {promise}
*/
export const deleteSubscription = (subscriptionId) => {
const request = {
methodname: 'core_calendar_delete_subscription',
args: {
subscriptionid: subscriptionId
}
};
return Ajax.call([request])[0];
};
+78
View File
@@ -0,0 +1,78 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* CSS selectors for the calendar.
*
* @module core_calendar/selectors
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define([], function() {
return {
eventFilterItem: "[data-action='filter-event-type']",
eventType: {
site: "[data-eventtype-site]",
category: "[data-eventtype-category]",
course: "[data-eventtype-course]",
group: "[data-eventtype-group]",
user: "[data-eventtype-user]",
other: "[data-eventtype-other]",
},
popoverType: {
site: "[data-popover-eventtype-site]",
category: "[data-popover-eventtype-category]",
course: "[data-popover-eventtype-course]",
group: "[data-popover-eventtype-group]",
user: "[data-popover-eventtype-user]",
other: "[data-popover-eventtype-other]",
},
calendarPeriods: {
month: "[data-period='month']",
},
courseSelector: 'select[name="course"]',
viewSelector: 'div[data-region="view-selector"]',
actions: {
create: '[data-action="new-event-button"]',
edit: '[data-action="edit"]',
remove: '[data-action="delete"]',
viewEvent: '[data-action="view-event"]',
deleteSubscription: '[data-action="delete-subscription"]',
},
elements: {
courseSelector: 'select[name="course"]',
dateContainer: '.clickable.hasevent',
dateContent: '[data-region="day-content"]',
monthDetailed: '.calendarmonth.calendartable',
},
today: '.today',
day: '[data-region="day"]',
calendarMain: '[data-region="calendar"]',
wrapper: '.calendarwrapper',
eventItem: '[data-type="event"]',
links: {
navLink: '.calendarwrapper .arrow_link',
eventLink: "[data-region='event-item']",
miniDayLink: "[data-region='mini-day-link']",
dayLink: "[data-action='view-day-link']",
},
containers: {
loadingIcon: '[data-region="overlay-icon-container"]',
},
mainCalendar: '.maincalendar .heightcontainer',
fullCalendarView: 'page-calendar-view',
pageHeaderHeadings: '.page-header-headings h1',
};
});
+181
View File
@@ -0,0 +1,181 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A javascript module to handle summary modal.
*
* @module core_calendar/summary_modal
* @copyright 2017 Simey Lameze <simey@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import * as CustomEvents from 'core/custom_interaction_events';
import Modal from 'core/modal';
import CalendarEvents from './events';
import * as CalendarCrud from 'core_calendar/crud';
import * as ModalEvents from 'core/modal_events';
const SELECTORS = {
ROOT: "[data-region='summary-modal-container']",
EDIT_BUTTON: '[data-action="edit"]',
DELETE_BUTTON: '[data-action="delete"]',
};
export default class ModalEventSummary extends Modal {
static TEMPLATE = 'core_calendar/event_summary_modal';
static TYPE = 'core_calendar-event_summary';
/**
* Get the edit button element from the footer. The button is cached
* as it's not expected to change.
*
* @method getEditButton
* @return {object} button element
*/
getEditButton() {
if (typeof this.editButton == 'undefined') {
this.editButton = this.getFooter().find(SELECTORS.EDIT_BUTTON);
}
return this.editButton;
}
/**
* Get the delete button element from the footer. The button is cached
* as it's not expected to change.
*
* @method getDeleteButton
* @return {object} button element
*/
getDeleteButton() {
if (typeof this.deleteButton == 'undefined') {
this.deleteButton = this.getFooter().find(SELECTORS.DELETE_BUTTON);
}
return this.deleteButton;
}
/**
* Get the id for the event being shown in this modal. This value is
* not cached because it will change depending on which event is
* being displayed.
*
* @method getEventId
* @return {int}
*/
getEventId() {
return this.getBody().find(SELECTORS.ROOT).attr('data-event-id');
}
/**
* Get the title for the event being shown in this modal. This value is
* not cached because it will change depending on which event is
* being displayed.
*
* @method getEventTitle
* @return {String}
*/
getEventTitle() {
return this.getBody().find(SELECTORS.ROOT).attr('data-event-title');
}
/**
* Get the number of events in the series for the event being shown in
* this modal. This value is not cached because it will change
* depending on which event is being displayed.
*
* @method getEventCount
* @return {int}
*/
getEventCount() {
return this.getBody().find(SELECTORS.ROOT).attr('data-event-count');
}
/**
* Get the url for the event being shown in this modal.
*
* @method getEventUrl
* @return {String}
*/
getEditUrl() {
return this.getBody().find(SELECTORS.ROOT).attr('data-edit-url');
}
/**
* Is this an action event.
*
* @method getEventUrl
* @return {String}
*/
isActionEvent() {
return (this.getBody().find(SELECTORS.ROOT).attr('data-action-event') == 'true');
}
/**
* Set up all of the event handling for the modal.
*
* @method registerEventListeners
*/
registerEventListeners() {
// Apply parent event listeners.
super.registerEventListeners(this);
// We have to wait for the modal to finish rendering in order to ensure that
// the data-event-title property is available to use as the modal title.
M.util.js_pending('core_calendar/summary_modal:registerEventListeners:bodyRendered');
this.getRoot().on(ModalEvents.bodyRendered, function() {
this.getModal().data({
eventTitle: this.getEventTitle(),
eventId: this.getEventId(),
eventCount: this.getEventCount(),
})
.attr('data-type', 'event');
CalendarCrud.registerRemove(this.getModal());
M.util.js_complete('core_calendar/summary_modal:registerEventListeners:bodyRendered');
}.bind(this));
$('body').on(CalendarEvents.deleted, function() {
// Close the dialogue on delete.
this.hide();
}.bind(this));
CustomEvents.define(this.getEditButton(), [
CustomEvents.events.activate
]);
this.getEditButton().on(CustomEvents.events.activate, function(e, data) {
if (this.isActionEvent()) {
// Action events cannot be edited on the event form and must be redirected to the module UI.
$('body').trigger(CalendarEvents.editActionEvent, [this.getEditUrl()]);
} else {
// When the edit button is clicked we fire an event for the calendar UI to handle.
// We don't care how the UI chooses to handle it.
$('body').trigger(CalendarEvents.editEvent, [this.getEventId()]);
}
// There is nothing else for us to do so let's hide.
this.hide();
// We've handled this event so no need to propagate it.
e.preventDefault();
e.stopPropagation();
data.originalEvent.preventDefault();
data.originalEvent.stopPropagation();
}.bind(this));
}
}
ModalEventSummary.registerModalType();
+618
View File
@@ -0,0 +1,618 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* A javascript module to handler calendar view changes.
*
* @module core_calendar/view_manager
* @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
import $ from 'jquery';
import Templates from 'core/templates';
import Notification from 'core/notification';
import * as CalendarRepository from 'core_calendar/repository';
import CalendarEvents from 'core_calendar/events';
import * as CalendarSelectors from 'core_calendar/selectors';
import ModalEvents from 'core/modal_events';
import SummaryModal from 'core_calendar/summary_modal';
import CustomEvents from 'core/custom_interaction_events';
import {getString} from 'core/str';
import Pending from 'core/pending';
import {prefetchStrings} from 'core/prefetch';
import Url from 'core/url';
import Config from 'core/config';
/**
* Limit number of events per day
*
*/
const LIMIT_DAY_EVENTS = 5;
/**
* Hide day events if more than 5.
*
*/
export const foldDayEvents = () => {
const root = $(CalendarSelectors.elements.monthDetailed);
const days = root.find(CalendarSelectors.day);
if (days.length === 0) {
return;
}
days.each(function() {
const dayContainer = $(this);
const eventsSelector = `${CalendarSelectors.elements.dateContent} ul li[data-event-eventtype]`;
const filteredEventsSelector = `${CalendarSelectors.elements.dateContent} ul li[data-event-filtered="true"]`;
const moreEventsSelector = `${CalendarSelectors.elements.dateContent} [data-action="view-more-events"]`;
const events = dayContainer.find(eventsSelector);
if (events.length === 0) {
return;
}
const filteredEvents = dayContainer.find(filteredEventsSelector);
const numberOfFiltered = filteredEvents.length;
const numberOfEvents = events.length - numberOfFiltered;
let count = 1;
events.each(function() {
const event = $(this);
const isNotFiltered = event.attr('data-event-filtered') !== 'true';
const offset = (numberOfEvents === LIMIT_DAY_EVENTS) ? 0 : 1;
if (isNotFiltered) {
if (count > LIMIT_DAY_EVENTS - offset) {
event.attr('data-event-folded', 'true');
event.hide();
} else {
event.attr('data-event-folded', 'false');
event.show();
count++;
}
} else {
// It's being filtered out.
event.attr('data-event-folded', 'false');
}
});
const moreEventsLink = dayContainer.find(moreEventsSelector);
if (numberOfEvents > LIMIT_DAY_EVENTS) {
const numberOfHiddenEvents = numberOfEvents - LIMIT_DAY_EVENTS + 1;
moreEventsLink.show();
getString('moreevents', 'calendar', numberOfHiddenEvents).then(str => {
const link = moreEventsLink.find('strong a');
moreEventsLink.attr('data-event-folded', 'false');
link.text(str);
return str;
}).catch(Notification.exception);
} else {
moreEventsLink.hide();
}
});
};
/**
* Register and handle month calendar events.
*
* @param {string} pendingId pending id.
*/
export const registerEventListenersForMonthDetailed = (pendingId) => {
const events = `${CalendarEvents.viewUpdated}`;
$('body').on(events, function(e) {
foldDayEvents(e);
});
foldDayEvents();
$('body').on(CalendarEvents.filterChanged, function(e, data) {
const root = $(CalendarSelectors.elements.monthDetailed);
const pending = new Pending(pendingId);
const target = root.find(CalendarSelectors.eventType[data.type]);
const transitionPromise = $.Deferred();
if (data.hidden) {
transitionPromise.then(function() {
target.attr('data-event-filtered', 'true');
return target.hide().promise();
}).fail();
} else {
transitionPromise.then(function() {
target.attr('data-event-filtered', 'false');
return target.show().promise();
}).fail();
}
transitionPromise.then(function() {
foldDayEvents();
return;
})
.always(pending.resolve)
.fail();
transitionPromise.resolve();
});
};
/**
* Register event listeners for the module.
*
* @param {object} root The root element.
* @param {boolean} isCalendarBlock - A flag indicating whether this is a calendar block.
*/
const registerEventListeners = (root, isCalendarBlock) => {
root = $(root);
// Bind click events to event links.
root.on('click', CalendarSelectors.links.eventLink, (e) => {
const target = e.target;
let eventLink = null;
let eventId = null;
const pendingPromise = new Pending('core_calendar/view_manager:eventLink:click');
if (target.matches(CalendarSelectors.actions.viewEvent)) {
eventLink = target;
} else {
eventLink = target.closest(CalendarSelectors.actions.viewEvent);
}
if (eventLink) {
eventId = eventLink.dataset.eventId;
} else {
eventId = target.querySelector(CalendarSelectors.actions.viewEvent).dataset.eventId;
}
if (eventId) {
// A link was found. Show the modal.
e.preventDefault();
// We've handled the event so stop it from bubbling
// and causing the day click handler to fire.
e.stopPropagation();
renderEventSummaryModal(eventId)
.then(pendingPromise.resolve)
.catch();
} else {
pendingPromise.resolve();
}
});
root.on('click', CalendarSelectors.links.navLink, (e) => {
const wrapper = root.find(CalendarSelectors.wrapper);
const view = wrapper.data('view');
const courseId = wrapper.data('courseid');
const categoryId = wrapper.data('categoryid');
const link = e.currentTarget;
if (view === 'month' || view === 'monthblock') {
changeMonth(root, link.href, link.dataset.year, link.dataset.month,
courseId, categoryId, link.dataset.day, isCalendarBlock);
e.preventDefault();
} else if (view === 'day') {
changeDay(root, link.href, link.dataset.year, link.dataset.month, link.dataset.day,
courseId, categoryId, isCalendarBlock);
e.preventDefault();
}
});
const viewSelector = root.find(CalendarSelectors.viewSelector);
CustomEvents.define(viewSelector, [CustomEvents.events.activate]);
viewSelector.on(
CustomEvents.events.activate,
(e) => {
e.preventDefault();
const option = e.target;
if (option.classList.contains('active')) {
return;
}
const view = option.dataset.view,
year = option.dataset.year,
month = option.dataset.month,
day = option.dataset.day,
courseId = option.dataset.courseid,
categoryId = option.dataset.categoryid;
if (view == 'month') {
refreshMonthContent(root, year, month, courseId, categoryId, root, 'core_calendar/calendar_month', day)
.then(() => {
updateUrl('?view=month&course=' + courseId);
return;
}).fail(Notification.exception);
} else if (view == 'day') {
refreshDayContent(root, year, month, day, courseId, categoryId, root, 'core_calendar/calendar_day')
.then(() => {
updateUrl('?view=day&course=' + courseId);
return;
}).fail(Notification.exception);
} else if (view == 'upcoming') {
reloadCurrentUpcoming(root, courseId, categoryId, root, 'core_calendar/calendar_upcoming')
.then(() => {
updateUrl('?view=upcoming&course=' + courseId);
return;
}).fail(Notification.exception);
}
}
);
};
/**
* Refresh the month content.
*
* @param {object} root The root element.
* @param {number} year Year
* @param {number} month Month
* @param {number} courseId The id of the course whose events are shown
* @param {number} categoryId The id of the category whose events are shown
* @param {object} target The element being replaced. If not specified, the calendarwrapper is used.
* @param {string} template The template to be rendered.
* @param {number} day Day (optional)
* @return {promise}
*/
export const refreshMonthContent = (root, year, month, courseId, categoryId, target = null, template = '', day = 1) => {
startLoading(root);
target = target || root.find(CalendarSelectors.wrapper);
template = template || root.attr('data-template');
M.util.js_pending([root.get('id'), year, month, courseId].join('-'));
const includenavigation = root.data('includenavigation');
const mini = root.data('mini');
const viewMode = target.data('view');
return CalendarRepository.getCalendarMonthData(year, month, courseId, categoryId, includenavigation, mini, day, viewMode)
.then(context => {
return Templates.render(template, context);
})
.then((html, js) => {
return Templates.replaceNode(target, html, js);
})
.then(() => {
document.querySelector('body').dispatchEvent(new CustomEvent(CalendarEvents.viewUpdated));
return;
})
.always(() => {
M.util.js_complete([root.get('id'), year, month, courseId].join('-'));
return stopLoading(root);
})
.fail(Notification.exception);
};
/**
* Handle changes to the current calendar view.
*
* @param {object} root The container element
* @param {string} url The calendar url to be shown
* @param {number} year Year
* @param {number} month Month
* @param {number} courseId The id of the course whose events are shown
* @param {number} categoryId The id of the category whose events are shown
* @param {number} day Day (optional)
* @param {boolean} [isCalendarBlock=false] - A flag indicating whether this is a calendar block.
* @return {promise}
*/
export const changeMonth = (root, url, year, month, courseId, categoryId, day = 1, isCalendarBlock = false) => {
return refreshMonthContent(root, year, month, courseId, categoryId, null, '', day)
.then((...args) => {
if (url.length && url !== '#' && !isCalendarBlock) {
updateUrl(url);
}
return args;
})
.then((...args) => {
$('body').trigger(CalendarEvents.monthChanged, [year, month, courseId, categoryId, day, isCalendarBlock]);
return args;
});
};
/**
* Reload the current month view data.
*
* @param {object} root The container element.
* @param {number} courseId The course id.
* @param {number} categoryId The id of the category whose events are shown
* @return {promise}
*/
export const reloadCurrentMonth = (root, courseId = 0, categoryId = 0) => {
const year = root.find(CalendarSelectors.wrapper).data('year');
const month = root.find(CalendarSelectors.wrapper).data('month');
const day = root.find(CalendarSelectors.wrapper).data('day');
courseId = courseId || root.find(CalendarSelectors.wrapper).data('courseid');
categoryId = categoryId || root.find(CalendarSelectors.wrapper).data('categoryid');
return refreshMonthContent(root, year, month, courseId, categoryId, null, '', day).
then((...args) => {
$('body').trigger(CalendarEvents.courseChanged, [year, month, courseId, categoryId]);
return args;
});
};
/**
* Refresh the day content.
*
* @param {object} root The root element.
* @param {number} year Year
* @param {number} month Month
* @param {number} day Day
* @param {number} courseId The id of the course whose events are shown
* @param {number} categoryId The id of the category whose events are shown
* @param {object} target The element being replaced. If not specified, the calendarwrapper is used.
* @param {string} template The template to be rendered.
* @param {boolean} isCalendarBlock - A flag indicating whether this is a calendar block.
*
* @return {promise}
*/
export const refreshDayContent = (root, year, month, day, courseId, categoryId,
target = null, template = '', isCalendarBlock = false) => {
startLoading(root);
if (!target || target.length == 0){
target = root.find(CalendarSelectors.wrapper);
}
template = template || root.attr('data-template');
M.util.js_pending([root.get('id'), year, month, day, courseId, categoryId].join('-'));
const includenavigation = root.data('includenavigation');
return CalendarRepository.getCalendarDayData(year, month, day, courseId, categoryId, includenavigation)
.then((context) => {
context.viewingday = true;
context.showviewselector = true;
context.iscalendarblock = isCalendarBlock;
return Templates.render(template, context);
})
.then((html, js) => {
return Templates.replaceNode(target, html, js);
})
.then(() => {
document.querySelector('body').dispatchEvent(new CustomEvent(CalendarEvents.viewUpdated));
return;
})
.always(() => {
M.util.js_complete([root.get('id'), year, month, day, courseId, categoryId].join('-'));
return stopLoading(root);
})
.fail(Notification.exception);
};
/**
* Reload the current day view data.
*
* @param {object} root The container element.
* @param {number} courseId The course id.
* @param {number} categoryId The id of the category whose events are shown
* @return {promise}
*/
export const reloadCurrentDay = (root, courseId = 0, categoryId = 0) => {
const wrapper = root.find(CalendarSelectors.wrapper);
const year = wrapper.data('year');
const month = wrapper.data('month');
const day = wrapper.data('day');
courseId = courseId || root.find(CalendarSelectors.wrapper).data('courseid');
categoryId = categoryId || root.find(CalendarSelectors.wrapper).data('categoryid');
return refreshDayContent(root, year, month, day, courseId, categoryId);
};
/**
* Handle changes to the current calendar view.
*
* @param {object} root The root element.
* @param {String} url The calendar url to be shown
* @param {Number} year Year
* @param {Number} month Month
* @param {Number} day Day
* @param {Number} courseId The id of the course whose events are shown
* @param {Number} categoryId The id of the category whose events are shown
* @param {boolean} [isCalendarBlock=false] - A flag indicating whether this is a calendar block.
* @return {promise}
*/
export const changeDay = (root, url, year, month, day, courseId, categoryId, isCalendarBlock = false) => {
return refreshDayContent(root, year, month, day, courseId, categoryId, null, '', isCalendarBlock)
.then((...args) => {
if (url.length && url !== '#' && !isCalendarBlock) {
updateUrl(url);
}
return args;
})
.then((...args) => {
$('body').trigger(CalendarEvents.dayChanged, [year, month, courseId, categoryId, isCalendarBlock]);
return args;
});
};
/**
* Update calendar URL.
*
* @param {String} url The calendar url to be updated.
*/
export const updateUrl = (url) => {
const viewingFullCalendar = document.getElementById(CalendarSelectors.fullCalendarView);
// We want to update the url only if the user is viewing the full calendar.
if (viewingFullCalendar) {
window.history.pushState({}, '', url);
}
};
/**
* Set the element state to loading.
*
* @param {object} root The container element
* @method startLoading
*/
const startLoading = (root) => {
const loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);
loadingIconContainer.removeClass('hidden');
};
/**
* Remove the loading state from the element.
*
* @param {object} root The container element
* @method stopLoading
*/
const stopLoading = (root) => {
const loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);
loadingIconContainer.addClass('hidden');
};
/**
* Reload the current month view data.
*
* @param {object} root The container element.
* @param {number} courseId The course id.
* @param {number} categoryId The id of the category whose events are shown
* @param {object} target The element being replaced. If not specified, the calendarwrapper is used.
* @param {string} template The template to be rendered.
* @return {promise}
*/
export const reloadCurrentUpcoming = (root, courseId = 0, categoryId = 0, target = null, template = '') => {
startLoading(root);
target = target || root.find(CalendarSelectors.wrapper);
template = template || root.attr('data-template');
courseId = courseId || root.find(CalendarSelectors.wrapper).data('courseid');
categoryId = categoryId || root.find(CalendarSelectors.wrapper).data('categoryid');
return CalendarRepository.getCalendarUpcomingData(courseId, categoryId)
.then((context) => {
context.viewingupcoming = true;
context.showviewselector = true;
return Templates.render(template, context);
})
.then((html, js) => {
return Templates.replaceNode(target, html, js);
})
.then(() => {
document.querySelector('body').dispatchEvent(new CustomEvent(CalendarEvents.viewUpdated));
return;
})
.always(function() {
return stopLoading(root);
})
.fail(Notification.exception);
};
/**
* Get the CSS class to apply for the given event type.
*
* @param {string} eventType The calendar event type
* @return {string}
*/
const getEventTypeClassFromType = (eventType) => {
return 'calendar_event_' + eventType;
};
/**
* Render the event summary modal.
*
* @param {Number} eventId The calendar event id.
* @returns {Promise}
*/
const renderEventSummaryModal = (eventId) => {
const pendingPromise = new Pending('core_calendar/view_manager:renderEventSummaryModal');
// Calendar repository promise.
return CalendarRepository.getEventById(eventId)
.then((getEventResponse) => {
if (!getEventResponse.event) {
throw new Error('Error encountered while trying to fetch calendar event with ID: ' + eventId);
}
return getEventResponse.event;
})
.then(eventData => {
// Build the modal parameters from the event data.
const modalParams = {
title: eventData.name,
body: Templates.render('core_calendar/event_summary_body', eventData),
templateContext: {
canedit: eventData.canedit,
candelete: eventData.candelete,
headerclasses: getEventTypeClassFromType(eventData.normalisedeventtype),
isactionevent: eventData.isactionevent,
url: eventData.url,
action: eventData.action
}
};
// Create the modal.
return SummaryModal.create(modalParams);
})
.then(modal => {
// Handle hidden event.
modal.getRoot().on(ModalEvents.hidden, function() {
// Destroy when hidden.
modal.destroy();
});
// Finally, render the modal!
modal.show();
return modal;
})
.then(modal => {
pendingPromise.resolve();
return modal;
})
.catch(Notification.exception);
};
/**
* Initializes the calendar component by prefetching strings, folding day events,
* and registering event listeners.
*
* @param {HTMLElement} root - The root element where the calendar view manager and event listeners will be attached.
* @param {string} view - A flag indicating whether this is a calendar block.
* @param {boolean} isCalendarBlock - A flag indicating whether this is a calendar block.
*/
export const init = (root, view, isCalendarBlock) => {
prefetchStrings('calendar', ['moreevents']);
foldDayEvents();
registerEventListeners(root, isCalendarBlock);
const calendarTable = root.find(CalendarSelectors.elements.monthDetailed);
if (calendarTable.length) {
const pendingId = `month-detailed-${calendarTable.id}-filterChanged`;
registerEventListenersForMonthDetailed(calendarTable, pendingId);
}
};
/**
* Handles the change of course and updates the relevant elements on the page.
*
* @param {integer} courseId - The ID of the new course.
* @param {string} courseName - The name of the new course.
* @returns {Promise<void>} - A promise that resolves after the updates are applied.
*/
export const handleCourseChange = async(courseId, courseName) => {
// Select the <ul> element inside the data-region="view-selector".
const ulElement = document.querySelector(CalendarSelectors.viewSelector + ' ul');
// Select all <li><a> elements within the <ul>.
const liElements = ulElement.querySelectorAll('li a');
// Loop through the selected elements and update the courseid.
liElements.forEach(element => {
element.setAttribute('data-courseid', courseId);
});
const calendar = await getString('calendar', 'calendar');
const pageHeaderHeadingsElement = document.querySelector(CalendarSelectors.pageHeaderHeadings);
const courseUrl = Url.relativeUrl('/course/view.php', {id: courseId});
// Apply the page header text.
if (courseId !== Config.siteId) {
pageHeaderHeadingsElement.innerHTML = calendar + ': <a href="' + courseUrl + '">' + courseName + '</a>';
} else {
pageHeaderHeadingsElement.innerHTML = calendar;
}
};