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