1 // This file is part of Moodle - http://moodle.org/
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.
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.
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/>.
17 * Contain the logic for the quick add or update event modal.
19 * @module calendar/modal_quick_add_event
20 * @class modal_quick_add_event
22 * @copyright 2017 Ryan Wyllie <ryan@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
31 'core/custom_interaction_events',
33 'core/modal_registry',
35 'core_calendar/events',
36 'core_calendar/repository',
37 'core_calendar/event_form'
54 var registered = false;
56 MORELESS_BUTTON: '[data-action="more-less-toggle"]',
57 SAVE_BUTTON: '[data-action="save"]',
58 LOADING_ICON_CONTAINER: '[data-region="loading-icon-container"]',
62 * Constructor for the Modal.
64 * @param {object} root The root jQuery element for the modal
66 var ModalEventForm = function(root) {
67 Modal.call(this, root);
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);
75 ModalEventForm.TYPE = 'core_calendar-modal_event_form';
76 ModalEventForm.prototype = Object.create(Modal.prototype);
77 ModalEventForm.prototype.constructor = ModalEventForm;
80 * Set the event id to the given value.
83 * @param {int} id The event id
85 ModalEventForm.prototype.setEventId = function(id) {
90 * Retrieve the current event id, if any.
93 * @return {int|null} The event id
95 ModalEventForm.prototype.getEventId = function() {
100 * Check if the modal has an event id.
105 ModalEventForm.prototype.hasEventId = function() {
106 return this.eventId !== null;
110 * Get the form element from the modal.
115 ModalEventForm.prototype.getForm = function() {
116 return this.getBody().find('form');
120 * Disable the buttons in the footer.
122 * @method disableButtons
124 ModalEventForm.prototype.disableButtons = function() {
125 this.saveButton.prop('disabled', true);
126 this.moreLessButton.prop('disabled', true);
130 * Enable the buttons in the footer.
132 * @method enableButtons
134 ModalEventForm.prototype.enableButtons = function() {
135 this.saveButton.prop('disabled', false);
136 this.moreLessButton.prop('disabled', false);
140 * Set the more/less button in the footer to the "more"
143 * @method setMoreButton
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);
154 * Set the more/less button in the footer to the "less"
157 * @method setLessButton
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);
168 * Toggle the more/less button in the footer from the current
169 * state to it's opposite state.
171 * @method toggleMoreLessButton
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();
180 form.trigger(EventForm.events.HIDE_ADVANCED);
181 this.setMoreButton();
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.
190 * @method reloadTitleContent
191 * @return {object} A promise resolved with the new title text
193 ModalEventForm.prototype.reloadTitleContent = function() {
194 if (this.reloadingTitle) {
195 return this.titlePromise;
198 this.reloadingTitle = true;
200 if (this.hasEventId()) {
201 this.titlePromise = Str.get_string('editevent', 'calendar');
203 this.titlePromise = Str.get_string('newevent', 'calendar');
206 this.titlePromise.then(function(string) {
207 this.setTitle(string);
211 this.reloadingTitle = false;
215 return this.titlePromise;
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.
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.
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
232 ModalEventForm.prototype.reloadBodyContent = function(formData, hasError) {
233 if (this.reloadingBody) {
234 return this.bodyPromise;
237 this.reloadingBody = true;
238 this.disableButtons();
240 var contextId = this.saveButton.attr('data-context-id');
243 if (this.hasEventId()) {
244 args.eventid = this.getEventId();
247 if (typeof formData !== 'undefined') {
248 args.formdata = formData;
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();
261 .catch(Notification.exception)
263 this.reloadingBody = false;
267 return this.bodyPromise;
271 * Reload both the title and body content.
273 * @method reloadAllContent
274 * @return {object} promise
276 ModalEventForm.prototype.reloadAllContent = function() {
277 return $.when(this.reloadTitleContent(), this.reloadBodyContent());
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.
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.
291 ModalEventForm.prototype.show = function() {
292 this.reloadAllContent();
293 Modal.prototype.show.call(this);
297 * Clear the event id from the modal when it's closed so
298 * that it is loaded fresh next time it's displayed.
300 * The event id will be set by the calling code if it wants
301 * to edit a specific event.
305 ModalEventForm.prototype.hide = function() {
306 Modal.prototype.hide.call(this);
307 this.setEventId(null);
311 * Get the serialised form data.
313 * @method getFormData
314 * @return {string} serialised form data
316 ModalEventForm.prototype.getFormData = function() {
317 return this.getForm().serialize();
321 * Send the form data to the server to create or update
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.
328 * On success the modal is hidden and the page is reloaded so that the
329 * new event will display.
332 * @return {object} A promise
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);
350 // No problemo! Our work here is done.
353 // Trigger the appropriate calendar event so that the view can
355 if (this.hasEventId()) {
356 $('body').trigger(CalendarEvents.updated, [response.event]);
358 $('body').trigger(CalendarEvents.created, [response.event]);
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();
370 .catch(Notification.exception);
374 * Set up all of the event handling for the modal.
376 * @method registerEventListeners
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();
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) {
396 // Stop the form from actually submitting and prevent it's
397 // propagation because we have already handled the event.
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();
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();
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();
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.
428 ModalRegistry.register(ModalEventForm.TYPE, ModalEventForm, 'calendar/modal_event_form');
432 return ModalEventForm;