MDL-59753 core_calendar: change course selector to normal select
[moodle.git] / calendar / amd / src / calendar.js
CommitLineData
3b0738f0
SL
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/>.
15
16/**
aa091225
RW
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.
3b0738f0
SL
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 */
aa091225
RW
28define([
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',
695c5726
AN
40 'core_calendar/events',
41 'core_calendar/view_manager'
aa091225
RW
42 ],
43 function(
44 $,
45 Ajax,
46 Str,
47 Templates,
48 Notification,
49 CustomEvents,
50 ModalEvents,
51 ModalFactory,
52 ModalEventForm,
53 SummaryModal,
54 CalendarRepository,
695c5726
AN
55 CalendarEvents,
56 CalendarViewManager
aa091225
RW
57 ) {
58
59 var SELECTORS = {
60 ROOT: "[data-region='calendar']",
f6e8cc83
RW
61 DAY: "[data-region='day']",
62 EVENT_ITEM: "[data-region='event-item']",
aa091225 63 EVENT_LINK: "[data-action='view-event']",
5ca142dc
RW
64 NEW_EVENT_BUTTON: "[data-action='new-event-button']",
65 DAY_CONTENT: "[data-region='day-content']",
66 LOADING_ICON: '.loading-icon',
29158c8b
SL
67 VIEW_DAY_LINK: "[data-action='view-day-link']",
68 CALENDAR_MONTH_WRAPPER: ".calendarwrapper"
aa091225
RW
69 };
70
71 /**
72 * Get the event type lang string.
73 *
74 * @param {String} eventType The event type.
75 * @return {promise} The lang string promise.
76 */
77 var getEventType = function(eventType) {
78 var lang = 'type' + eventType;
79 return Str.get_string(lang, 'core_calendar').then(function(langStr) {
80 return langStr;
81 });
82 };
83
84 /**
85 * Get the event source.
86 *
87 * @param {Object} subscription The event subscription object.
88 * @return {promise} The lang string promise.
89 */
90 var getEventSource = function(subscription) {
91 return Str.get_string('subsource', 'core_calendar', subscription).then(function(langStr) {
92 if (subscription.url) {
93 return '<a href="' + subscription.url + '">' + langStr + '</a>';
94 }
95 return langStr;
96 });
97 };
a43ec9eb 98
aa091225
RW
99 /**
100 * Render the event summary modal.
101 *
102 * @param {Number} eventId The calendar event id.
103 */
104 var renderEventSummaryModal = function(eventId) {
105 // Calendar repository promise.
106 CalendarRepository.getEventById(eventId).then(function(getEventResponse) {
107 if (!getEventResponse.event) {
108 throw new Error('Error encountered while trying to fetch calendar event with ID: ' + eventId);
109 }
110 var eventData = getEventResponse.event;
111 var eventTypePromise = getEventType(eventData.eventtype);
112
113 // If the calendar event has event source, get the source's language string/link.
114 if (eventData.displayeventsource) {
115 eventData.subscription = JSON.parse(eventData.subscription);
116 var eventSourceParams = {
117 url: eventData.subscription.url,
118 name: eventData.subscription.name
119 };
120 var eventSourcePromise = getEventSource(eventSourceParams);
121
122 // Return event data with event type and event source info.
123 return $.when(eventTypePromise, eventSourcePromise).then(function(eventType, eventSource) {
a43ec9eb 124 eventData.eventtype = eventType;
aa091225 125 eventData.source = eventSource;
a43ec9eb 126 return eventData;
3b0738f0 127 });
aa091225 128 }
3d67e83f 129
aa091225
RW
130 // Return event data with event type info.
131 return eventTypePromise.then(function(eventType) {
132 eventData.eventtype = eventType;
133 return eventData;
134 });
135
136 }).then(function(eventData) {
137 // Build the modal parameters from the event data.
138 var modalParams = {
139 title: eventData.name,
140 type: SummaryModal.TYPE,
4df03a27
SL
141 body: Templates.render('core_calendar/event_summary_body', eventData),
142 templateContext: {
143 canedit: eventData.canedit,
4757780a
SL
144 candelete: eventData.candelete,
145 isactionevent: eventData.isactionevent,
146 url: eventData.url
4df03a27 147 }
aa091225 148 };
4df03a27 149
aa091225
RW
150 // Create the modal.
151 return ModalFactory.create(modalParams);
152
153 }).done(function(modal) {
154 // Handle hidden event.
155 modal.getRoot().on(ModalEvents.hidden, function() {
156 // Destroy when hidden.
157 modal.destroy();
158 });
159
160 // Finally, render the modal!
161 modal.show();
162
163 }).fail(Notification.exception);
164 };
165
5ca142dc
RW
166 /**
167 * Handler for the drag and drop move event. Provides a loading indicator
168 * while the request is sent to the server to update the event start date.
169 *
170 * Triggers a eventMoved calendar javascript event if the event was successfully
171 * updated.
172 *
173 * @param {event} e The calendar move event
aefb2950
RW
174 * @param {int} eventId The event id being moved
175 * @param {object|null} originElement The jQuery element for where the event is moving from
5ca142dc
RW
176 * @param {object} destinationElement The jQuery element for where the event is moving to
177 */
aefb2950
RW
178 var handleMoveEvent = function(e, eventId, originElement, destinationElement) {
179 var originTimestamp = null;
5ca142dc
RW
180 var destinationTimestamp = destinationElement.attr('data-day-timestamp');
181
aefb2950
RW
182 if (originElement) {
183 originTimestamp = originElement.attr('data-day-timestamp');
184 }
185
5ca142dc 186 // If the event has actually changed day.
aefb2950 187 if (!originElement || originTimestamp != destinationTimestamp) {
5ca142dc
RW
188 Templates.render('core/loading', {})
189 .then(function(html, js) {
190 // First we show some loading icons in each of the days being affected.
5ca142dc 191 destinationElement.find(SELECTORS.DAY_CONTENT).addClass('hidden');
5ca142dc 192 Templates.appendNodeContents(destinationElement, html, js);
aefb2950
RW
193
194 if (originElement) {
195 originElement.find(SELECTORS.DAY_CONTENT).addClass('hidden');
196 Templates.appendNodeContents(originElement, html, js);
197 }
5ca142dc
RW
198 return;
199 })
200 .then(function() {
201 // Send a request to the server to make the change.
202 return CalendarRepository.updateEventStartDay(eventId, destinationTimestamp);
203 })
204 .then(function() {
205 // If the update was successful then broadcast an event letting the calendar
206 // know that an event has been moved.
aefb2950 207 $('body').trigger(CalendarEvents.eventMoved, [eventId, originElement, destinationElement]);
5ca142dc
RW
208 return;
209 })
210 .always(function() {
211 // Always remove the loading icons regardless of whether the update
212 // request was successful or not.
5ca142dc 213 var destinationLoadingElement = destinationElement.find(SELECTORS.LOADING_ICON);
5ca142dc 214 destinationElement.find(SELECTORS.DAY_CONTENT).removeClass('hidden');
5ca142dc 215 Templates.replaceNode(destinationLoadingElement, '', '');
aefb2950
RW
216
217 if (originElement) {
218 var originLoadingElement = originElement.find(SELECTORS.LOADING_ICON);
219 originElement.find(SELECTORS.DAY_CONTENT).removeClass('hidden');
220 Templates.replaceNode(originLoadingElement, '', '');
221 }
5ca142dc
RW
222 return;
223 })
224 .fail(Notification.exception);
225 }
226 };
227
aa091225
RW
228 /**
229 * Create the event form modal for creating new events and
230 * editing existing events.
231 *
232 * @method registerEventFormModal
233 * @param {object} root The calendar root element
234 * @return {object} The create modal promise
235 */
236 var registerEventFormModal = function(root) {
237 var newEventButton = root.find(SELECTORS.NEW_EVENT_BUTTON);
238 var contextId = newEventButton.attr('data-context-id');
239
240 return ModalFactory.create(
241 {
242 type: ModalEventForm.TYPE,
243 large: true,
244 templateContext: {
245 contextid: contextId
3d67e83f 246 }
aa091225 247 },
6a7ad2cb 248 [root, SELECTORS.NEW_EVENT_BUTTON]
aa091225
RW
249 );
250 };
a43ec9eb 251
aa091225
RW
252 /**
253 * Listen to and handle any calendar events fired by the calendar UI.
254 *
255 * @method registerCalendarEventListeners
256 * @param {object} root The calendar root element
257 * @param {object} eventFormModalPromise A promise reolved with the event form modal
258 */
259 var registerCalendarEventListeners = function(root, eventFormModalPromise) {
29158c8b
SL
260 var body = $('body'),
261 courseId = $(root).find(SELECTORS.CALENDAR_MONTH_WRAPPER).data('courseid');
aa091225 262
d6942fb5 263 body.on(CalendarEvents.created, function() {
b31a1695 264 CalendarViewManager.reloadCurrentMonth(root);
d6942fb5
RW
265 });
266 body.on(CalendarEvents.deleted, function() {
b31a1695 267 CalendarViewManager.reloadCurrentMonth(root);
d6942fb5
RW
268 });
269 body.on(CalendarEvents.updated, function() {
b31a1695 270 CalendarViewManager.reloadCurrentMonth(root);
d6942fb5 271 });
c34e2002
SL
272 body.on(CalendarEvents.editActionEvent, function(e, url) {
273 // Action events needs to be edit directly on the course module.
274 window.location.assign(url);
275 });
5ca142dc
RW
276 // Handle the event fired by the drag and drop code.
277 body.on(CalendarEvents.moveEvent, handleMoveEvent);
278 // When an event is successfully moved we should updated the UI.
279 body.on(CalendarEvents.eventMoved, function() {
aefb2950 280 CalendarViewManager.reloadCurrentMonth(root);
5ca142dc 281 });
a43ec9eb 282
aa091225
RW
283 eventFormModalPromise.then(function(modal) {
284 // When something within the calendar tells us the user wants
285 // to edit an event then show the event form modal.
286 body.on(CalendarEvents.editEvent, function(e, eventId) {
287 modal.setEventId(eventId);
288 modal.show();
3b0738f0 289 });
29158c8b 290 modal.setCourseId(courseId);
aa091225
RW
291 return;
292 });
293 };
294
295 /**
296 * Register event listeners for the module.
297 */
298 var registerEventListeners = function() {
299 var root = $(SELECTORS.ROOT);
300
301 // Bind click events to event links.
f6e8cc83 302 root.on('click', SELECTORS.EVENT_ITEM, function(e) {
aa091225 303 e.preventDefault();
f6e8cc83
RW
304 // We've handled the event so stop it from bubbling
305 // and causing the day click handler to fire.
306 e.stopPropagation();
307
308 var target = $(e.target);
309 var eventId = null;
310
311 if (target.is(SELECTORS.EVENT_LINK)) {
312 eventId = target.attr('data-event-id');
313 } else {
314 eventId = target.find(SELECTORS.EVENT_LINK).attr('data-event-id');
315 }
316
aa091225
RW
317 renderEventSummaryModal(eventId);
318 });
319
320 var eventFormPromise = registerEventFormModal(root);
321 registerCalendarEventListeners(root, eventFormPromise);
f6e8cc83
RW
322
323 // Bind click events to calendar days.
324 root.on('click', SELECTORS.DAY, function(e) {
325 var target = $(e.target);
326
327 if (!target.is(SELECTORS.VIEW_DAY_LINK)) {
328 var startTime = $(this).attr('data-new-event-timestamp');
329 eventFormPromise.then(function(modal) {
330 modal.setStartTime(startTime);
331 modal.show();
332 return;
333 });
334
335 e.preventDefault();
336 }
337 });
aa091225
RW
338 };
339
340 return {
341 init: function() {
695c5726 342 CalendarViewManager.init();
aa091225
RW
343 registerEventListeners();
344 }
345 };
346});