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