51f073914315e738e4c4ae88dbe172d0808bd8f0
[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 /**
1029  * Search a Moodle form to find all the fdate_time_selector and fdate_selector
1030  * elements, and add date_selector_calendar instance to each.
1031  */
1032 function init_date_selectors(firstdayofweek) {
1033     var els = YAHOO.util.Dom.getElementsByClassName('fdate_time_selector', 'fieldset');
1034     for (var i = 0; i < els.length; i++) {
1035         new date_selector_calendar(els[i], firstdayofweek);
1036     }
1037     els = YAHOO.util.Dom.getElementsByClassName('fdate_selector', 'fieldset');
1038     for (i = 0; i < els.length; i++) {
1039         new date_selector_calendar(els[i], firstdayofweek);
1040     }
1043 /**
1044  * Constructor for a JavaScript object that connects to a fdate_time_selector
1045  * or a fdate_selector in a Moodle form, and shows a popup calendar whenever
1046  * that element has keyboard focus.
1047  * @param el the fieldset class="fdate_time_selector" or "fdate_selector".
1048  */
1049 function date_selector_calendar(el, firstdayofweek) {
1050     // Ensure that the shared div and calendar exist.
1051     if (!date_selector_calendar.panel) {
1052         date_selector_calendar.panel = new YAHOO.widget.Panel('date_selector_calendar_panel',
1053                 {visible: false, draggable: false});
1054         var div = document.createElement('div');
1055         date_selector_calendar.panel.setBody(div);
1056         date_selector_calendar.panel.render(document.body);
1058         YAHOO.util.Event.addListener(document, 'click', date_selector_calendar.document_click);
1059         date_selector_calendar.panel.showEvent.subscribe(function() {
1060             date_selector_calendar.panel.fireEvent('changeContent');
1061         });
1062         date_selector_calendar.panel.hideEvent.subscribe(date_selector_calendar.release_current);
1064         date_selector_calendar.calendar = new YAHOO.widget.Calendar(div,
1065                 {iframe: false, hide_blank_weeks: true, start_weekday: firstdayofweek});
1066         date_selector_calendar.calendar.renderEvent.subscribe(function() {
1067             date_selector_calendar.panel.fireEvent('changeContent');
1068             date_selector_calendar.delayed_reposition();
1069         });
1070     }
1072     this.fieldset = el;
1073     var controls = el.getElementsByTagName('select');
1074     for (var i = 0; i < controls.length; i++) {
1075         if (/\[year\]$/.test(controls[i].name)) {
1076             this.yearselect = controls[i];
1077         } else if (/\[month\]$/.test(controls[i].name)) {
1078             this.monthselect = controls[i];
1079         } else if (/\[day\]$/.test(controls[i].name)) {
1080             this.dayselect = controls[i];
1081         } else {
1082             YAHOO.util.Event.addFocusListener(controls[i], date_selector_calendar.cancel_any_timeout, this);
1083             YAHOO.util.Event.addBlurListener(controls[i], this.blur_event, this);
1084         }
1085     }
1086     if (!(this.yearselect && this.monthselect && this.dayselect)) {
1087         throw 'Failed to initialise calendar.';
1088     }
1089     YAHOO.util.Event.addFocusListener([this.yearselect, this.monthselect, this.dayselect], this.focus_event, this);
1090     YAHOO.util.Event.addBlurListener([this.yearselect, this.monthselect, this.dayselect], this.blur_event, this);
1092     this.enablecheckbox = el.getElementsByTagName('input')[0];
1093     if (this.enablecheckbox) {
1094         YAHOO.util.Event.addFocusListener(this.enablecheckbox, this.focus_event, this);
1095         YAHOO.util.Event.addListener(this.enablecheckbox, 'change', this.focus_event, this);
1096         YAHOO.util.Event.addBlurListener(this.enablecheckbox, this.blur_event, this);
1097     }
1100 /** The pop-up calendar that contains the calendar. */
1101 date_selector_calendar.panel = null;
1103 /** The shared YAHOO.widget.Calendar used by all date_selector_calendars. */
1104 date_selector_calendar.calendar = null;
1106 /** The date_selector_calendar that currently owns the shared stuff. */
1107 date_selector_calendar.currentowner = null;
1109 /** Used as a timeout when hiding the calendar on blur - so we don't hide the calendar
1110  * if we are just jumping from on of our controls to another. */
1111 date_selector_calendar.hidetimeout = null;
1113 /** Timeout for repositioning after a delay after a change of months. */
1114 date_selector_calendar.repositiontimeout = null;
1116 /** Member variables. Pointers to various bits of the DOM. */
1117 date_selector_calendar.prototype.fieldset = null;
1118 date_selector_calendar.prototype.yearselect = null;
1119 date_selector_calendar.prototype.monthselect = null;
1120 date_selector_calendar.prototype.dayselect = null;
1121 date_selector_calendar.prototype.enablecheckbox = null;
1123 date_selector_calendar.cancel_any_timeout = function() {
1124     if (date_selector_calendar.hidetimeout) {
1125         clearTimeout(date_selector_calendar.hidetimeout);
1126         date_selector_calendar.hidetimeout = null;
1127     }
1128     if (date_selector_calendar.repositiontimeout) {
1129         clearTimeout(date_selector_calendar.repositiontimeout);
1130         date_selector_calendar.repositiontimeout = null;
1131     }
1134 date_selector_calendar.delayed_reposition = function() {
1135     if (date_selector_calendar.repositiontimeout) {
1136         clearTimeout(date_selector_calendar.repositiontimeout);
1137         date_selector_calendar.repositiontimeout = null;
1138     }
1139     date_selector_calendar.repositiontimeout = setTimeout(date_selector_calendar.fix_position, 500);
1142 date_selector_calendar.fix_position = function() {
1143     if (date_selector_calendar.currentowner) {
1144         date_selector_calendar.panel.cfg.setProperty('context', [date_selector_calendar.currentowner.fieldset, 'bl', 'tl']);
1145     }
1148 date_selector_calendar.release_current = function() {
1149     if (date_selector_calendar.currentowner) {
1150         date_selector_calendar.currentowner.release_calendar();
1151     }
1154 date_selector_calendar.prototype.focus_event = function(e, me) {
1155     date_selector_calendar.cancel_any_timeout();
1156     if (me.enablecheckbox == null || me.enablecheckbox.checked) {
1157         me.claim_calendar();
1158     } else {
1159         if (date_selector_calendar.currentowner) {
1160             date_selector_calendar.currentowner.release_calendar();
1161         }
1162     }
1165 date_selector_calendar.prototype.blur_event = function(e, me) {
1166     date_selector_calendar.hidetimeout = setTimeout(date_selector_calendar.release_current, 300);
1169 date_selector_calendar.prototype.handle_select_change = function(e, me) {
1170     me.set_date_from_selects();
1173 date_selector_calendar.document_click = function(event) {
1174     if (date_selector_calendar.currentowner) {
1175         var currentcontainer = date_selector_calendar.currentowner.fieldset;
1176         var eventarget = YAHOO.util.Event.getTarget(event);
1177         if (YAHOO.util.Dom.isAncestor(currentcontainer, eventarget)) {
1178             setTimeout(function() {date_selector_calendar.cancel_any_timeout()}, 100);
1179         } else {
1180             date_selector_calendar.currentowner.release_calendar();
1181         }
1182     }
1185 date_selector_calendar.prototype.claim_calendar = function() {
1186     date_selector_calendar.cancel_any_timeout();
1187     if (date_selector_calendar.currentowner == this) {
1188         return;
1189     }
1190     if (date_selector_calendar.currentowner) {
1191         date_selector_calendar.currentowner.release_calendar();
1192     }
1194     if (date_selector_calendar.currentowner != this) {
1195         this.connect_handlers();
1196     }
1197     date_selector_calendar.currentowner = this;
1199     date_selector_calendar.calendar.cfg.setProperty('mindate', new Date(this.yearselect.options[0].value, 0, 1));
1200     date_selector_calendar.calendar.cfg.setProperty('maxdate', new Date(this.yearselect.options[this.yearselect.options.length - 1].value, 11, 31));
1201     this.fieldset.insertBefore(date_selector_calendar.panel.element, this.yearselect.nextSibling);
1202     this.set_date_from_selects();
1203     date_selector_calendar.panel.show();
1204     var me = this;
1205     setTimeout(function() {date_selector_calendar.cancel_any_timeout()}, 100);
1208 date_selector_calendar.prototype.set_date_from_selects = function() {
1209     var year = parseInt(this.yearselect.value);
1210     var month = parseInt(this.monthselect.value) - 1;
1211     var day = parseInt(this.dayselect.value);
1212     date_selector_calendar.calendar.select(new Date(year, month, day));
1213     date_selector_calendar.calendar.setMonth(month);
1214     date_selector_calendar.calendar.setYear(year);
1215     date_selector_calendar.calendar.render();
1216     date_selector_calendar.fix_position();
1219 date_selector_calendar.prototype.set_selects_from_date = function(eventtype, args) {
1220     var date = args[0][0];
1221     var newyear = date[0];
1222     var newindex = newyear - this.yearselect.options[0].value;
1223     this.yearselect.selectedIndex = newindex;
1224     this.monthselect.selectedIndex = date[1] - this.monthselect.options[0].value;
1225     this.dayselect.selectedIndex = date[2] - this.dayselect.options[0].value;
1228 date_selector_calendar.prototype.connect_handlers = function() {
1229     YAHOO.util.Event.addListener([this.yearselect, this.monthselect, this.dayselect], 'change', this.handle_select_change, this);
1230     date_selector_calendar.calendar.selectEvent.subscribe(this.set_selects_from_date, this, true);
1233 date_selector_calendar.prototype.release_calendar = function() {
1234     date_selector_calendar.panel.hide();
1235     date_selector_calendar.currentowner = null;
1236     YAHOO.util.Event.removeListener([this.yearselect, this.monthselect, this.dayselect], this.handle_select_change);
1237     date_selector_calendar.calendar.selectEvent.unsubscribe(this.set_selects_from_date, this);
1240 function filterByParent(elCollection, parentFinder) {
1241     var filteredCollection = [];
1242     for (var i = 0; i < elCollection.length; ++i) {
1243         var findParent = parentFinder(elCollection[i]);
1244         if (findParent.nodeName.toUpperCase != 'BODY') {
1245             filteredCollection.push(elCollection[i]);
1246         }
1247     }
1248     return filteredCollection;
1251 /*
1252     All this is here just so that IE gets to handle oversized blocks
1253     in a visually pleasing manner. It does a browser detect. So sue me.
1254 */
1256 function fix_column_widths() {
1257     var agt = navigator.userAgent.toLowerCase();
1258     if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1259         fix_column_width('left-column');
1260         fix_column_width('right-column');
1261     }
1264 function fix_column_width(colName) {
1265     if(column = document.getElementById(colName)) {
1266         if(!column.offsetWidth) {
1267             setTimeout("fix_column_width('" + colName + "')", 20);
1268             return;
1269         }
1271         var width = 0;
1272         var nodes = column.childNodes;
1274         for(i = 0; i < nodes.length; ++i) {
1275             if(nodes[i].className.indexOf("block") != -1 ) {
1276                 if(width < nodes[i].offsetWidth) {
1277                     width = nodes[i].offsetWidth;
1278                 }
1279             }
1280         }
1282         for(i = 0; i < nodes.length; ++i) {
1283             if(nodes[i].className.indexOf("block") != -1 ) {
1284                 nodes[i].style.width = width + 'px';
1285             }
1286         }
1287     }
1291 /*
1292    Insert myValue at current cursor position
1293  */
1294 function insertAtCursor(myField, myValue) {
1295     // IE support
1296     if (document.selection) {
1297         myField.focus();
1298         sel = document.selection.createRange();
1299         sel.text = myValue;
1300     }
1301     // Mozilla/Netscape support
1302     else if (myField.selectionStart || myField.selectionStart == '0') {
1303         var startPos = myField.selectionStart;
1304         var endPos = myField.selectionEnd;
1305         myField.value = myField.value.substring(0, startPos)
1306             + myValue + myField.value.substring(endPos, myField.value.length);
1307     } else {
1308         myField.value += myValue;
1309     }
1313 /*
1314         Call instead of setting window.onload directly or setting body onload=.
1315         Adds your function to a chain of functions rather than overwriting anything
1316         that exists.
1317 */
1318 function addonload(fn) {
1319     var oldhandler=window.onload;
1320     window.onload=function() {
1321         if(oldhandler) oldhandler();
1322             fn();
1323     }
1325 /**
1326  * Replacement for getElementsByClassName in browsers that aren't cool enough
1327  *
1328  * Relying on the built-in getElementsByClassName is far, far faster than
1329  * using YUI.
1330  *
1331  * Note: the third argument used to be an object with odd behaviour. It now
1332  * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1333  * mimicked if you pass an object.
1334  *
1335  * @param {Node} oElm The top-level node for searching. To search a whole
1336  *                    document, use `document`.
1337  * @param {String} strTagName filter by tag names
1338  * @param {String} name same as HTML5 spec
1339  */
1340 function getElementsByClassName(oElm, strTagName, name) {
1341     // for backwards compatibility
1342     if(typeof name == "object") {
1343         var names = new Array();
1344         for(var i=0; i<name.length; i++) names.push(names[i]);
1345         name = names.join('');
1346     }
1347     // use native implementation if possible
1348     if (oElm.getElementsByClassName && Array.filter) {
1349         if (strTagName == '*') {
1350             return oElm.getElementsByClassName(name);
1351         } else {
1352             return Array.filter(oElm.getElementsByClassName(name), function(el) {
1353                 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1354             });
1355         }
1356     }
1357     // native implementation unavailable, fall back to slow method
1358     var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1359     var arrReturnElements = new Array();
1360     var arrRegExpClassNames = new Array();
1361     var names = name.split(' ');
1362     for(var i=0; i<names.length; i++) {
1363         arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1364     }
1365     var oElement;
1366     var bMatchesAll;
1367     for(var j=0; j<arrElements.length; j++) {
1368         oElement = arrElements[j];
1369         bMatchesAll = true;
1370         for(var k=0; k<arrRegExpClassNames.length; k++) {
1371             if(!arrRegExpClassNames[k].test(oElement.className)) {
1372                 bMatchesAll = false;
1373                 break;
1374             }
1375         }
1376         if(bMatchesAll) {
1377             arrReturnElements.push(oElement);
1378         }
1379     }
1380     return (arrReturnElements)
1383 function openpopup(event, args) {
1385     if (event) {
1386         YAHOO.util.Event.preventDefault(event);
1387     }
1388    
1389     var fullurl = args.url;
1390     if (!args.url.match(/https?:\/\//)) {
1391         fullurl = M.cfg.wwwroot + args.url;
1392     }
1393     var windowobj = window.open(fullurl,args.name,args.options);
1394     if (!windowobj) {
1395         return true;
1396     }
1397     if (args.fullscreen) {
1398         windowobj.moveTo(0,0);
1399         windowobj.resizeTo(screen.availWidth,screen.availHeight);
1400     }
1401     windowobj.focus();
1402     
1403     return false;
1406 /* This is only used on a few help pages. */
1407 emoticons_help = {
1408     inputarea: null,
1410     init: function(formname, fieldname, listid) {
1411         if (!opener || !opener.document.forms[formname]) {
1412             return;
1413         }
1414         emoticons_help.inputarea = opener.document.forms[formname][fieldname];
1415         if (!emoticons_help.inputarea) {
1416             return;
1417         }
1418         var emoticons = document.getElementById(listid).getElementsByTagName('li');
1419         for (var i = 0; i < emoticons.length; i++) {
1420             var text = emoticons[i].getElementsByTagName('img')[0].alt;
1421             YAHOO.util.Event.addListener(emoticons[i], 'click', emoticons_help.inserttext, text);
1422         }
1423     },
1425     inserttext: function(e, text) {
1426         text = ' ' + text + ' ';
1427         if (emoticons_help.inputarea.createTextRange && emoticons_help.inputarea.caretPos) {
1428             var caretPos = emoticons_help.inputarea.caretPos;
1429             caretPos.text = caretPos.text.charAt(caretPos.text.length - 1) == ' ' ? text + ' ' : text;
1430         } else {
1431             emoticons_help.inputarea.value  += text;
1432         }
1433         emoticons_help.inputarea.focus();
1434     }
1437 /**
1438  * Oject to handle expanding and collapsing blocks when an icon is clicked on.
1439  * @constructor
1440  * @param String id the HTML id for the div.
1441  * @param String userpref the user preference that records the state of this block.
1442  * @param String visibletooltip tool tip/alt to show when the block is visible.
1443  * @param String hiddentooltip tool tip/alt to show when the block is hidden.
1444  * @param String visibleicon URL of the icon to show when the block is visible.
1445  * @param String hiddenicon URL of the icon to show when the block is hidden.
1446  */
1447 function block_hider(id, userpref, visibletooltip, hiddentooltip, visibleicon, hiddenicon) {
1448     // Find the elemen that is the block.
1449     this.block = document.getElementById(id);
1450     var title_div = YAHOO.util.Dom.getElementsByClassName('title', 'div', this.block);
1451     if (!title_div || !title_div[0]) {
1452         return this;
1453     }
1454     title_div = title_div[0];
1455     this.ishidden = YAHOO.util.Dom.hasClass(this.block, 'hidden');
1457     // Record the pref name
1458     this.userpref = userpref;
1459     this.visibletooltip = visibletooltip;
1460     this.hiddentooltip = hiddentooltip;
1461     this.visibleicon = visibleicon;
1462     this.hiddenicon = hiddenicon;
1464     // Add the icon.
1465     this.icon = document.createElement('input');
1466     this.icon.type = 'image';
1467     this.update_state();
1469     var blockactions = YAHOO.util.Dom.getElementsByClassName('block_action', 'div', title_div);
1470     if (blockactions && blockactions[0]) {
1471         blockactions[0].insertBefore(this.icon, blockactions[0].firstChild);
1472     }
1474     // Hook up the event handler.
1475     YAHOO.util.Event.addListener(this.icon, 'click', this.handle_click, null, this);
1478 /** Handle click on a block show/hide icon. */
1479 block_hider.prototype.handle_click = function(e) {
1480     YAHOO.util.Event.stopEvent(e);
1481     this.ishidden = !this.ishidden;
1482     this.update_state();
1483     M.util.set_user_preference(this.userpref, this.ishidden);
1486 /** Set the state of the block show/hide icon to this.ishidden. */
1487 block_hider.prototype.update_state = function () {
1488     if (this.ishidden) {
1489         YAHOO.util.Dom.addClass(this.block, 'hidden');
1490         this.icon.alt = this.hiddentooltip;
1491         this.icon.title = this.hiddentooltip;
1492         this.icon.src = this.hiddenicon;
1493     } else {
1494         YAHOO.util.Dom.removeClass(this.block, 'hidden');
1495         this.icon.alt = this.visibletooltip;
1496         this.icon.title = this.visibletooltip;
1497         this.icon.src = this.visibleicon;
1498     }
1501 /** Close the current browser window. */
1502 function close_window(e) {
1503     YAHOO.util.Event.preventDefault(e);
1504     self.close();
1507 /**
1508  * Close the current browser window, forcing the window/tab that opened this
1509  * popup to reload itself. */
1510 function close_window_reloading_opener() {
1511     if (window.opener) {
1512         window.opener.location.reload(1);
1513         close_window();
1514         // Intentionally, only try to close the window if there is some evidence we are in a popup.
1515     }
1518 /**
1519  * Used in a couple of modules to hide navigation areas when using AJAX
1520  */
1522 function show_item(itemid) {
1523     var item = document.getElementById(itemid);
1524     if (item) {
1525         item.style.display = "";
1526     }
1529 function destroy_item(itemid) {
1530     var item = document.getElementById(itemid);
1531     if (item) {
1532         item.parentNode.removeChild(item);
1533     }
1535 /**
1536  * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1537  * @param controlid the control id.
1538  */
1539 function focuscontrol(controlid) {
1540     var control = document.getElementById(controlid);
1541     if (control) {
1542         control.focus();
1543     }
1546 /**
1547  * Transfers keyboard focus to an HTML element based on the old style style of focus
1548  * This function should be removed as soon as it is no longer used
1549  */
1550 function old_onload_focus(formid, controlname) {
1551     if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1552         document.forms[formid].elements[controlname].focus();
1553     }
1556 function build_querystring(obj) {
1557     return convert_object_to_string(obj, '&');
1560 function build_windowoptionsstring(obj) {
1561     return convert_object_to_string(obj, ',');
1564 function convert_object_to_string(obj, separator) {
1565     if (typeof obj !== 'object') {
1566         return null;
1567     }
1568     var list = [];
1569     for(var k in obj) {
1570         k = encodeURIComponent(k);
1571         var value = obj[k];
1572         if(obj[k] instanceof Array) {
1573             for(var i in value) {
1574                 list.push(k+'[]='+encodeURIComponent(value[i]));
1575             }
1576         } else {
1577             list.push(k+'='+encodeURIComponent(value));
1578         }
1579     }
1580     return list.join(separator);
1583 function stripHTML(str) {
1584     var re = /<\S[^><]*>/g;
1585     var ret = str.replace(re, "");
1586     return ret;
1589 Number.prototype.fixed=function(n){
1590     with(Math)
1591         return round(Number(this)*pow(10,n))/pow(10,n);
1593 function update_progress_bar (id, width, pt, msg, es){
1594     var percent = pt*100;
1595     var status = document.getElementById("status_"+id);
1596     var percent_indicator = document.getElementById("pt_"+id);
1597     var progress_bar = document.getElementById("progress_"+id);
1598     var time_es = document.getElementById("time_"+id);
1599     status.innerHTML = msg;
1600     percent_indicator.innerHTML = percent.fixed(2) + '%';
1601     if(percent == 100) {
1602         progress_bar.style.background = "green";
1603         time_es.style.display = "none";
1604     } else {
1605         progress_bar.style.background = "#FFCC66";
1606         if (es == Infinity){
1607             time_es.innerHTML = "Initializing...";
1608         }else {
1609             time_es.innerHTML = es.fixed(2)+" sec";
1610             time_es.style.display
1611                 = "block";
1612         }
1613     }
1614     progress_bar.style.width = width + "px";
1618 function frame_breakout(e, properties) {
1619     this.setAttribute('target', properties.framename);
1623 // ===== Deprecated core Javascript functions for Moodle ====
1624 //       DO NOT USE!!!!!!!
1625 // Do not put this stuff in separate file because it only adds extra load on servers!
1627 /**
1628  * Used in a couple of modules to hide navigation areas when using AJAX
1629  */
1630 function hide_item(itemid) {
1631     // use class='hiddenifjs' instead
1632     var item = document.getElementById(itemid);
1633     if (item) {
1634         item.style.display = "none";
1635     }
1638 M.util.help_icon = {
1639     Y : null,
1640     instance : null,
1641     add : function(Y, properties) {
1642         this.Y = Y;
1643         properties.node = Y.one('#'+properties.id);
1644         if (properties.node) {
1645             properties.node.on('click', this.display, this, properties);
1646         }
1647     },
1648     display : function(event, args) {
1649         event.preventDefault();
1650         if (M.util.help_icon.instance === null) {
1651             var Y = M.util.help_icon.Y;
1652             Y.use('overlay', 'io', 'event-mouseenter', 'node', 'event-key', function(Y) {
1653                 var help_content_overlay = {
1654                     helplink : null,
1655                     overlay : null,
1656                     init : function() {
1658                         var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img  src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
1659                         // Create an overlay from markup
1660                         this.overlay = new Y.Overlay({
1661                             headerContent: closebtn,
1662                             bodyContent: '',
1663                             id: 'helppopupbox',
1664                             width:'400px',                            
1665                             visible : false,
1666                             constrain : true                            
1667                         });
1668                         this.overlay.render(Y.one(document.body));
1670                         closebtn.on('click', this.overlay.hide, this.overlay);
1671                         
1672                         var boundingBox = this.overlay.get("boundingBox");
1674                         //  Hide the menu if the user clicks outside of its content
1675                         boundingBox.get("ownerDocument").on("mousedown", function (event) {
1676                             var oTarget = event.target;
1677                             var menuButton = Y.one("#"+args.id);
1678                             
1679                             if (!oTarget.compareTo(menuButton) &&
1680                                 !menuButton.contains(oTarget) &&
1681                                 !oTarget.compareTo(boundingBox) &&
1682                                 !boundingBox.contains(oTarget)) {
1683                                 this.overlay.hide();
1684                             }
1685                         }, this);
1687                         Y.on("key", this.close, closebtn , "down:13", this);
1688                         closebtn.on('click', this.close, this);
1689                     },
1691                     close : function(e) {
1692                         e.preventDefault();
1693                         this.helplink.focus();
1694                         this.overlay.hide();
1695                     },
1697                     display : function(event, args) {
1698                         this.helplink = args.node;
1699                         this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1700                         this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
1702                         var fullurl = args.url;
1703                         if (!args.url.match(/https?:\/\//)) {
1704                             fullurl = M.cfg.wwwroot + args.url;
1705                         }
1707                         var ajaxurl = fullurl + '&ajax=1';
1709                         var cfg = {
1710                             method: 'get',
1711                             context : this,
1712                             on: {
1713                                 success: function(id, o, node) {
1714                                     this.display_callback(o.responseText);
1715                                 },
1716                                 failure: function(id, o, node) {
1717                                     var debuginfo = o.statusText;
1718                                     if (M.cfg.developerdebug) {
1719                                         o.statusText += ' (' + ajaxurl + ')';
1720                                     }
1721                                     this.display_callback('bodyContent',debuginfo);
1722                                 }
1723                             }
1724                         };
1726                         Y.io(ajaxurl, cfg);
1727                         this.overlay.show();
1729                         Y.one('#closehelpbox').focus();
1730                     },
1732                     display_callback : function(content) {
1733                         this.overlay.set('bodyContent', content);
1734                     },
1736                     hideContent : function() {
1737                         help = this;
1738                         help.overlay.hide();
1739                     }
1740                 }
1741                 help_content_overlay.init();
1742                 M.util.help_icon.instance = help_content_overlay;
1743                 M.util.help_icon.instance.display(event, args);
1744             });
1745         } else {
1746             M.util.help_icon.instance.display(event, args);
1747         }
1748     },
1749     init : function(Y) {                
1750         this.Y = Y;        
1751     }
1754 /**
1755  * Custom menu namespace
1756  */
1757 M.core_custom_menu = {
1758     /**
1759      * This method is used to initialise a custom menu given the id that belongs
1760      * to the custom menu's root node.
1761      * 
1762      * @param {YUI} Y
1763      * @param {string} nodeid
1764      */
1765     init : function(Y, nodeid) {
1766         var node = Y.one('#'+nodeid);
1767         if (node) {
1768             Y.use('node-menunav', function(Y) {
1769                 // Get the node
1770                 // Remove the javascript-disabled class.... obviously javascript is enabled.
1771                 node.removeClass('javascript-disabled');
1772                 // Initialise the menunav plugin
1773                 node.plug(Y.Plugin.NodeMenuNav);
1774             });
1775         }
1776     }
1779 /**
1780  * Used to store form manipulation methods and enhancments
1781  */
1782 M.form = M.form || {};
1784 /**
1785  * Converts a nbsp indented select box into a multi drop down custom control much
1786  * like the custom menu. It also selectable categories on or off.
1787  *
1788  * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1789  *
1790  * @param {YUI} Y
1791  * @param {string} id
1792  * @param {Array} options
1793  */
1794 M.form.init_smartselect = function(Y, id, options) {
1795     if (!id.match(/^id_/)) {
1796         id = 'id_'+id;
1797     }
1798     var select = Y.one('select#'+id);
1799     if (!select) {
1800         return false;
1801     }
1802     Y.use('event-delegate',function(){
1803         var smartselect = {
1804             id : id,
1805             structure : [],
1806             options : [],
1807             submenucount : 0,
1808             currentvalue : null,
1809             currenttext : null,
1810             shownevent : null,
1811             cfg : {
1812                 selectablecategories : true,
1813                 mode : null
1814             },
1815             nodes : {
1816                 select : null,
1817                 loading : null,
1818                 menu : null
1819             },
1820             init : function(Y, id, args, nodes) {
1821                 if (typeof(args)=='object') {
1822                     for (var i in this.cfg) {
1823                         if (args[i] || args[i]===false) {
1824                             this.cfg[i] = args[i];
1825                         }
1826                     }
1827                 }
1828                 
1829                 // Display a loading message first up
1830                 this.nodes.select = nodes.select;
1832                 this.currentvalue = this.nodes.select.get('selectedIndex');
1833                 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1835                 var options = Array();
1836                 options[''] = {text:this.currenttext,value:'',depth:0,children:[]}
1837                 this.nodes.select.all('option').each(function(option, index) {
1838                     var rawtext = option.get('innerHTML');
1839                     var text = rawtext.replace(/^(&nbsp;)*/, '');
1840                     if (rawtext === text) {
1841                         text = rawtext.replace(/^(\s)*/, '');
1842                         var depth = (rawtext.length - text.length ) + 1;
1843                     } else {
1844                         var depth = ((rawtext.length - text.length )/12)+1;
1845                     }
1846                     option.set('innerHTML', text);
1847                     options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1848                 }, this);
1850                 this.structure = [];
1851                 var structcount = 0;
1852                 for (var i in options) {
1853                     var o = options[i];
1854                     if (o.depth == 0) {
1855                         this.structure.push(o);
1856                         structcount++;
1857                     } else {
1858                         var d = o.depth;
1859                         var current = this.structure[structcount-1];
1860                         for (var j = 0; j < o.depth-1;j++) {
1861                             if (current && current.children) {
1862                                 current = current.children[current.children.length-1];
1863                             }
1864                         }
1865                         if (current && current.children) {
1866                             current.children.push(o);
1867                         }
1868                     }
1869                 }
1871                 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1872                 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1873                 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1874                 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1876                 if (this.cfg.mode == null) {
1877                     var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1878                     if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1879                         this.cfg.mode = 'compact';
1880                     } else {
1881                         this.cfg.mode = 'spanning';
1882                     }
1883                 }
1885                 if (this.cfg.mode == 'compact') {
1886                     this.nodes.menu.addClass('compactmenu');
1887                 } else {
1888                     this.nodes.menu.addClass('spanningmenu');
1889                     this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1890                 }
1892                 Y.one(document.body).append(this.nodes.menu);
1893                 var pos = this.nodes.select.getXY();
1894                 pos[0] += 1;
1895                 this.nodes.menu.setXY(pos);
1896                 this.nodes.menu.on('click', this.handle_click, this);
1898                 Y.one(window).on('resize', function(){
1899                      var pos = this.nodes.select.getXY();
1900                     pos[0] += 1;
1901                     this.nodes.menu.setXY(pos);
1902                  }, this);
1903             },
1904             generate_menu_content : function() {
1905                 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1906                 content += this.generate_submenu_content(this.structure[0], true);
1907                 content += '</ul></div>';
1908                 return content;
1909             },
1910             generate_submenu_content : function(item, rootelement) {
1911                 this.submenucount++;
1912                 var content = '';
1913                 if (item.children.length > 0) {
1914                     if (rootelement) {
1915                         content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1916                         content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1917                         content += '<div class="smartselect_menu_content">';
1918                     } else {
1919                         content += '<li class="smartselect_submenuitem">';
1920                         var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1921                         content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1922                         content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1923                         content += '<div class="smartselect_submenu_content">';
1924                     }
1925                     content += '<ul>';
1926                     for (var i in item.children) {
1927                         content += this.generate_submenu_content(item.children[i],false);
1928                     }
1929                     content += '</ul>';
1930                     content += '</div>';
1931                     content += '</div>';
1932                     if (rootelement) {
1933                     } else {
1934                         content += '</li>';
1935                     }
1936                 } else {
1937                     content += '<li class="smartselect_menuitem">';
1938                     content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1939                     content += '</li>';
1940                 }
1941                 return content;
1942             },
1943             select : function(e) {
1944                 var t = e.target;
1945                 e.halt();
1946                 this.currenttext = t.get('innerHTML');
1947                 this.currentvalue = t.getAttribute('value');
1948                 this.nodes.select.set('selectedIndex', this.currentvalue);
1949                 this.hide_menu();
1950             },
1951             handle_click : function(e) {
1952                 var target = e.target;
1953                 if (target.hasClass('smartselect_mask')) {
1954                     this.show_menu(e);
1955                 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1956                     this.select(e);
1957                 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1958                     this.show_sub_menu(e);
1959                 }
1960             },
1961             show_menu : function(e) {
1962                 e.halt();
1963                 var menu = e.target.ancestor().one('.smartselect_menu');
1964                 menu.addClass('visible');
1965                 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1966             },
1967             show_sub_menu : function(e) {
1968                 e.halt();
1969                 var target = e.target;
1970                 if (!target.hasClass('smartselect_submenuitem')) {
1971                     target = target.ancestor('.smartselect_submenuitem');
1972                 }
1973                 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1974                     target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1975                     return;
1976                 }
1977                 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1978                 target.one('.smartselect_submenu').addClass('visible');
1979             },
1980             hide_menu : function() {
1981                 this.nodes.menu.all('.visible').removeClass('visible');
1982                 if (this.shownevent) {
1983                     this.shownevent.detach();
1984                 }
1985             }
1986         }
1987         smartselect.init(Y, id, options, {select:select});
1988     });