on-demand release 4.0dev+
[moodle.git] / lib / yui / build / moodle-core-notification-dialogue / moodle-core-notification-dialogue.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         var bb = this.get('boundingBox');
479         bb.delegate('key', function(e) {
480             var target = e.target;
481             var direction = 'forward';
482             if (e.shiftKey) {
483                 direction = 'backward';
484             }
485             if (this.trapFocus(target, direction)) {
486                 e.preventDefault();
487             }
488         }, 'down:9', CAN_RECEIVE_FOCUS_SELECTOR, this);
489     },
491     /**
492      * Trap the tab focus within the open modal.
493      *
494      * @method trapFocus
495      * @param {string} target the element target
496      * @param {string} direction tab key for forward and tab+shift for backward
497      * @return {Boolean} The result of the focus action.
498      */
499     trapFocus: function(target, direction) {
500         var bb = this.get('boundingBox'),
501             firstitem = bb.one(CAN_RECEIVE_FOCUS_SELECTOR),
502             lastitem = bb.all(CAN_RECEIVE_FOCUS_SELECTOR).pop();
504         if (target === lastitem && direction === 'forward') { // Tab key.
505             return firstitem.focus();
506         } else if (target === firstitem && direction === 'backward') {  // Tab+shift key.
507             return lastitem.focus();
508         }
509     },
511     /**
512      * Sets the appropriate aria attributes on this dialogue and the other
513      * elements in the DOM to ensure that screen readers are able to navigate
514      * the dialogue popup correctly.
515      *
516      * @method setAccessibilityVisible
517      */
518     setAccessibilityVisible: function() {
519         // Get the element that contains this dialogue because we need it
520         // to filter out from the document.body child elements.
521         var container = this.get(BASE);
523         // We need to get a list containing each sibling element and the shallowest
524         // non-ancestral nodes in the DOM. We can shortcut this a little by leveraging
525         // the fact that this dialogue is always appended to the document body therefore
526         // it's siblings are the shallowest non-ancestral nodes. If that changes then
527         // this code should also be updated.
528         Y.one(document.body).get('children').each(function(node) {
529             // Skip the element that contains us.
530             if (node !== container) {
531                 var hidden = node.get('aria-hidden');
532                 // If they are already hidden we can ignore them.
533                 if (hidden !== 'true') {
534                     // Save their current state.
535                     node.setData('previous-aria-hidden', hidden);
536                     this._hiddenSiblings.push(node);
538                     // Hide this node from screen readers.
539                     node.set('aria-hidden', 'true');
540                 }
541             }
542         }, this);
544         // Make us visible to screen readers.
545         container.set('aria-hidden', 'false');
546     },
548     /**
549      * Restores the aria visibility on the DOM elements changed when displaying
550      * the dialogue popup and makes the dialogue aria hidden to allow screen
551      * readers to navigate the main page correctly when the dialogue is closed.
552      *
553      * @method setAccessibilityHidden
554      */
555     setAccessibilityHidden: function() {
556         var container = this.get(BASE);
557         container.set('aria-hidden', 'true');
559         // Restore the sibling nodes back to their original values.
560         Y.Array.each(this._hiddenSiblings, function(node) {
561             var previousValue = node.getData('previous-aria-hidden');
562             // If the element didn't previously have an aria-hidden attribute
563             // then we can just remove the one we set.
564             if (previousValue === null) {
565                 node.removeAttribute('aria-hidden');
566             } else {
567                 // Otherwise set it back to the old value (which will be false).
568                 node.set('aria-hidden', previousValue);
569             }
570         });
572         // Clear the cache. No longer need to store these.
573         this._hiddenSiblings = [];
574     }
575 }, {
576     NAME: DIALOGUE_NAME,
577     CSS_PREFIX: DIALOGUE_PREFIX,
578     ATTRS: {
579         /**
580          * Any additional classes to add to the base Node.
581          *
582          * @attribute additionalBaseClass
583          * @type String
584          * @default ''
585          */
586         additionalBaseClass: {
587             value: ''
588         },
590         /**
591          * The Notification base Node.
592          *
593          * @attribute notificationBase
594          * @type Node
595          */
596         notificationBase: {
598         },
600         /**
601          * Whether to display the dialogue modally and with a
602          * lightbox style.
603          *
604          * @attribute lightbox
605          * @type Boolean
606          * @default true
607          * @deprecated Since Moodle 2.7. Please use modal instead.
608          */
609         lightbox: {
610             lazyAdd: false,
611             setter: function(value) {
612                 this.set('modal', value);
613             }
614         },
616         /**
617          * Whether to display a close button on the dialogue.
618          *
619          * Note, we do not recommend hiding the close button as this has
620          * potential accessibility concerns.
621          *
622          * @attribute closeButton
623          * @type Boolean
624          * @default true
625          */
626         closeButton: {
627             validator: Y.Lang.isBoolean,
628             value: true
629         },
631         /**
632          * The title for the close button if one is to be shown.
633          *
634          * @attribute closeButtonTitle
635          * @type String
636          * @default 'Close'
637          */
638         closeButtonTitle: {
639             validator: Y.Lang.isString,
640             value: M.util.get_string('closebuttontitle', 'moodle')
641         },
643         /**
644          * Whether to display the dialogue centrally on the screen.
645          *
646          * @attribute center
647          * @type Boolean
648          * @default true
649          */
650         center: {
651             validator: Y.Lang.isBoolean,
652             value: true
653         },
655         /**
656          * Whether to make the dialogue movable around the page.
657          *
658          * @attribute draggable
659          * @type Boolean
660          * @default false
661          */
662         draggable: {
663             validator: Y.Lang.isBoolean,
664             value: false
665         },
667         /**
668          * Used to generate a unique id for the dialogue.
669          *
670          * @attribute COUNT
671          * @type String
672          * @default null
673          * @writeonce
674          */
675         COUNT: {
676             writeOnce: true,
677             valueFn: function() {
678                 return Y.stamp(this);
679             }
680         },
682         /**
683          * Used to disable the fullscreen resizing behaviour if required.
684          *
685          * @attribute responsive
686          * @type Boolean
687          * @default true
688          */
689         responsive: {
690             validator: Y.Lang.isBoolean,
691             value: true
692         },
694         /**
695          * The width that this dialogue should be resized to fullscreen.
696          *
697          * @attribute responsiveWidth
698          * @type Number
699          * @default 768
700          */
701         responsiveWidth: {
702             value: 768
703         },
705         /**
706          * Selector to a node that should recieve focus when this dialogue is shown.
707          *
708          * The default behaviour is to focus on the header.
709          *
710          * @attribute focusOnShowSelector
711          * @default null
712          * @type String
713          */
714         focusOnShowSelector: {
715             value: null
716         }
717     }
718 });
720 Y.Base.modifyAttrs(DIALOGUE, {
721     /**
722      * String with units, or number, representing the width of the Widget.
723      * If a number is provided, the default unit, defined by the Widgets
724      * DEF_UNIT, property is used.
725      *
726      * If a value of 'auto' is used, then an empty String is instead
727      * returned.
728      *
729      * @attribute width
730      * @default '400px'
731      * @type {String|Number}
732      */
733     width: {
734         value: '400px',
735         setter: function(value) {
736             if (value === 'auto') {
737                 return '';
738             }
739             return value;
740         }
741     },
743     /**
744      * Boolean indicating whether or not the Widget is visible.
745      *
746      * We override this from the default Widget attribute value.
747      *
748      * @attribute visible
749      * @default false
750      * @type Boolean
751      */
752     visible: {
753         value: false
754     },
756     /**
757      * A convenience Attribute, which can be used as a shortcut for the
758      * `align` Attribute.
759      *
760      * Note: We override this in Moodle such that it sets a value for the
761      * `center` attribute if set. The `centered` will always return false.
762      *
763      * @attribute centered
764      * @type Boolean|Node
765      * @default false
766      */
767     centered: {
768         setter: function(value) {
769             if (value) {
770                 this.set('center', true);
771             }
772             return false;
773         }
774     },
776     /**
777      * Boolean determining whether to render the widget during initialisation.
778      *
779      * We override this to change the default from false to true for the dialogue.
780      * We then proceed to early render the dialogue during our initialisation rather than waiting
781      * for YUI to render it after that.
782      *
783      * @attribute render
784      * @type Boolean
785      * @default true
786      */
787     render: {
788         value: true,
789         writeOnce: true
790     },
792     /**
793      * Any additional classes to add to the boundingBox.
794      *
795      * @attribute extraClasses
796      * @type Array
797      * @default []
798      */
799     extraClasses: {
800         value: []
801     },
803     /**
804      * Identifier for the widget.
805      *
806      * @attribute id
807      * @type String
808      * @default a product of guid().
809      * @writeOnce
810      */
811     id: {
812         writeOnce: true,
813         valueFn: function() {
814             var id = 'moodle-dialogue-' + Y.stamp(this);
815             return id;
816         }
817     },
819     /**
820      * Collection containing the widget's buttons.
821      *
822      * @attribute buttons
823      * @type Object
824      * @default {}
825      */
826     buttons: {
827         getter: Y.WidgetButtons.prototype._getButtons,
828         setter: Y.WidgetButtons.prototype._setButtons,
829         valueFn: function() {
830             if (this.get('closeButton') === false) {
831                 return null;
832             } else {
833                 return [
834                     {
835                         section: Y.WidgetStdMod.HEADER,
836                         classNames: 'closebutton',
837                         action: function() {
838                             this.hide();
839                         }
840                     }
841                 ];
842             }
843         }
844     }
845 });
847 Y.Base.mix(DIALOGUE, [Y.M.core.WidgetFocusAfterHide]);
849 M.core.dialogue = DIALOGUE;
850 /**
851  * A dialogue type designed to display informative messages to users.
852  *
853  * @module moodle-core-notification
854  */
856 /**
857  * Extends core Dialogue to provide a type of dialogue which can be used
858  * for informative message which are modal, and centered.
859  *
860  * @param {Object} config Object literal specifying the dialogue configuration properties.
861  * @constructor
862  * @class M.core.notification.info
863  * @extends M.core.dialogue
864  */
865 var INFO = function() {
866     INFO.superclass.constructor.apply(this, arguments);
867 };
869 Y.extend(INFO, M.core.dialogue, {
870     initializer: function() {
871         this.show();
872     }
873 }, {
874     NAME: 'Moodle information dialogue',
875     CSS_PREFIX: DIALOGUE_PREFIX
876 });
878 Y.Base.modifyAttrs(INFO, {
879    /**
880     * Whether the widget should be modal or not.
881     *
882     * We override this to change the default from false to true for a subset of dialogues.
883     *
884     * @attribute modal
885     * @type Boolean
886     * @default true
887     */
888     modal: {
889         validator: Y.Lang.isBoolean,
890         value: true
891     }
892 });
894 M.core.notification = M.core.notification || {};
895 M.core.notification.info = INFO;
898 }, '@VERSION@', {
899     "requires": [
900         "base",
901         "node",
902         "panel",
903         "escape",
904         "event-key",
905         "dd-plugin",
906         "moodle-core-widget-focusafterclose",
907         "moodle-core-lockscroll"
908     ]
909 });