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