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
 * Javascript events for the `core_form` subsystem.
18
 *
19
 * @module core_form/events
20
 * @copyright 2021 Huong Nguyen <huongnv13@gmail.com>
21
 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22
 * @since 3.10
23
 *
24
 * @example <caption>Example of listening to a form event.</caption>
25
 * import {eventTypes as formEventTypes} from 'core_form/events';
26
 *
27
 * document.addEventListener(formEventTypes.formSubmittedByJavascript, e => {
28
 *     window.console.log(e.target); // The form that was submitted.
29
 *     window.console.log(e.detail.skipValidation); // Whether form validation was skipped.
30
 * });
31
 */
32
 
33
import {getString} from 'core/str';
34
import {dispatchEvent} from 'core/event_dispatcher';
35
 
36
let changesMadeString;
37
 
38
/**
39
 * Prevent user navigate away when upload progress still running.
40
 * @param {Event} e The event
41
 */
42
const changesMadeCheck = e => {
43
    if (e) {
44
        e.returnValue = changesMadeString;
45
    }
46
};
47
 
48
/**
49
 * Events for `core_form`.
50
 *
51
 * @constant
52
 * @property {String} formError See {@link event:core_form/error}
53
 * @property {String} formFieldValidationFailed See {@link event:core_form/fieldValidationFailed}
54
 * @property {String} formSubmittedByJavascript See {@link event:core_form/submittedByJavascript}
55
 * @property {String} uploadChanged See {@link event:core_form/uploadChanged}
56
 * @property {String} fieldStructureChanged See {@link event:core_form/fieldStructureChanged}
57
 */
58
export const eventTypes = {
59
    /**
60
     * An event triggered when a form contains an error
61
     *
62
     * @event formError
63
     * @type {CustomEvent}
64
     * @property {HTMLElement} target The form field which errored
65
     */
66
    formError: 'core_form/error',
67
 
68
    /**
69
     * An event triggered when an mform is about to be submitted via javascript.
70
     *
71
     * @event core_form/submittedByJavascript
72
     * @type {CustomEvent}
73
     * @property {HTMLElement} target The form that was submitted
74
     * @property {object} detail
75
     * @property {boolean} detail.skipValidation Whether the form was submitted without validation (i.e. via a Cancel button)
76
     * @property {boolean} detail.fallbackHandled Whether the legacy YUI event has been handled
77
     */
78
    formSubmittedByJavascript: 'core_form/submittedByJavascript',
79
 
80
    /**
81
     * An event triggered upon form field validation failure.
82
     *
83
     * @event core_form/fieldValidationFailed
84
     * @type {CustomEvent}
85
     * @property {HTMLElement} target The field that failed validation
86
     * @property {object} detail
87
     * @property {String} detail.message The message displayed upon failure
88
     */
89
    formFieldValidationFailed: 'core_form/fieldValidationFailed',
90
 
91
    /**
92
     * An event triggered when an upload is started
93
     *
94
     * @event core_form/uploadStarted
95
     * @type {CustomEvent}
96
     * @property {HTMLElement} target The location where the upload began
97
     */
98
    uploadStarted: 'core_form/uploadStarted',
99
 
100
    /**
101
     * An event triggered when an upload completes
102
     *
103
     * @event core_form/uploadCompleted
104
     * @type {CustomEvent}
105
     * @property {HTMLElement} target The location where the upload completed
106
     */
107
    uploadCompleted: 'core_form/uploadCompleted',
108
 
109
    /**
110
     * An event triggered when a file upload field has been changed.
111
     *
112
     * @event core_form/uploadChanged
113
     * @type {CustomEvent}
114
     * @property {HTMLElement} target The form field which was changed
115
     */
116
    uploadChanged: 'core_form/uploadChanged',
117
 
118
    /**
119
     * An event triggered when a form field structure has changed.
120
     *
121
     * @event core_form/fieldStructureChanged
122
     * @type {CustomEvent}
123
     * @property {HTMLElement} target The form field that has changed
124
     */
125
    fieldStructureChanged: 'core_form/fieldStructureChanged',
126
};
127
 
128
// These are only imported for legacy.
129
import jQuery from 'jquery';
130
import Y from 'core/yui';
131
 
132
/**
133
 * Trigger an event to indicate that a form field contained an error.
134
 *
135
 * @method notifyFormError
136
 * @param {HTMLElement} field The form field causing the error
137
 * @returns {CustomEvent}
138
 * @fires formError
139
 */
140
export const notifyFormError = field => dispatchEvent(eventTypes.formError, {}, field);
141
 
142
/**
143
 * Trigger an event to indiciate that a form was submitted by Javascript.
144
 *
145
 * @method
146
 * @param {HTMLElement} form The form that was submitted
147
 * @param {Boolean} skipValidation Submit the form without validation. E.g. "Cancel".
148
 * @param {Boolean} fallbackHandled The legacy YUI event has been handled
149
 * @returns {CustomEvent}
150
 * @fires formSubmittedByJavascript
151
 */
152
export const notifyFormSubmittedByJavascript = (form, skipValidation = false, fallbackHandled = false) => {
153
    if (skipValidation) {
154
        window.skipClientValidation = true;
155
    }
156
 
157
    const customEvent = dispatchEvent(
158
        eventTypes.formSubmittedByJavascript,
159
        {
160
            skipValidation,
161
            fallbackHandled,
162
        },
163
        form
164
    );
165
 
166
    if (skipValidation) {
167
        window.skipClientValidation = false;
168
    }
169
 
170
    return customEvent;
171
};
172
 
173
/**
174
 * Trigger an event to indicate that a form field contained an error.
175
 *
176
 * @method notifyFieldValidationFailure
177
 * @param {HTMLElement} field The field which failed validation
178
 * @param {String} message The message displayed
179
 * @returns {CustomEvent}
180
 * @fires formFieldValidationFailed
181
 */
182
export const notifyFieldValidationFailure = (field, message) => dispatchEvent(
183
    eventTypes.formFieldValidationFailed,
184
    {
185
        message,
186
    },
187
    field,
188
    {
189
        cancelable: true
190
    }
191
);
192
 
193
/**
194
 * Trigger an event to indicate that an upload was started.
195
 *
196
 * @method
197
 * @param {String} elementId The element which was uploaded to
198
 * @returns {CustomEvent}
199
 * @fires uploadStarted
200
 */
201
export const notifyUploadStarted = async elementId => {
202
    // Add an additional check for changes made.
203
    changesMadeString = await getString('changesmadereallygoaway', 'moodle');
204
    window.addEventListener('beforeunload', changesMadeCheck);
205
 
206
    return dispatchEvent(
207
        eventTypes.uploadStarted,
208
        {},
209
        document.getElementById(elementId),
210
        {
211
            bubbles: true,
212
            cancellable: false,
213
        }
214
    );
215
};
216
 
217
/**
218
 * Trigger an event to indicate that an upload was completed.
219
 *
220
 * @method
221
 * @param {String} elementId The element which was uploaded to
222
 * @returns {CustomEvent}
223
 * @fires uploadCompleted
224
 */
225
export const notifyUploadCompleted = elementId => {
226
    // Remove the additional check for changes made.
227
    window.removeEventListener('beforeunload', changesMadeCheck);
228
 
229
    return dispatchEvent(
230
        eventTypes.uploadCompleted,
231
        {},
232
        document.getElementById(elementId),
233
        {
234
            bubbles: true,
235
            cancellable: false,
236
        }
237
    );
238
};
239
 
240
/**
241
 * Trigger upload start event.
242
 *
243
 * @method
244
 * @param {String} elementId
245
 * @returns {CustomEvent}
246
 * @fires uploadStarted
247
 * @deprecated Since Moodle 4.0 See {@link module:core_form/events.notifyUploadStarted notifyUploadStarted}
248
 */
249
export const triggerUploadStarted = notifyUploadStarted;
250
 
251
/**
252
 * Trigger upload complete event.
253
 *
254
 * @method
255
 * @param {String} elementId
256
 * @returns {CustomEvent}
257
 * @fires uploadCompleted
258
 * @deprecated Since Moodle 4.0 See {@link module:core_form/events.notifyUploadCompleted notifyUploadCompleted}
259
 */
260
export const triggerUploadCompleted = notifyUploadCompleted;
261
 
262
/**
263
 * List of the events.
264
 *
265
 * @deprecated since Moodle 4.0. See {@link module:core_form/events.eventTypes eventTypes} instead.
266
 **/
267
export const types = {
268
    uploadStarted: 'core_form/uploadStarted',
269
    uploadCompleted: 'core_form/uploadCompleted',
270
};
271
 
272
let legacyEventsRegistered = false;
273
if (!legacyEventsRegistered) {
274
    // The following event triggers are legacy and will be removed in the future.
275
    // The following approach provides a backwards-compatability layer for the new events.
276
    // Code should be updated to make use of native events.
277
    Y.use('event', 'moodle-core-event', () => {
278
 
279
        // Watch for the new native formError event, and trigger the legacy YUI event.
280
        document.addEventListener(eventTypes.formError, e => {
281
            const element = Y.one(e.target);
282
            const formElement = Y.one(e.target.closest('form'));
283
 
284
            Y.Global.fire(
285
                M.core.globalEvents.FORM_ERROR,
286
                {
287
                    formid: formElement.generateID(),
288
                    elementid: element.generateID(),
289
                }
290
            );
291
        });
292
 
293
        // Watch for the new native formSubmittedByJavascript event, and trigger the legacy YUI event.
294
        document.addEventListener(eventTypes.formSubmittedByJavascript, e => {
295
            if (e.detail.fallbackHandled) {
296
                // This event was originally generated by a YUI event.
297
                // Do not generate another as this will recurse.
298
                return;
299
            }
300
 
301
            if (e.skipValidation) {
302
                window.skipClientValidation = true;
303
            }
304
 
305
            // Trigger the legacy YUI event.
306
            const form = Y.one(e.target);
307
            form.fire(
308
                M.core.event.FORM_SUBMIT_AJAX,
309
                {
310
                    currentTarget: form,
311
                    fallbackHandled: true,
312
                }
313
            );
314
 
315
            if (e.skipValidation) {
316
                window.skipClientValidation = false;
317
            }
318
        });
319
    });
320
 
321
    // Watch for the new native formFieldValidationFailed event, and trigger the legacy jQuery event.
322
    document.addEventListener(eventTypes.formFieldValidationFailed, e => {
323
        // Note: The "core_form-field-validation" event is hard-coded in core/event.
324
        // This is not included to prevent cyclic module dependencies.
325
        const legacyEvent = jQuery.Event("core_form-field-validation");
326
 
327
        jQuery(e.target).trigger(legacyEvent, e.detail.message);
328
    });
329
 
330
    legacyEventsRegistered = true;
331
}
332
 
333
/**
334
 * Trigger an event to notify the file upload field has been changed.
335
 *
336
 * @method
337
 * @param {string} elementId The element which was changed
338
 * @returns {CustomEvent}
339
 * @fires uploadChanged
340
 */
341
export const notifyUploadChanged = elementId => dispatchEvent(
342
    eventTypes.uploadChanged,
343
    {},
344
    document.getElementById(elementId),
345
    {
346
        bubbles: true,
347
        cancellable: false,
348
    }
349
);
350
 
351
/**
352
 * Trigger an event to notify the field structure has changed.
353
 *
354
 * @method
355
 * @param {string} elementId The element which was changed
356
 * @returns {CustomEvent}
357
 * @fires fieldStructureChanged
358
 */
359
export const notifyFieldStructureChanged = elementId => dispatchEvent(
360
    eventTypes.fieldStructureChanged,
361
    {},
362
    document.getElementById(elementId),
363
    {
364
        bubbles: true,
365
        cancellable: false,
366
    }
367
);