Merge branch 'MDL-47736_master_fix2' of git://github.com/markn86/moodle
[moodle.git] / lib / yui / build / moodle-core-notification-dialogue / moodle-core-notification-dialogue-debug.js
CommitLineData
78686995
AN
1YUI.add('moodle-core-notification-dialogue', function (Y, NAME) {
2
3var DIALOGUE_PREFIX,
4 BASE,
78686995
AN
5 CONFIRMYES,
6 CONFIRMNO,
7 TITLE,
8 QUESTION,
9 CSS;
10
11DIALOGUE_PREFIX = 'moodle-dialogue',
12BASE = 'notificationBase',
78686995
AN
13CONFIRMYES = 'yesLabel',
14CONFIRMNO = 'noLabel',
15TITLE = 'title',
16QUESTION = 'question',
17CSS = {
18 BASE : 'moodle-dialogue-base',
19 WRAP : 'moodle-dialogue-wrap',
20 HEADER : 'moodle-dialogue-hd',
21 BODY : 'moodle-dialogue-bd',
22 CONTENT : 'moodle-dialogue-content',
23 FOOTER : 'moodle-dialogue-ft',
24 HIDDEN : 'hidden',
25 LIGHTBOX : 'moodle-dialogue-lightbox'
26};
27
28// Set up the namespace once.
29M.core = M.core || {};
30/**
31 * The generic dialogue class for use in Moodle.
32 *
33 * @module moodle-core-notification
34 * @submodule moodle-core-notification-dialogue
35 */
36
37var DIALOGUE_NAME = 'Moodle dialogue',
bf7c86cf 38 DIALOGUE,
f2b235cb
SH
39 DIALOGUE_FULLSCREEN_CLASS = DIALOGUE_PREFIX + '-fullscreen',
40 DIALOGUE_HIDDEN_CLASS = DIALOGUE_PREFIX + '-hidden',
f2b235cb 41 DIALOGUE_SELECTOR =' [role=dialog]',
586d393f 42 MENUBAR_SELECTOR = '[role=menubar]',
ecf02bf5
BB
43 DOT = '.',
44 HAS_ZINDEX = 'moodle-has-zindex',
586d393f 45 CAN_RECEIVE_FOCUS_SELECTOR = 'input:not([type="hidden"]), a[href], button, textarea, select, [tabindex]';
78686995
AN
46
47/**
48 * A re-usable dialogue box with Moodle classes applied.
49 *
4246e5c7 50 * @param {Object} c Object literal specifying the dialogue configuration properties.
78686995
AN
51 * @constructor
52 * @class M.core.dialogue
1f777e5c 53 * @extends Panel
78686995 54 */
4246e5c7
AN
55DIALOGUE = function(c) {
56 var config = Y.clone(c);
d10e6118
AN
57 config.COUNT = Y.stamp(this);
58 var id = 'moodle-dialogue-' + config.COUNT;
78686995
AN
59 config.notificationBase =
60 Y.Node.create('<div class="'+CSS.BASE+'">')
61 .append(Y.Node.create('<div id="'+id+'" role="dialog" aria-labelledby="'+id+'-header-text" class="'+CSS.WRAP+'"></div>')
baffb422 62 .append(Y.Node.create('<div id="'+id+'-header-text" class="'+CSS.HEADER+' yui3-widget-hd"></div>'))
78686995
AN
63 .append(Y.Node.create('<div class="'+CSS.BODY+' yui3-widget-bd"></div>'))
64 .append(Y.Node.create('<div class="'+CSS.FOOTER+' yui3-widget-ft"></div>')));
65 Y.one(document.body).append(config.notificationBase);
b59f2e3b
SH
66
67 if (config.additionalBaseClass) {
68 config.notificationBase.addClass(config.additionalBaseClass);
69 }
70
78686995 71 config.srcNode = '#'+id;
78686995 72
78686995
AN
73 // closeButton param to keep the stable versions API.
74 if (config.closeButton === false) {
75 config.buttons = null;
76 } else {
77 config.buttons = [
78 {
79 section: Y.WidgetStdMod.HEADER,
80 classNames: 'closebutton',
81 action: function () {
82 this.hide();
83 }
84 }
85 ];
86 }
87 DIALOGUE.superclass.constructor.apply(this, [config]);
88
89 if (config.closeButton !== false) {
90 // The buttons constructor does not allow custom attributes
91 this.get('buttons').header[0].setAttribute('title', this.get('closeButtonTitle'));
92 }
93};
94Y.extend(DIALOGUE, Y.Panel, {
d61c96b6
DW
95 // Window resize event listener.
96 _resizeevent : null,
97 // Orientation change event listener.
98 _orientationevent : null,
1389bcd7 99 _calculatedzindex : false,
1f777e5c 100
bf7c86cf
DW
101 /**
102 * Initialise the dialogue.
103 *
104 * @method initializer
bf7c86cf 105 */
2f5c1441 106 initializer : function() {
bf7c86cf
DW
107 var bb;
108
d9bf4be4
SH
109 if (this.get('render')) {
110 this.render();
111 }
ce5867a1 112 this.makeResponsive();
bf7c86cf 113 this.after('visibleChange', this.visibilityChanged, this);
2f5c1441 114 if (this.get('center')) {
dd66b6ab
DW
115 this.centerDialogue();
116 }
78686995 117
73747aea
AN
118 if (this.get('modal')) {
119 this.plug(Y.M.core.LockScroll);
120 }
121
78686995
AN
122 // Workaround upstream YUI bug http://yuilibrary.com/projects/yui3/ticket/2532507
123 // and allow setting of z-index in theme.
d61c96b6 124 bb = this.get('boundingBox');
ecf02bf5 125 bb.addClass(HAS_ZINDEX);
bf7c86cf 126
2f5c1441
AN
127 // Add any additional classes that were specified.
128 Y.Array.each(this.get('extraClasses'), bb.addClass, bb);
129
130 if (this.get('visible')) {
bf7c86cf
DW
131 this.applyZIndex();
132 }
1389bcd7
JF
133 // Recalculate the zIndex every time the modal is altered.
134 this.on('maskShow', this.applyZIndex);
ce5867a1
DW
135 // We must show - after the dialogue has been positioned,
136 // either by centerDialogue or makeResonsive. This is because the show() will trigger
137 // a focus on the dialogue, which will scroll the page. If the dialogue has not
138 // been positioned it will scroll back to the top of the page.
2f5c1441 139 if (this.get('visible')) {
ce5867a1 140 this.show();
586d393f 141 this.keyDelegation();
ce5867a1 142 }
0860dd78
AN
143
144 // Remove the dialogue from the DOM when it is destroyed.
145 this.after('destroyedChange', function(){
146 this.get(BASE).remove(true);
147 }, this);
bf7c86cf
DW
148 },
149
150 /**
151 * Either set the zindex to the supplied value, or set it to one more than the highest existing
152 * dialog in the page.
153 *
1f777e5c 154 * @method applyZIndex
bf7c86cf
DW
155 */
156 applyZIndex : function() {
1389bcd7
JF
157 var highestzindex = 1,
158 zindexvalue = 1,
f2b235cb 159 bb = this.get('boundingBox'),
1389bcd7 160 ol = this.get('maskNode'),
f2b235cb 161 zindex = this.get('zIndex');
1389bcd7 162 if (zindex !== 0 && !this._calculatedzindex) {
bf7c86cf 163 // The zindex was specified so we should use that.
f2b235cb 164 bb.setStyle('zIndex', zindex);
bf7c86cf 165 } else {
f2b235cb 166 // Determine the correct zindex by looking at all existing dialogs and menubars in the page.
ecf02bf5 167 Y.all(DIALOGUE_SELECTOR + ', ' + MENUBAR_SELECTOR + ', ' + DOT + HAS_ZINDEX).each(function (node) {
f2b235cb
SH
168 var zindex = this.findZIndex(node);
169 if (zindex > highestzindex) {
170 highestzindex = zindex;
bf7c86cf 171 }
f2b235cb 172 }, this);
bf7c86cf 173 // Only set the zindex if we found a wrapper.
1389bcd7
JF
174 zindexvalue = (highestzindex + 1).toString();
175 bb.setStyle('zIndex', zindexvalue);
1389bcd7 176 this.set('zIndex', zindexvalue);
0ef60744
AN
177 if (this.get('modal')) {
178 ol.setStyle('zIndex', zindexvalue);
5298d02f 179
180 // In IE8, the z-indexes do not take effect properly unless you toggle
181 // the lightbox from 'fixed' to 'static' and back. This code does so
182 // using the minimum setTimeouts that still actually work.
183 if (Y.UA.ie && Y.UA.compareVersions(Y.UA.ie, 9) < 0) {
184 setTimeout(function() {
185 ol.setStyle('position', 'static');
186 setTimeout(function() {
187 ol.setStyle('position', 'fixed');
188 }, 0);
189 }, 0);
190 }
0ef60744 191 }
1389bcd7 192 this._calculatedzindex = true;
bf7c86cf
DW
193 }
194 },
195
f2b235cb
SH
196 /**
197 * Finds the zIndex of the given node or its parent.
198 *
199 * @method findZIndex
1f777e5c
AN
200 * @param {Node} node The Node to apply the zIndex to.
201 * @return {Number} Either the zIndex, or 0 if one was not found.
f2b235cb
SH
202 */
203 findZIndex : function(node) {
204 // In most cases the zindex is set on the parent of the dialog.
205 var zindex = node.getStyle('zIndex') || node.ancestor().getStyle('zIndex');
206 if (zindex) {
207 return parseInt(zindex, 10);
208 }
209 return 0;
210 },
211
bf7c86cf
DW
212 /**
213 * Event listener for the visibility changed event.
214 *
215 * @method visibilityChanged
1f777e5c 216 * @param {EventFacade} e
bf7c86cf 217 */
78686995 218 visibilityChanged : function(e) {
586d393f 219 var titlebar, bb;
78686995
AN
220 if (e.attrName === 'visible') {
221 this.get('maskNode').addClass(CSS.LIGHTBOX);
d61c96b6 222 if (e.prevVal && !e.newVal) {
586d393f 223 bb = this.get('boundingBox');
d61c96b6
DW
224 if (this._resizeevent) {
225 this._resizeevent.detach();
226 this._resizeevent = null;
227 }
228 if (this._orientationevent) {
229 this._orientationevent.detach();
230 this._orientationevent = null;
231 }
586d393f 232 bb.detach('key', this.keyDelegation);
d61c96b6 233 }
bf7c86cf
DW
234 if (!e.prevVal && e.newVal) {
235 // This needs to be done each time the dialog is shown as new dialogs may have been opened.
236 this.applyZIndex();
237 // This needs to be done each time the dialog is shown as the window may have been resized.
238 this.makeResponsive();
239 if (!this.shouldResizeFullscreen()) {
240 if (this.get('draggable')) {
241 titlebar = '#' + this.get('id') + ' .' + CSS.HEADER;
242 this.plug(Y.Plugin.Drag, {handles : [titlebar]});
243 Y.one(titlebar).setStyle('cursor', 'move');
244 }
245 }
586d393f 246 this.keyDelegation();
bf7c86cf 247 }
78686995
AN
248 if (this.get('center') && !e.prevVal && e.newVal) {
249 this.centerDialogue();
250 }
78686995
AN
251 }
252 },
bf7c86cf
DW
253 /**
254 * If the responsive attribute is set on the dialog, and the window size is
255 * smaller than the responsive width - make the dialog fullscreen.
256 *
257 * @method makeResponsive
bf7c86cf
DW
258 */
259 makeResponsive : function() {
78686995 260 var bb = this.get('boundingBox'),
bf7c86cf
DW
261 content;
262
263 if (this.shouldResizeFullscreen()) {
d61c96b6
DW
264 // Make this dialogue fullscreen on a small screen.
265 // Disable the page scrollbars.
bf7c86cf 266
d61c96b6
DW
267 // Size and position the fullscreen dialog.
268
2a808cef
DW
269 bb.addClass(DIALOGUE_FULLSCREEN_CLASS);
270 bb.setStyles({'left' : null,
271 'top' : null,
272 'width' : null,
273 'height' : null,
274 'right' : null,
275 'bottom' : null});
d61c96b6
DW
276
277 content = Y.one('#' + this.get('id') + ' .' + CSS.BODY);
d61c96b6
DW
278 } else {
279 if (this.get('responsive')) {
280 // We must reset any of the fullscreen changes.
2a808cef
DW
281 bb.removeClass(DIALOGUE_FULLSCREEN_CLASS)
282 .setStyles({'width' : this.get('width'),
bf7c86cf 283 'height' : this.get('height')});
d61c96b6 284 content = Y.one('#' + this.get('id') + ' .' + CSS.BODY);
d61c96b6 285 }
d61c96b6 286 }
bf7c86cf
DW
287 },
288 /**
289 * Center the dialog on the screen.
290 *
291 * @method centerDialogue
bf7c86cf
DW
292 */
293 centerDialogue : function() {
294 var bb = this.get('boundingBox'),
295 hidden = bb.hasClass(DIALOGUE_HIDDEN_CLASS),
296 x,
297 y;
298
299 // Don't adjust the position if we are in full screen mode.
300 if (this.shouldResizeFullscreen()) {
301 return;
302 }
303 if (hidden) {
304 bb.setStyle('top', '-1000px').removeClass(DIALOGUE_HIDDEN_CLASS);
305 }
306 x = Math.max(Math.round((bb.get('winWidth') - bb.get('offsetWidth'))/2), 15);
307 y = Math.max(Math.round((bb.get('winHeight') - bb.get('offsetHeight'))/2), 15) + Y.one(window).get('scrollTop');
308 bb.setStyles({ 'left' : x, 'top' : y});
78686995
AN
309
310 if (hidden) {
bf7c86cf 311 bb.addClass(DIALOGUE_HIDDEN_CLASS);
78686995 312 }
d61c96b6 313 },
bf7c86cf 314 /**
1f777e5c
AN
315 * Return whether this dialogue should be fullscreen or not.
316 *
bf7c86cf
DW
317 * Responsive attribute must be true and we should not be in an iframe and the screen width should
318 * be less than the responsive width.
319 *
320 * @method shouldResizeFullscreen
1f777e5c 321 * @return {Boolean}
bf7c86cf
DW
322 */
323 shouldResizeFullscreen : function() {
324 return (window === window.parent) && this.get('responsive') &&
325 Math.floor(Y.one(document.body).get('winWidth')) < this.get('responsiveWidth');
2eaaae00
JF
326 },
327
2eaaae00
JF
328 show : function() {
329 var result = null,
330 header = this.headerNode,
e5ddec38 331 content = this.bodyNode,
c1660772 332 focusSelector = this.get('focusOnShowSelector'),
e5ddec38 333 focusNode = null;
2eaaae00 334
b959e508
AN
335 result = DIALOGUE.superclass.show.call(this);
336
73747aea
AN
337 // Lock scroll if the plugin is present.
338 if (this.lockScroll) {
b959e508
AN
339 // We need to force the scroll locking for full screen dialogues, even if they have a small vertical size to
340 // prevent the background scrolling while the dialogue is open.
341 this.lockScroll.enableScrollLock(this.shouldResizeFullscreen());
73747aea
AN
342 }
343
c1660772
DW
344 // Try and find a node to focus on using the focusOnShowSelector attribute.
345 if (focusSelector !== null) {
e5ddec38 346 focusNode = this.get('boundingBox').one(focusSelector);
2eaaae00 347 }
e5ddec38
DW
348 if (!focusNode) {
349 // Fall back to the header or the content if no focus node was found yet.
350 if (header && header !== '') {
351 focusNode = header;
352 } else if (content && content !== '') {
353 focusNode = content;
354 }
355 }
c1660772
DW
356 if (focusNode) {
357 focusNode.focus();
358 }
2eaaae00 359 return result;
586d393f 360 },
73747aea 361
bf24abd2
AN
362 hide: function(e) {
363 if (e) {
364 // If the event was closed by an escape key event, then we need to check that this
365 // dialogue is currently focused to prevent closing all dialogues in the stack.
366 if (e.type === 'key' && e.keyCode === 27 && !this.get('focused')) {
367 return;
368 }
369 }
370
73747aea
AN
371 // Unlock scroll if the plugin is present.
372 if (this.lockScroll) {
373 this.lockScroll.disableScrollLock();
374 }
375
376 return DIALOGUE.superclass.hide.call(this, arguments);
377 },
586d393f 378 /**
379 * Setup key delegation to keep tabbing within the open dialogue.
380 *
381 * @method keyDelegation
382 */
383 keyDelegation : function() {
384 var bb = this.get('boundingBox');
385 bb.delegate('key', function(e){
386 var target = e.target;
387 var direction = 'forward';
388 if (e.shiftKey) {
389 direction = 'backward';
390 }
391 if (this.trapFocus(target, direction)) {
392 e.preventDefault();
393 }
394 }, 'down:9', CAN_RECEIVE_FOCUS_SELECTOR, this);
395 },
1f777e5c 396
586d393f 397 /**
398 * Trap the tab focus within the open modal.
399 *
1f777e5c
AN
400 * @method trapFocus
401 * @param {string} target the element target
402 * @param {string} direction tab key for forward and tab+shift for backward
403 * @return {Boolean} The result of the focus action.
586d393f 404 */
405 trapFocus : function(target, direction) {
406 var bb = this.get('boundingBox'),
407 firstitem = bb.one(CAN_RECEIVE_FOCUS_SELECTOR),
408 lastitem = bb.all(CAN_RECEIVE_FOCUS_SELECTOR).pop();
409
410 if (target === lastitem && direction === 'forward') { // Tab key.
411 return firstitem.focus();
412 } else if (target === firstitem && direction === 'backward') { // Tab+shift key.
413 return lastitem.focus();
414 }
78686995
AN
415 }
416}, {
417 NAME : DIALOGUE_NAME,
418 CSS_PREFIX : DIALOGUE_PREFIX,
419 ATTRS : {
420 notificationBase : {
421
422 },
423
424 /**
425 * Whether to display the dialogue modally and with a
426 * lightbox style.
427 *
428 * @attribute lightbox
429 * @type Boolean
430 * @default true
cff3b8fe 431 * @deprecated Since Moodle 2.7. Please use modal instead.
78686995 432 */
cff3b8fe
AN
433 lightbox: {
434 lazyAdd: false,
435 setter: function(value) {
436 Y.log("The lightbox attribute of M.core.dialogue has been deprecated since Moodle 2.7, please use the modal attribute instead",
437 'warn', 'moodle-core-notification-dialogue');
438 this.set('modal', value);
439 }
78686995
AN
440 },
441
442 /**
443 * Whether to display a close button on the dialogue.
444 *
445 * Note, we do not recommend hiding the close button as this has
446 * potential accessibility concerns.
447 *
448 * @attribute closeButton
449 * @type Boolean
450 * @default true
451 */
452 closeButton : {
453 validator : Y.Lang.isBoolean,
454 value : true
455 },
456
457 /**
458 * The title for the close button if one is to be shown.
459 *
460 * @attribute closeButtonTitle
461 * @type String
462 * @default 'Close'
463 */
464 closeButtonTitle : {
465 validator : Y.Lang.isString,
0d1d5423 466 value: M.util.get_string('closebuttontitle', 'moodle')
78686995
AN
467 },
468
469 /**
470 * Whether to display the dialogue centrally on the screen.
471 *
472 * @attribute center
473 * @type Boolean
474 * @default true
475 */
476 center : {
477 validator : Y.Lang.isBoolean,
478 value : true
479 },
480
481 /**
482 * Whether to make the dialogue movable around the page.
483 *
484 * @attribute draggable
485 * @type Boolean
486 * @default false
487 */
488 draggable : {
489 validator : Y.Lang.isBoolean,
490 value : false
491 },
bf7c86cf
DW
492
493 /**
494 * Used to generate a unique id for the dialogue.
495 *
496 * @attribute COUNT
d10e6118
AN
497 * @type String
498 * @default null
bf7c86cf 499 */
78686995 500 COUNT: {
d10e6118 501 value: null
d61c96b6 502 },
bf7c86cf
DW
503
504 /**
505 * Used to disable the fullscreen resizing behaviour if required.
506 *
507 * @attribute responsive
508 * @type Boolean
509 * @default true
510 */
d61c96b6
DW
511 responsive : {
512 validator : Y.Lang.isBoolean,
513 value : true
514 },
bf7c86cf
DW
515
516 /**
517 * The width that this dialogue should be resized to fullscreen.
518 *
519 * @attribute responsiveWidth
1f777e5c 520 * @type Number
bf7c86cf
DW
521 * @default 768
522 */
d61c96b6
DW
523 responsiveWidth : {
524 value : 768
c1660772
DW
525 },
526
527 /**
528 * Selector to a node that should recieve focus when this dialogue is shown.
529 *
530 * The default behaviour is to focus on the header.
531 *
532 * @attribute focusOnShowSelector
533 * @default null
534 * @type String
535 */
536 focusOnShowSelector: {
537 value: null
78686995 538 }
c1660772 539
78686995
AN
540 }
541});
542
16d02434
AN
543Y.Base.modifyAttrs(DIALOGUE, {
544 /**
545 * String with units, or number, representing the width of the Widget.
546 * If a number is provided, the default unit, defined by the Widgets
547 * DEF_UNIT, property is used.
548 *
549 * If a value of 'auto' is used, then an empty String is instead
550 * returned.
551 *
552 * @attribute width
553 * @default '400px'
554 * @type {String|Number}
555 */
556 width: {
557 value: '400px',
558 setter: function(value) {
559 if (value === 'auto') {
560 return '';
561 }
562 return value;
563 }
c46cca4f
AN
564 },
565
566 /**
567 * Boolean indicating whether or not the Widget is visible.
568 *
569 * We override this from the default Widget attribute value.
570 *
571 * @attribute visible
572 * @default false
573 * @type Boolean
574 */
575 visible: {
576 value: false
a67233e7
AN
577 },
578
579 /**
580 * A convenience Attribute, which can be used as a shortcut for the
581 * `align` Attribute.
582 *
583 * Note: We override this in Moodle such that it sets a value for the
584 * `center` attribute if set. The `centered` will always return false.
585 *
586 * @attribute centered
587 * @type Boolean|Node
588 * @default false
589 */
590 centered: {
591 setter: function(value) {
592 if (value) {
593 this.set('center', true);
594 }
595 return false;
596 }
d9bf4be4
SH
597 },
598
599 /**
600 * Boolean determining whether to render the widget during initialisation.
601 *
602 * We override this to change the default from false to true for the dialogue.
603 * We then proceed to early render the dialogue during our initialisation rather than waiting
604 * for YUI to render it after that.
605 *
606 * @attribute render
607 * @type Boolean
608 * @default true
609 */
610 render : {
611 value : true,
612 writeOnce : true
2f5c1441
AN
613 },
614
615 /**
616 * Any additional classes to add to the boundingBox.
617 *
1f777e5c 618 * @attribute extraClasses
2f5c1441
AN
619 * @type Array
620 * @default []
621 */
622 extraClasses: {
623 value: []
16d02434
AN
624 }
625});
626
29ee3cf7
AN
627Y.Base.mix(DIALOGUE, [Y.M.core.WidgetFocusAfterHide]);
628
78686995 629M.core.dialogue = DIALOGUE;
cfa770b4
AN
630/**
631 * A dialogue type designed to display informative messages to users.
632 *
633 * @module moodle-core-notification
634 */
635
636/**
637 * Extends core Dialogue to provide a type of dialogue which can be used
638 * for informative message which are modal, and centered.
639 *
640 * @param {Object} config Object literal specifying the dialogue configuration properties.
641 * @constructor
642 * @class M.core.notification.info
643 * @extends M.core.dialogue
644 */
645var INFO = function() {
646 INFO.superclass.constructor.apply(this, arguments);
647};
648
649Y.extend(INFO, M.core.dialogue, {
650}, {
651 NAME: 'Moodle information dialogue',
652 CSS_PREFIX: DIALOGUE_PREFIX
653});
654
655Y.Base.modifyAttrs(INFO, {
656 /**
657 * Boolean indicating whether or not the Widget is visible.
658 *
659 * We override this from the default M.core.dialogue attribute value.
660 *
661 * @attribute visible
662 * @default true
663 * @type Boolean
664 */
665 visible: {
666 value: true
667 },
668
669 /**
670 * Whether the widget should be modal or not.
671 *
672 * We override this to change the default from false to true for a subset of dialogues.
673 *
674 * @attribute modal
675 * @type Boolean
676 * @default true
677 */
678 modal: {
679 validator: Y.Lang.isBoolean,
680 value: true
681 }
682});
683
684M.core.notification = M.core.notification || {};
685M.core.notification.info = INFO;
78686995
AN
686
687
29ee3cf7
AN
688}, '@VERSION@', {
689 "requires": [
690 "base",
691 "node",
692 "panel",
cd6e149c 693 "escape",
29ee3cf7
AN
694 "event-key",
695 "dd-plugin",
696 "moodle-core-widget-focusafterclose",
697 "moodle-core-lockscroll"
698 ]
699});