MDL-59940 calendar: Fix eslint errors
[moodle.git] / calendar / amd / src / calendar.js
1 // This file is part of Moodle - http://moodle.org/
2 //
3 // Moodle is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, either version 3 of the License, or
6 // (at your option) any later version.
7 //
8 // Moodle is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * This module is the highest level module for the calendar. It is
18  * responsible for initialising all of the components required for
19  * the calendar to run. It also coordinates the interaction between
20  * components by listening for and responding to different events
21  * triggered within the calendar UI.
22  *
23  * @module     core_calendar/calendar
24  * @package    core_calendar
25  * @copyright  2017 Simey Lameze <simey@moodle.com>
26  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27  */
28 define([
29             'jquery',
30             'core/ajax',
31             'core/str',
32             'core/templates',
33             'core/notification',
34             'core/custom_interaction_events',
35             'core/modal_events',
36             'core/modal_factory',
37             'core_calendar/modal_event_form',
38             'core_calendar/summary_modal',
39             'core_calendar/repository',
40             'core_calendar/events',
41             'core_calendar/view_manager'
42         ],
43         function(
44             $,
45             Ajax,
46             Str,
47             Templates,
48             Notification,
49             CustomEvents,
50             ModalEvents,
51             ModalFactory,
52             ModalEventForm,
53             SummaryModal,
54             CalendarRepository,
55             CalendarEvents,
56             CalendarViewManager
57         ) {
59     var SELECTORS = {
60         ROOT: "[data-region='calendar']",
61         DAY: "[data-region='day']",
62         EVENT_ITEM: "[data-region='event-item']",
63         EVENT_LINK: "[data-action='view-event']",
64         NEW_EVENT_BUTTON: "[data-action='new-event-button']",
65         DAY_CONTENT: "[data-region='day-content']",
66         LOADING_ICON: '.loading-icon',
67         VIEW_DAY_LINK: "[data-action='view-day-link']",
68         CALENDAR_MONTH_WRAPPER: ".calendarwrapper",
69         COURSE_SELECTOR: 'select[name="course"]',
70         TODAY: '.today',
71     };
73     /**
74      * Get the event type lang string.
75      *
76      * @param {String} eventType The event type.
77      * @return {promise} The lang string promise.
78      */
79     var getEventType = function(eventType) {
80         var lang = 'type' + eventType;
81         return Str.get_string(lang, 'core_calendar').then(function(langStr) {
82             return langStr;
83         });
84     };
86     /**
87      * Render the event summary modal.
88      *
89      * @param {Number} eventId The calendar event id.
90      */
91     var renderEventSummaryModal = function(eventId) {
92         // Calendar repository promise.
93         CalendarRepository.getEventById(eventId).then(function(getEventResponse) {
94             if (!getEventResponse.event) {
95                 throw new Error('Error encountered while trying to fetch calendar event with ID: ' + eventId);
96             }
97             var eventData = getEventResponse.event;
99             return getEventType(eventData.eventtype).then(function(eventType) {
100                 eventData.eventtype = eventType;
101                 return eventData;
102             });
103         }).then(function(eventData) {
104             // Build the modal parameters from the event data.
105             var modalParams = {
106                 title: eventData.name,
107                 type: SummaryModal.TYPE,
108                 body: Templates.render('core_calendar/event_summary_body', eventData),
109                 templateContext: {
110                     canedit: eventData.canedit,
111                     candelete: eventData.candelete,
112                     isactionevent: eventData.isactionevent,
113                     url: eventData.url
114                 }
115             };
117             // Create the modal.
118             return ModalFactory.create(modalParams);
120         }).done(function(modal) {
121             // Handle hidden event.
122             modal.getRoot().on(ModalEvents.hidden, function() {
123                 // Destroy when hidden.
124                 modal.destroy();
125             });
127             // Finally, render the modal!
128             modal.show();
130         }).fail(Notification.exception);
131     };
133     /**
134      * Handler for the drag and drop move event. Provides a loading indicator
135      * while the request is sent to the server to update the event start date.
136      *
137      * Triggers a eventMoved calendar javascript event if the event was successfully
138      * updated.
139      *
140      * @param {event} e The calendar move event
141      * @param {int} eventId The event id being moved
142      * @param {object|null} originElement The jQuery element for where the event is moving from
143      * @param {object} destinationElement The jQuery element for where the event is moving to
144      */
145     var handleMoveEvent = function(e, eventId, originElement, destinationElement) {
146         var originTimestamp = null;
147         var destinationTimestamp = destinationElement.attr('data-day-timestamp');
149         if (originElement) {
150             originTimestamp = originElement.attr('data-day-timestamp');
151         }
153         // If the event has actually changed day.
154         if (!originElement || originTimestamp != destinationTimestamp) {
155             Templates.render('core/loading', {})
156                 .then(function(html, js) {
157                     // First we show some loading icons in each of the days being affected.
158                     destinationElement.find(SELECTORS.DAY_CONTENT).addClass('hidden');
159                     Templates.appendNodeContents(destinationElement, html, js);
161                     if (originElement) {
162                         originElement.find(SELECTORS.DAY_CONTENT).addClass('hidden');
163                         Templates.appendNodeContents(originElement, html, js);
164                     }
165                     return;
166                 })
167                 .then(function() {
168                     // Send a request to the server to make the change.
169                     return CalendarRepository.updateEventStartDay(eventId, destinationTimestamp);
170                 })
171                 .then(function() {
172                     // If the update was successful then broadcast an event letting the calendar
173                     // know that an event has been moved.
174                     $('body').trigger(CalendarEvents.eventMoved, [eventId, originElement, destinationElement]);
175                     return;
176                 })
177                 .always(function() {
178                     // Always remove the loading icons regardless of whether the update
179                     // request was successful or not.
180                     var destinationLoadingElement = destinationElement.find(SELECTORS.LOADING_ICON);
181                     destinationElement.find(SELECTORS.DAY_CONTENT).removeClass('hidden');
182                     Templates.replaceNode(destinationLoadingElement, '', '');
184                     if (originElement) {
185                         var originLoadingElement = originElement.find(SELECTORS.LOADING_ICON);
186                         originElement.find(SELECTORS.DAY_CONTENT).removeClass('hidden');
187                         Templates.replaceNode(originLoadingElement, '', '');
188                     }
189                     return;
190                 })
191                 .fail(Notification.exception);
192         }
193     };
195     /**
196      * Create the event form modal for creating new events and
197      * editing existing events.
198      *
199      * @method registerEventFormModal
200      * @param {object} root The calendar root element
201      * @return {object} The create modal promise
202      */
203     var registerEventFormModal = function(root) {
204         var newEventButton = root.find(SELECTORS.NEW_EVENT_BUTTON);
205         var contextId = newEventButton.attr('data-context-id');
207         return ModalFactory.create(
208             {
209                 type: ModalEventForm.TYPE,
210                 large: true,
211                 templateContext: {
212                     contextid: contextId
213                 }
214             }
215         );
216     };
218     /**
219      * Listen to and handle any calendar events fired by the calendar UI.
220      *
221      * @method registerCalendarEventListeners
222      * @param {object} root The calendar root element
223      * @param {object} eventFormModalPromise A promise reolved with the event form modal
224      */
225     var registerCalendarEventListeners = function(root, eventFormModalPromise) {
226         var body = $('body'),
227             courseId = $(root).find(SELECTORS.CALENDAR_MONTH_WRAPPER).data('courseid');
229         body.on(CalendarEvents.created, function() {
230             CalendarViewManager.reloadCurrentMonth(root);
231         });
232         body.on(CalendarEvents.deleted, function() {
233             CalendarViewManager.reloadCurrentMonth(root);
234         });
235         body.on(CalendarEvents.updated, function() {
236             CalendarViewManager.reloadCurrentMonth(root);
237         });
238         body.on(CalendarEvents.editActionEvent, function(e, url) {
239             // Action events needs to be edit directly on the course module.
240             window.location.assign(url);
241         });
242         // Handle the event fired by the drag and drop code.
243         body.on(CalendarEvents.moveEvent, handleMoveEvent);
244         // When an event is successfully moved we should updated the UI.
245         body.on(CalendarEvents.eventMoved, function() {
246             CalendarViewManager.reloadCurrentMonth(root);
247         });
249         eventFormModalPromise.then(function(modal) {
250             // When something within the calendar tells us the user wants
251             // to edit an event then show the event form modal.
252             body.on(CalendarEvents.editEvent, function(e, eventId) {
253                 modal.setEventId(eventId);
254                 modal.show();
255             });
256             modal.setCourseId(courseId);
257             return;
258         })
259         .fail(Notification.exception);
260     };
262     /**
263      * Register event listeners for the module.
264      *
265      * @param {object} root The calendar root element
266      */
267     var registerEventListeners = function(root) {
268         // Bind click events to event links.
269         root.on('click', SELECTORS.EVENT_ITEM, function(e) {
270             e.preventDefault();
271             // We've handled the event so stop it from bubbling
272             // and causing the day click handler to fire.
273             e.stopPropagation();
275             var target = $(e.target);
276             var eventId = null;
278             if (target.is(SELECTORS.EVENT_LINK)) {
279                 eventId = target.attr('data-event-id');
280             } else {
281                 eventId = target.find(SELECTORS.EVENT_LINK).attr('data-event-id');
282             }
284             renderEventSummaryModal(eventId);
285         });
287         root.on('change', SELECTORS.COURSE_SELECTOR, function() {
288             var selectElement = $(this);
289             var courseId = selectElement.val();
290             CalendarViewManager.reloadCurrentMonth(root, courseId)
291                 .then(function() {
292                     // We need to get the selector again because the content has changed.
293                     return root.find(SELECTORS.COURSE_SELECTOR).val(courseId);
294                 })
295                 .fail(Notification.exception);
296         });
298         var eventFormPromise = registerEventFormModal(root);
299         registerCalendarEventListeners(root, eventFormPromise);
301         // Bind click event on the new event button.
302         root.on('click', SELECTORS.NEW_EVENT_BUTTON, function(e) {
303             eventFormPromise.then(function(modal) {
304                 // Attempt to find the cell for today.
305                 // If it can't be found, then use the start time of the first day on the calendar.
306                 var today = root.find(SELECTORS.TODAY);
307                 if (!today.length) {
308                     modal.setStartTime(root.find(SELECTORS.DAY).attr('data-new-event-timestamp'));
309                 }
311                 modal.show();
312                 return;
313             })
314             .fail(Notification.exception);
316             e.preventDefault();
317         });
319         // Bind click events to calendar days.
320         root.on('click', SELECTORS.DAY, function(e) {
321             var target = $(e.target);
323             if (!target.is(SELECTORS.VIEW_DAY_LINK)) {
324                 var startTime = $(this).attr('data-new-event-timestamp');
325                 eventFormPromise.then(function(modal) {
326                     modal.setStartTime(startTime);
327                     modal.show();
328                     return;
329                 })
330                 .fail(Notification.exception);
332                 e.preventDefault();
333             }
334         });
335     };
337     return {
338         init: function(root) {
339             root = $(root);
341             CalendarViewManager.init(root);
342             registerEventListeners(root);
343         }
344     };
345 });