Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

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