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