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