MDL-59382 calendar: fix CiBoT warnings
[moodle.git] / calendar / amd / src / modal_event_form.js
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/>.
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  */
25 define([
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         ) {
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     };
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;
69         this.reloadingBody = false;
70         this.reloadingTitle = false;
71         this.saveButton = this.getFooter().find(SELECTORS.SAVE_BUTTON);
72         this.moreLessButton = this.getFooter().find(SELECTORS.MORELESS_BUTTON);
73     };
75     ModalEventForm.TYPE = 'core_calendar-modal_event_form';
76     ModalEventForm.prototype = Object.create(Modal.prototype);
77     ModalEventForm.prototype.constructor = ModalEventForm;
79     /**
80      * Set the event id to the given value.
81      *
82      * @method setEventId
83      * @param {int} id The event id
84      */
85     ModalEventForm.prototype.setEventId = function(id) {
86         this.eventId = id;
87     };
89     /**
90      * Retrieve the current event id, if any.
91      *
92      * @method getEventId
93      * @return {int|null} The event id
94      */
95     ModalEventForm.prototype.getEventId = function() {
96         return this.eventId;
97     };
99     /**
100      * Check if the modal has an event id.
101      *
102      * @method hasEventId
103      * @return {bool}
104      */
105     ModalEventForm.prototype.hasEventId = function() {
106         return this.eventId !== null;
107     };
109     /**
110      * Get the form element from the modal.
111      *
112      * @method getForm
113      * @return {object}
114      */
115     ModalEventForm.prototype.getForm = function() {
116         return this.getBody().find('form');
117     };
119     /**
120      * Disable the buttons in the footer.
121      *
122      * @method disableButtons
123      */
124     ModalEventForm.prototype.disableButtons = function() {
125         this.saveButton.prop('disabled', true);
126         this.moreLessButton.prop('disabled', true);
127     };
129     /**
130      * Enable the buttons in the footer.
131      *
132      * @method enableButtons
133      */
134     ModalEventForm.prototype.enableButtons = function() {
135         this.saveButton.prop('disabled', false);
136         this.moreLessButton.prop('disabled', false);
137     };
139     /**
140      * Set the more/less button in the footer to the "more"
141      * state.
142      *
143      * @method setMoreButton
144      */
145     ModalEventForm.prototype.setMoreButton = function() {
146         this.moreLessButton.attr('data-collapsed', 'true');
147         Str.get_string('more', 'calendar').then(function(string) {
148             this.moreLessButton.text(string);
149             return;
150         }.bind(this));
151     };
153     /**
154      * Set the more/less button in the footer to the "less"
155      * state.
156      *
157      * @method setLessButton
158      */
159     ModalEventForm.prototype.setLessButton = function() {
160         this.moreLessButton.attr('data-collapsed', 'false');
161         Str.get_string('less', 'calendar').then(function(string) {
162             this.moreLessButton.text(string);
163             return;
164         }.bind(this));
165     };
167     /**
168      * Toggle the more/less button in the footer from the current
169      * state to it's opposite state.
170      *
171      * @method toggleMoreLessButton
172      */
173     ModalEventForm.prototype.toggleMoreLessButton = function() {
174         var form = this.getForm();
176         if (this.moreLessButton.attr('data-collapsed') == 'true') {
177             form.trigger(EventForm.events.SHOW_ADVANCED);
178             this.setLessButton();
179         } else {
180             form.trigger(EventForm.events.HIDE_ADVANCED);
181             this.setMoreButton();
182         }
183     };
185     /**
186      * Reload the title for the modal to the appropriate value
187      * depending on whether we are creating a new event or
188      * editing an existing event.
189      *
190      * @method reloadTitleContent
191      * @return {object} A promise resolved with the new title text
192      */
193     ModalEventForm.prototype.reloadTitleContent = function() {
194         if (this.reloadingTitle) {
195             return this.titlePromise;
196         }
198         this.reloadingTitle = true;
200         if (this.hasEventId()) {
201             this.titlePromise = Str.get_string('editevent', 'calendar');
202         } else {
203             this.titlePromise = Str.get_string('newevent', 'calendar');
204         }
206         this.titlePromise.then(function(string) {
207             this.setTitle(string);
208             return string;
209         }.bind(this))
210         .always(function() {
211             this.reloadingTitle = false;
212             return;
213         }.bind(this));
215         return this.titlePromise;
216     };
218     /**
219      * Send a request to the server to get the event_form in a fragment
220      * and render the result in the body of the modal.
221      *
222      * If serialised form data is provided then it will be sent in the
223      * request to the server to have the form rendered with the data. This
224      * is used when the form had a server side error and we need the server
225      * to re-render it for us to display the error to the user.
226      *
227      * @method reloadBodyContent
228      * @param {string} formData The serialised form data
229      * @param {bool} hasError True if we know the form data is erroneous
230      * @return {object} A promise resolved with the fragment html and js from
231      */
232     ModalEventForm.prototype.reloadBodyContent = function(formData, hasError) {
233         if (this.reloadingBody) {
234             return this.bodyPromise;
235         }
237         this.reloadingBody = true;
238         this.disableButtons();
240         var contextId = this.saveButton.attr('data-context-id');
241         var args = {};
243         if (this.hasEventId()) {
244             args.eventid = this.getEventId();
245         }
247         if (typeof formData !== 'undefined') {
248             args.formdata = formData;
249         }
251         args.haserror = (typeof hasError == 'undefined') ? false : hasError;
253         this.bodyPromise = Fragment.loadFragment('calendar', 'event_form', contextId, args);
255         this.setBody(this.bodyPromise);
257         this.bodyPromise.then(function() {
258             this.enableButtons();
259             return;
260         }.bind(this))
261         .catch(Notification.exception)
262         .always(function() {
263             this.reloadingBody = false;
264             return;
265         }.bind(this));
267         return this.bodyPromise;
268     };
270     /**
271      * Reload both the title and body content.
272      *
273      * @method reloadAllContent
274      * @return {object} promise
275      */
276     ModalEventForm.prototype.reloadAllContent = function() {
277         return $.when(this.reloadTitleContent(), this.reloadBodyContent());
278     };
280     /**
281      * Kick off a reload the modal content before showing it. This
282      * is to allow us to re-use the same modal for creating and
283      * editing different events within the page.
284      *
285      * We do the reload when showing the modal rather than hiding it
286      * to save a request to the server if the user closes the modal
287      * and never re-opens it.
288      *
289      * @method show
290      */
291     ModalEventForm.prototype.show = function() {
292         this.reloadAllContent();
293         Modal.prototype.show.call(this);
294     };
296     /**
297      * Clear the event id from the modal when it's closed so
298      * that it is loaded fresh next time it's displayed.
299      *
300      * The event id will be set by the calling code if it wants
301      * to edit a specific event.
302      *
303      * @method hide
304      */
305     ModalEventForm.prototype.hide = function() {
306         Modal.prototype.hide.call(this);
307         this.setEventId(null);
308     };
310     /**
311      * Get the serialised form data.
312      *
313      * @method getFormData
314      * @return {string} serialised form data
315      */
316     ModalEventForm.prototype.getFormData = function() {
317         return this.getForm().serialize();
318     };
320     /**
321      * Send the form data to the server to create or update
322      * an event.
323      *
324      * If there is a server side validation error then we re-request the
325      * rendered form (with the data) from the server in order to get the
326      * server side errors to display.
327      *
328      * On success the modal is hidden and the page is reloaded so that the
329      * new event will display.
330      *
331      * @method save
332      * @return {object} A promise
333      */
334     ModalEventForm.prototype.save = function() {
335         var loadingContainer = this.saveButton.find(SELECTORS.LOADING_ICON_CONTAINER);
337         loadingContainer.removeClass('hidden');
338         this.disableButtons();
340         var formData = this.getFormData();
341         // Send the form data to the server for processing.
342         return Repository.submitCreateUpdateForm(formData)
343             .then(function(response) {
344                 if (response.validationerror) {
345                     // If there was a server side validation error then
346                     // we need to re-request the rendered form from the server
347                     // in order to display the error for the user.
348                     return this.reloadBodyContent(formData, true);
349                 } else {
350                     // No problemo! Our work here is done.
351                     this.hide();
353                     // Trigger the appropriate calendar event so that the view can
354                     // be updated.
355                     if (this.hasEventId()) {
356                         $('body').trigger(CalendarEvents.updated, [response.event]);
357                     } else {
358                         $('body').trigger(CalendarEvents.created, [response.event]);
359                     }
360                 }
362                 return;
363             }.bind(this))
364             .always(function() {
365                 // Regardless of success or error we should always stop
366                 // the loading icon and re-enable the buttons.
367                 loadingContainer.addClass('hidden');
368                 this.enableButtons();
369             }.bind(this))
370             .catch(Notification.exception);
371     };
373     /**
374      * Set up all of the event handling for the modal.
375      *
376      * @method registerEventListeners
377      */
378     ModalEventForm.prototype.registerEventListeners = function() {
379         // Apply parent event listeners.
380         Modal.prototype.registerEventListeners.call(this);
382         // When the user clicks the save button we trigger the form submission. We need to
383         // trigger an actual submission because there is some JS code in the form that is
384         // listening for this event and doing some stuff (e.g. saving draft areas etc).
385         this.getModal().on(CustomEvents.events.activate, SELECTORS.SAVE_BUTTON, function(e, data) {
386             this.getForm().submit();
387             data.originalEvent.preventDefault();
388             e.stopPropagation();
389         }.bind(this));
391         // Catch the submit event before it is actually processed by the browser and
392         // prevent the submission. We'll take it from here.
393         this.getModal().on('submit', function(e) {
394             this.save();
396             // Stop the form from actually submitting and prevent it's
397             // propagation because we have already handled the event.
398             e.preventDefault();
399             e.stopPropagation();
400         }.bind(this));
402         // Toggle the state of the more/less button in the footer.
403         this.getModal().on(CustomEvents.events.activate, SELECTORS.MORELESS_BUTTON, function(e, data) {
404             this.toggleMoreLessButton();
406             data.originalEvent.preventDefault();
407             e.stopPropagation();
408         }.bind(this));
410         // When the event form tells us that the advanced fields are shown
411         // then the more/less button should be set to less to allow the user
412         // to hide the advanced fields.
413         this.getModal().on(EventForm.events.ADVANCED_SHOWN, function() {
414             this.setLessButton();
415         }.bind(this));
417         // When the event form tells us that the advanced fields are hidden
418         // then the more/less button should be set to more to allow the user
419         // to show the advanced fields.
420         this.getModal().on(EventForm.events.ADVANCED_HIDDEN, function() {
421             this.setMoreButton();
422         }.bind(this));
423     };
425     // Automatically register with the modal registry the first time this module is imported so that you can create modals
426     // of this type using the modal factory.
427     if (!registered) {
428         ModalRegistry.register(ModalEventForm.TYPE, ModalEventForm, 'calendar/modal_event_form');
429         registered = true;
430     }
432     return ModalEventForm;
433 });