weekly release 2.6dev
[moodle.git] / lib / yui / build / moodle-core-notification-dialogue / moodle-core-notification-dialogue-debug.js
CommitLineData
78686995
AN
1YUI.add('moodle-core-notification-dialogue', function (Y, NAME) {
2
3var DIALOGUE_PREFIX,
4 BASE,
5 COUNT,
6 CONFIRMYES,
7 CONFIRMNO,
8 TITLE,
9 QUESTION,
10 CSS;
11
12DIALOGUE_PREFIX = 'moodle-dialogue',
13BASE = 'notificationBase',
14COUNT = 0,
15CONFIRMYES = 'yesLabel',
16CONFIRMNO = 'noLabel',
17TITLE = 'title',
18QUESTION = 'question',
19CSS = {
20 BASE : 'moodle-dialogue-base',
21 WRAP : 'moodle-dialogue-wrap',
22 HEADER : 'moodle-dialogue-hd',
23 BODY : 'moodle-dialogue-bd',
24 CONTENT : 'moodle-dialogue-content',
25 FOOTER : 'moodle-dialogue-ft',
26 HIDDEN : 'hidden',
27 LIGHTBOX : 'moodle-dialogue-lightbox'
28};
29
30// Set up the namespace once.
31M.core = M.core || {};
32/**
33 * The generic dialogue class for use in Moodle.
34 *
35 * @module moodle-core-notification
36 * @submodule moodle-core-notification-dialogue
37 */
38
39var DIALOGUE_NAME = 'Moodle dialogue',
bf7c86cf 40 DIALOGUE,
f2b235cb
SH
41 DIALOGUE_FULLSCREEN_CLASS = DIALOGUE_PREFIX + '-fullscreen',
42 DIALOGUE_HIDDEN_CLASS = DIALOGUE_PREFIX + '-hidden',
f2b235cb 43 DIALOGUE_SELECTOR =' [role=dialog]',
2a808cef 44 MENUBAR_SELECTOR = '[role=menubar]';
78686995
AN
45
46/**
47 * A re-usable dialogue box with Moodle classes applied.
48 *
49 * @param {Object} config Object literal specifying the dialogue configuration properties.
50 * @constructor
51 * @class M.core.dialogue
52 * @extends Y.Panel
53 */
54DIALOGUE = function(config) {
55 COUNT++;
bf7c86cf 56 var id = 'moodle-dialogue-'+COUNT;
78686995
AN
57 config.notificationBase =
58 Y.Node.create('<div class="'+CSS.BASE+'">')
59 .append(Y.Node.create('<div id="'+id+'" role="dialog" aria-labelledby="'+id+'-header-text" class="'+CSS.WRAP+'"></div>')
baffb422 60 .append(Y.Node.create('<div id="'+id+'-header-text" class="'+CSS.HEADER+' yui3-widget-hd"></div>'))
78686995
AN
61 .append(Y.Node.create('<div class="'+CSS.BODY+' yui3-widget-bd"></div>'))
62 .append(Y.Node.create('<div class="'+CSS.FOOTER+' yui3-widget-ft"></div>')));
63 Y.one(document.body).append(config.notificationBase);
b59f2e3b
SH
64
65 if (config.additionalBaseClass) {
66 config.notificationBase.addClass(config.additionalBaseClass);
67 }
68
78686995
AN
69 config.srcNode = '#'+id;
70 config.width = config.width || '400px';
71 config.visible = config.visible || false;
4fd8adab 72 config.center = config.centered && true;
78686995
AN
73 config.centered = false;
74 config.COUNT = COUNT;
75
b59f2e3b
SH
76 if (config.width === 'auto') {
77 delete config.width;
78 }
79
78686995
AN
80 // lightbox param to keep the stable versions API.
81 if (config.lightbox !== false) {
82 config.modal = true;
83 }
84 delete config.lightbox;
85
86 // closeButton param to keep the stable versions API.
87 if (config.closeButton === false) {
88 config.buttons = null;
89 } else {
90 config.buttons = [
91 {
92 section: Y.WidgetStdMod.HEADER,
93 classNames: 'closebutton',
94 action: function () {
95 this.hide();
96 }
97 }
98 ];
99 }
100 DIALOGUE.superclass.constructor.apply(this, [config]);
101
102 if (config.closeButton !== false) {
103 // The buttons constructor does not allow custom attributes
104 this.get('buttons').header[0].setAttribute('title', this.get('closeButtonTitle'));
105 }
106};
107Y.extend(DIALOGUE, Y.Panel, {
d61c96b6
DW
108 // Window resize event listener.
109 _resizeevent : null,
110 // Orientation change event listener.
111 _orientationevent : null,
112
bf7c86cf
DW
113 /**
114 * Initialise the dialogue.
115 *
116 * @method initializer
117 * @return void
118 */
119 initializer : function(config) {
120 var bb;
121
78686995 122 this.render();
ce5867a1 123 this.makeResponsive();
bf7c86cf 124 this.after('visibleChange', this.visibilityChanged, this);
dd66b6ab
DW
125 if (config.center) {
126 this.centerDialogue();
127 }
78686995
AN
128 this.set('COUNT', COUNT);
129
130 // Workaround upstream YUI bug http://yuilibrary.com/projects/yui3/ticket/2532507
131 // and allow setting of z-index in theme.
d61c96b6 132 bb = this.get('boundingBox');
bf7c86cf
DW
133
134 if (config.extraClasses) {
135 Y.Array.each(config.extraClasses, bb.addClass, bb);
136 }
137 if (config.visible) {
138 this.applyZIndex();
139 }
ce5867a1
DW
140 // We must show - after the dialogue has been positioned,
141 // either by centerDialogue or makeResonsive. This is because the show() will trigger
142 // a focus on the dialogue, which will scroll the page. If the dialogue has not
143 // been positioned it will scroll back to the top of the page.
144 if (config.visible) {
145 this.show();
146 }
bf7c86cf
DW
147 },
148
149 /**
150 * Either set the zindex to the supplied value, or set it to one more than the highest existing
151 * dialog in the page.
152 *
153 * @method visibilityChanged
154 * @return void
155 */
156 applyZIndex : function() {
157 var highestzindex = 0,
f2b235cb
SH
158 bb = this.get('boundingBox'),
159 zindex = this.get('zIndex');
160 if (zindex) {
bf7c86cf 161 // The zindex was specified so we should use that.
f2b235cb 162 bb.setStyle('zIndex', zindex);
bf7c86cf 163 } else {
f2b235cb
SH
164 // Determine the correct zindex by looking at all existing dialogs and menubars in the page.
165 Y.all(DIALOGUE_SELECTOR+', '+MENUBAR_SELECTOR).each(function (node) {
166 var zindex = this.findZIndex(node);
167 if (zindex > highestzindex) {
168 highestzindex = zindex;
bf7c86cf 169 }
f2b235cb 170 }, this);
bf7c86cf
DW
171 // Only set the zindex if we found a wrapper.
172 if (highestzindex > 0) {
f2b235cb 173 bb.setStyle('zIndex', (highestzindex + 1).toString());
bf7c86cf
DW
174 }
175 }
176 },
177
f2b235cb
SH
178 /**
179 * Finds the zIndex of the given node or its parent.
180 *
181 * @method findZIndex
182 * @param Node node
183 * @returns int Return either the zIndex of 0 if one was not found.
184 */
185 findZIndex : function(node) {
186 // In most cases the zindex is set on the parent of the dialog.
187 var zindex = node.getStyle('zIndex') || node.ancestor().getStyle('zIndex');
188 if (zindex) {
189 return parseInt(zindex, 10);
190 }
191 return 0;
192 },
193
bf7c86cf
DW
194 /**
195 * Event listener for the visibility changed event.
196 *
197 * @method visibilityChanged
198 * @return void
199 */
78686995
AN
200 visibilityChanged : function(e) {
201 var titlebar;
202 if (e.attrName === 'visible') {
203 this.get('maskNode').addClass(CSS.LIGHTBOX);
d61c96b6
DW
204 if (e.prevVal && !e.newVal) {
205 if (this._resizeevent) {
206 this._resizeevent.detach();
207 this._resizeevent = null;
208 }
209 if (this._orientationevent) {
210 this._orientationevent.detach();
211 this._orientationevent = null;
212 }
213 }
bf7c86cf
DW
214 if (!e.prevVal && e.newVal) {
215 // This needs to be done each time the dialog is shown as new dialogs may have been opened.
216 this.applyZIndex();
217 // This needs to be done each time the dialog is shown as the window may have been resized.
218 this.makeResponsive();
219 if (!this.shouldResizeFullscreen()) {
220 if (this.get('draggable')) {
221 titlebar = '#' + this.get('id') + ' .' + CSS.HEADER;
222 this.plug(Y.Plugin.Drag, {handles : [titlebar]});
223 Y.one(titlebar).setStyle('cursor', 'move');
224 }
225 }
226 }
78686995
AN
227 if (this.get('center') && !e.prevVal && e.newVal) {
228 this.centerDialogue();
229 }
78686995
AN
230 }
231 },
bf7c86cf
DW
232 /**
233 * If the responsive attribute is set on the dialog, and the window size is
234 * smaller than the responsive width - make the dialog fullscreen.
235 *
236 * @method makeResponsive
237 * @return void
238 */
239 makeResponsive : function() {
78686995 240 var bb = this.get('boundingBox'),
bf7c86cf
DW
241 content;
242
243 if (this.shouldResizeFullscreen()) {
d61c96b6
DW
244 // Make this dialogue fullscreen on a small screen.
245 // Disable the page scrollbars.
bf7c86cf 246
d61c96b6
DW
247 // Size and position the fullscreen dialog.
248
2a808cef
DW
249 bb.addClass(DIALOGUE_FULLSCREEN_CLASS);
250 bb.setStyles({'left' : null,
251 'top' : null,
252 'width' : null,
253 'height' : null,
254 'right' : null,
255 'bottom' : null});
d61c96b6
DW
256
257 content = Y.one('#' + this.get('id') + ' .' + CSS.BODY);
258 content.setStyle('overflow', 'auto');
d61c96b6
DW
259 } else {
260 if (this.get('responsive')) {
261 // We must reset any of the fullscreen changes.
2a808cef
DW
262 bb.removeClass(DIALOGUE_FULLSCREEN_CLASS)
263 .setStyles({'width' : this.get('width'),
bf7c86cf 264 'height' : this.get('height')});
d61c96b6
DW
265 content = Y.one('#' + this.get('id') + ' .' + CSS.BODY);
266 content.setStyle('overflow', 'inherit');
267
d61c96b6 268 }
d61c96b6 269 }
bf7c86cf
DW
270 },
271 /**
272 * Center the dialog on the screen.
273 *
274 * @method centerDialogue
275 * @return void
276 */
277 centerDialogue : function() {
278 var bb = this.get('boundingBox'),
279 hidden = bb.hasClass(DIALOGUE_HIDDEN_CLASS),
280 x,
281 y;
282
283 // Don't adjust the position if we are in full screen mode.
284 if (this.shouldResizeFullscreen()) {
285 return;
286 }
287 if (hidden) {
288 bb.setStyle('top', '-1000px').removeClass(DIALOGUE_HIDDEN_CLASS);
289 }
290 x = Math.max(Math.round((bb.get('winWidth') - bb.get('offsetWidth'))/2), 15);
291 y = Math.max(Math.round((bb.get('winHeight') - bb.get('offsetHeight'))/2), 15) + Y.one(window).get('scrollTop');
292 bb.setStyles({ 'left' : x, 'top' : y});
78686995
AN
293
294 if (hidden) {
bf7c86cf 295 bb.addClass(DIALOGUE_HIDDEN_CLASS);
78686995 296 }
d61c96b6 297 },
bf7c86cf
DW
298 /**
299 * Return if this dialogue should be fullscreen or not.
300 * Responsive attribute must be true and we should not be in an iframe and the screen width should
301 * be less than the responsive width.
302 *
303 * @method shouldResizeFullscreen
304 * @return Boolean
305 */
306 shouldResizeFullscreen : function() {
307 return (window === window.parent) && this.get('responsive') &&
308 Math.floor(Y.one(document.body).get('winWidth')) < this.get('responsiveWidth');
2eaaae00
JF
309 },
310
311 /**
312 * Override the show method to set keyboard focus on the dialogue.
313 *
314 * @method show
315 * @return void
316 */
317 show : function() {
318 var result = null,
319 header = this.headerNode,
320 content = this.bodyNode;
321
322 result = DIALOGUE.superclass.show.call(this);
323 if (header && header !== '') {
324 header.focus();
325 } else if (content && content !== '') {
326 content.focus();
327 }
328 return result;
78686995
AN
329 }
330}, {
331 NAME : DIALOGUE_NAME,
332 CSS_PREFIX : DIALOGUE_PREFIX,
333 ATTRS : {
334 notificationBase : {
335
336 },
337
338 /**
339 * Whether to display the dialogue modally and with a
340 * lightbox style.
341 *
342 * @attribute lightbox
343 * @type Boolean
344 * @default true
345 */
346 lightbox : {
347 validator : Y.Lang.isBoolean,
348 value : true
349 },
350
351 /**
352 * Whether to display a close button on the dialogue.
353 *
354 * Note, we do not recommend hiding the close button as this has
355 * potential accessibility concerns.
356 *
357 * @attribute closeButton
358 * @type Boolean
359 * @default true
360 */
361 closeButton : {
362 validator : Y.Lang.isBoolean,
363 value : true
364 },
365
366 /**
367 * The title for the close button if one is to be shown.
368 *
369 * @attribute closeButtonTitle
370 * @type String
371 * @default 'Close'
372 */
373 closeButtonTitle : {
374 validator : Y.Lang.isString,
375 value : 'Close'
376 },
377
378 /**
379 * Whether to display the dialogue centrally on the screen.
380 *
381 * @attribute center
382 * @type Boolean
383 * @default true
384 */
385 center : {
386 validator : Y.Lang.isBoolean,
387 value : true
388 },
389
390 /**
391 * Whether to make the dialogue movable around the page.
392 *
393 * @attribute draggable
394 * @type Boolean
395 * @default false
396 */
397 draggable : {
398 validator : Y.Lang.isBoolean,
399 value : false
400 },
bf7c86cf
DW
401
402 /**
403 * Used to generate a unique id for the dialogue.
404 *
405 * @attribute COUNT
406 * @type Integer
407 * @default 0
408 */
78686995
AN
409 COUNT: {
410 value: 0
d61c96b6 411 },
bf7c86cf
DW
412
413 /**
414 * Used to disable the fullscreen resizing behaviour if required.
415 *
416 * @attribute responsive
417 * @type Boolean
418 * @default true
419 */
d61c96b6
DW
420 responsive : {
421 validator : Y.Lang.isBoolean,
422 value : true
423 },
bf7c86cf
DW
424
425 /**
426 * The width that this dialogue should be resized to fullscreen.
427 *
428 * @attribute responsiveWidth
429 * @type Integer
430 * @default 768
431 */
d61c96b6
DW
432 responsiveWidth : {
433 value : 768
78686995
AN
434 }
435 }
436});
437
438M.core.dialogue = DIALOGUE;
439
440
441}, '@VERSION@', {"requires": ["base", "node", "panel", "event-key", "dd-plugin"]});