MDL-21963 rewritten progress bar internals, hopefully working now properly
[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     });
288 };
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('maincontent', 'width') - 15;
314         if (newwidth > 600) {
315             obj.setStyle('width', newwidth  + 'px');
316         } else {
317             obj.setStyle('width', '600px');
318         }
320         var headerheight = get_htmlelement_size('page-header', 'height');
321         var footerheight = get_htmlelement_size('page-footer', 'height');
322         var newheight = parseInt(YAHOO.util.Dom.getViewportHeight()) - footerheight - headerheight - 20;
323         if (newheight < 400) {
324             newheight = 400;
325         }
326         obj.setStyle('height', newheight+'px');
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.test(cssselector) && e.target.get('nodeName')!='A' && e.target.get('nodeName')!='IMG') {
415                 this.ancestor().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);
428 };
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     });
594 };
596 M.util.init_block_hider = function(Y, config) {
597     Y.use('base', 'node', function(Y) {
598         M.util.block_hider = M.util.block_hider || (function(){
599             var blockhider = function() {
600                 blockhider.superclass.constructor.apply(this, arguments);
601             };
602             blockhider.prototype = {
603                 initializer : function(config) {
604                     this.set('block', '#'+this.get('id'));
605                     var b = this.get('block'),
606                         t = b.one('.title'),
607                         a = null;
608                     if (t && (a = t.one('.block_action'))) {
609                         var hide = Y.Node.create('<img class="block-hider-hide" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
610                         hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
611                         var show = Y.Node.create('<img class="block-hider-show" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
612                         show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
613                         a.insert(show, 0).insert(hide, 0);
614                     }
615                 },
616                 updateState : function(e, hide) {
617                     M.util.set_user_preference(this.get('preference'), hide);
618                     if (hide) {
619                         this.get('block').addClass('hidden');
620                     } else {
621                         this.get('block').removeClass('hidden');
622                     }
623                 }
624             };
625             Y.extend(blockhider, Y.Base, blockhider.prototype, {
626                 NAME : 'blockhider',
627                 ATTRS : {
628                     id : {},
629                     preference : {},
630                     iconVisible : {
631                         value : M.util.image_url('t/switch_minus', 'moodle')
632                     },
633                     iconHidden : {
634                         value : M.util.image_url('t/switch_plus', 'moodle')
635                     },
636                     block : {
637                         setter : function(node) {
638                             return Y.one(node);
639                         }
640                     }
641                 }
642             });
643             return blockhider;
644         })();
645         new M.util.block_hider(config);
646     });
647 };
649 /**
650  * Returns a string registered in advance for usage in JavaScript
651  *
652  * If you do not pass the third parameter, the function will just return
653  * the corresponding value from the M.str object. If the third parameter is
654  * provided, the function performs {$a} placeholder substitution in the
655  * same way as PHP get_string() in Moodle does.
656  *
657  * @param {String} identifier string identifier
658  * @param {String} component the component providing the string
659  * @param {Object|String} a optional variable to populate placeholder with
660  */
661 M.util.get_string = function(identifier, component, a) {
662     var stringvalue;
664     if (M.cfg.developerdebug) {
665         // creating new instance if YUI is not optimal but it seems to be better way then
666         // require the instance via the function API - note that it is used in rare cases
667         // for debugging only anyway
668         var Y = new YUI({ debug : true });
669     }
671     if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
672         stringvalue = '[[' + identifier + ',' + component + ']]';
673         if (M.cfg.developerdebug) {
674             Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
675         }
676         return stringvalue;
677     }
679     stringvalue = M.str[component][identifier];
681     if (typeof a == 'undefined') {
682         // no placeholder substitution requested
683         return stringvalue;
684     }
686     if (typeof a == 'number' || typeof a == 'string') {
687         // replace all occurrences of {$a} with the placeholder value
688         stringvalue = stringvalue.replace(/\{\$a\}/g, a);
689         return stringvalue;
690     }
692     if (typeof a == 'object') {
693         // replace {$a->key} placeholders
694         for (var key in a) {
695             if (typeof a[key] != 'number' && typeof a[key] != 'string') {
696                 if (M.cfg.developerdebug) {
697                     Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
698                 }
699                 continue;
700             }
701             var search = '{$a->' + key + '}';
702             search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
703             search = new RegExp(search, 'g');
704             stringvalue = stringvalue.replace(search, a[key]);
705         }
706         return stringvalue;
707     }
709     if (M.cfg.developerdebug) {
710         Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
711     }
712     return stringvalue;
713 };
715 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
717 function checkall() {
718     var inputs = document.getElementsByTagName('input');
719     for (var i = 0; i < inputs.length; i++) {
720         if (inputs[i].type == 'checkbox') {
721             inputs[i].checked = true;
722         }
723     }
726 function checknone() {
727     var inputs = document.getElementsByTagName('input');
728     for (var i = 0; i < inputs.length; i++) {
729         if (inputs[i].type == 'checkbox') {
730             inputs[i].checked = false;
731         }
732     }
735 /**
736  * Either check, or uncheck, all checkboxes inside the element with id is
737  * @param id the id of the container
738  * @param checked the new state, either '' or 'checked'.
739  */
740 function select_all_in_element_with_id(id, checked) {
741     var container = document.getElementById(id);
742     if (!container) {
743         return;
744     }
745     var inputs = container.getElementsByTagName('input');
746     for (var i = 0; i < inputs.length; ++i) {
747         if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
748             inputs[i].checked = checked;
749         }
750     }
753 function select_all_in(elTagName, elClass, elId) {
754     var inputs = document.getElementsByTagName('input');
755     inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
756     for(var i = 0; i < inputs.length; ++i) {
757         if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
758             inputs[i].checked = 'checked';
759         }
760     }
763 function deselect_all_in(elTagName, elClass, elId) {
764     var inputs = document.getElementsByTagName('INPUT');
765     inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
766     for(var i = 0; i < inputs.length; ++i) {
767         if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
768             inputs[i].checked = '';
769         }
770     }
773 function confirm_if(expr, message) {
774     if(!expr) {
775         return true;
776     }
777     return confirm(message);
781 /*
782     findParentNode (start, elementName, elementClass, elementID)
784     Travels up the DOM hierarchy to find a parent element with the
785     specified tag name, class, and id. All conditions must be met,
786     but any can be ommitted. Returns the BODY element if no match
787     found.
788 */
789 function findParentNode(el, elName, elClass, elId) {
790     while (el.nodeName.toUpperCase() != 'BODY') {
791         if ((!elName || el.nodeName.toUpperCase() == elName) &&
792             (!elClass || el.className.indexOf(elClass) != -1) &&
793             (!elId || el.id == elId)) {
794             break;
795         }
796         el = el.parentNode;
797     }
798     return el;
800 /*
801     findChildNode (start, elementName, elementClass, elementID)
803     Travels down the DOM hierarchy to find all child elements with the
804     specified tag name, class, and id. All conditions must be met,
805     but any can be ommitted.
806     Doesn't examine children of matches.
807 */
808 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
809     var children = new Array();
810     for (var i = 0; i < start.childNodes.length; i++) {
811         var classfound = false;
812         var child = start.childNodes[i];
813         if((child.nodeType == 1) &&//element node type
814                   (elementClass && (typeof(child.className)=='string'))) {
815             var childClasses = child.className.split(/\s+/);
816             for (var childClassIndex in childClasses) {
817                 if (childClasses[childClassIndex]==elementClass) {
818                     classfound = true;
819                     break;
820                 }
821             }
822         }
823         if(child.nodeType == 1) { //element node type
824             if  ( (!tagName || child.nodeName == tagName) &&
825                 (!elementClass || classfound)&&
826                 (!elementID || child.id == elementID) &&
827                 (!elementName || child.name == elementName))
828             {
829                 children = children.concat(child);
830             } else {
831                 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
832             }
833         }
834     }
835     return children;
838 function unmaskPassword(id) {
839   var pw = document.getElementById(id);
840   var chb = document.getElementById(id+'unmask');
842   try {
843     // first try IE way - it can not set name attribute later
844     if (chb.checked) {
845       var newpw = document.createElement('<input type="text" name="'+pw.name+'">');
846     } else {
847       var newpw = document.createElement('<input type="password" name="'+pw.name+'">');
848     }
849     newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
850   } catch (e) {
851     var newpw = document.createElement('input');
852     newpw.setAttribute('name', pw.name);
853     if (chb.checked) {
854       newpw.setAttribute('type', 'text');
855     } else {
856       newpw.setAttribute('type', 'password');
857     }
858     newpw.setAttribute('class', pw.getAttribute('class'));
859   }
860   newpw.id = pw.id;
861   newpw.size = pw.size;
862   newpw.onblur = pw.onblur;
863   newpw.onchange = pw.onchange;
864   newpw.value = pw.value;
865   pw.parentNode.replaceChild(newpw, pw);
868 function filterByParent(elCollection, parentFinder) {
869     var filteredCollection = [];
870     for (var i = 0; i < elCollection.length; ++i) {
871         var findParent = parentFinder(elCollection[i]);
872         if (findParent.nodeName.toUpperCase != 'BODY') {
873             filteredCollection.push(elCollection[i]);
874         }
875     }
876     return filteredCollection;
879 /*
880     All this is here just so that IE gets to handle oversized blocks
881     in a visually pleasing manner. It does a browser detect. So sue me.
882 */
884 function fix_column_widths() {
885     var agt = navigator.userAgent.toLowerCase();
886     if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
887         fix_column_width('left-column');
888         fix_column_width('right-column');
889     }
892 function fix_column_width(colName) {
893     if(column = document.getElementById(colName)) {
894         if(!column.offsetWidth) {
895             setTimeout("fix_column_width('" + colName + "')", 20);
896             return;
897         }
899         var width = 0;
900         var nodes = column.childNodes;
902         for(i = 0; i < nodes.length; ++i) {
903             if(nodes[i].className.indexOf("block") != -1 ) {
904                 if(width < nodes[i].offsetWidth) {
905                     width = nodes[i].offsetWidth;
906                 }
907             }
908         }
910         for(i = 0; i < nodes.length; ++i) {
911             if(nodes[i].className.indexOf("block") != -1 ) {
912                 nodes[i].style.width = width + 'px';
913             }
914         }
915     }
919 /*
920    Insert myValue at current cursor position
921  */
922 function insertAtCursor(myField, myValue) {
923     // IE support
924     if (document.selection) {
925         myField.focus();
926         sel = document.selection.createRange();
927         sel.text = myValue;
928     }
929     // Mozilla/Netscape support
930     else if (myField.selectionStart || myField.selectionStart == '0') {
931         var startPos = myField.selectionStart;
932         var endPos = myField.selectionEnd;
933         myField.value = myField.value.substring(0, startPos)
934             + myValue + myField.value.substring(endPos, myField.value.length);
935     } else {
936         myField.value += myValue;
937     }
941 /*
942         Call instead of setting window.onload directly or setting body onload=.
943         Adds your function to a chain of functions rather than overwriting anything
944         that exists.
945 */
946 function addonload(fn) {
947     var oldhandler=window.onload;
948     window.onload=function() {
949         if(oldhandler) oldhandler();
950             fn();
951     }
953 /**
954  * Replacement for getElementsByClassName in browsers that aren't cool enough
955  *
956  * Relying on the built-in getElementsByClassName is far, far faster than
957  * using YUI.
958  *
959  * Note: the third argument used to be an object with odd behaviour. It now
960  * acts like the 'name' in the HTML5 spec, though the old behaviour is still
961  * mimicked if you pass an object.
962  *
963  * @param {Node} oElm The top-level node for searching. To search a whole
964  *                    document, use `document`.
965  * @param {String} strTagName filter by tag names
966  * @param {String} name same as HTML5 spec
967  */
968 function getElementsByClassName(oElm, strTagName, name) {
969     // for backwards compatibility
970     if(typeof name == "object") {
971         var names = new Array();
972         for(var i=0; i<name.length; i++) names.push(names[i]);
973         name = names.join('');
974     }
975     // use native implementation if possible
976     if (oElm.getElementsByClassName && Array.filter) {
977         if (strTagName == '*') {
978             return oElm.getElementsByClassName(name);
979         } else {
980             return Array.filter(oElm.getElementsByClassName(name), function(el) {
981                 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
982             });
983         }
984     }
985     // native implementation unavailable, fall back to slow method
986     var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
987     var arrReturnElements = new Array();
988     var arrRegExpClassNames = new Array();
989     var names = name.split(' ');
990     for(var i=0; i<names.length; i++) {
991         arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
992     }
993     var oElement;
994     var bMatchesAll;
995     for(var j=0; j<arrElements.length; j++) {
996         oElement = arrElements[j];
997         bMatchesAll = true;
998         for(var k=0; k<arrRegExpClassNames.length; k++) {
999             if(!arrRegExpClassNames[k].test(oElement.className)) {
1000                 bMatchesAll = false;
1001                 break;
1002             }
1003         }
1004         if(bMatchesAll) {
1005             arrReturnElements.push(oElement);
1006         }
1007     }
1008     return (arrReturnElements)
1011 function openpopup(event, args) {
1013     if (event) {
1014         if (event.preventDefault) {
1015             event.preventDefault();
1016         } else {
1017             event.returnValue = false;
1018         }
1019     }
1021     var fullurl = args.url;
1022     if (!args.url.match(/https?:\/\//)) {
1023         fullurl = M.cfg.wwwroot + args.url;
1024     }
1025     var windowobj = window.open(fullurl,args.name,args.options);
1026     if (!windowobj) {
1027         return true;
1028     }
1029     if (args.fullscreen) {
1030         windowobj.moveTo(0,0);
1031         windowobj.resizeTo(screen.availWidth,screen.availHeight);
1032     }
1033     windowobj.focus();
1035     return false;
1038 /** Close the current browser window. */
1039 function close_window(e) {
1040     if (e.preventDefault) {
1041         e.preventDefault();
1042     } else {
1043         e.returnValue = false;
1044     }
1045     self.close();
1048 /**
1049  * Close the current browser window, forcing the window/tab that opened this
1050  * popup to reload itself. */
1051 function close_window_reloading_opener() {
1052     if (window.opener) {
1053         window.opener.location.reload(1);
1054         close_window();
1055         // Intentionally, only try to close the window if there is some evidence we are in a popup.
1056     }
1059 /**
1060  * Used in a couple of modules to hide navigation areas when using AJAX
1061  */
1063 function show_item(itemid) {
1064     var item = document.getElementById(itemid);
1065     if (item) {
1066         item.style.display = "";
1067     }
1070 function destroy_item(itemid) {
1071     var item = document.getElementById(itemid);
1072     if (item) {
1073         item.parentNode.removeChild(item);
1074     }
1076 /**
1077  * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1078  * @param controlid the control id.
1079  */
1080 function focuscontrol(controlid) {
1081     var control = document.getElementById(controlid);
1082     if (control) {
1083         control.focus();
1084     }
1087 /**
1088  * Transfers keyboard focus to an HTML element based on the old style style of focus
1089  * This function should be removed as soon as it is no longer used
1090  */
1091 function old_onload_focus(formid, controlname) {
1092     if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1093         document.forms[formid].elements[controlname].focus();
1094     }
1097 function build_querystring(obj) {
1098     return convert_object_to_string(obj, '&');
1101 function build_windowoptionsstring(obj) {
1102     return convert_object_to_string(obj, ',');
1105 function convert_object_to_string(obj, separator) {
1106     if (typeof obj !== 'object') {
1107         return null;
1108     }
1109     var list = [];
1110     for(var k in obj) {
1111         k = encodeURIComponent(k);
1112         var value = obj[k];
1113         if(obj[k] instanceof Array) {
1114             for(var i in value) {
1115                 list.push(k+'[]='+encodeURIComponent(value[i]));
1116             }
1117         } else {
1118             list.push(k+'='+encodeURIComponent(value));
1119         }
1120     }
1121     return list.join(separator);
1124 function stripHTML(str) {
1125     var re = /<\S[^><]*>/g;
1126     var ret = str.replace(re, "");
1127     return ret;
1130 Number.prototype.fixed=function(n){
1131     with(Math)
1132         return round(Number(this)*pow(10,n))/pow(10,n);
1133 };
1134 function update_progress_bar (id, width, pt, msg, es){
1135     var percent = pt;
1136     var status = document.getElementById("status_"+id);
1137     var percent_indicator = document.getElementById("pt_"+id);
1138     var progress_bar = document.getElementById("progress_"+id);
1139     var time_es = document.getElementById("time_"+id);
1140     status.innerHTML = msg;
1141     percent_indicator.innerHTML = percent.fixed(2) + '%';
1142     if(percent == 100) {
1143         progress_bar.style.background = "green";
1144         time_es.style.display = "none";
1145     } else {
1146         progress_bar.style.background = "#FFCC66";
1147         if (es == '?'){
1148             time_es.innerHTML = "";
1149         }else {
1150             time_es.innerHTML = es.fixed(2)+" sec";
1151             time_es.style.display
1152                 = "block";
1153         }
1154     }
1155     progress_bar.style.width = width + "px";
1159 function frame_breakout(e, properties) {
1160     this.setAttribute('target', properties.framename);
1164 // ===== Deprecated core Javascript functions for Moodle ====
1165 //       DO NOT USE!!!!!!!
1166 // Do not put this stuff in separate file because it only adds extra load on servers!
1168 /**
1169  * Used in a couple of modules to hide navigation areas when using AJAX
1170  */
1171 function hide_item(itemid) {
1172     // use class='hiddenifjs' instead
1173     var item = document.getElementById(itemid);
1174     if (item) {
1175         item.style.display = "none";
1176     }
1179 M.util.help_icon = {
1180     Y : null,
1181     instance : null,
1182     add : function(Y, properties) {
1183         this.Y = Y;
1184         properties.node = Y.one('#'+properties.id);
1185         if (properties.node) {
1186             properties.node.on('click', this.display, this, properties);
1187         }
1188     },
1189     display : function(event, args) {
1190         event.preventDefault();
1191         if (M.util.help_icon.instance === null) {
1192             var Y = M.util.help_icon.Y;
1193             Y.use('overlay', 'io', 'event-mouseenter', 'node', 'event-key', function(Y) {
1194                 var help_content_overlay = {
1195                     helplink : null,
1196                     overlay : null,
1197                     init : function() {
1199                         var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img  src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
1200                         // Create an overlay from markup
1201                         this.overlay = new Y.Overlay({
1202                             headerContent: closebtn,
1203                             bodyContent: '',
1204                             id: 'helppopupbox',
1205                             width:'400px',
1206                             visible : false,
1207                             constrain : true
1208                         });
1209                         this.overlay.render(Y.one(document.body));
1211                         closebtn.on('click', this.overlay.hide, this.overlay);
1213                         var boundingBox = this.overlay.get("boundingBox");
1215                         //  Hide the menu if the user clicks outside of its content
1216                         boundingBox.get("ownerDocument").on("mousedown", function (event) {
1217                             var oTarget = event.target;
1218                             var menuButton = Y.one("#"+args.id);
1220                             if (!oTarget.compareTo(menuButton) &&
1221                                 !menuButton.contains(oTarget) &&
1222                                 !oTarget.compareTo(boundingBox) &&
1223                                 !boundingBox.contains(oTarget)) {
1224                                 this.overlay.hide();
1225                             }
1226                         }, this);
1228                         Y.on("key", this.close, closebtn , "down:13", this);
1229                         closebtn.on('click', this.close, this);
1230                     },
1232                     close : function(e) {
1233                         e.preventDefault();
1234                         this.helplink.focus();
1235                         this.overlay.hide();
1236                     },
1238                     display : function(event, args) {
1239                         this.helplink = args.node;
1240                         this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1241                         this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
1243                         var fullurl = args.url;
1244                         if (!args.url.match(/https?:\/\//)) {
1245                             fullurl = M.cfg.wwwroot + args.url;
1246                         }
1248                         var ajaxurl = fullurl + '&ajax=1';
1250                         var cfg = {
1251                             method: 'get',
1252                             context : this,
1253                             on: {
1254                                 success: function(id, o, node) {
1255                                     this.display_callback(o.responseText);
1256                                 },
1257                                 failure: function(id, o, node) {
1258                                     var debuginfo = o.statusText;
1259                                     if (M.cfg.developerdebug) {
1260                                         o.statusText += ' (' + ajaxurl + ')';
1261                                     }
1262                                     this.display_callback('bodyContent',debuginfo);
1263                                 }
1264                             }
1265                         };
1267                         Y.io(ajaxurl, cfg);
1268                         this.overlay.show();
1270                         Y.one('#closehelpbox').focus();
1271                     },
1273                     display_callback : function(content) {
1274                         this.overlay.set('bodyContent', content);
1275                     },
1277                     hideContent : function() {
1278                         help = this;
1279                         help.overlay.hide();
1280                     }
1281                 };
1282                 help_content_overlay.init();
1283                 M.util.help_icon.instance = help_content_overlay;
1284                 M.util.help_icon.instance.display(event, args);
1285             });
1286         } else {
1287             M.util.help_icon.instance.display(event, args);
1288         }
1289     },
1290     init : function(Y) {
1291         this.Y = Y;
1292     }
1293 };
1295 /**
1296  * Custom menu namespace
1297  */
1298 M.core_custom_menu = {
1299     /**
1300      * This method is used to initialise a custom menu given the id that belongs
1301      * to the custom menu's root node.
1302      *
1303      * @param {YUI} Y
1304      * @param {string} nodeid
1305      */
1306     init : function(Y, nodeid) {
1307         var node = Y.one('#'+nodeid);
1308         if (node) {
1309             Y.use('node-menunav', function(Y) {
1310                 // Get the node
1311                 // Remove the javascript-disabled class.... obviously javascript is enabled.
1312                 node.removeClass('javascript-disabled');
1313                 // Initialise the menunav plugin
1314                 node.plug(Y.Plugin.NodeMenuNav);
1315             });
1316         }
1317     }
1318 };
1320 /**
1321  * Used to store form manipulation methods and enhancments
1322  */
1323 M.form = M.form || {};
1325 /**
1326  * Converts a nbsp indented select box into a multi drop down custom control much
1327  * like the custom menu. It also selectable categories on or off.
1328  *
1329  * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1330  *
1331  * @param {YUI} Y
1332  * @param {string} id
1333  * @param {Array} options
1334  */
1335 M.form.init_smartselect = function(Y, id, options) {
1336     if (!id.match(/^id_/)) {
1337         id = 'id_'+id;
1338     }
1339     var select = Y.one('select#'+id);
1340     if (!select) {
1341         return false;
1342     }
1343     Y.use('event-delegate',function(){
1344         var smartselect = {
1345             id : id,
1346             structure : [],
1347             options : [],
1348             submenucount : 0,
1349             currentvalue : null,
1350             currenttext : null,
1351             shownevent : null,
1352             cfg : {
1353                 selectablecategories : true,
1354                 mode : null
1355             },
1356             nodes : {
1357                 select : null,
1358                 loading : null,
1359                 menu : null
1360             },
1361             init : function(Y, id, args, nodes) {
1362                 if (typeof(args)=='object') {
1363                     for (var i in this.cfg) {
1364                         if (args[i] || args[i]===false) {
1365                             this.cfg[i] = args[i];
1366                         }
1367                     }
1368                 }
1370                 // Display a loading message first up
1371                 this.nodes.select = nodes.select;
1373                 this.currentvalue = this.nodes.select.get('selectedIndex');
1374                 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1376                 var options = Array();
1377                 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1378                 this.nodes.select.all('option').each(function(option, index) {
1379                     var rawtext = option.get('innerHTML');
1380                     var text = rawtext.replace(/^(&nbsp;)*/, '');
1381                     if (rawtext === text) {
1382                         text = rawtext.replace(/^(\s)*/, '');
1383                         var depth = (rawtext.length - text.length ) + 1;
1384                     } else {
1385                         var depth = ((rawtext.length - text.length )/12)+1;
1386                     }
1387                     option.set('innerHTML', text);
1388                     options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1389                 }, this);
1391                 this.structure = [];
1392                 var structcount = 0;
1393                 for (var i in options) {
1394                     var o = options[i];
1395                     if (o.depth == 0) {
1396                         this.structure.push(o);
1397                         structcount++;
1398                     } else {
1399                         var d = o.depth;
1400                         var current = this.structure[structcount-1];
1401                         for (var j = 0; j < o.depth-1;j++) {
1402                             if (current && current.children) {
1403                                 current = current.children[current.children.length-1];
1404                             }
1405                         }
1406                         if (current && current.children) {
1407                             current.children.push(o);
1408                         }
1409                     }
1410                 }
1412                 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1413                 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1414                 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1415                 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1417                 if (this.cfg.mode == null) {
1418                     var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1419                     if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1420                         this.cfg.mode = 'compact';
1421                     } else {
1422                         this.cfg.mode = 'spanning';
1423                     }
1424                 }
1426                 if (this.cfg.mode == 'compact') {
1427                     this.nodes.menu.addClass('compactmenu');
1428                 } else {
1429                     this.nodes.menu.addClass('spanningmenu');
1430                     this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1431                 }
1433                 Y.one(document.body).append(this.nodes.menu);
1434                 var pos = this.nodes.select.getXY();
1435                 pos[0] += 1;
1436                 this.nodes.menu.setXY(pos);
1437                 this.nodes.menu.on('click', this.handle_click, this);
1439                 Y.one(window).on('resize', function(){
1440                      var pos = this.nodes.select.getXY();
1441                     pos[0] += 1;
1442                     this.nodes.menu.setXY(pos);
1443                  }, this);
1444             },
1445             generate_menu_content : function() {
1446                 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1447                 content += this.generate_submenu_content(this.structure[0], true);
1448                 content += '</ul></div>';
1449                 return content;
1450             },
1451             generate_submenu_content : function(item, rootelement) {
1452                 this.submenucount++;
1453                 var content = '';
1454                 if (item.children.length > 0) {
1455                     if (rootelement) {
1456                         content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1457                         content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1458                         content += '<div class="smartselect_menu_content">';
1459                     } else {
1460                         content += '<li class="smartselect_submenuitem">';
1461                         var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1462                         content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1463                         content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1464                         content += '<div class="smartselect_submenu_content">';
1465                     }
1466                     content += '<ul>';
1467                     for (var i in item.children) {
1468                         content += this.generate_submenu_content(item.children[i],false);
1469                     }
1470                     content += '</ul>';
1471                     content += '</div>';
1472                     content += '</div>';
1473                     if (rootelement) {
1474                     } else {
1475                         content += '</li>';
1476                     }
1477                 } else {
1478                     content += '<li class="smartselect_menuitem">';
1479                     content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1480                     content += '</li>';
1481                 }
1482                 return content;
1483             },
1484             select : function(e) {
1485                 var t = e.target;
1486                 e.halt();
1487                 this.currenttext = t.get('innerHTML');
1488                 this.currentvalue = t.getAttribute('value');
1489                 this.nodes.select.set('selectedIndex', this.currentvalue);
1490                 this.hide_menu();
1491             },
1492             handle_click : function(e) {
1493                 var target = e.target;
1494                 if (target.hasClass('smartselect_mask')) {
1495                     this.show_menu(e);
1496                 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1497                     this.select(e);
1498                 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1499                     this.show_sub_menu(e);
1500                 }
1501             },
1502             show_menu : function(e) {
1503                 e.halt();
1504                 var menu = e.target.ancestor().one('.smartselect_menu');
1505                 menu.addClass('visible');
1506                 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1507             },
1508             show_sub_menu : function(e) {
1509                 e.halt();
1510                 var target = e.target;
1511                 if (!target.hasClass('smartselect_submenuitem')) {
1512                     target = target.ancestor('.smartselect_submenuitem');
1513                 }
1514                 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1515                     target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1516                     return;
1517                 }
1518                 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1519                 target.one('.smartselect_submenu').addClass('visible');
1520             },
1521             hide_menu : function() {
1522                 this.nodes.menu.all('.visible').removeClass('visible');
1523                 if (this.shownevent) {
1524                     this.shownevent.detach();
1525                 }
1526             }
1527         };
1528         smartselect.init(Y, id, options, {select:select});
1529     });
1530 };
1532 M.util.init_flvflowplayer = function (id, playerpath, fileurl) {
1533     $f(id, playerpath, {
1534         plugins: {
1535                 controls: {
1536                        autoHide: true
1537                 }
1538         },
1539         clip: {
1540             url: fileurl,
1541             autoPlay: false,
1542             autoBuffering: true
1543         }
1544     });
1545 };
1547 M.util.init_mp3flowplayer = function (id, playerpath, audioplayerpath, fileurl, color) {
1549     $f(id, playerpath, {
1551             plugins: {
1552                     controls: {
1553                             fullscreen: false,
1554                             height: 25,
1555                             autoHide: false,
1556                             background: '#'+color['bgColour'],
1557                             buttonColor: '#'+color['btnColour'],
1558                             sliderColor: '#'+color['handleColour'],
1559                             volumeSliderColor: '#'+color['handleColour'],
1560                             volumeColor: '#'+color['trackColour'],
1561                             durationColor: '#'+color['fontColour'],
1562                             buttonOverColor: '#'+color['iconOverColour'],
1563                             progressColor: '#'+color['handleColour']
1564                     },
1565                     audio: {url: audioplayerpath}
1566             },
1567             clip: {url: fileurl,
1568                     provider: "audio",
1569                     autoPlay: false
1570             }
1571     });
1572 };
1574 M.util.init_mp3flowplayerplugin = function (id, playerpath, audioplayerpath, fileurl, color) {
1575     $f(id, playerpath, {
1576             plugins: {
1577                     controls: {
1578                             all: false,
1579                             play: true,
1580                             pause: true,
1581                             scrubber: true,
1582                             autoHide: false,
1583                             height: 15,
1584                             background: '#'+color['bgColour'],
1585                             buttonColor: '#'+color['btnColour'],
1586                             sliderColor: '#'+color['handleColour'],
1587                             volumeSliderColor: '#'+color['handleColour'],
1588                             progressColor: '#'+color['handleColour'],
1589                             volumeColor: '#'+color['trackColour'],
1590                             buttonOverColor: '#'+color['iconOverColour']
1591                     },
1592                     audio: {url: audioplayerpath}
1593             },
1594             clip: {url: fileurl,
1595                     provider: "audio",
1596                     autoPlay: false
1597             }
1598     });
1599 };