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