MDL-59672 coding style: whitespace fixes
[moodle.git] / calendar / amd / src / modal_event_form.js
CommitLineData
aa091225
RW
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 * Contain the logic for the quick add or update event modal.
18 *
19 * @module calendar/modal_quick_add_event
20 * @class modal_quick_add_event
21 * @package core
22 * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
24 */
25define([
26 'jquery',
27 'core/event',
28 'core/str',
29 'core/notification',
30 'core/templates',
31 'core/custom_interaction_events',
32 'core/modal',
33 'core/modal_registry',
34 'core/fragment',
35 'core_calendar/events',
36 'core_calendar/repository',
37 'core_calendar/event_form'
38 ],
39 function(
40 $,
41 Event,
42 Str,
43 Notification,
44 Templates,
45 CustomEvents,
46 Modal,
47 ModalRegistry,
48 Fragment,
49 CalendarEvents,
50 Repository,
51 EventForm
52 ) {
53
54 var registered = false;
55 var SELECTORS = {
56 MORELESS_BUTTON: '[data-action="more-less-toggle"]',
57 SAVE_BUTTON: '[data-action="save"]',
58 LOADING_ICON_CONTAINER: '[data-region="loading-icon-container"]',
59 };
60
61 /**
62 * Constructor for the Modal.
63 *
64 * @param {object} root The root jQuery element for the modal
65 */
66 var ModalEventForm = function(root) {
67 Modal.call(this, root);
68 this.eventId = null;
f6e8cc83 69 this.startTime = null;
29158c8b 70 this.courseId = null;
aa091225
RW
71 this.reloadingBody = false;
72 this.reloadingTitle = false;
73 this.saveButton = this.getFooter().find(SELECTORS.SAVE_BUTTON);
74 this.moreLessButton = this.getFooter().find(SELECTORS.MORELESS_BUTTON);
75 };
76
77 ModalEventForm.TYPE = 'core_calendar-modal_event_form';
78 ModalEventForm.prototype = Object.create(Modal.prototype);
79 ModalEventForm.prototype.constructor = ModalEventForm;
dd04df41 80
29158c8b
SL
81 /**
82 * Set the course id to the given value.
83 *
84 * @method setCourseId
85 * @param {int} id The event id
86 */
87 ModalEventForm.prototype.setCourseId = function(id) {
88 this.courseId = id;
89 };
90
91 /**
92 * Retrieve the current course id, if any.
93 *
94 * @method getCourseId
95 * @return {int|null} The event id
96 */
97 ModalEventForm.prototype.getCourseId = function() {
98 return this.courseId;
99 };
100
101 /**
102 * Check if the modal has an course id.
103 *
104 * @method hasCourseId
105 * @return {bool}
106 */
107 ModalEventForm.prototype.hasCourseId = function() {
108 return this.courseId !== null;
109 };
aa091225
RW
110
111 /**
112 * Set the event id to the given value.
113 *
114 * @method setEventId
115 * @param {int} id The event id
116 */
117 ModalEventForm.prototype.setEventId = function(id) {
118 this.eventId = id;
119 };
120
121 /**
122 * Retrieve the current event id, if any.
123 *
124 * @method getEventId
125 * @return {int|null} The event id
126 */
127 ModalEventForm.prototype.getEventId = function() {
128 return this.eventId;
129 };
130
131 /**
132 * Check if the modal has an event id.
133 *
134 * @method hasEventId
135 * @return {bool}
136 */
137 ModalEventForm.prototype.hasEventId = function() {
138 return this.eventId !== null;
139 };
140
f6e8cc83
RW
141 /**
142 * Set the start time to the given value.
143 *
144 * @method setStartTime
145 * @param {int} time The start time
146 */
147 ModalEventForm.prototype.setStartTime = function(time) {
148 this.startTime = time;
149 };
150
151 /**
152 * Retrieve the current start time, if any.
153 *
154 * @method getStartTime
155 * @return {int|null} The start time
156 */
157 ModalEventForm.prototype.getStartTime = function() {
158 return this.startTime;
159 };
160
161 /**
162 * Check if the modal has start time.
163 *
164 * @method hasStartTime
165 * @return {bool}
166 */
167 ModalEventForm.prototype.hasStartTime = function() {
168 return this.startTime !== null;
169 };
170
aa091225
RW
171 /**
172 * Get the form element from the modal.
173 *
174 * @method getForm
175 * @return {object}
176 */
177 ModalEventForm.prototype.getForm = function() {
178 return this.getBody().find('form');
179 };
180
181 /**
182 * Disable the buttons in the footer.
183 *
184 * @method disableButtons
185 */
186 ModalEventForm.prototype.disableButtons = function() {
187 this.saveButton.prop('disabled', true);
188 this.moreLessButton.prop('disabled', true);
189 };
190
191 /**
192 * Enable the buttons in the footer.
193 *
194 * @method enableButtons
195 */
196 ModalEventForm.prototype.enableButtons = function() {
197 this.saveButton.prop('disabled', false);
198 this.moreLessButton.prop('disabled', false);
199 };
200
201 /**
202 * Set the more/less button in the footer to the "more"
203 * state.
204 *
205 * @method setMoreButton
206 */
207 ModalEventForm.prototype.setMoreButton = function() {
208 this.moreLessButton.attr('data-collapsed', 'true');
209 Str.get_string('more', 'calendar').then(function(string) {
210 this.moreLessButton.text(string);
d6942fb5 211 return;
aa091225
RW
212 }.bind(this));
213 };
214
215 /**
216 * Set the more/less button in the footer to the "less"
217 * state.
218 *
219 * @method setLessButton
220 */
221 ModalEventForm.prototype.setLessButton = function() {
222 this.moreLessButton.attr('data-collapsed', 'false');
223 Str.get_string('less', 'calendar').then(function(string) {
224 this.moreLessButton.text(string);
d6942fb5 225 return;
aa091225
RW
226 }.bind(this));
227 };
228
229 /**
230 * Toggle the more/less button in the footer from the current
231 * state to it's opposite state.
232 *
233 * @method toggleMoreLessButton
234 */
235 ModalEventForm.prototype.toggleMoreLessButton = function() {
236 var form = this.getForm();
237
238 if (this.moreLessButton.attr('data-collapsed') == 'true') {
239 form.trigger(EventForm.events.SHOW_ADVANCED);
240 this.setLessButton();
241 } else {
242 form.trigger(EventForm.events.HIDE_ADVANCED);
243 this.setMoreButton();
244 }
245 };
246
247 /**
248 * Reload the title for the modal to the appropriate value
249 * depending on whether we are creating a new event or
250 * editing an existing event.
251 *
252 * @method reloadTitleContent
253 * @return {object} A promise resolved with the new title text
254 */
255 ModalEventForm.prototype.reloadTitleContent = function() {
256 if (this.reloadingTitle) {
257 return this.titlePromise;
258 }
259
260 this.reloadingTitle = true;
261
262 if (this.hasEventId()) {
263 this.titlePromise = Str.get_string('editevent', 'calendar');
264 } else {
265 this.titlePromise = Str.get_string('newevent', 'calendar');
266 }
267
268 this.titlePromise.then(function(string) {
269 this.setTitle(string);
270 return string;
271 }.bind(this))
272 .always(function() {
273 this.reloadingTitle = false;
274 return;
275 }.bind(this));
276
277 return this.titlePromise;
278 };
279
280 /**
281 * Send a request to the server to get the event_form in a fragment
282 * and render the result in the body of the modal.
283 *
284 * If serialised form data is provided then it will be sent in the
285 * request to the server to have the form rendered with the data. This
286 * is used when the form had a server side error and we need the server
287 * to re-render it for us to display the error to the user.
288 *
289 * @method reloadBodyContent
290 * @param {string} formData The serialised form data
291 * @param {bool} hasError True if we know the form data is erroneous
292 * @return {object} A promise resolved with the fragment html and js from
293 */
294 ModalEventForm.prototype.reloadBodyContent = function(formData, hasError) {
295 if (this.reloadingBody) {
296 return this.bodyPromise;
297 }
298
299 this.reloadingBody = true;
300 this.disableButtons();
301
302 var contextId = this.saveButton.attr('data-context-id');
303 var args = {};
304
305 if (this.hasEventId()) {
306 args.eventid = this.getEventId();
307 }
308
f6e8cc83
RW
309 if (this.hasStartTime()) {
310 args.starttime = this.getStartTime();
311 }
312
dd04df41 313 if (this.hasCourseId()) {
29158c8b
SL
314 args.courseid = this.getCourseId();
315 }
316
aa091225
RW
317 if (typeof formData !== 'undefined') {
318 args.formdata = formData;
319 }
320
321 args.haserror = (typeof hasError == 'undefined') ? false : hasError;
322
323 this.bodyPromise = Fragment.loadFragment('calendar', 'event_form', contextId, args);
324
325 this.setBody(this.bodyPromise);
326
327 this.bodyPromise.then(function() {
328 this.enableButtons();
329 return;
330 }.bind(this))
331 .catch(Notification.exception)
332 .always(function() {
333 this.reloadingBody = false;
334 return;
335 }.bind(this));
336
337 return this.bodyPromise;
338 };
339
340 /**
341 * Reload both the title and body content.
342 *
343 * @method reloadAllContent
344 * @return {object} promise
345 */
346 ModalEventForm.prototype.reloadAllContent = function() {
347 return $.when(this.reloadTitleContent(), this.reloadBodyContent());
348 };
349
350 /**
351 * Kick off a reload the modal content before showing it. This
352 * is to allow us to re-use the same modal for creating and
353 * editing different events within the page.
354 *
355 * We do the reload when showing the modal rather than hiding it
356 * to save a request to the server if the user closes the modal
357 * and never re-opens it.
358 *
359 * @method show
360 */
361 ModalEventForm.prototype.show = function() {
362 this.reloadAllContent();
363 Modal.prototype.show.call(this);
364 };
365
366 /**
367 * Clear the event id from the modal when it's closed so
368 * that it is loaded fresh next time it's displayed.
369 *
370 * The event id will be set by the calling code if it wants
371 * to edit a specific event.
372 *
373 * @method hide
374 */
375 ModalEventForm.prototype.hide = function() {
376 Modal.prototype.hide.call(this);
377 this.setEventId(null);
f6e8cc83 378 this.setStartTime(null);
aa091225
RW
379 };
380
381 /**
382 * Get the serialised form data.
383 *
384 * @method getFormData
385 * @return {string} serialised form data
386 */
387 ModalEventForm.prototype.getFormData = function() {
388 return this.getForm().serialize();
389 };
390
391 /**
392 * Send the form data to the server to create or update
393 * an event.
394 *
395 * If there is a server side validation error then we re-request the
396 * rendered form (with the data) from the server in order to get the
397 * server side errors to display.
398 *
399 * On success the modal is hidden and the page is reloaded so that the
400 * new event will display.
401 *
402 * @method save
403 * @return {object} A promise
404 */
405 ModalEventForm.prototype.save = function() {
406 var loadingContainer = this.saveButton.find(SELECTORS.LOADING_ICON_CONTAINER);
407
408 loadingContainer.removeClass('hidden');
409 this.disableButtons();
410
411 var formData = this.getFormData();
412 // Send the form data to the server for processing.
413 return Repository.submitCreateUpdateForm(formData)
414 .then(function(response) {
415 if (response.validationerror) {
416 // If there was a server side validation error then
417 // we need to re-request the rendered form from the server
418 // in order to display the error for the user.
419 return this.reloadBodyContent(formData, true);
420 } else {
421 // No problemo! Our work here is done.
422 this.hide();
423
424 // Trigger the appropriate calendar event so that the view can
425 // be updated.
426 if (this.hasEventId()) {
427 $('body').trigger(CalendarEvents.updated, [response.event]);
428 } else {
429 $('body').trigger(CalendarEvents.created, [response.event]);
430 }
431 }
d6942fb5
RW
432
433 return;
aa091225
RW
434 }.bind(this))
435 .always(function() {
436 // Regardless of success or error we should always stop
437 // the loading icon and re-enable the buttons.
438 loadingContainer.addClass('hidden');
439 this.enableButtons();
440 }.bind(this))
441 .catch(Notification.exception);
442 };
443
444 /**
445 * Set up all of the event handling for the modal.
446 *
447 * @method registerEventListeners
448 */
449 ModalEventForm.prototype.registerEventListeners = function() {
450 // Apply parent event listeners.
451 Modal.prototype.registerEventListeners.call(this);
452
453 // When the user clicks the save button we trigger the form submission. We need to
454 // trigger an actual submission because there is some JS code in the form that is
455 // listening for this event and doing some stuff (e.g. saving draft areas etc).
456 this.getModal().on(CustomEvents.events.activate, SELECTORS.SAVE_BUTTON, function(e, data) {
457 this.getForm().submit();
458 data.originalEvent.preventDefault();
459 e.stopPropagation();
460 }.bind(this));
461
462 // Catch the submit event before it is actually processed by the browser and
463 // prevent the submission. We'll take it from here.
464 this.getModal().on('submit', function(e) {
465 this.save();
466
467 // Stop the form from actually submitting and prevent it's
468 // propagation because we have already handled the event.
469 e.preventDefault();
470 e.stopPropagation();
471 }.bind(this));
472
473 // Toggle the state of the more/less button in the footer.
474 this.getModal().on(CustomEvents.events.activate, SELECTORS.MORELESS_BUTTON, function(e, data) {
475 this.toggleMoreLessButton();
476
477 data.originalEvent.preventDefault();
478 e.stopPropagation();
479 }.bind(this));
480
481 // When the event form tells us that the advanced fields are shown
482 // then the more/less button should be set to less to allow the user
483 // to hide the advanced fields.
484 this.getModal().on(EventForm.events.ADVANCED_SHOWN, function() {
485 this.setLessButton();
486 }.bind(this));
487
488 // When the event form tells us that the advanced fields are hidden
489 // then the more/less button should be set to more to allow the user
490 // to show the advanced fields.
491 this.getModal().on(EventForm.events.ADVANCED_HIDDEN, function() {
492 this.setMoreButton();
493 }.bind(this));
494 };
495
496 // Automatically register with the modal registry the first time this module is imported so that you can create modals
497 // of this type using the modal factory.
498 if (!registered) {
499 ModalRegistry.register(ModalEventForm.TYPE, ModalEventForm, 'calendar/modal_event_form');
500 registered = true;
501 }
502
503 return ModalEventForm;
504});