MDL-65318 core_calendar: Additional fixes
[moodle.git] / calendar / amd / src / view_manager.js
CommitLineData
695c5726
AN
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/**
17 * A javascript module to handler calendar view changes.
18 *
19 * @module core_calendar/view_manager
20 * @package core_calendar
21 * @copyright 2017 Andrew Nicols <andrew@nicols.co.uk>
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23 */
3ea4f446
AN
24define([
25 'jquery',
26 'core/templates',
6c3f463d 27 'core/str',
3ea4f446
AN
28 'core/notification',
29 'core_calendar/repository',
30 'core_calendar/events',
31 'core_calendar/selectors',
6c3f463d
AN
32 'core/modal_factory',
33 'core/modal_events',
34 'core_calendar/summary_modal',
3ea4f446
AN
35], function(
36 $,
37 Templates,
6c3f463d 38 Str,
3ea4f446
AN
39 Notification,
40 CalendarRepository,
41 CalendarEvents,
6c3f463d
AN
42 CalendarSelectors,
43 ModalFactory,
44 ModalEvents,
45 SummaryModal
3ea4f446 46) {
695c5726 47
695c5726
AN
48 /**
49 * Register event listeners for the module.
50 *
51 * @param {object} root The root element.
52 */
53 var registerEventListeners = function(root) {
54 root = $(root);
55
6c3f463d
AN
56 // Bind click events to event links.
57 root.on('click', CalendarSelectors.links.eventLink, function(e) {
6c3f463d
AN
58 var target = $(e.target);
59 var eventId = null;
60
c8419f87
AN
61 var eventLink;
62 if (target.is(CalendarSelectors.actions.viewEvent)) {
63 eventLink = target;
64 } else {
65 eventLink = target.closest(CalendarSelectors.actions.viewEvent);
66 }
6c3f463d
AN
67
68 if (eventLink.length) {
69 eventId = eventLink.data('eventId');
70 } else {
71 eventId = target.find(CalendarSelectors.actions.viewEvent).data('eventId');
72 }
73
c8419f87
AN
74 if (eventId) {
75 // A link was found. Show the modal.
76
77 e.preventDefault();
78 // We've handled the event so stop it from bubbling
79 // and causing the day click handler to fire.
80 e.stopPropagation();
81
82 renderEventSummaryModal(eventId);
83 }
6c3f463d
AN
84 });
85
fd2f1ae1
AN
86
87 root.on('click', CalendarSelectors.links.navLink, function(e) {
3ea4f446
AN
88 var wrapper = root.find(CalendarSelectors.wrapper);
89 var view = wrapper.data('view');
d0e56d84
AN
90 var courseId = wrapper.data('courseid');
91 var categoryId = wrapper.data('categoryid');
695c5726 92 var link = $(e.currentTarget);
695c5726 93
3ea4f446
AN
94 if (view === 'month') {
95 changeMonth(root, link.attr('href'), link.data('year'), link.data('month'), courseId, categoryId);
96 e.preventDefault();
97 } else if (view === 'day') {
98 changeDay(root, link.attr('href'), link.data('year'), link.data('month'), link.data('day'),
99 courseId, categoryId);
100 e.preventDefault();
101 }
102
695c5726
AN
103 });
104 };
105
516e7444
SL
106 /**
107 * Refresh the month content.
108 *
f58424c7
AN
109 * @param {object} root The root element.
110 * @param {Number} year Year
111 * @param {Number} month Month
516e7444 112 * @param {Number} courseid The id of the course whose events are shown
d0e56d84 113 * @param {Number} categoryid The id of the category whose events are shown
b0146885 114 * @param {object} target The element being replaced. If not specified, the calendarwrapper is used.
516e7444
SL
115 * @return {promise}
116 */
d0e56d84 117 var refreshMonthContent = function(root, year, month, courseid, categoryid, target) {
b31a1695
SL
118 startLoading(root);
119
3ea4f446 120 target = target || root.find(CalendarSelectors.wrapper);
b0146885 121
529e8776 122 M.util.js_pending([root.get('id'), year, month, courseid].join('-'));
fee025ec 123 var includenavigation = root.data('includenavigation');
60908b21
RW
124 var mini = root.data('mini');
125 return CalendarRepository.getCalendarMonthData(year, month, courseid, categoryid, includenavigation, mini)
516e7444 126 .then(function(context) {
2281a835 127 return Templates.render(root.attr('data-template'), context);
516e7444
SL
128 })
129 .then(function(html, js) {
b0146885 130 return Templates.replaceNode(target, html, js);
516e7444 131 })
c8b6e9ab
AN
132 .then(function() {
133 $('body').trigger(CalendarEvents.viewUpdated);
134 return;
135 })
b31a1695 136 .always(function() {
529e8776 137 M.util.js_complete([root.get('id'), year, month, courseid].join('-'));
b31a1695
SL
138 return stopLoading(root);
139 })
516e7444
SL
140 .fail(Notification.exception);
141 };
142
695c5726
AN
143 /**
144 * Handle changes to the current calendar view.
145 *
d0e56d84 146 * @param {object} root The container element
a4af4c96 147 * @param {String} url The calendar url to be shown
f58424c7
AN
148 * @param {Number} year Year
149 * @param {Number} month Month
695c5726 150 * @param {Number} courseid The id of the course whose events are shown
d0e56d84 151 * @param {Number} categoryid The id of the category whose events are shown
516e7444 152 * @return {promise}
695c5726 153 */
d0e56d84
AN
154 var changeMonth = function(root, url, year, month, courseid, categoryid) {
155 return refreshMonthContent(root, year, month, courseid, categoryid)
516e7444 156 .then(function() {
41fa6a24
AN
157 if (url.length && url !== '#') {
158 window.history.pushState({}, '', url);
159 }
47b55dad 160 return arguments;
516e7444
SL
161 })
162 .then(function() {
d0e56d84 163 $('body').trigger(CalendarEvents.monthChanged, [year, month, courseid, categoryid]);
47b55dad 164 return arguments;
516e7444
SL
165 });
166 };
167
168 /**
169 * Reload the current month view data.
170 *
afa8c3da
SL
171 * @param {object} root The container element.
172 * @param {Number} courseId The course id.
d0e56d84 173 * @param {Number} categoryId The id of the category whose events are shown
516e7444
SL
174 * @return {promise}
175 */
d0e56d84 176 var reloadCurrentMonth = function(root, courseId, categoryId) {
3ea4f446
AN
177 var year = root.find(CalendarSelectors.wrapper).data('year');
178 var month = root.find(CalendarSelectors.wrapper).data('month');
516e7444 179
d0e56d84 180 if (typeof courseId === 'undefined') {
3ea4f446 181 courseId = root.find(CalendarSelectors.wrapper).data('courseid');
afa8c3da 182 }
d0e56d84
AN
183
184 if (typeof categoryId === 'undefined') {
3ea4f446 185 categoryId = root.find(CalendarSelectors.wrapper).data('categoryid');
d0e56d84
AN
186 }
187
188 return refreshMonthContent(root, year, month, courseId, categoryId);
3ea4f446
AN
189 };
190
191
192 /**
193 * Refresh the day content.
194 *
195 * @param {object} root The root element.
196 * @param {Number} year Year
197 * @param {Number} month Month
198 * @param {Number} day Day
199 * @param {Number} courseid The id of the course whose events are shown
200 * @param {Number} categoryId The id of the category whose events are shown
201 * @param {object} target The element being replaced. If not specified, the calendarwrapper is used.
202 * @return {promise}
203 */
204 var refreshDayContent = function(root, year, month, day, courseid, categoryId, target) {
205 startLoading(root);
206
207 target = target || root.find(CalendarSelectors.wrapper);
208
209 M.util.js_pending([root.get('id'), year, month, day, courseid, categoryId].join('-'));
210 var includenavigation = root.data('includenavigation');
211 return CalendarRepository.getCalendarDayData(year, month, day, courseid, categoryId, includenavigation)
212 .then(function(context) {
213 return Templates.render(root.attr('data-template'), context);
214 })
215 .then(function(html, js) {
216 return Templates.replaceNode(target, html, js);
217 })
218 .then(function() {
219 $('body').trigger(CalendarEvents.viewUpdated);
220 return;
221 })
222 .always(function() {
223 M.util.js_complete([root.get('id'), year, month, day, courseid, categoryId].join('-'));
224 return stopLoading(root);
225 })
226 .fail(Notification.exception);
227 };
228
229 /**
230 * Reload the current day view data.
231 *
232 * @param {object} root The container element.
233 * @param {Number} courseId The course id.
234 * @param {Number} categoryId The id of the category whose events are shown
235 * @return {promise}
236 */
237 var reloadCurrentDay = function(root, courseId, categoryId) {
238 var wrapper = root.find(CalendarSelectors.wrapper);
239 var year = wrapper.data('year');
240 var month = wrapper.data('month');
241 var day = wrapper.data('day');
d0e56d84 242
3ea4f446
AN
243 if (!courseId) {
244 courseId = root.find(CalendarSelectors.wrapper).data('courseid');
245 }
246
247 if (typeof categoryId === 'undefined') {
248 categoryId = root.find(CalendarSelectors.wrapper).data('categoryid');
249 }
250
251 return refreshDayContent(root, year, month, day, courseId, categoryId);
252 };
253
254 /**
255 * Handle changes to the current calendar view.
256 *
257 * @param {object} root The root element.
258 * @param {String} url The calendar url to be shown
259 * @param {Number} year Year
260 * @param {Number} month Month
261 * @param {Number} day Day
262 * @param {Number} courseId The id of the course whose events are shown
263 * @param {Number} categoryId The id of the category whose events are shown
264 * @return {promise}
265 */
266 var changeDay = function(root, url, year, month, day, courseId, categoryId) {
267 return refreshDayContent(root, year, month, day, courseId, categoryId)
268 .then(function() {
269 if (url.length && url !== '#') {
270 window.history.pushState({}, '', url);
271 }
272 return arguments;
273 })
274 .then(function() {
275 $('body').trigger(CalendarEvents.dayChanged, [year, month, day, courseId, categoryId]);
276 return arguments;
277 });
695c5726
AN
278 };
279
b31a1695
SL
280 /**
281 * Set the element state to loading.
282 *
283 * @param {object} root The container element
284 * @method startLoading
285 */
286 var startLoading = function(root) {
fd2f1ae1 287 var loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);
b31a1695
SL
288
289 loadingIconContainer.removeClass('hidden');
290 };
291
292 /**
293 * Remove the loading state from the element.
294 *
295 * @param {object} root The container element
296 * @method stopLoading
297 */
298 var stopLoading = function(root) {
fd2f1ae1 299 var loadingIconContainer = root.find(CalendarSelectors.containers.loadingIcon);
b31a1695
SL
300
301 loadingIconContainer.addClass('hidden');
302 };
303
2ca4dc8a
SL
304 /**
305 * Reload the current month view data.
306 *
307 * @param {object} root The container element.
308 * @param {Number} courseId The course id.
d523cbcb 309 * @param {Number} categoryId The id of the category whose events are shown
2ca4dc8a
SL
310 * @return {promise}
311 */
d523cbcb 312 var reloadCurrentUpcoming = function(root, courseId, categoryId) {
2ca4dc8a
SL
313 startLoading(root);
314
3ea4f446 315 var target = root.find(CalendarSelectors.wrapper);
2ca4dc8a 316
d523cbcb 317 if (typeof courseId === 'undefined') {
3ea4f446 318 courseId = root.find(CalendarSelectors.wrapper).data('courseid');
e00aed51
AN
319 }
320
d523cbcb
AN
321 if (typeof categoryId === 'undefined') {
322 categoryId = root.find(CalendarSelectors.wrapper).data('categoryid');
323 }
324
325 return CalendarRepository.getCalendarUpcomingData(courseId, categoryId)
2ca4dc8a
SL
326 .then(function(context) {
327 return Templates.render(root.attr('data-template'), context);
328 })
329 .then(function(html, js) {
2ca4dc8a
SL
330 return Templates.replaceNode(target, html, js);
331 })
332 .then(function() {
333 $('body').trigger(CalendarEvents.viewUpdated);
334 return;
335 })
336 .always(function() {
337 return stopLoading(root);
338 })
339 .fail(Notification.exception);
340 };
341
89260305
RW
342 /**
343 * Get the CSS class to apply for the given event type.
344 *
345 * @param {String} eventType The calendar event type
346 * @return {String}
347 */
348 var getEventTypeClassFromType = function(eventType) {
ab709ed2 349 return 'calendar_event_' + eventType;
89260305
RW
350 };
351
6c3f463d
AN
352 /**
353 * Render the event summary modal.
354 *
355 * @param {Number} eventId The calendar event id.
356 */
357 var renderEventSummaryModal = function(eventId) {
358 var typeClass = '';
359
360 // Calendar repository promise.
361 CalendarRepository.getEventById(eventId).then(function(getEventResponse) {
362 if (!getEventResponse.event) {
363 throw new Error('Error encountered while trying to fetch calendar event with ID: ' + eventId);
364 }
365 var eventData = getEventResponse.event;
ab709ed2 366 typeClass = getEventTypeClassFromType(eventData.normalisedeventtype);
6c3f463d 367
ab709ed2 368 return eventData;
6c3f463d
AN
369 }).then(function(eventData) {
370 // Build the modal parameters from the event data.
371 var modalParams = {
372 title: eventData.name,
373 type: SummaryModal.TYPE,
374 body: Templates.render('core_calendar/event_summary_body', eventData),
375 templateContext: {
376 canedit: eventData.canedit,
377 candelete: eventData.candelete,
378 headerclasses: typeClass,
379 isactionevent: eventData.isactionevent,
380 url: eventData.url
381 }
382 };
383
384 // Create the modal.
385 return ModalFactory.create(modalParams);
386
387 }).done(function(modal) {
388 // Handle hidden event.
389 modal.getRoot().on(ModalEvents.hidden, function() {
390 // Destroy when hidden.
391 modal.destroy();
392 });
393
394 // Finally, render the modal!
395 modal.show();
396
397 }).fail(Notification.exception);
398 };
399
695c5726 400 return {
6397ec54
AN
401 init: function(root) {
402 registerEventListeners(root);
516e7444
SL
403 },
404 reloadCurrentMonth: reloadCurrentMonth,
405 changeMonth: changeMonth,
2ca4dc8a 406 refreshMonthContent: refreshMonthContent,
3ea4f446
AN
407 reloadCurrentDay: reloadCurrentDay,
408 changeDay: changeDay,
409 refreshDayContent: refreshDayContent,
2ca4dc8a 410 reloadCurrentUpcoming: reloadCurrentUpcoming
695c5726
AN
411 };
412 });