897f0c5481b8b3ca14e7163037b8171602f6f226
[moodle.git] / lib / yui / chooserdialogue / chooserdialogue.js
1 YUI.add('moodle-core-chooserdialogue', function(Y) {
3     var CHOOSERDIALOGUE = function() {
4         CHOOSERDIALOGUE.superclass.constructor.apply(this, arguments);
5     };
7     Y.extend(CHOOSERDIALOGUE, Y.Base, {
8         // The panel widget
9         panel: null,
11         // The submit button - we disable this until an element is set
12         submitbutton : null,
14         // The chooserdialogue container
15         container : null,
17         // Any event listeners we may need to cancel later
18         listenevents : [],
20         // The initial overflow setting
21         initialoverflow : '',
23         bodycontent : null,
24         headercontent : null,
25         instanceconfig : null,
27         setup_chooser_dialogue : function(bodycontent, headercontent, config) {
28             this.bodycontent = bodycontent;
29             this.headercontent = headercontent;
30             this.instanceconfig = config;
31         },
33         prepare_chooser : function () {
34             if (this.panel) {
35                 return;
36             }
38             // Set Default options
39             var paramkey,
40                 params = {
41                 bodyContent : this.bodycontent.get('innerHTML'),
42                 headerContent : this.headercontent.get('innerHTML'),
43                 width : '540px',
44                 draggable : true,
45                 visible : false, // Hide by default
46                 zindex : 100, // Display in front of other items
47                 lightbox : true, // This dialogue should be modal
48                 shim : true,
49                 closeButtonTitle : this.get('closeButtonTitle')
50             };
52             // Override with additional options
53             for (paramkey in this.instanceconfig) {
54               params[paramkey] = this.instanceconfig[paramkey];
55             }
57             // Create the panel
58             this.panel = new M.core.dialogue(params);
60             // Remove the template for the chooser
61             this.bodycontent.remove();
62             this.headercontent.remove();
64             // Hide and then render the panel
65             this.panel.hide();
66             this.panel.render();
68             // Set useful links
69             this.container = this.panel.get('boundingBox').one('.choosercontainer');
70             this.options = this.container.all('.option input[type=radio]');
72             // Add the chooserdialogue class to the container for styling
73             this.panel.get('boundingBox').addClass('chooserdialogue');
74         },
76         /**
77          * Display the module chooser
78          *
79          * @param e Event Triggering Event
80          * @return void
81          */
82         display_chooser : function (e) {
83             var bb, dialogue, thisevent;
84             this.prepare_chooser();
86             // Stop the default event actions before we proceed
87             e.preventDefault();
89             bb = this.panel.get('boundingBox');
90             dialogue = this.container.one('.alloptions');
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             }
100             // This will detect a change in orientation and retrigger centering
101             thisevent = Y.one('document').on('orientationchange', function() {
102                 this.center_dialogue(dialogue);
103             }, this);
104             this.listenevents.push(thisevent);
106             // Detect window resizes (most browsers)
107             thisevent = Y.one('window').on('resize', function() {
108                 this.center_dialogue(dialogue);
109             }, this);
110             this.listenevents.push(thisevent);
112             // These will trigger a check_options call to display the correct help
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) {
118                 if (e.target.ancestor('div.option')) {
119                     this.check_options();
121                     // Prevent duplicate submissions
122                     this.submitbutton.setAttribute('disabled', 'disabled');
123                     this.options.setAttribute('disabled', 'disabled');
124                     this.cancel_listenevents();
126                     this.container.one('form').submit();
127                 }
128             }, this);
129             this.listenevents.push(thisevent);
131             this.container.one('form').on('submit', function() {
132                 // Prevent duplicate submissions on submit
133                 this.submitbutton.setAttribute('disabled', 'disabled');
134                 this.options.setAttribute('disabled', 'disabled');
135                 this.cancel_listenevents();
136             }, this);
138             // Hook onto the cancel button to hide the form
139             thisevent = this.container.one('.addcancel').on('click', this.cancel_popup, this);
140             this.listenevents.push(thisevent);
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);
144             this.listenevents.push(thisevent);
146             // Grab global keyup events and handle them
147             thisevent = Y.one('document').on('keydown', this.handle_key_press, this);
148             this.listenevents.push(thisevent);
150             // Add references to various elements we adjust
151             this.jumplink     = this.container.one('.jump');
152             this.submitbutton = this.container.one('.submitbutton');
154             // Disable the submit element until the user makes a selection
155             this.submitbutton.set('disabled', 'true');
157             // Ensure that the options are shown
158             this.options.removeAttribute('disabled');
160             // Display the panel
161             this.panel.show();
163             // Re-centre the dialogue after we've shown it.
164             this.center_dialogue(dialogue);
166             // Finally, focus the first radio element - this enables form selection via the keyboard
167             this.container.one('.option input[type=radio]').focus();
169             // Trigger check_options to set the initial jumpurl
170             this.check_options();
171         },
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;
184             while (this.listenevents.length) {
185                 thisevent = this.listenevents.shift();
186                 thisevent.detach();
187             }
188         },
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
194          * all, whilst fitting the box within the current viewport.
195          *
196          * @param dialogue Y.Node The dialogue
197          * @return void
198          */
199         center_dialogue : function(dialogue) {
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;
206             // Try and set a sensible max-height -- this must be done before setting the top
207             // Set a default height of 640px
208             newheight = this.get('maxheight');
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             }
218             // Set a fixed position if the window is large enough
219             if (newheight > this.get('minheight')) {
220                 bb.setStyle('position', 'fixed');
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                 }
227             } else {
228                 bb.setStyle('position', 'absolute');
229                 offsettop = Y.one('window').get('scrollTop');
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                 }
236             }
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
240             totalheight = newheight;
241             newheight = newheight - (15 + 15 + 40 + 40);
242             dialogue.setStyle('maxHeight', newheight + 'px');
244             // Re-calculate the location now that we've changed the size
245             dialoguetop = Math.max(12, ((winheight - totalheight) / 2)) + offsettop;
247             // We need to set the height for the yui3-widget - can't work
248             // out what we're setting at present -- shoud be the boudingBox
249             bb.setStyle('top', dialoguetop + 'px');
251             // Calculate the left location of the chooser
252             // We don't set a minimum width in the same way as we do height as the width would be far lower than the
253             // optimal width for moodle anyway.
254             dialoguewidth = bb.get('offsetWidth');
255             dialogueleft = (winwidth - dialoguewidth) / 2;
256             bb.setStyle('left', dialogueleft + 'px');
257         },
259         handle_key_press : function(e) {
260             if (e.keyCode === 27) {
261                 this.cancel_popup(e);
262             }
263         },
265         cancel_popup : function (e) {
266             // Prevent normal form submission before hiding
267             e.preventDefault();
268             this.hide();
269         },
271         hide : function() {
272             // Cancel all listen events
273             this.cancel_listenevents();
275             // Re-enable the page scrollbars
276             if (Y.UA.ie > 0) {
277                 Y.one('html').setStyle('overflow', this.initialoverflow);
278             } else {
279                 Y.one('body').setStyle('overflow', this.initialoverflow);
280             }
282             this.container.detachAll();
283             this.panel.hide();
284         },
286         check_options : function() {
287             // Check which options are set, and change the parent class
288             // to show/hide help as required
289             this.options.each(function(thisoption) {
290                 var optiondiv = thisoption.get('parentNode').get('parentNode');
291                 if (thisoption.get('checked')) {
292                     optiondiv.addClass('selected');
294                     // Trigger any events for this option
295                     this.option_selected(thisoption);
297                     // Ensure that the form may be submitted
298                     this.submitbutton.removeAttribute('disabled');
300                     // Ensure that the radio remains focus so that keyboard navigation is still possible
301                     thisoption.focus();
302                 } else {
303                     optiondiv.removeClass('selected');
304                 }
305             }, this);
306         },
308         option_selected : function() {
309         }
310     },
311     {
312         NAME : 'moodle-core-chooserdialogue',
313         ATTRS : {
314             minheight : {
315                 value : 300
316             },
317             maxheight : {
318                 value : 660
319             },
320             closeButtonTitle : {
321                 validator : Y.Lang.isString,
322                 value : 'Close'
323             }
324         }
325     });
326     M.core = M.core || {};
327     M.core.chooserdialogue = CHOOSERDIALOGUE;
328 },
329 '@VERSION@', {
330     requires:['base', 'panel', 'moodle-core-notification']
332 );