form-dateselector MDL-23096 Converted the date selector to a YUI-Moodle module
[moodle.git] / lib / javascript-static.js
1 // Miscellaneous core Javascript functions for Moodle
2 // Global M object is initilised in inline javascript
4 /**
5  * Add module to list of available modules that can be laoded from YUI.
6  * @param {Array} modules
7  */
8 M.yui.add_module = function(modules) {
9     for (var modname in modules) {
10         M.yui.loader.modules[modname] = modules[modname];
11     }
12 };
13 /**
14  * The gallery version to use when loading YUI modules from the gallery.
15  * Will be changed every time when using local galleries.
16  */
17 M.yui.galleryversion = '2010.04.21-21-51';
19 /**
20  * Various utility functions
21  */
22 M.util = M.util || {};
24 /**
25  * Language strings - initialised from page footer.
26  */
27 M.str = M.str || {};
29 /**
30  * Returns url for images.
31  * @param {String} imagename
32  * @param {String} component
33  * @return {String}
34  */
35 M.util.image_url = function(imagename, component) {
36     var url = M.cfg.wwwroot + '/theme/image.php?theme=' + M.cfg.theme + '&image=' + imagename;
38     if (M.cfg.themerev > 0) {
39         url = url + '&rev=' + M.cfg.themerev;
40     }
42     if (component && component != '' && component != 'moodle' && component != 'core') {
43         url = url + '&component=' + component;
44     }
46     return url;
47 };
49 M.util.create_UFO_object = function (eid, FO) {
50     UFO.create(FO, eid);
51 }
53 M.util.in_array = function(item, array){
54     for( var i = 0; i<array.length; i++){
55         if(item==array[i]){
56             return true;
57         }
58     }
59     return false;
60 }
62 /**
63  * Init a collapsible region, see print_collapsible_region in weblib.php
64  * @param {YUI} Y YUI3 instance with all libraries loaded
65  * @param {String} id the HTML id for the div.
66  * @param {String} userpref the user preference that records the state of this box. false if none.
67  * @param {String} strtooltip
68  */
69 M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
70     Y.use('anim', function(Y) {
71         new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
72     });
73 };
75 /**
76  * Object to handle a collapsible region : instantiate and forget styled object
77  *
78  * @class
79  * @constructor
80  * @param {YUI} Y YUI3 instance with all libraries loaded
81  * @param {String} id The HTML id for the div.
82  * @param {String} userpref The user preference that records the state of this box. false if none.
83  * @param {String} strtooltip
84  */
85 M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
86     // Record the pref name
87     this.userpref = userpref;
89     // Find the divs in the document.
90     this.div = Y.one('#'+id);
92     // Get the caption for the collapsible region
93     var caption = this.div.one('#'+id + '_caption');
94     caption.setAttribute('title', strtooltip);
96     // Create a link
97     var a = Y.Node.create('<a href="#"></a>');
98     // Create a local scoped lamba function to move nodes to a new link
99     var movenode = function(node){
100         node.remove();
101         a.append(node);
102     };
103     // Apply the lamba function on each of the captions child nodes
104     caption.get('children').each(movenode, this);
105     caption.append(a);
107     // Get the height of the div at this point before we shrink it if required
108     var height = this.div.get('offsetHeight');
109     if (this.div.hasClass('collapsed')) {
110         // Add the correct image and record the YUI node created in the process
111         this.icon = Y.Node.create('<img src="'+M.util.image_url('t/collapsed', 'moodle')+'" alt="" />');
112         // Shrink the div as it is collapsed by default
113         this.div.setStyle('height', caption.get('offsetHeight')+'px');
114     } else {
115         // Add the correct image and record the YUI node created in the process
116         this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
117     }
118     a.append(this.icon);
120     // Create the animation.
121     var animation = new Y.Anim({
122         node: this.div,
123         duration: 0.3,
124         easing: Y.Easing.easeBoth,
125         to: {height:caption.get('offsetHeight')},
126         from: {height:height}
127     });
129     // Handler for the animation finishing.
130     animation.on('end', function() {
131         this.div.toggleClass('collapsed');
132         if (this.div.hasClass('collapsed')) {
133             this.icon.set('src', M.util.image_url('t/collapsed', 'moodle'));
134         } else {
135             this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
136         }
137     }, this);
139     // Hook up the event handler.
140     a.on('click', function(e, animation) {
141         e.preventDefault();
142         // Animate to the appropriate size.
143         if (animation.get('running')) {
144             animation.stop();
145         }
146         animation.set('reverse', this.div.hasClass('collapsed'));
147         // Update the user preference.
148         if (this.userpref) {
149             M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
150         }
151         animation.run();
152     }, this, animation);
153 };
155 /**
156  * The user preference that stores the state of this box.
157  * @property userpref
158  * @type String
159  */
160 M.util.CollapsibleRegion.prototype.userpref = null;
162 /**
163  * The key divs that make up this
164  * @property div
165  * @type Y.Node
166  */
167 M.util.CollapsibleRegion.prototype.div = null;
169 /**
170  * The key divs that make up this
171  * @property icon
172  * @type Y.Node
173  */
174 M.util.CollapsibleRegion.prototype.icon = null;
176 /**
177  * Makes a best effort to connect back to Moodle to update a user preference,
178  * however, there is no mechanism for finding out if the update succeeded.
179  *
180  * Before you can use this function in your JavsScript, you must have called
181  * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
182  * the udpate is allowed, and how to safely clean and submitted values.
183  *
184  * @param String name the name of the setting to udpate.
185  * @param String the value to set it to.
186  */
187 M.util.set_user_preference = function(name, value) {
188     YUI(M.yui.loader).use('io', function(Y) {
189         var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
190                 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
192         // If we are a developer, ensure that failures are reported.
193         var cfg = {
194                 method: 'get',
195                 on: {}
196             };
197         if (M.cfg.developerdebug) {
198             cfg.on.failure = function(id, o, args) {
199                 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
200             }
201         }
203         // Make the request.
204         Y.io(url, cfg);
205     });
206 };
208 /**
209  * Prints a confirmation dialog in the style of DOM.confirm().
210  * @param object event A YUI DOM event or null if launched manually
211  * @param string message The message to show in the dialog
212  * @param string url The URL to forward to if YES is clicked. Disabled if fn is given
213  * @param function fn A JS function to run if YES is clicked.
214  */
215 M.util.show_confirm_dialog = function(e, args) {
216     var target = e.target;
217     if (e.preventDefault) {
218         e.preventDefault();
219     }
221     YUI(M.yui.loader).use('yui2-container', 'yui2-event', function(Y) {
222         var simpledialog = new YAHOO.widget.SimpleDialog('confirmdialog',
223             {width: '300px',
224               fixedcenter: true,
225               modal: true,
226               visible: false,
227               draggable: false
228             }
229         );
231         simpledialog.setHeader(M.str.admin.confirmation);
232         simpledialog.setBody(args.message);
233         simpledialog.cfg.setProperty('icon', YAHOO.widget.SimpleDialog.ICON_WARN);
235         var handle_cancel = function() {
236             simpledialog.hide();
237         };
239         var handle_yes = function() {
240             simpledialog.hide();
242             if (args.callback) {
243                 // args comes from PHP, so callback will be a string, needs to be evaluated by JS
244                 var callback = null;
245                 if (Y.Lang.isFunction(args.callback)) {
246                     callback = args.callback;
247                 } else {
248                     callback = eval('('+args.callback+')');
249                 }
251                 if (Y.Lang.isObject(args.scope)) {
252                     var sc = args.scope;
253                 } else {
254                     var sc = e.target;
255                 }
257                 if (args.callbackargs) {
258                     callback.apply(sc, args.callbackargs);
259                 } else {
260                     callback.apply(sc);
261                 }
262                 return;
263             }
265             if (target.get('tagName').toLowerCase() == 'a') {
266                 window.location = target.get('href');
267             } else if (target.get('tagName').toLowerCase() == 'input') {
268                 var parentelement = target.get('parentNode');
269                 while (parentelement.get('tagName').toLowerCase() != 'form' && parentelement.get('tagName').toLowerCase() != 'body') {
270                     parentelement = parentelement.get('parentNode');
271                 }
272                 if (parentelement.get('tagName').toLowerCase() == 'form') {
273                     parentelement.submit();
274                 }
275             } else if (M.cfg.developerdebug) {
276                 alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A or INPUT");
277             }
278         };
280         var buttons = [ {text: M.str.moodle.cancel, handler: handle_cancel, isDefault: true},
281                         {text: M.str.moodle.yes, handler: handle_yes} ];
283         simpledialog.cfg.queueProperty('buttons', buttons);
285         simpledialog.render(document.body);
286         simpledialog.show();
287     });
290 /** Useful for full embedding of various stuff */
291 M.util.init_maximised_embed = function(Y, id) {
292     var obj = Y.one('#'+id);
293     if (!obj) {
294         return;
295     }
298     var get_htmlelement_size = function(el, prop) {
299         if (Y.Lang.isString(el)) {
300             el = Y.one('#' + el);
301         }
302         var val = el.getStyle(prop);
303         if (val == 'auto') {
304             val = el.getComputedStyle(prop);
305         }
306         return parseInt(val);
307     };
309     var resize_object = function() {
310         obj.setStyle('width', '0px');
311         obj.setStyle('height', '0px');
312         var newwidth = get_htmlelement_size('content', 'width') - 15;
314         if (newwidth > 600) {
315             obj.setStyle('width', newwidth  + 'px');
316         } else {
317             obj.setStyle('width', '600px');
318         }
319         var pageheight = get_htmlelement_size('page', 'height');
320         var objheight = get_htmlelement_size(obj, 'height');
321         var newheight = objheight + parseInt(obj.get('winHeight')) - pageheight - 30;
322         if (newheight > 400) {
323             obj.setStyle('height', newheight + 'px');
324         } else {
325             obj.setStyle('height', '400px');
326         }
327     };
329     resize_object();
330     // fix layout if window resized too
331     window.onresize = function() {
332         resize_object();
333     };
334 };
336 /**
337  * Attach handler to single_select
338  */
339 M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
340     Y.use('event-key', function() {
341         var select = Y.one('#'+selectid);
342         if (select) {
343             // Try to get the form by id
344             var form = Y.one('#'+formid) || (function(){
345                 // Hmmm the form's id may have been overriden by an internal input
346                 // with the name id which will KILL IE.
347                 // We need to manually iterate at this point because if the case
348                 // above is true YUI's ancestor method will also kill IE!
349                 var form = select;
350                 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
351                     form = form.ancestor();
352                 }
353                 return form;
354             })();
355             // Make sure we have the form
356             if (form) {
357                 // Create a function to handle our change event
358                 var processchange = function(e, lastindex) {
359                     if ((nothing===false || select.get('value') != nothing) && lastindex != select.get('selectedIndex')) {
360                         this.submit();
361                     }
362                 }
363                 // Attach the change event to the keypress, blur, and click actions.
364                 // We don't use the change event because IE fires it on every arrow up/down
365                 // event.... usability
366                 Y.on('key', processchange, select, 'press:13', form, select.get('selectedIndex'));
367                 select.on('blur', processchange, form, select.get('selectedIndex'));
368                 select.on('click', processchange, form, select.get('selectedIndex'));
369             }
370         }
371     });
372 };
374 /**
375  * Attach handler to url_select
376  */
377 M.util.init_url_select = function(Y, formid, selectid, nothing) {
378     YUI(M.yui.loader).use('node', function(Y) {
379         Y.on('change', function() {
380             if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
381                 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
382             }
383         },
384         '#'+selectid);
385     });
386 };
388 /**
389  * Breaks out all links to the top frame - used in frametop page layout.
390  */
391 M.util.init_frametop = function(Y) {
392     Y.all('a').each(function(node) {
393         node.set('target', '_top');
394     });
395     Y.all('form').each(function(node) {
396         node.set('target', '_top');
397     });
398 };
400 /**
401  * Finds all nodes that match the given CSS selector and attaches events to them
402  * so that they toggle a given classname when clicked.
403  *
404  * @param {YUI} Y
405  * @param {string} id An id containing elements to target
406  * @param {string} cssselector A selector to use to find targets
407  * @param {string} toggleclassname A classname to toggle
408  */
409 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname) {
410     var node = Y.one('#'+id);
411     node.all(cssselector).each(function(node){
412         node.on('click', function(e){
413             e.stopPropagation();
414             if (e.target.get('nodeName')!='A' && e.target.get('nodeName')!='IMG') {
415                 this.toggleClass(toggleclassname);
416             }
417         }, node);
418     });
419     // Attach this click event to the node rather than all selectors... will be much better
420     // for performance
421     node.on('click', function(e){
422         if (e.target.hasClass('addtoall')) {
423             node.all(cssselector).addClass(toggleclassname);
424         } else if (e.target.hasClass('removefromall')) {
425             node.all(cssselector+'.'+toggleclassname).removeClass(toggleclassname);
426         }
427     }, node);
430 /**
431  * Initialises a colour picker
432  *
433  * Designed to be used with admin_setting_configcolourpicker although could be used
434  * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
435  * above or below the input (must have the same parent) and then call this with the
436  * id.
437  *
438  * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
439  * contrib/blocks. For better docs refer to that.
440  *
441  * @param {YUI} Y
442  * @param {int} id
443  * @param {object} previewconf
444  */
445 M.util.init_colour_picker = function(Y, id, previewconf) {
446     /**
447      * We need node and event-mouseenter
448      */
449     Y.use('node', 'event-mouseenter', function(){
450         /**
451          * The colour picker object
452          */
453         var colourpicker = {
454             box : null,
455             input : null,
456             image : null,
457             preview : null,
458             current : null,
459             eventClick : null,
460             eventMouseEnter : null,
461             eventMouseLeave : null,
462             eventMouseMove : null,
463             width : 300,
464             height :  100,
465             factor : 5,
466             /**
467              * Initalises the colour picker by putting everything together and wiring the events
468              */
469             init : function() {
470                 this.input = Y.one('#'+id);
471                 this.box = this.input.ancestor().one('.admin_colourpicker');
472                 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
473                 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
474                 this.preview = Y.Node.create('<div class="previewcolour"></div>');
475                 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
476                 this.current = Y.Node.create('<div class="currentcolour"></div>');
477                 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
478                 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
480                 if (typeof(previewconf) === 'object' && previewconf !== null) {
481                     Y.one('#'+id+'_preview').on('click', function(e){
482                         if (Y.Lang.isString(previewconf.selector)) {
483                             Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
484                         } else {
485                             for (var i in previewconf.selector) {
486                                 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
487                             }
488                         }
489                     }, this);
490                 }
492                 this.eventClick = this.image.on('click', this.pickColour, this);
493                 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
494             },
495             /**
496              * Starts to follow the mouse once it enter the image
497              */
498             startFollow : function(e) {
499                 this.eventMouseEnter.detach();
500                 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
501                 this.eventMouseMove = this.image.on('mousemove', function(e){
502                     this.preview.setStyle('backgroundColor', this.determineColour(e));
503                 }, this);
504             },
505             /**
506              * Stops following the mouse
507              */
508             endFollow : function(e) {
509                 this.eventMouseMove.detach();
510                 this.eventMouseLeave.detach();
511                 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
512             },
513             /**
514              * Picks the colour the was clicked on
515              */
516             pickColour : function(e) {
517                 var colour = this.determineColour(e);
518                 this.input.set('value', colour);
519                 this.current.setStyle('backgroundColor', colour);
520             },
521             /**
522              * Calculates the colour fromthe given co-ordinates
523              */
524             determineColour : function(e) {
525                 var eventx = Math.floor(e.pageX-e.target.getX());
526                 var eventy = Math.floor(e.pageY-e.target.getY());
528                 var imagewidth = this.width;
529                 var imageheight = this.height;
530                 var factor = this.factor;
531                 var colour = [255,0,0];
533                 var matrices = [
534                     [  0,  1,  0],
535                     [ -1,  0,  0],
536                     [  0,  0,  1],
537                     [  0, -1,  0],
538                     [  1,  0,  0],
539                     [  0,  0, -1]
540                 ];
542                 var matrixcount = matrices.length;
543                 var limit = Math.round(imagewidth/matrixcount);
544                 var heightbreak = Math.round(imageheight/2);
546                 for (var x = 0; x < imagewidth; x++) {
547                     var divisor = Math.floor(x / limit);
548                     var matrix = matrices[divisor];
550                     colour[0] += matrix[0]*factor;
551                     colour[1] += matrix[1]*factor;
552                     colour[2] += matrix[2]*factor;
554                     if (eventx==x) {
555                         break;
556                     }
557                 }
559                 var pixel = [colour[0], colour[1], colour[2]];
560                 if (eventy < heightbreak) {
561                     pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
562                     pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
563                     pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
564                 } else if (eventy > heightbreak) {
565                     pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
566                     pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
567                     pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
568                 }
570                 return this.convert_rgb_to_hex(pixel);
571             },
572             /**
573              * Converts an RGB value to Hex
574              */
575             convert_rgb_to_hex : function(rgb) {
576                 var hex = '#';
577                 var hexchars = "0123456789ABCDEF";
578                 for (var i=0; i<3; i++) {
579                     var number = Math.abs(rgb[i]);
580                     if (number == 0 || isNaN(number)) {
581                         hex += '00';
582                     } else {
583                         hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
584                     }
585                 }
586                 return hex;
587             }
588         }
589         /**
590          * Initialise the colour picker :) Hoorah
591          */
592         colourpicker.init();
593     });
596 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
598 function popupchecker(msg) {
599     var testwindow = window.open('', '', 'width=1,height=1,left=0,top=0,scrollbars=no');
600     if (!testwindow) {
601         alert(msg);
602     } else {
603         testwindow.close();
604     }
607 function checkall() {
608     var inputs = document.getElementsByTagName('input');
609     for (var i = 0; i < inputs.length; i++) {
610         if (inputs[i].type == 'checkbox') {
611             inputs[i].checked = true;
612         }
613     }
616 function checknone() {
617     var inputs = document.getElementsByTagName('input');
618     for (var i = 0; i < inputs.length; i++) {
619         if (inputs[i].type == 'checkbox') {
620             inputs[i].checked = false;
621         }
622     }
625 function lockoptions(formid, master, subitems) {
626   // Subitems is an array of names of sub items.
627   // Optionally, each item in subitems may have a
628   // companion hidden item in the form with the
629   // same name but prefixed by "h".
630   var form = document.forms[formid];
632   if (eval("form."+master+".checked")) {
633     for (i=0; i<subitems.length; i++) {
634       unlockoption(form, subitems[i]);
635     }
636   } else {
637     for (i=0; i<subitems.length; i++) {
638       lockoption(form, subitems[i]);
639     }
640   }
641   return(true);
644 function lockoption(form,item) {
645   eval("form."+item+".disabled=true");/* IE thing */
646   if(form.elements['h'+item]) {
647     eval("form.h"+item+".value=1");
648   }
651 function unlockoption(form,item) {
652   eval("form."+item+".disabled=false");/* IE thing */
653   if(form.elements['h'+item]) {
654     eval("form.h"+item+".value=0");
655   }
658 /**
659  * Get the value of the 'virtual form element' with a particular name. That is,
660  * abstracts away the difference between a normal form element, like a select
661  * which is a single HTML element with a .value property, and a set of radio
662  * buttons, which is several HTML elements.
663  *
664  * @param form a HTML form.
665  * @param master the name of an element in that form.
666  * @return the value of that element.
667  */
668 function get_form_element_value(form, name) {
669     var element = form[name];
670     if (!element) {
671         return null;
672     }
673     if (element.tagName) {
674         // Ordinarly thing like a select box.
675         return element.value;
676     }
677     // Array of things, like radio buttons.
678     for (var j = 0; j < element.length; j++) {
679         var el = element[j];
680         if (el.checked) {
681             return el.value;
682         }
683     }
684     return null;
688 /**
689  * Set the disabled state of the 'virtual form element' with a particular name.
690  * This abstracts away the difference between a normal form element, like a select
691  * which is a single HTML element with a .value property, and a set of radio
692  * buttons, which is several HTML elements.
693  *
694  * @param form a HTML form.
695  * @param master the name of an element in that form.
696  * @param disabled the disabled state to set.
697  */
698 function set_form_element_disabled(form, name, disabled) {
699     var element = form[name];
700     if (!element) {
701         return;
702     }
703     if (element.tagName) {
704         // Ordinarly thing like a select box.
705         element.disabled = disabled;
706     }
707     // Array of things, like radio buttons.
708     for (var j = 0; j < element.length; j++) {
709         var el = element[j];
710         el.disabled = disabled;
711     }
714 /**
715  * Set the hidden state of the 'virtual form element' with a particular name.
716  * This abstracts away the difference between a normal form element, like a select
717  * which is a single HTML element with a .value property, and a set of radio
718  * buttons, which is several HTML elements.
719  *
720  * @param form a HTML form.
721  * @param master the name of an element in that form.
722  * @param hidden the hidden state to set.
723  */
724 function set_form_element_hidden(form, name, hidden) {
725     var element = form[name];
726     if (!element) {
727         return;
728     }
729     if (element.tagName) {
730         var el = findParentNode(element, 'DIV', 'fitem', false);
731         if (el!=null) {
732             el.style.display = hidden ? 'none' : '';
733             el.style.visibility = hidden ? 'hidden' : '';
734         }
735     }
736     // Array of things, like radio buttons.
737     for (var j = 0; j < element.length; j++) {
738         var el = findParentNode(element[j], 'DIV', 'fitem', false);
739         if (el!=null) {
740             el.style.display = hidden ? 'none' : '';
741             el.style.visibility = hidden ? 'hidden' : '';
742         }
743     }
746 function lockoptionsall(formid) {
747     var form = document.forms[formid];
748     var dependons = eval(formid + 'items');
749     var tolock = [];
750     var tohide = [];
751     for (var dependon in dependons) {
752         // change for MooTools compatibility
753         if (!dependons.propertyIsEnumerable(dependon)) {
754             continue;
755         }
756         if (!form[dependon]) {
757             continue;
758         }
759         for (var condition in dependons[dependon]) {
760             for (var value in dependons[dependon][condition]) {
761                 var lock;
762                 var hide = false;
763                 switch (condition) {
764                   case 'notchecked':
765                       lock = !form[dependon].checked;break;
766                   case 'checked':
767                       lock = form[dependon].checked;break;
768                   case 'noitemselected':
769                       lock = form[dependon].selectedIndex == -1;break;
770                   case 'eq':
771                       lock = get_form_element_value(form, dependon) == value;break;
772                   case 'hide':
773                       // hide as well as disable
774                       hide = true;break;
775                   default:
776                       lock = get_form_element_value(form, dependon) != value;break;
777                 }
778                 for (var ei in dependons[dependon][condition][value]) {
779                     var eltolock = dependons[dependon][condition][value][ei];
780                     if (hide) {
781                         tohide[eltolock] = true;
782                     }
783                     if (tolock[eltolock] != null) {
784                         tolock[eltolock] = lock || tolock[eltolock];
785                     } else {
786                         tolock[eltolock] = lock;
787                     }
788                 }
789             }
790         }
791     }
792     for (var el in tolock) {
793         // change for MooTools compatibility
794         if (!tolock.propertyIsEnumerable(el)) {
795             continue;
796         }
797         set_form_element_disabled(form, el, tolock[el]);
798         if (tohide.propertyIsEnumerable(el)) {
799             set_form_element_hidden(form, el, tolock[el]);
800     }
801     }
802     return true;
805 function lockoptionsallsetup(formid) {
806     var form = document.forms[formid];
807     var dependons = eval(formid+'items');
808     for (var dependon in dependons) {
809         // change for MooTools compatibility
810         if (!dependons.propertyIsEnumerable(dependon)) {
811             continue;
812         }
813         var masters = form[dependon];
814         if (!masters) {
815             continue;
816         }
817         if (masters.tagName) {
818             // If master is radio buttons, we get an array, otherwise we don't.
819             // Convert both cases to an array for convinience.
820             masters = [masters];
821         }
822         for (var j = 0; j < masters.length; j++) {
823             master = masters[j];
824             master.formid = formid;
825             master.onclick  = function() {return lockoptionsall(this.formid);};
826             master.onblur   = function() {return lockoptionsall(this.formid);};
827             master.onchange = function() {return lockoptionsall(this.formid);};
828         }
829     }
830     for (var i = 0; i < form.elements.length; i++) {
831         var formelement = form.elements[i];
832         if (formelement.type=='reset') {
833             formelement.formid = formid;
834             formelement.onclick  = function() {this.form.reset();return lockoptionsall(this.formid);};
835             formelement.onblur   = function() {this.form.reset();return lockoptionsall(this.formid);};
836             formelement.onchange = function() {this.form.reset();return lockoptionsall(this.formid);};
837         }
838     }
839     return lockoptionsall(formid);
842 /**
843  * Either check, or uncheck, all checkboxes inside the element with id is
844  * @param id the id of the container
845  * @param checked the new state, either '' or 'checked'.
846  */
847 function select_all_in_element_with_id(id, checked) {
848     var container = document.getElementById(id);
849     if (!container) {
850         return;
851     }
852     var inputs = container.getElementsByTagName('input');
853     for (var i = 0; i < inputs.length; ++i) {
854         if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
855             inputs[i].checked = checked;
856         }
857     }
860 function select_all_in(elTagName, elClass, elId) {
861     var inputs = document.getElementsByTagName('input');
862     inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
863     for(var i = 0; i < inputs.length; ++i) {
864         if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
865             inputs[i].checked = 'checked';
866         }
867     }
870 function deselect_all_in(elTagName, elClass, elId) {
871     var inputs = document.getElementsByTagName('INPUT');
872     inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
873     for(var i = 0; i < inputs.length; ++i) {
874         if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
875             inputs[i].checked = '';
876         }
877     }
880 function confirm_if(expr, message) {
881     if(!expr) {
882         return true;
883     }
884     return confirm(message);
888 /*
889     findParentNode (start, elementName, elementClass, elementID)
891     Travels up the DOM hierarchy to find a parent element with the
892     specified tag name, class, and id. All conditions must be met,
893     but any can be ommitted. Returns the BODY element if no match
894     found.
895 */
896 function findParentNode(el, elName, elClass, elId) {
897     while (el.nodeName.toUpperCase() != 'BODY') {
898         if ((!elName || el.nodeName.toUpperCase() == elName) &&
899             (!elClass || el.className.indexOf(elClass) != -1) &&
900             (!elId || el.id == elId)) {
901             break;
902         }
903         el = el.parentNode;
904     }
905     return el;
907 /*
908     findChildNode (start, elementName, elementClass, elementID)
910     Travels down the DOM hierarchy to find all child elements with the
911     specified tag name, class, and id. All conditions must be met,
912     but any can be ommitted.
913     Doesn't examine children of matches.
914 */
915 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
916     var children = new Array();
917     for (var i = 0; i < start.childNodes.length; i++) {
918         var classfound = false;
919         var child = start.childNodes[i];
920         if((child.nodeType == 1) &&//element node type
921                   (elementClass && (typeof(child.className)=='string'))) {
922             var childClasses = child.className.split(/\s+/);
923             for (var childClassIndex in childClasses) {
924                 if (childClasses[childClassIndex]==elementClass) {
925                     classfound = true;
926                     break;
927                 }
928             }
929         }
930         if(child.nodeType == 1) { //element node type
931             if  ( (!tagName || child.nodeName == tagName) &&
932                 (!elementClass || classfound)&&
933                 (!elementID || child.id == elementID) &&
934                 (!elementName || child.name == elementName))
935             {
936                 children = children.concat(child);
937             } else {
938                 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
939             }
940         }
941     }
942     return children;
944 /*
945     elementSetHide (elements, hide)
947     Adds or removes the "hide" class for the specified elements depending on boolean hide.
948 */
949 function elementShowAdvanced(elements, show) {
950     for (var elementIndex in elements) {
951         element = elements[elementIndex];
952         element.className = element.className.replace(new RegExp(' ?hide'), '')
953         if(!show) {
954             element.className += ' hide';
955         }
956     }
959 function showAdvancedInit(addBefore, nameAttr, buttonLabel, hideText, showText) {
960     var showHideButton = document.createElement("input");
961     showHideButton.type = 'button';
962     showHideButton.value = buttonLabel;
963     showHideButton.name = nameAttr;
964     showHideButton.moodle = {
965         hideLabel: M.str.form.hideadvanced,
966         showLabel: M.str.form.showadvanced
967     };
968     YAHOO.util.Event.addListener(showHideButton, 'click', showAdvancedOnClick);
969     el = document.getElementById(addBefore);
970     el.parentNode.insertBefore(showHideButton, el);
973 function showAdvancedOnClick(e) {
974     var button = e.target ? e.target : e.srcElement;
976     var toSet=findChildNodes(button.form, null, 'advanced');
977     var buttontext = '';
978     if (button.form.elements['mform_showadvanced_last'].value == '0' ||  button.form.elements['mform_showadvanced_last'].value == '' ) {
979         elementShowAdvanced(toSet, true);
980         buttontext = button.moodle.hideLabel;
981         button.form.elements['mform_showadvanced_last'].value = '1';
982     } else {
983         elementShowAdvanced(toSet, false);
984         buttontext = button.moodle.showLabel;
985         button.form.elements['mform_showadvanced_last'].value = '0';
986     }
987     var formelements = button.form.elements;
988     // Fixed MDL-10506
989     for (var i = 0; i < formelements.length; i++) {
990         if (formelements[i] && formelements[i].name && (formelements[i].name=='mform_showadvanced')) {
991             formelements[i].value = buttontext;
992         }
993     }
994     //never submit the form if js is enabled.
995     return false;
998 function unmaskPassword(id) {
999   var pw = document.getElementById(id);
1000   var chb = document.getElementById(id+'unmask');
1002   try {
1003     // first try IE way - it can not set name attribute later
1004     if (chb.checked) {
1005       var newpw = document.createElement('<input type="text" name="'+pw.name+'">');
1006     } else {
1007       var newpw = document.createElement('<input type="password" name="'+pw.name+'">');
1008     }
1009     newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
1010   } catch (e) {
1011     var newpw = document.createElement('input');
1012     newpw.setAttribute('name', pw.name);
1013     if (chb.checked) {
1014       newpw.setAttribute('type', 'text');
1015     } else {
1016       newpw.setAttribute('type', 'password');
1017     }
1018     newpw.setAttribute('class', pw.getAttribute('class'));
1019   }
1020   newpw.id = pw.id;
1021   newpw.size = pw.size;
1022   newpw.onblur = pw.onblur;
1023   newpw.onchange = pw.onchange;
1024   newpw.value = pw.value;
1025   pw.parentNode.replaceChild(newpw, pw);
1028 function filterByParent(elCollection, parentFinder) {
1029     var filteredCollection = [];
1030     for (var i = 0; i < elCollection.length; ++i) {
1031         var findParent = parentFinder(elCollection[i]);
1032         if (findParent.nodeName.toUpperCase != 'BODY') {
1033             filteredCollection.push(elCollection[i]);
1034         }
1035     }
1036     return filteredCollection;
1039 /*
1040     All this is here just so that IE gets to handle oversized blocks
1041     in a visually pleasing manner. It does a browser detect. So sue me.
1042 */
1044 function fix_column_widths() {
1045     var agt = navigator.userAgent.toLowerCase();
1046     if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1047         fix_column_width('left-column');
1048         fix_column_width('right-column');
1049     }
1052 function fix_column_width(colName) {
1053     if(column = document.getElementById(colName)) {
1054         if(!column.offsetWidth) {
1055             setTimeout("fix_column_width('" + colName + "')", 20);
1056             return;
1057         }
1059         var width = 0;
1060         var nodes = column.childNodes;
1062         for(i = 0; i < nodes.length; ++i) {
1063             if(nodes[i].className.indexOf("block") != -1 ) {
1064                 if(width < nodes[i].offsetWidth) {
1065                     width = nodes[i].offsetWidth;
1066                 }
1067             }
1068         }
1070         for(i = 0; i < nodes.length; ++i) {
1071             if(nodes[i].className.indexOf("block") != -1 ) {
1072                 nodes[i].style.width = width + 'px';
1073             }
1074         }
1075     }
1079 /*
1080    Insert myValue at current cursor position
1081  */
1082 function insertAtCursor(myField, myValue) {
1083     // IE support
1084     if (document.selection) {
1085         myField.focus();
1086         sel = document.selection.createRange();
1087         sel.text = myValue;
1088     }
1089     // Mozilla/Netscape support
1090     else if (myField.selectionStart || myField.selectionStart == '0') {
1091         var startPos = myField.selectionStart;
1092         var endPos = myField.selectionEnd;
1093         myField.value = myField.value.substring(0, startPos)
1094             + myValue + myField.value.substring(endPos, myField.value.length);
1095     } else {
1096         myField.value += myValue;
1097     }
1101 /*
1102         Call instead of setting window.onload directly or setting body onload=.
1103         Adds your function to a chain of functions rather than overwriting anything
1104         that exists.
1105 */
1106 function addonload(fn) {
1107     var oldhandler=window.onload;
1108     window.onload=function() {
1109         if(oldhandler) oldhandler();
1110             fn();
1111     }
1113 /**
1114  * Replacement for getElementsByClassName in browsers that aren't cool enough
1115  *
1116  * Relying on the built-in getElementsByClassName is far, far faster than
1117  * using YUI.
1118  *
1119  * Note: the third argument used to be an object with odd behaviour. It now
1120  * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1121  * mimicked if you pass an object.
1122  *
1123  * @param {Node} oElm The top-level node for searching. To search a whole
1124  *                    document, use `document`.
1125  * @param {String} strTagName filter by tag names
1126  * @param {String} name same as HTML5 spec
1127  */
1128 function getElementsByClassName(oElm, strTagName, name) {
1129     // for backwards compatibility
1130     if(typeof name == "object") {
1131         var names = new Array();
1132         for(var i=0; i<name.length; i++) names.push(names[i]);
1133         name = names.join('');
1134     }
1135     // use native implementation if possible
1136     if (oElm.getElementsByClassName && Array.filter) {
1137         if (strTagName == '*') {
1138             return oElm.getElementsByClassName(name);
1139         } else {
1140             return Array.filter(oElm.getElementsByClassName(name), function(el) {
1141                 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1142             });
1143         }
1144     }
1145     // native implementation unavailable, fall back to slow method
1146     var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1147     var arrReturnElements = new Array();
1148     var arrRegExpClassNames = new Array();
1149     var names = name.split(' ');
1150     for(var i=0; i<names.length; i++) {
1151         arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1152     }
1153     var oElement;
1154     var bMatchesAll;
1155     for(var j=0; j<arrElements.length; j++) {
1156         oElement = arrElements[j];
1157         bMatchesAll = true;
1158         for(var k=0; k<arrRegExpClassNames.length; k++) {
1159             if(!arrRegExpClassNames[k].test(oElement.className)) {
1160                 bMatchesAll = false;
1161                 break;
1162             }
1163         }
1164         if(bMatchesAll) {
1165             arrReturnElements.push(oElement);
1166         }
1167     }
1168     return (arrReturnElements)
1171 function openpopup(event, args) {
1173     if (event) {
1174         YAHOO.util.Event.preventDefault(event);
1175     }
1176    
1177     var fullurl = args.url;
1178     if (!args.url.match(/https?:\/\//)) {
1179         fullurl = M.cfg.wwwroot + args.url;
1180     }
1181     var windowobj = window.open(fullurl,args.name,args.options);
1182     if (!windowobj) {
1183         return true;
1184     }
1185     if (args.fullscreen) {
1186         windowobj.moveTo(0,0);
1187         windowobj.resizeTo(screen.availWidth,screen.availHeight);
1188     }
1189     windowobj.focus();
1190     
1191     return false;
1194 /* This is only used on a few help pages. */
1195 emoticons_help = {
1196     inputarea: null,
1198     init: function(formname, fieldname, listid) {
1199         if (!opener || !opener.document.forms[formname]) {
1200             return;
1201         }
1202         emoticons_help.inputarea = opener.document.forms[formname][fieldname];
1203         if (!emoticons_help.inputarea) {
1204             return;
1205         }
1206         var emoticons = document.getElementById(listid).getElementsByTagName('li');
1207         for (var i = 0; i < emoticons.length; i++) {
1208             var text = emoticons[i].getElementsByTagName('img')[0].alt;
1209             YAHOO.util.Event.addListener(emoticons[i], 'click', emoticons_help.inserttext, text);
1210         }
1211     },
1213     inserttext: function(e, text) {
1214         text = ' ' + text + ' ';
1215         if (emoticons_help.inputarea.createTextRange && emoticons_help.inputarea.caretPos) {
1216             var caretPos = emoticons_help.inputarea.caretPos;
1217             caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text + ' ' : text;
1218         } else {
1219             emoticons_help.inputarea.value  += text;
1220         }
1221         emoticons_help.inputarea.focus();
1222     }
1225 /**
1226  * Oject to handle expanding and collapsing blocks when an icon is clicked on.
1227  * @constructor
1228  * @param String id the HTML id for the div.
1229  * @param String userpref the user preference that records the state of this block.
1230  * @param String visibletooltip tool tip/alt to show when the block is visible.
1231  * @param String hiddentooltip tool tip/alt to show when the block is hidden.
1232  * @param String visibleicon URL of the icon to show when the block is visible.
1233  * @param String hiddenicon URL of the icon to show when the block is hidden.
1234  */
1235 function block_hider(id, userpref, visibletooltip, hiddentooltip, visibleicon, hiddenicon) {
1236     // Find the elemen that is the block.
1237     this.block = document.getElementById(id);
1238     var title_div = YAHOO.util.Dom.getElementsByClassName('title', 'div', this.block);
1239     if (!title_div || !title_div[0]) {
1240         return this;
1241     }
1242     title_div = title_div[0];
1243     this.ishidden = YAHOO.util.Dom.hasClass(this.block, 'hidden');
1245     // Record the pref name
1246     this.userpref = userpref;
1247     this.visibletooltip = visibletooltip;
1248     this.hiddentooltip = hiddentooltip;
1249     this.visibleicon = visibleicon;
1250     this.hiddenicon = hiddenicon;
1252     // Add the icon.
1253     this.icon = document.createElement('input');
1254     this.icon.type = 'image';
1255     this.update_state();
1257     var blockactions = YAHOO.util.Dom.getElementsByClassName('block_action', 'div', title_div);
1258     if (blockactions && blockactions[0]) {
1259         blockactions[0].insertBefore(this.icon, blockactions[0].firstChild);
1260     }
1262     // Hook up the event handler.
1263     YAHOO.util.Event.addListener(this.icon, 'click', this.handle_click, null, this);
1266 /** Handle click on a block show/hide icon. */
1267 block_hider.prototype.handle_click = function(e) {
1268     YAHOO.util.Event.stopEvent(e);
1269     this.ishidden = !this.ishidden;
1270     this.update_state();
1271     M.util.set_user_preference(this.userpref, this.ishidden);
1274 /** Set the state of the block show/hide icon to this.ishidden. */
1275 block_hider.prototype.update_state = function () {
1276     if (this.ishidden) {
1277         YAHOO.util.Dom.addClass(this.block, 'hidden');
1278         this.icon.alt = this.hiddentooltip;
1279         this.icon.title = this.hiddentooltip;
1280         this.icon.src = this.hiddenicon;
1281     } else {
1282         YAHOO.util.Dom.removeClass(this.block, 'hidden');
1283         this.icon.alt = this.visibletooltip;
1284         this.icon.title = this.visibletooltip;
1285         this.icon.src = this.visibleicon;
1286     }
1289 /** Close the current browser window. */
1290 function close_window(e) {
1291     YAHOO.util.Event.preventDefault(e);
1292     self.close();
1295 /**
1296  * Close the current browser window, forcing the window/tab that opened this
1297  * popup to reload itself. */
1298 function close_window_reloading_opener() {
1299     if (window.opener) {
1300         window.opener.location.reload(1);
1301         close_window();
1302         // Intentionally, only try to close the window if there is some evidence we are in a popup.
1303     }
1306 /**
1307  * Used in a couple of modules to hide navigation areas when using AJAX
1308  */
1310 function show_item(itemid) {
1311     var item = document.getElementById(itemid);
1312     if (item) {
1313         item.style.display = "";
1314     }
1317 function destroy_item(itemid) {
1318     var item = document.getElementById(itemid);
1319     if (item) {
1320         item.parentNode.removeChild(item);
1321     }
1323 /**
1324  * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1325  * @param controlid the control id.
1326  */
1327 function focuscontrol(controlid) {
1328     var control = document.getElementById(controlid);
1329     if (control) {
1330         control.focus();
1331     }
1334 /**
1335  * Transfers keyboard focus to an HTML element based on the old style style of focus
1336  * This function should be removed as soon as it is no longer used
1337  */
1338 function old_onload_focus(formid, controlname) {
1339     if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1340         document.forms[formid].elements[controlname].focus();
1341     }
1344 function build_querystring(obj) {
1345     return convert_object_to_string(obj, '&');
1348 function build_windowoptionsstring(obj) {
1349     return convert_object_to_string(obj, ',');
1352 function convert_object_to_string(obj, separator) {
1353     if (typeof obj !== 'object') {
1354         return null;
1355     }
1356     var list = [];
1357     for(var k in obj) {
1358         k = encodeURIComponent(k);
1359         var value = obj[k];
1360         if(obj[k] instanceof Array) {
1361             for(var i in value) {
1362                 list.push(k+'[]='+encodeURIComponent(value[i]));
1363             }
1364         } else {
1365             list.push(k+'='+encodeURIComponent(value));
1366         }
1367     }
1368     return list.join(separator);
1371 function stripHTML(str) {
1372     var re = /<\S[^><]*>/g;
1373     var ret = str.replace(re, "");
1374     return ret;
1377 Number.prototype.fixed=function(n){
1378     with(Math)
1379         return round(Number(this)*pow(10,n))/pow(10,n);
1381 function update_progress_bar (id, width, pt, msg, es){
1382     var percent = pt*100;
1383     var status = document.getElementById("status_"+id);
1384     var percent_indicator = document.getElementById("pt_"+id);
1385     var progress_bar = document.getElementById("progress_"+id);
1386     var time_es = document.getElementById("time_"+id);
1387     status.innerHTML = msg;
1388     percent_indicator.innerHTML = percent.fixed(2) + '%';
1389     if(percent == 100) {
1390         progress_bar.style.background = "green";
1391         time_es.style.display = "none";
1392     } else {
1393         progress_bar.style.background = "#FFCC66";
1394         if (es == Infinity){
1395             time_es.innerHTML = "Initializing...";
1396         }else {
1397             time_es.innerHTML = es.fixed(2)+" sec";
1398             time_es.style.display
1399                 = "block";
1400         }
1401     }
1402     progress_bar.style.width = width + "px";
1406 function frame_breakout(e, properties) {
1407     this.setAttribute('target', properties.framename);
1411 // ===== Deprecated core Javascript functions for Moodle ====
1412 //       DO NOT USE!!!!!!!
1413 // Do not put this stuff in separate file because it only adds extra load on servers!
1415 /**
1416  * Used in a couple of modules to hide navigation areas when using AJAX
1417  */
1418 function hide_item(itemid) {
1419     // use class='hiddenifjs' instead
1420     var item = document.getElementById(itemid);
1421     if (item) {
1422         item.style.display = "none";
1423     }
1426 M.util.help_icon = {
1427     Y : null,
1428     instance : null,
1429     add : function(Y, properties) {
1430         this.Y = Y;
1431         properties.node = Y.one('#'+properties.id);
1432         if (properties.node) {
1433             properties.node.on('click', this.display, this, properties);
1434         }
1435     },
1436     display : function(event, args) {
1437         event.preventDefault();
1438         if (M.util.help_icon.instance === null) {
1439             var Y = M.util.help_icon.Y;
1440             Y.use('overlay', 'io', 'event-mouseenter', 'node', 'event-key', function(Y) {
1441                 var help_content_overlay = {
1442                     helplink : null,
1443                     overlay : null,
1444                     init : function() {
1446                         var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img  src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
1447                         // Create an overlay from markup
1448                         this.overlay = new Y.Overlay({
1449                             headerContent: closebtn,
1450                             bodyContent: '',
1451                             id: 'helppopupbox',
1452                             width:'400px',                            
1453                             visible : false,
1454                             constrain : true                            
1455                         });
1456                         this.overlay.render(Y.one(document.body));
1458                         closebtn.on('click', this.overlay.hide, this.overlay);
1459                         
1460                         var boundingBox = this.overlay.get("boundingBox");
1462                         //  Hide the menu if the user clicks outside of its content
1463                         boundingBox.get("ownerDocument").on("mousedown", function (event) {
1464                             var oTarget = event.target;
1465                             var menuButton = Y.one("#"+args.id);
1466                             
1467                             if (!oTarget.compareTo(menuButton) &&
1468                                 !menuButton.contains(oTarget) &&
1469                                 !oTarget.compareTo(boundingBox) &&
1470                                 !boundingBox.contains(oTarget)) {
1471                                 this.overlay.hide();
1472                             }
1473                         }, this);
1475                         Y.on("key", this.close, closebtn , "down:13", this);
1476                         closebtn.on('click', this.close, this);
1477                     },
1479                     close : function(e) {
1480                         e.preventDefault();
1481                         this.helplink.focus();
1482                         this.overlay.hide();
1483                     },
1485                     display : function(event, args) {
1486                         this.helplink = args.node;
1487                         this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1488                         this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
1490                         var fullurl = args.url;
1491                         if (!args.url.match(/https?:\/\//)) {
1492                             fullurl = M.cfg.wwwroot + args.url;
1493                         }
1495                         var ajaxurl = fullurl + '&ajax=1';
1497                         var cfg = {
1498                             method: 'get',
1499                             context : this,
1500                             on: {
1501                                 success: function(id, o, node) {
1502                                     this.display_callback(o.responseText);
1503                                 },
1504                                 failure: function(id, o, node) {
1505                                     var debuginfo = o.statusText;
1506                                     if (M.cfg.developerdebug) {
1507                                         o.statusText += ' (' + ajaxurl + ')';
1508                                     }
1509                                     this.display_callback('bodyContent',debuginfo);
1510                                 }
1511                             }
1512                         };
1514                         Y.io(ajaxurl, cfg);
1515                         this.overlay.show();
1517                         Y.one('#closehelpbox').focus();
1518                     },
1520                     display_callback : function(content) {
1521                         this.overlay.set('bodyContent', content);
1522                     },
1524                     hideContent : function() {
1525                         help = this;
1526                         help.overlay.hide();
1527                     }
1528                 }
1529                 help_content_overlay.init();
1530                 M.util.help_icon.instance = help_content_overlay;
1531                 M.util.help_icon.instance.display(event, args);
1532             });
1533         } else {
1534             M.util.help_icon.instance.display(event, args);
1535         }
1536     },
1537     init : function(Y) {                
1538         this.Y = Y;        
1539     }
1542 /**
1543  * Custom menu namespace
1544  */
1545 M.core_custom_menu = {
1546     /**
1547      * This method is used to initialise a custom menu given the id that belongs
1548      * to the custom menu's root node.
1549      * 
1550      * @param {YUI} Y
1551      * @param {string} nodeid
1552      */
1553     init : function(Y, nodeid) {
1554         var node = Y.one('#'+nodeid);
1555         if (node) {
1556             Y.use('node-menunav', function(Y) {
1557                 // Get the node
1558                 // Remove the javascript-disabled class.... obviously javascript is enabled.
1559                 node.removeClass('javascript-disabled');
1560                 // Initialise the menunav plugin
1561                 node.plug(Y.Plugin.NodeMenuNav);
1562             });
1563         }
1564     }
1567 /**
1568  * Used to store form manipulation methods and enhancments
1569  */
1570 M.form = M.form || {};
1572 /**
1573  * Converts a nbsp indented select box into a multi drop down custom control much
1574  * like the custom menu. It also selectable categories on or off.
1575  *
1576  * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1577  *
1578  * @param {YUI} Y
1579  * @param {string} id
1580  * @param {Array} options
1581  */
1582 M.form.init_smartselect = function(Y, id, options) {
1583     if (!id.match(/^id_/)) {
1584         id = 'id_'+id;
1585     }
1586     var select = Y.one('select#'+id);
1587     if (!select) {
1588         return false;
1589     }
1590     Y.use('event-delegate',function(){
1591         var smartselect = {
1592             id : id,
1593             structure : [],
1594             options : [],
1595             submenucount : 0,
1596             currentvalue : null,
1597             currenttext : null,
1598             shownevent : null,
1599             cfg : {
1600                 selectablecategories : true,
1601                 mode : null
1602             },
1603             nodes : {
1604                 select : null,
1605                 loading : null,
1606                 menu : null
1607             },
1608             init : function(Y, id, args, nodes) {
1609                 if (typeof(args)=='object') {
1610                     for (var i in this.cfg) {
1611                         if (args[i] || args[i]===false) {
1612                             this.cfg[i] = args[i];
1613                         }
1614                     }
1615                 }
1616                 
1617                 // Display a loading message first up
1618                 this.nodes.select = nodes.select;
1620                 this.currentvalue = this.nodes.select.get('selectedIndex');
1621                 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1623                 var options = Array();
1624                 options[''] = {text:this.currenttext,value:'',depth:0,children:[]}
1625                 this.nodes.select.all('option').each(function(option, index) {
1626                     var rawtext = option.get('innerHTML');
1627                     var text = rawtext.replace(/^(&nbsp;)*/, '');
1628                     if (rawtext === text) {
1629                         text = rawtext.replace(/^(\s)*/, '');
1630                         var depth = (rawtext.length - text.length ) + 1;
1631                     } else {
1632                         var depth = ((rawtext.length - text.length )/12)+1;
1633                     }
1634                     option.set('innerHTML', text);
1635                     options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1636                 }, this);
1638                 this.structure = [];
1639                 var structcount = 0;
1640                 for (var i in options) {
1641                     var o = options[i];
1642                     if (o.depth == 0) {
1643                         this.structure.push(o);
1644                         structcount++;
1645                     } else {
1646                         var d = o.depth;
1647                         var current = this.structure[structcount-1];
1648                         for (var j = 0; j < o.depth-1;j++) {
1649                             if (current && current.children) {
1650                                 current = current.children[current.children.length-1];
1651                             }
1652                         }
1653                         if (current && current.children) {
1654                             current.children.push(o);
1655                         }
1656                     }
1657                 }
1659                 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1660                 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1661                 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1662                 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1664                 if (this.cfg.mode == null) {
1665                     var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1666                     if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1667                         this.cfg.mode = 'compact';
1668                     } else {
1669                         this.cfg.mode = 'spanning';
1670                     }
1671                 }
1673                 if (this.cfg.mode == 'compact') {
1674                     this.nodes.menu.addClass('compactmenu');
1675                 } else {
1676                     this.nodes.menu.addClass('spanningmenu');
1677                     this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1678                 }
1680                 Y.one(document.body).append(this.nodes.menu);
1681                 var pos = this.nodes.select.getXY();
1682                 pos[0] += 1;
1683                 this.nodes.menu.setXY(pos);
1684                 this.nodes.menu.on('click', this.handle_click, this);
1686                 Y.one(window).on('resize', function(){
1687                      var pos = this.nodes.select.getXY();
1688                     pos[0] += 1;
1689                     this.nodes.menu.setXY(pos);
1690                  }, this);
1691             },
1692             generate_menu_content : function() {
1693                 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1694                 content += this.generate_submenu_content(this.structure[0], true);
1695                 content += '</ul></div>';
1696                 return content;
1697             },
1698             generate_submenu_content : function(item, rootelement) {
1699                 this.submenucount++;
1700                 var content = '';
1701                 if (item.children.length > 0) {
1702                     if (rootelement) {
1703                         content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1704                         content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1705                         content += '<div class="smartselect_menu_content">';
1706                     } else {
1707                         content += '<li class="smartselect_submenuitem">';
1708                         var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1709                         content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1710                         content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1711                         content += '<div class="smartselect_submenu_content">';
1712                     }
1713                     content += '<ul>';
1714                     for (var i in item.children) {
1715                         content += this.generate_submenu_content(item.children[i],false);
1716                     }
1717                     content += '</ul>';
1718                     content += '</div>';
1719                     content += '</div>';
1720                     if (rootelement) {
1721                     } else {
1722                         content += '</li>';
1723                     }
1724                 } else {
1725                     content += '<li class="smartselect_menuitem">';
1726                     content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1727                     content += '</li>';
1728                 }
1729                 return content;
1730             },
1731             select : function(e) {
1732                 var t = e.target;
1733                 e.halt();
1734                 this.currenttext = t.get('innerHTML');
1735                 this.currentvalue = t.getAttribute('value');
1736                 this.nodes.select.set('selectedIndex', this.currentvalue);
1737                 this.hide_menu();
1738             },
1739             handle_click : function(e) {
1740                 var target = e.target;
1741                 if (target.hasClass('smartselect_mask')) {
1742                     this.show_menu(e);
1743                 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1744                     this.select(e);
1745                 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1746                     this.show_sub_menu(e);
1747                 }
1748             },
1749             show_menu : function(e) {
1750                 e.halt();
1751                 var menu = e.target.ancestor().one('.smartselect_menu');
1752                 menu.addClass('visible');
1753                 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1754             },
1755             show_sub_menu : function(e) {
1756                 e.halt();
1757                 var target = e.target;
1758                 if (!target.hasClass('smartselect_submenuitem')) {
1759                     target = target.ancestor('.smartselect_submenuitem');
1760                 }
1761                 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1762                     target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1763                     return;
1764                 }
1765                 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1766                 target.one('.smartselect_submenu').addClass('visible');
1767             },
1768             hide_menu : function() {
1769                 this.nodes.menu.all('.visible').removeClass('visible');
1770                 if (this.shownevent) {
1771                     this.shownevent.detach();
1772                 }
1773             }
1774         }
1775         smartselect.init(Y, id, options, {select:select});
1776     });