MDL-37566 AJAX Fix issues with minimum height and centring
[moodle.git] / lib / yui / chooserdialogue / chooserdialogue.js
CommitLineData
01e0e704
ARN
1YUI.add('moodle-core-chooserdialogue', function(Y) {
2
3 var CHOOSERDIALOGUE = function() {
4 CHOOSERDIALOGUE.superclass.constructor.apply(this, arguments);
a6d96f27 5 };
01e0e704
ARN
6
7 Y.extend(CHOOSERDIALOGUE, Y.Base, {
7b67e0c5
DM
8 // The panel widget
9 panel: null,
01e0e704
ARN
10
11 // The submit button - we disable this until an element is set
12 submitbutton : null,
13
14 // The chooserdialogue container
15 container : null,
16
d9bd472b
ARN
17 // Any event listeners we may need to cancel later
18 listenevents : [],
19
b95b0508
ARN
20 // The initial overflow setting
21 initialoverflow : '',
22
21b678db
ARN
23 bodycontent : null,
24 headercontent : null,
25 instanceconfig : null,
26
01e0e704 27 setup_chooser_dialogue : function(bodycontent, headercontent, config) {
21b678db
ARN
28 this.bodycontent = bodycontent;
29 this.headercontent = headercontent;
30 this.instanceconfig = config;
31 },
32
33 prepare_chooser : function () {
f96ec5ad 34 if (this.panel) {
21b678db
ARN
35 return;
36 }
37
01e0e704 38 // Set Default options
a6d96f27
ARN
39 var paramkey,
40 params = {
21b678db
ARN
41 bodyContent : this.bodycontent.get('innerHTML'),
42 headerContent : this.headercontent.get('innerHTML'),
a0bef1fb 43 width : '540px',
984ec46e 44 draggable : true,
01e0e704
ARN
45 visible : false, // Hide by default
46 zindex : 100, // Display in front of other items
4bcbdb3e
DM
47 lightbox : true, // This dialogue should be modal
48 shim : true,
49 closeButtonTitle : this.get('closeButtonTitle')
21b678db 50 };
01e0e704
ARN
51
52 // Override with additional options
21b678db
ARN
53 for (paramkey in this.instanceconfig) {
54 params[paramkey] = this.instanceconfig[paramkey];
01e0e704
ARN
55 }
56
7b67e0c5
DM
57 // Create the panel
58 this.panel = new M.core.dialogue(params);
01e0e704
ARN
59
60 // Remove the template for the chooser
21b678db
ARN
61 this.bodycontent.remove();
62 this.headercontent.remove();
01e0e704 63
7b67e0c5
DM
64 // Hide and then render the panel
65 this.panel.hide();
66 this.panel.render();
01e0e704
ARN
67
68 // Set useful links
7b67e0c5 69 this.container = this.panel.get('boundingBox').one('.choosercontainer');
01e0e704 70 this.options = this.container.all('.option input[type=radio]');
255dd8d1
ARN
71
72 // Add the chooserdialogue class to the container for styling
7b67e0c5 73 this.panel.get('boundingBox').addClass('chooserdialogue');
01e0e704 74 },
d9bd472b 75
01e0e704
ARN
76 /**
77 * Display the module chooser
78 *
79 * @param e Event Triggering Event
80 * @return void
81 */
82 display_chooser : function (e) {
a6d96f27 83 var bb, dialogue, thisevent;
21b678db
ARN
84 this.prepare_chooser();
85
01e0e704
ARN
86 // Stop the default event actions before we proceed
87 e.preventDefault();
88
a6d96f27
ARN
89 bb = this.panel.get('boundingBox');
90 dialogue = this.container.one('.alloptions');
01e0e704 91
b95b0508
ARN
92 // Get the overflow setting when the chooser was opened - we
93 // may need this later
94 if (Y.UA.ie > 0) {
95 this.initialoverflow = Y.one('html').getStyle('overflow');
96 } else {
97 this.initialoverflow = Y.one('body').getStyle('overflow');
98 }
99
baca9096 100 // This will detect a change in orientation and retrigger centering
a6d96f27 101 thisevent = Y.one('document').on('orientationchange', function() {
baca9096
ARN
102 this.center_dialogue(dialogue);
103 }, this);
104 this.listenevents.push(thisevent);
105
106 // Detect window resizes (most browsers)
a6d96f27 107 thisevent = Y.one('window').on('resize', function() {
baca9096
ARN
108 this.center_dialogue(dialogue);
109 }, this);
110 this.listenevents.push(thisevent);
111
01e0e704 112 // These will trigger a check_options call to display the correct help
d9bd472b
ARN
113 thisevent = this.container.on('click', this.check_options, this);
114 this.listenevents.push(thisevent);
115 thisevent = this.container.on('key_up', this.check_options, this);
116 this.listenevents.push(thisevent);
117 thisevent = this.container.on('dblclick', function(e) {
01e0e704
ARN
118 if (e.target.ancestor('div.option')) {
119 this.check_options();
d9bd472b
ARN
120
121 // Prevent duplicate submissions
122 this.submitbutton.setAttribute('disabled', 'disabled');
123 this.options.setAttribute('disabled', 'disabled');
124 this.cancel_listenevents();
125
01e0e704
ARN
126 this.container.one('form').submit();
127 }
128 }, this);
d9bd472b
ARN
129 this.listenevents.push(thisevent);
130
a6d96f27 131 this.container.one('form').on('submit', function() {
d9bd472b
ARN
132 // Prevent duplicate submissions on submit
133 this.submitbutton.setAttribute('disabled', 'disabled');
134 this.options.setAttribute('disabled', 'disabled');
135 this.cancel_listenevents();
136 }, this);
01e0e704
ARN
137
138 // Hook onto the cancel button to hide the form
0a2fb910 139 thisevent = this.container.one('.addcancel').on('click', this.cancel_popup, this);
605f92ca 140 this.listenevents.push(thisevent);
7b67e0c5
DM
141
142 // Hide will be managed by cancel_popup after restoring the body overflow
143 thisevent = bb.one('button.closebutton').on('click', this.cancel_popup, this);
605f92ca 144 this.listenevents.push(thisevent);
01e0e704
ARN
145
146 // Grab global keyup events and handle them
8a185b9e 147 thisevent = Y.one('document').on('keydown', this.handle_key_press, this);
605f92ca 148 this.listenevents.push(thisevent);
01e0e704
ARN
149
150 // Add references to various elements we adjust
0a2fb910
ARN
151 this.jumplink = this.container.one('.jump');
152 this.submitbutton = this.container.one('.submitbutton');
01e0e704
ARN
153
154 // Disable the submit element until the user makes a selection
155 this.submitbutton.set('disabled', 'true');
156
d9bd472b
ARN
157 // Ensure that the options are shown
158 this.options.removeAttribute('disabled');
159
7b67e0c5
DM
160 // Display the panel
161 this.panel.show();
01e0e704 162
af75421c
ARN
163 // Re-centre the dialogue after we've shown it.
164 this.center_dialogue(dialogue);
165
01e0e704
ARN
166 // Finally, focus the first radio element - this enables form selection via the keyboard
167 this.container.one('.option input[type=radio]').focus();
168
169 // Trigger check_options to set the initial jumpurl
170 this.check_options();
171 },
d9bd472b
ARN
172
173 /**
174 * Cancel any listen events in the listenevents queue
175 *
176 * Several locations add event handlers which should only be called before the form is submitted. This provides
177 * a way of cancelling those events.
178 *
179 * @return void
180 */
181 cancel_listenevents : function () {
182 // Detach all listen events to prevent duplicate triggers
183 var thisevent;
a6d96f27
ARN
184 while (this.listenevents.length) {
185 thisevent = this.listenevents.shift();
d9bd472b
ARN
186 thisevent.detach();
187 }
188 },
189
01e0e704
ARN
190 /**
191 * Calculate the optimum height of the chooser dialogue
192 *
193 * This tries to set a sensible maximum and minimum to ensure that some options are always shown, and preferably
d9bd472b 194 * all, whilst fitting the box within the current viewport.
01e0e704
ARN
195 *
196 * @param dialogue Y.Node The dialogue
197 * @return void
198 */
af75421c 199 center_dialogue : function(dialogue) {
a6d96f27
ARN
200 var bb = this.panel.get('boundingBox'),
201 winheight = bb.get('winHeight'),
202 winwidth = bb.get('winWidth'),
203 offsettop = 0,
204 newheight, totalheight, dialoguetop, dialoguewidth, dialogueleft;
01e0e704
ARN
205
206 // Try and set a sensible max-height -- this must be done before setting the top
207 // Set a default height of 640px
a6d96f27 208 newheight = this.get('maxheight');
01e0e704
ARN
209 if (winheight <= newheight) {
210 // Deal with smaller window sizes
211 if (winheight <= this.get('minheight')) {
212 newheight = this.get('minheight');
213 } else {
214 newheight = winheight;
215 }
216 }
217
218 // Set a fixed position if the window is large enough
219 if (newheight > this.get('minheight')) {
af75421c 220 bb.setStyle('position', 'fixed');
b95b0508
ARN
221 // Disable the page scrollbars
222 if (Y.UA.ie > 0) {
223 Y.one('html').setStyle('overflow', 'hidden');
224 } else {
225 Y.one('body').setStyle('overflow', 'hidden');
226 }
01e0e704 227 } else {
af75421c
ARN
228 bb.setStyle('position', 'absolute');
229 offsettop = Y.one('window').get('scrollTop');
b95b0508
ARN
230 // Ensure that the page scrollbars are enabled
231 if (Y.UA.ie > 0) {
232 Y.one('html').setStyle('overflow', this.initialoverflow);
233 } else {
234 Y.one('body').setStyle('overflow', this.initialoverflow);
235 }
01e0e704
ARN
236 }
237
238 // Take off 15px top and bottom for borders, plus 40px each for the title and button area before setting the
239 // new max-height
a6d96f27 240 totalheight = newheight;
01e0e704 241 newheight = newheight - (15 + 15 + 40 + 40);
c1b75ece 242 dialogue.setStyle('maxHeight', newheight + 'px');
01e0e704 243
c5f60942
ARN
244 dialogueheight = bb.getStyle('height');
245 if (dialogueheight.match(/.*px$/)) {
246 dialogueheight = dialogueheight.replace(/px$/, '');
247 } else {
248 dialogueheight = totalheight;
249 }
250
251 if (dialogueheight < this.get('baseheight')) {
252 dialogueheight = this.get('baseheight');
253 dialogue.setStyle('height', dialogueheight + 'px');
254 }
255
256
01e0e704 257 // Re-calculate the location now that we've changed the size
c5f60942 258 dialoguetop = Math.max(12, ((winheight - dialogueheight) / 2)) + offsettop;
af75421c
ARN
259
260 // We need to set the height for the yui3-widget - can't work
261 // out what we're setting at present -- shoud be the boudingBox
262 bb.setStyle('top', dialoguetop + 'px');
7db27680
ARN
263
264 // Calculate the left location of the chooser
265 // We don't set a minimum width in the same way as we do height as the width would be far lower than the
266 // optimal width for moodle anyway.
a6d96f27
ARN
267 dialoguewidth = bb.get('offsetWidth');
268 dialogueleft = (winwidth - dialoguewidth) / 2;
7db27680 269 bb.setStyle('left', dialogueleft + 'px');
01e0e704 270 },
d9bd472b 271
01e0e704 272 handle_key_press : function(e) {
a6d96f27 273 if (e.keyCode === 27) {
01e0e704
ARN
274 this.cancel_popup(e);
275 }
276 },
d9bd472b 277
01e0e704
ARN
278 cancel_popup : function (e) {
279 // Prevent normal form submission before hiding
280 e.preventDefault();
281 this.hide();
282 },
d9bd472b 283
01e0e704 284 hide : function() {
dfe15e0e
ARN
285 // Cancel all listen events
286 this.cancel_listenevents();
b95b0508
ARN
287
288 // Re-enable the page scrollbars
289 if (Y.UA.ie > 0) {
290 Y.one('html').setStyle('overflow', this.initialoverflow);
291 } else {
292 Y.one('body').setStyle('overflow', this.initialoverflow);
293 }
294
01e0e704 295 this.container.detachAll();
7b67e0c5 296 this.panel.hide();
01e0e704 297 },
d9bd472b 298
a6d96f27 299 check_options : function() {
01e0e704
ARN
300 // Check which options are set, and change the parent class
301 // to show/hide help as required
302 this.options.each(function(thisoption) {
303 var optiondiv = thisoption.get('parentNode').get('parentNode');
304 if (thisoption.get('checked')) {
305 optiondiv.addClass('selected');
306
307 // Trigger any events for this option
308 this.option_selected(thisoption);
309
310 // Ensure that the form may be submitted
311 this.submitbutton.removeAttribute('disabled');
312
313 // Ensure that the radio remains focus so that keyboard navigation is still possible
314 thisoption.focus();
315 } else {
316 optiondiv.removeClass('selected');
317 }
318 }, this);
319 },
d9bd472b 320
a6d96f27 321 option_selected : function() {
01e0e704
ARN
322 }
323 },
324 {
325 NAME : 'moodle-core-chooserdialogue',
326 ATTRS : {
327 minheight : {
328 value : 300
329 },
c5f60942
ARN
330 baseheight: {
331 value : 400
332 },
01e0e704
ARN
333 maxheight : {
334 value : 660
4bcbdb3e
DM
335 },
336 closeButtonTitle : {
337 validator : Y.Lang.isString,
338 value : 'Close'
01e0e704
ARN
339 }
340 }
341 });
342 M.core = M.core || {};
343 M.core.chooserdialogue = CHOOSERDIALOGUE;
344},
345'@VERSION@', {
7b67e0c5 346 requires:['base', 'panel', 'moodle-core-notification']
01e0e704
ARN
347}
348);