on-demand release 4.0dev+
[moodle.git] / lib / yui / build / moodle-core-notification-dialogue / moodle-core-notification-dialogue-debug.js
1 YUI.add('moodle-core-notification-dialogue', function (Y, NAME) {
3 /* eslint-disable no-unused-vars, no-unused-expressions */
4 var DIALOGUE_PREFIX,
5     BASE,
6     CONFIRMYES,
7     CONFIRMNO,
8     TITLE,
9     QUESTION,
10     CSS_CLASSES;
12 DIALOGUE_PREFIX = 'moodle-dialogue';
13 BASE = 'notificationBase';
14 CONFIRMYES = 'yesLabel';
15 CONFIRMNO = 'noLabel';
16 TITLE = 'title';
17 QUESTION = 'question';
18 CSS_CLASSES = {
19     BASE: 'moodle-dialogue-base',
20     WRAP: 'moodle-dialogue-wrap',
21     HEADER: 'moodle-dialogue-hd',
22     BODY: 'moodle-dialogue-bd',
23     CONTENT: 'moodle-dialogue-content',
24     FOOTER: 'moodle-dialogue-ft',
25     HIDDEN: 'hidden',
26     LIGHTBOX: 'moodle-dialogue-lightbox'
27 };
29 // Set up the namespace once.
30 M.core = M.core || {};
31 /**
32  * The generic dialogue class for use in Moodle.
33  *
34  * @module moodle-core-notification
35  * @submodule moodle-core-notification-dialogue
36  */
38 var DIALOGUE_NAME = 'Moodle dialogue',
39     DIALOGUE,
40     DIALOGUE_FULLSCREEN_CLASS = DIALOGUE_PREFIX + '-fullscreen',
41     DIALOGUE_HIDDEN_CLASS = DIALOGUE_PREFIX + '-hidden',
42     DIALOGUE_SELECTOR = ' [role=dialog]',
43     MENUBAR_SELECTOR = '[role=menubar]',
44     DOT = '.',
45     HAS_ZINDEX = 'moodle-has-zindex',
46     CAN_RECEIVE_FOCUS_SELECTOR = 'input:not([type="hidden"]):not([disabled]):not([tabindex^="-"]),' +
47         'a[href]:not([disabled]):not([tabindex^="-"]),' +
48         'button:not([disabled]):not([tabindex^="-"]),' +
49         'textarea:not([disabled]):not([tabindex^="-"]),' +
50         'select:not([disabled]):not([tabindex^="-"]),' +
51         '[tabindex]:not([disabled]):not([tabindex^="-"])',
52     FORM_SELECTOR = 'form';
54 /**
55  * A re-usable dialogue box with Moodle classes applied.
56  *
57  * @param {Object} c Object literal specifying the dialogue configuration properties.
58  * @constructor
59  * @class M.core.dialogue
60  * @extends Panel
61  */
62 DIALOGUE = function(config) {
63     // The code below is a hack to add the custom content node to the DOM, on the fly, per-instantiation and to assign the value
64     // of 'srcNode' to this newly created node. Normally (see docs: https://yuilibrary.com/yui/docs/widget/widget-extend.html),
65     // this node would be pre-existing in the DOM, and an id string would simply be passed in as a property of the config object
66     // during widget instantiation, however, because we're creating it on the fly (and 'config.srcNode' isn't set yet), care must
67     // be taken to add it to the DOM and to properly set the value of 'config.srcNode' before calling the parent constructor.
68     // Note: additional classes can be added to this content node by setting the 'additionalBaseClass' config property (a string).
69     var id = 'moodle-dialogue-' + Y.stamp(this) + '-wrap'; // Can't use this.get('id') as it's not set at this stage.
70     config.notificationBase =
71         Y.Node.create('<div class="' + CSS_CLASSES.BASE + '">')
72             .append(Y.Node.create(
73                 '<div id="' + id + '" role="dialog" ' +
74                 'aria-labelledby="' + id + '-header-text" class="' + CSS_CLASSES.WRAP + '"  aria-live="polite"></div>'
75             )
76             .append(Y.Node.create('<div class="' + CSS_CLASSES.HEADER + ' yui3-widget-hd"></div>'))
77             .append(Y.Node.create('<div class="' + CSS_CLASSES.BODY + ' yui3-widget-bd"></div>'))
78             .append(Y.Node.create('<div class="' + CSS_CLASSES.FOOTER + ' yui3-widget-ft"></div>')));
79     config.attachmentPoint = config.attachmentPoint || document.body;
80     Y.one(config.attachmentPoint).append(config.notificationBase);
81     config.srcNode = '#' + id;
82     delete config.buttons; // Don't let anyone pass in buttons as we want to control these during init. addButton can be used later.
83     DIALOGUE.superclass.constructor.apply(this, [config]);
84 };
85 Y.extend(DIALOGUE, Y.Panel, {
86     // Window resize event listener.
87     _resizeevent: null,
88     // Orientation change event listener.
89     _orientationevent: null,
90     _calculatedzindex: false,
91     // Current maskNode id
92     _currentMaskNodeId: null,
93     /**
94      * The original position of the dialogue before it was reposition to
95      * avoid browser jumping.
96      *
97      * @property _originalPosition
98      * @protected
99      * @type Array
100      */
101     _originalPosition: null,
103     /**
104      * The list of elements that have been aria hidden when displaying
105      * this dialogue.
106      *
107      * @property _hiddenSiblings
108      * @protected
109      * @type Array
110      */
111     _hiddenSiblings: null,
113     /**
114      * Hide the modal only if it doesn't contain a form.
115      *
116      * @method hideIfNotForm
117      */
118     hideIfNotForm: function() {
119         var bb = this.get('boundingBox'),
120             formElement = bb.one(FORM_SELECTOR);
122         if (formElement === null) {
123             this.hide();
124         }
125     },
127     /**
128      * Initialise the dialogue.
129      *
130      * @method initializer
131      */
132     initializer: function() {
133         var bb;
135         if (this.get('closeButton') !== false) {
136             var title = this.get('closeButtonTitle');
137             // The buttons constructor does not allow custom attributes.
138             this.get('buttons').header[0].setAttribute('title', title);
139             this.get('buttons').header[0].setAttribute('aria-label', title);
140         }
142         this.setStdModContent(Y.WidgetStdMod.HEADER,
143             '<h5 id="' + this.get('id') + '-wrap-header-text">' + this.get('headerContent') + '</h5>',
144             Y.WidgetStdMod.REPLACE);
146         // Initialise the element cache.
147         this._hiddenSiblings = [];
149         if (this.get('render')) {
150             this.render();
151         }
152         this.after('visibleChange', this.visibilityChanged, this);
153         if (this.get('center')) {
154             this.centerDialogue();
155         }
157         if (this.get('modal')) {
158             // If we're a modal then make sure our container is ARIA
159             // hidden by default. ARIA visibility is managed for modal dialogues.
160             this.get(BASE).set('aria-hidden', 'true');
161             this.plug(Y.M.core.LockScroll);
162         }
164         // Remove the `focusoutside` listener.
165         // It conflicts with the ARIA focuslock manager which supports both YUI and non-YUI dialogues.
166         this.set('focusOn', Y.Array(this.get('focusOn')).filter(function(node) {
167             return node.eventName !== 'focusoutside';
168         }));
170         // Workaround upstream YUI bug http://yuilibrary.com/projects/yui3/ticket/2532507
171         // and allow setting of z-index in theme.
172         bb = this.get('boundingBox');
173         bb.addClass(HAS_ZINDEX);
175         // Add any additional classes that were specified.
176         Y.Array.each(this.get('extraClasses'), bb.addClass, bb);
178         if (this.get('visible')) {
179             this.applyZIndex();
180         }
181         // Recalculate the zIndex every time the modal is altered.
182         this.on('maskShow', this.applyZIndex);
184         this.on('maskShow', function() {
185             // When the mask shows, position the boundingBox at the top-left of the window such that when it is
186             // focused, the position does not change.
187             var w = Y.one(Y.config.win),
188                 bb = this.get('boundingBox');
190             if (!this.get('center')) {
191                 this._originalPosition = bb.getXY();
192             }
194             // Check if maskNode already init click event.
195             var maskNode = this.get('maskNode');
196             if (this._currentMaskNodeId !== maskNode.get('_yuid')) {
197                 this._currentMaskNodeId = maskNode.get('_yuid');
198                 maskNode.on('click', this.hideIfNotForm, this);
199             }
201             if (bb.getStyle('position') !== 'fixed') {
202                 // If the boundingBox has been positioned in a fixed manner, then it will not position correctly to scrollTop.
203                 bb.setStyles({
204                     top: w.get('scrollTop'),
205                     left: w.get('scrollLeft')
206                 });
207             }
208         }, this);
210         // Add any additional classes to the content node if required.
211         var nBase = this.get('notificationBase');
212         var additionalClasses = this.get('additionalBaseClass');
213         if (additionalClasses !== '') {
214             nBase.addClass(additionalClasses);
215         }
217         // Remove the dialogue from the DOM when it is destroyed.
218         this.after('destroyedChange', function() {
219             this.get(BASE).remove(true);
220         }, this);
221     },
223     /**
224      * Either set the zindex to the supplied value, or set it to one more than the highest existing
225      * dialog in the page.
226      *
227      * @method applyZIndex
228      */
229     applyZIndex: function() {
230         var highestzindex = 1040,
231             zindexvalue = 1,
232             bb = this.get('boundingBox'),
233             ol = this.get('maskNode'),
234             zindex = this.get('zIndex');
235         if (zindex !== 0 && !this._calculatedzindex) {
236             // The zindex was specified so we should use that.
237             bb.setStyle('zIndex', zindex);
238         } else {
239             // Determine the correct zindex by looking at all existing dialogs and menubars in the page.
240             Y.all(DIALOGUE_SELECTOR + ', ' + MENUBAR_SELECTOR + ', ' + DOT + HAS_ZINDEX).each(function(node) {
241                 var zindex = this.findZIndex(node);
242                 if (zindex > highestzindex) {
243                     highestzindex = zindex;
244                 }
245             }, this);
246             // Only set the zindex if we found a wrapper.
247             zindexvalue = (highestzindex + 1).toString();
248             bb.setStyle('zIndex', zindexvalue);
249             this.set('zIndex', zindexvalue);
250             if (this.get('modal')) {
251                 ol.setStyle('zIndex', zindexvalue);
253                 // In IE8, the z-indexes do not take effect properly unless you toggle
254                 // the lightbox from 'fixed' to 'static' and back. This code does so
255                 // using the minimum setTimeouts that still actually work.
256                 if (Y.UA.ie && Y.UA.compareVersions(Y.UA.ie, 9) < 0) {
257                     setTimeout(function() {
258                         ol.setStyle('position', 'static');
259                         setTimeout(function() {
260                             ol.setStyle('position', 'fixed');
261                         }, 0);
262                     }, 0);
263                 }
264             }
265             this._calculatedzindex = true;
266         }
267     },
269     /**
270      * Finds the zIndex of the given node or its parent.
271      *
272      * @method findZIndex
273      * @param {Node} node The Node to apply the zIndex to.
274      * @return {Number} Either the zIndex, or 0 if one was not found.
275      */
276     findZIndex: function(node) {
277         // In most cases the zindex is set on the parent of the dialog.
278         var zindex = node.getStyle('zIndex') || node.ancestor().getStyle('zIndex');
279         if (zindex) {
280             return parseInt(zindex, 10);
281         }
282         return 0;
283     },
285     /**
286      * Event listener for the visibility changed event.
287      *
288      * @method visibilityChanged
289      * @param {EventFacade} e
290      */
291     visibilityChanged: function(e) {
292         var titlebar, bb;
293         if (e.attrName === 'visible') {
294             this.get('maskNode').addClass(CSS_CLASSES.LIGHTBOX);
295             // Going from visible to hidden.
296             if (e.prevVal && !e.newVal) {
297                 bb = this.get('boundingBox');
298                 if (this._resizeevent) {
299                     this._resizeevent.detach();
300                     this._resizeevent = null;
301                 }
302                 if (this._orientationevent) {
303                     this._orientationevent.detach();
304                     this._orientationevent = null;
305                 }
306                 require(['core/local/aria/focuslock'], function(FocusLockManager) {
307                     // Untrap focus when the dialogue is hidden.
308                     FocusLockManager.untrapFocus();
309                 });
311                 if (this.get('modal')) {
312                     // Hide this dialogue from screen readers.
313                     this.setAccessibilityHidden();
314                 }
315             }
316             // Going from hidden to visible.
317             if (!e.prevVal && e.newVal) {
318                 // This needs to be done each time the dialog is shown as new dialogs may have been opened.
319                 this.applyZIndex();
320                 // This needs to be done each time the dialog is shown as the window may have been resized.
321                 this.makeResponsive();
322                 if (!this.shouldResizeFullscreen()) {
323                     if (this.get('draggable')) {
324                         titlebar = '#' + this.get('id') + ' .' + CSS_CLASSES.HEADER;
325                         this.plug(Y.Plugin.Drag, {handles: [titlebar]});
326                         Y.one(titlebar).setStyle('cursor', 'move');
327                     }
328                 }
330                 // Only do accessibility hiding for modals because the ARIA spec
331                 // says that all ARIA dialogues should be modal.
332                 if (this.get('modal')) {
333                     // Make this dialogue visible to screen readers.
334                     this.setAccessibilityVisible();
335                 }
336             }
337             if (this.get('center') && !e.prevVal && e.newVal) {
338                 this.centerDialogue();
339             }
340         }
341     },
342     /**
343      * If the responsive attribute is set on the dialog, and the window size is
344      * smaller than the responsive width - make the dialog fullscreen.
345      *
346      * @method makeResponsive
347      */
348     makeResponsive: function() {
349         var bb = this.get('boundingBox');
351         if (this.shouldResizeFullscreen()) {
352             // Make this dialogue fullscreen on a small screen.
353             // Disable the page scrollbars.
355             // Size and position the fullscreen dialog.
357             bb.addClass(DIALOGUE_FULLSCREEN_CLASS);
358             bb.setStyles({'left': null,
359                           'top': null,
360                           'width': null,
361                           'height': null,
362                           'right': null,
363                           'bottom': null});
364         } else {
365             if (this.get('responsive')) {
366                 // We must reset any of the fullscreen changes.
367                 bb.removeClass(DIALOGUE_FULLSCREEN_CLASS)
368                     .setStyles({'width': this.get('width'),
369                                 'height': this.get('height')});
370             }
371         }
373         // Update Lock scroll if the plugin is present.
374         if (this.lockScroll) {
375             this.lockScroll.updateScrollLock(this.shouldResizeFullscreen());
376         }
377     },
378     /**
379      * Center the dialog on the screen.
380      *
381      * @method centerDialogue
382      */
383     centerDialogue: function() {
384         var bb = this.get('boundingBox'),
385             hidden = bb.hasClass(DIALOGUE_HIDDEN_CLASS),
386             x,
387             y;
389         // Don't adjust the position if we are in full screen mode.
390         if (this.shouldResizeFullscreen()) {
391             return;
392         }
393         if (hidden) {
394             bb.setStyle('top', '-1000px').removeClass(DIALOGUE_HIDDEN_CLASS);
395         }
396         x = Math.max(Math.round((bb.get('winWidth') - bb.get('offsetWidth')) / 2), 15);
397         y = Math.max(Math.round((bb.get('winHeight') - bb.get('offsetHeight')) / 2), 15) + Y.one(window).get('scrollTop');
398         bb.setStyles({'left': x, 'top': y});
400         if (hidden) {
401             bb.addClass(DIALOGUE_HIDDEN_CLASS);
402         }
403         this.makeResponsive();
404     },
405     /**
406      * Return whether this dialogue should be fullscreen or not.
407      *
408      * Responsive attribute must be true and we should not be in an iframe and the screen width should
409      * be less than the responsive width.
410      *
411      * @method shouldResizeFullscreen
412      * @return {Boolean}
413      */
414     shouldResizeFullscreen: function() {
415         return (window === window.parent) && this.get('responsive') &&
416                Math.floor(Y.one(document.body).get('winWidth')) < this.get('responsiveWidth');
417     },
419     _focus: function() {
420         this.focus();
421     },
423     show: function() {
424         var result = null,
425             content = this.bodyNode,
426             focusSelector = this.get('focusOnShowSelector'),
427             focusNode = null;
429         result = DIALOGUE.superclass.show.call(this);
431         if (!this.get('center') && this._originalPosition) {
432             // Restore the dialogue position to it's location before it was moved at show time.
433             this.get('boundingBox').setXY(this._originalPosition);
434         }
436         // Try and find a node to focus on using the focusOnShowSelector attribute.
437         if (focusSelector !== null) {
438             focusNode = this.get('boundingBox').one(focusSelector);
439         }
440         if (!focusNode) {
441             // Fall back to the first focusable element in the body of the dialogue if no focus node was found yet.
442             if (content && content !== '') {
443                 focusNode = content.one(CAN_RECEIVE_FOCUS_SELECTOR);
444             }
445         }
446         require(['core/local/aria/focuslock'], function(FocusLockManager) {
447             // Trap focus to the current bounding box.
448             FocusLockManager.trapFocus(this.get('boundingBox').getDOMNode());
449             if (focusNode) {
450                 focusNode.focus();
451             }
452         }.bind(this));
453         return result;
454     },
456     hide: function(e) {
457         if (e) {
458             // If the event was closed by an escape key event, then we need to check that this
459             // dialogue is currently focused to prevent closing all dialogues in the stack.
460             if (e.type === 'key' && e.keyCode === 27 && !this.get('focused')) {
461                 return;
462             }
463         }
465         // Unlock scroll if the plugin is present.
466         if (this.lockScroll) {
467             this.lockScroll.disableScrollLock();
468         }
470         return DIALOGUE.superclass.hide.call(this, arguments);
471     },
472     /**
473      * Setup key delegation to keep tabbing within the open dialogue.
474      *
475      * @method keyDelegation
476      */
477     keyDelegation: function() {
478         Y.log('The keyDelegation function has been deprecated in favour of the AMD core/local/aria/focuslock module');
479         var bb = this.get('boundingBox');
480         bb.delegate('key', function(e) {
481             var target = e.target;
482             var direction = 'forward';
483             if (e.shiftKey) {
484                 direction = 'backward';
485             }
486             if (this.trapFocus(target, direction)) {
487                 e.preventDefault();
488             }
489         }, 'down:9', CAN_RECEIVE_FOCUS_SELECTOR, this);
490     },
492     /**
493      * Trap the tab focus within the open modal.
494      *
495      * @method trapFocus
496      * @param {string} target the element target
497      * @param {string} direction tab key for forward and tab+shift for backward
498      * @return {Boolean} The result of the focus action.
499      */
500     trapFocus: function(target, direction) {
501         var bb = this.get('boundingBox'),
502             firstitem = bb.one(CAN_RECEIVE_FOCUS_SELECTOR),
503             lastitem = bb.all(CAN_RECEIVE_FOCUS_SELECTOR).pop();
505         if (target === lastitem && direction === 'forward') { // Tab key.
506             return firstitem.focus();
507         } else if (target === firstitem && direction === 'backward') {  // Tab+shift key.
508             return lastitem.focus();
509         }
510     },
512     /**
513      * Sets the appropriate aria attributes on this dialogue and the other
514      * elements in the DOM to ensure that screen readers are able to navigate
515      * the dialogue popup correctly.
516      *
517      * @method setAccessibilityVisible
518      */
519     setAccessibilityVisible: function() {
520         // Get the element that contains this dialogue because we need it
521         // to filter out from the document.body child elements.
522         var container = this.get(BASE);
524         // We need to get a list containing each sibling element and the shallowest
525         // non-ancestral nodes in the DOM. We can shortcut this a little by leveraging
526         // the fact that this dialogue is always appended to the document body therefore
527         // it's siblings are the shallowest non-ancestral nodes. If that changes then
528         // this code should also be updated.
529         Y.one(document.body).get('children').each(function(node) {
530             // Skip the element that contains us.
531             if (node !== container) {
532                 var hidden = node.get('aria-hidden');
533                 // If they are already hidden we can ignore them.
534                 if (hidden !== 'true') {
535                     // Save their current state.
536                     node.setData('previous-aria-hidden', hidden);
537                     this._hiddenSiblings.push(node);
539                     // Hide this node from screen readers.
540                     node.set('aria-hidden', 'true');
541                 }
542             }
543         }, this);
545         // Make us visible to screen readers.
546         container.set('aria-hidden', 'false');
547     },
549     /**
550      * Restores the aria visibility on the DOM elements changed when displaying
551      * the dialogue popup and makes the dialogue aria hidden to allow screen
552      * readers to navigate the main page correctly when the dialogue is closed.
553      *
554      * @method setAccessibilityHidden
555      */
556     setAccessibilityHidden: function() {
557         var container = this.get(BASE);
558         container.set('aria-hidden', 'true');
560         // Restore the sibling nodes back to their original values.
561         Y.Array.each(this._hiddenSiblings, function(node) {
562             var previousValue = node.getData('previous-aria-hidden');
563             // If the element didn't previously have an aria-hidden attribute
564             // then we can just remove the one we set.
565             if (previousValue === null) {
566                 node.removeAttribute('aria-hidden');
567             } else {
568                 // Otherwise set it back to the old value (which will be false).
569                 node.set('aria-hidden', previousValue);
570             }
571         });
573         // Clear the cache. No longer need to store these.
574         this._hiddenSiblings = [];
575     }
576 }, {
577     NAME: DIALOGUE_NAME,
578     CSS_PREFIX: DIALOGUE_PREFIX,
579     ATTRS: {
580         /**
581          * Any additional classes to add to the base Node.
582          *
583          * @attribute additionalBaseClass
584          * @type String
585          * @default ''
586          */
587         additionalBaseClass: {
588             value: ''
589         },
591         /**
592          * The Notification base Node.
593          *
594          * @attribute notificationBase
595          * @type Node
596          */
597         notificationBase: {
599         },
601         /**
602          * Whether to display the dialogue modally and with a
603          * lightbox style.
604          *
605          * @attribute lightbox
606          * @type Boolean
607          * @default true
608          * @deprecated Since Moodle 2.7. Please use modal instead.
609          */
610         lightbox: {
611             lazyAdd: false,
612             setter: function(value) {
613                 Y.log("The lightbox attribute of M.core.dialogue has been deprecated since Moodle 2.7, " +
614                       "please use the modal attribute instead",
615                     'warn', 'moodle-core-notification-dialogue');
616                 this.set('modal', value);
617             }
618         },
620         /**
621          * Whether to display a close button on the dialogue.
622          *
623          * Note, we do not recommend hiding the close button as this has
624          * potential accessibility concerns.
625          *
626          * @attribute closeButton
627          * @type Boolean
628          * @default true
629          */
630         closeButton: {
631             validator: Y.Lang.isBoolean,
632             value: true
633         },
635         /**
636          * The title for the close button if one is to be shown.
637          *
638          * @attribute closeButtonTitle
639          * @type String
640          * @default 'Close'
641          */
642         closeButtonTitle: {
643             validator: Y.Lang.isString,
644             value: M.util.get_string('closebuttontitle', 'moodle')
645         },
647         /**
648          * Whether to display the dialogue centrally on the screen.
649          *
650          * @attribute center
651          * @type Boolean
652          * @default true
653          */
654         center: {
655             validator: Y.Lang.isBoolean,
656             value: true
657         },
659         /**
660          * Whether to make the dialogue movable around the page.
661          *
662          * @attribute draggable
663          * @type Boolean
664          * @default false
665          */
666         draggable: {
667             validator: Y.Lang.isBoolean,
668             value: false
669         },
671         /**
672          * Used to generate a unique id for the dialogue.
673          *
674          * @attribute COUNT
675          * @type String
676          * @default null
677          * @writeonce
678          */
679         COUNT: {
680             writeOnce: true,
681             valueFn: function() {
682                 return Y.stamp(this);
683             }
684         },
686         /**
687          * Used to disable the fullscreen resizing behaviour if required.
688          *
689          * @attribute responsive
690          * @type Boolean
691          * @default true
692          */
693         responsive: {
694             validator: Y.Lang.isBoolean,
695             value: true
696         },
698         /**
699          * The width that this dialogue should be resized to fullscreen.
700          *
701          * @attribute responsiveWidth
702          * @type Number
703          * @default 768
704          */
705         responsiveWidth: {
706             value: 768
707         },
709         /**
710          * Selector to a node that should recieve focus when this dialogue is shown.
711          *
712          * The default behaviour is to focus on the header.
713          *
714          * @attribute focusOnShowSelector
715          * @default null
716          * @type String
717          */
718         focusOnShowSelector: {
719             value: null
720         }
721     }
722 });
724 Y.Base.modifyAttrs(DIALOGUE, {
725     /**
726      * String with units, or number, representing the width of the Widget.
727      * If a number is provided, the default unit, defined by the Widgets
728      * DEF_UNIT, property is used.
729      *
730      * If a value of 'auto' is used, then an empty String is instead
731      * returned.
732      *
733      * @attribute width
734      * @default '400px'
735      * @type {String|Number}
736      */
737     width: {
738         value: '400px',
739         setter: function(value) {
740             if (value === 'auto') {
741                 return '';
742             }
743             return value;
744         }
745     },
747     /**
748      * Boolean indicating whether or not the Widget is visible.
749      *
750      * We override this from the default Widget attribute value.
751      *
752      * @attribute visible
753      * @default false
754      * @type Boolean
755      */
756     visible: {
757         value: false
758     },
760     /**
761      * A convenience Attribute, which can be used as a shortcut for the
762      * `align` Attribute.
763      *
764      * Note: We override this in Moodle such that it sets a value for the
765      * `center` attribute if set. The `centered` will always return false.
766      *
767      * @attribute centered
768      * @type Boolean|Node
769      * @default false
770      */
771     centered: {
772         setter: function(value) {
773             if (value) {
774                 this.set('center', true);
775             }
776             return false;
777         }
778     },
780     /**
781      * Boolean determining whether to render the widget during initialisation.
782      *
783      * We override this to change the default from false to true for the dialogue.
784      * We then proceed to early render the dialogue during our initialisation rather than waiting
785      * for YUI to render it after that.
786      *
787      * @attribute render
788      * @type Boolean
789      * @default true
790      */
791     render: {
792         value: true,
793         writeOnce: true
794     },
796     /**
797      * Any additional classes to add to the boundingBox.
798      *
799      * @attribute extraClasses
800      * @type Array
801      * @default []
802      */
803     extraClasses: {
804         value: []
805     },
807     /**
808      * Identifier for the widget.
809      *
810      * @attribute id
811      * @type String
812      * @default a product of guid().
813      * @writeOnce
814      */
815     id: {
816         writeOnce: true,
817         valueFn: function() {
818             var id = 'moodle-dialogue-' + Y.stamp(this);
819             return id;
820         }
821     },
823     /**
824      * Collection containing the widget's buttons.
825      *
826      * @attribute buttons
827      * @type Object
828      * @default {}
829      */
830     buttons: {
831         getter: Y.WidgetButtons.prototype._getButtons,
832         setter: Y.WidgetButtons.prototype._setButtons,
833         valueFn: function() {
834             if (this.get('closeButton') === false) {
835                 return null;
836             } else {
837                 return [
838                     {
839                         section: Y.WidgetStdMod.HEADER,
840                         classNames: 'closebutton',
841                         action: function() {
842                             this.hide();
843                         }
844                     }
845                 ];
846             }
847         }
848     }
849 });
851 Y.Base.mix(DIALOGUE, [Y.M.core.WidgetFocusAfterHide]);
853 M.core.dialogue = DIALOGUE;
854 /**
855  * A dialogue type designed to display informative messages to users.
856  *
857  * @module moodle-core-notification
858  */
860 /**
861  * Extends core Dialogue to provide a type of dialogue which can be used
862  * for informative message which are modal, and centered.
863  *
864  * @param {Object} config Object literal specifying the dialogue configuration properties.
865  * @constructor
866  * @class M.core.notification.info
867  * @extends M.core.dialogue
868  */
869 var INFO = function() {
870     INFO.superclass.constructor.apply(this, arguments);
871 };
873 Y.extend(INFO, M.core.dialogue, {
874     initializer: function() {
875         this.show();
876     }
877 }, {
878     NAME: 'Moodle information dialogue',
879     CSS_PREFIX: DIALOGUE_PREFIX
880 });
882 Y.Base.modifyAttrs(INFO, {
883    /**
884     * Whether the widget should be modal or not.
885     *
886     * We override this to change the default from false to true for a subset of dialogues.
887     *
888     * @attribute modal
889     * @type Boolean
890     * @default true
891     */
892     modal: {
893         validator: Y.Lang.isBoolean,
894         value: true
895     }
896 });
898 M.core.notification = M.core.notification || {};
899 M.core.notification.info = INFO;
902 }, '@VERSION@', {
903     "requires": [
904         "base",
905         "node",
906         "panel",
907         "escape",
908         "event-key",
909         "dd-plugin",
910         "moodle-core-widget-focusafterclose",
911         "moodle-core-lockscroll"
912     ]
913 });