Merge branch 'w11_MDL-25352_20_replace' of git://github.com/skodak/moodle
[moodle.git] / lib / javascript-static.js
blob904cd4575a10a70f01267f7311bf05e5575fb480
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     }
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;
49 M.util.create_UFO_object = function (eid, FO) {
50     UFO.create(FO, eid);
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;
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     });
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);
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;
163  * The key divs that make up this
164  * @property div
165  * @type Y.Node
166  */
167 M.util.CollapsibleRegion.prototype.div = null;
170  * The key divs that make up this
171  * @property icon
172  * @type Y.Node
173  */
174 M.util.CollapsibleRegion.prototype.icon = null;
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.
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.
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     });
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             var targetancestor = null,
266                 targetform = null;
268             if (target.test('a')) {
269                 window.location = target.get('href');
270             } else if ((targetancestor = target.ancestor('a')) !== null) {
271                 window.location = targetancestor.get('href');
272             } else if (target.test('input')) {
273                 targetform = target.ancestor('form');
274                 if (targetform && targetform.submit) {
275                     targetform.submit();
276                 }
277             } else if (M.cfg.developerdebug) {
278                 alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A or INPUT");
279             }
280         };
282         var buttons = [ {text: M.str.moodle.cancel, handler: handle_cancel, isDefault: true},
283                         {text: M.str.moodle.yes, handler: handle_yes} ];
285         simpledialog.cfg.queueProperty('buttons', buttons);
287         simpledialog.render(document.body);
288         simpledialog.show();
289     });
292 /** Useful for full embedding of various stuff */
293 M.util.init_maximised_embed = function(Y, id) {
294     var obj = Y.one('#'+id);
295     if (!obj) {
296         return;
297     }
300     var get_htmlelement_size = function(el, prop) {
301         if (Y.Lang.isString(el)) {
302             el = Y.one('#' + el);
303         }
304         var val = el.getStyle(prop);
305         if (val == 'auto') {
306             val = el.getComputedStyle(prop);
307         }
308         return parseInt(val);
309     };
311     var resize_object = function() {
312         obj.setStyle('width', '0px');
313         obj.setStyle('height', '0px');
314         var newwidth = get_htmlelement_size('maincontent', 'width') - 15;
316         if (newwidth > 600) {
317             obj.setStyle('width', newwidth  + 'px');
318         } else {
319             obj.setStyle('width', '600px');
320         }
322         var headerheight = get_htmlelement_size('page-header', 'height');
323         var footerheight = get_htmlelement_size('page-footer', 'height');
324         var newheight = parseInt(YAHOO.util.Dom.getViewportHeight()) - footerheight - headerheight - 20;
325         if (newheight < 400) {
326             newheight = 400;
327         }
328         obj.setStyle('height', newheight+'px');
329     };
331     resize_object();
332     // fix layout if window resized too
333     window.onresize = function() {
334         resize_object();
335     };
339  * Attach handler to single_select
340  */
341 M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
342     Y.use('event-key', function() {
343         var select = Y.one('#'+selectid);
344         if (select) {
345             // Try to get the form by id
346             var form = Y.one('#'+formid) || (function(){
347                 // Hmmm the form's id may have been overriden by an internal input
348                 // with the name id which will KILL IE.
349                 // We need to manually iterate at this point because if the case
350                 // above is true YUI's ancestor method will also kill IE!
351                 var form = select;
352                 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
353                     form = form.ancestor();
354                 }
355                 return form;
356             })();
357             // Make sure we have the form
358             if (form) {
359                 // Create a function to handle our change event
360                 var processchange = function(e, lastindex) {
361                     if ((nothing===false || select.get('value') != nothing) && lastindex != select.get('selectedIndex')) {
362                         this.submit();
363                     }
364                 };
365                 // Attach the change event to the keypress, blur, and click actions.
366                 // We don't use the change event because IE fires it on every arrow up/down
367                 // event.... usability
368                 Y.on('key', processchange, select, 'press:13', form, select.get('selectedIndex'));
369                 select.on('blur', processchange, form, select.get('selectedIndex'));
370                 //little hack for chrome that need onChange event instead of onClick - see MDL-23224
371                 if (Y.UA.webkit) {
372                     select.on('change', processchange, form, select.get('selectedIndex'));
373                 } else {
374                     select.on('click', processchange, form, select.get('selectedIndex'));
375                 }
376             }
377         }
378     });
382  * Attach handler to url_select
383  */
384 M.util.init_url_select = function(Y, formid, selectid, nothing) {
385     YUI(M.yui.loader).use('node', function(Y) {
386         Y.on('change', function() {
387             if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
388                 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
389             }
390         },
391         '#'+selectid);
392     });
396  * Breaks out all links to the top frame - used in frametop page layout.
397  */
398 M.util.init_frametop = function(Y) {
399     Y.all('a').each(function(node) {
400         node.set('target', '_top');
401     });
402     Y.all('form').each(function(node) {
403         node.set('target', '_top');
404     });
408  * Finds all nodes that match the given CSS selector and attaches events to them
409  * so that they toggle a given classname when clicked.
411  * @param {YUI} Y
412  * @param {string} id An id containing elements to target
413  * @param {string} cssselector A selector to use to find targets
414  * @param {string} toggleclassname A classname to toggle
415  */
416 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
418     if (togglecssselector == '') {
419         togglecssselector = cssselector;
420     }
422     var node = Y.one('#'+id);
423     node.all(cssselector).each(function(n){
424         n.on('click', function(e){
425             e.stopPropagation();
426             if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
427                 if (this.test(togglecssselector)) {
428                     this.toggleClass(toggleclassname);
429                 } else {
430                     this.ancestor(togglecssselector).toggleClass(toggleclassname);
431             }
432             }
433         }, n);
434     });
435     // Attach this click event to the node rather than all selectors... will be much better
436     // for performance
437     node.on('click', function(e){
438         if (e.target.hasClass('addtoall')) {
439             this.all(togglecssselector).addClass(toggleclassname);
440         } else if (e.target.hasClass('removefromall')) {
441             this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
442         }
443     }, node);
447  * Initialises a colour picker
449  * Designed to be used with admin_setting_configcolourpicker although could be used
450  * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
451  * above or below the input (must have the same parent) and then call this with the
452  * id.
454  * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
455  * contrib/blocks. For better docs refer to that.
457  * @param {YUI} Y
458  * @param {int} id
459  * @param {object} previewconf
460  */
461 M.util.init_colour_picker = function(Y, id, previewconf) {
462     /**
463      * We need node and event-mouseenter
464      */
465     Y.use('node', 'event-mouseenter', function(){
466         /**
467          * The colour picker object
468          */
469         var colourpicker = {
470             box : null,
471             input : null,
472             image : null,
473             preview : null,
474             current : null,
475             eventClick : null,
476             eventMouseEnter : null,
477             eventMouseLeave : null,
478             eventMouseMove : null,
479             width : 300,
480             height :  100,
481             factor : 5,
482             /**
483              * Initalises the colour picker by putting everything together and wiring the events
484              */
485             init : function() {
486                 this.input = Y.one('#'+id);
487                 this.box = this.input.ancestor().one('.admin_colourpicker');
488                 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
489                 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
490                 this.preview = Y.Node.create('<div class="previewcolour"></div>');
491                 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
492                 this.current = Y.Node.create('<div class="currentcolour"></div>');
493                 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
494                 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
496                 if (typeof(previewconf) === 'object' && previewconf !== null) {
497                     Y.one('#'+id+'_preview').on('click', function(e){
498                         if (Y.Lang.isString(previewconf.selector)) {
499                             Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
500                         } else {
501                             for (var i in previewconf.selector) {
502                                 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
503                             }
504                         }
505                     }, this);
506                 }
508                 this.eventClick = this.image.on('click', this.pickColour, this);
509                 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
510             },
511             /**
512              * Starts to follow the mouse once it enter the image
513              */
514             startFollow : function(e) {
515                 this.eventMouseEnter.detach();
516                 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
517                 this.eventMouseMove = this.image.on('mousemove', function(e){
518                     this.preview.setStyle('backgroundColor', this.determineColour(e));
519                 }, this);
520             },
521             /**
522              * Stops following the mouse
523              */
524             endFollow : function(e) {
525                 this.eventMouseMove.detach();
526                 this.eventMouseLeave.detach();
527                 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
528             },
529             /**
530              * Picks the colour the was clicked on
531              */
532             pickColour : function(e) {
533                 var colour = this.determineColour(e);
534                 this.input.set('value', colour);
535                 this.current.setStyle('backgroundColor', colour);
536             },
537             /**
538              * Calculates the colour fromthe given co-ordinates
539              */
540             determineColour : function(e) {
541                 var eventx = Math.floor(e.pageX-e.target.getX());
542                 var eventy = Math.floor(e.pageY-e.target.getY());
544                 var imagewidth = this.width;
545                 var imageheight = this.height;
546                 var factor = this.factor;
547                 var colour = [255,0,0];
549                 var matrices = [
550                     [  0,  1,  0],
551                     [ -1,  0,  0],
552                     [  0,  0,  1],
553                     [  0, -1,  0],
554                     [  1,  0,  0],
555                     [  0,  0, -1]
556                 ];
558                 var matrixcount = matrices.length;
559                 var limit = Math.round(imagewidth/matrixcount);
560                 var heightbreak = Math.round(imageheight/2);
562                 for (var x = 0; x < imagewidth; x++) {
563                     var divisor = Math.floor(x / limit);
564                     var matrix = matrices[divisor];
566                     colour[0] += matrix[0]*factor;
567                     colour[1] += matrix[1]*factor;
568                     colour[2] += matrix[2]*factor;
570                     if (eventx==x) {
571                         break;
572                     }
573                 }
575                 var pixel = [colour[0], colour[1], colour[2]];
576                 if (eventy < heightbreak) {
577                     pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
578                     pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
579                     pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
580                 } else if (eventy > heightbreak) {
581                     pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
582                     pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
583                     pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
584                 }
586                 return this.convert_rgb_to_hex(pixel);
587             },
588             /**
589              * Converts an RGB value to Hex
590              */
591             convert_rgb_to_hex : function(rgb) {
592                 var hex = '#';
593                 var hexchars = "0123456789ABCDEF";
594                 for (var i=0; i<3; i++) {
595                     var number = Math.abs(rgb[i]);
596                     if (number == 0 || isNaN(number)) {
597                         hex += '00';
598                     } else {
599                         hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
600                     }
601                 }
602                 return hex;
603             }
604         };
605         /**
606          * Initialise the colour picker :) Hoorah
607          */
608         colourpicker.init();
609     });
612 M.util.init_block_hider = function(Y, config) {
613     Y.use('base', 'node', function(Y) {
614         M.util.block_hider = M.util.block_hider || (function(){
615             var blockhider = function() {
616                 blockhider.superclass.constructor.apply(this, arguments);
617             };
618             blockhider.prototype = {
619                 initializer : function(config) {
620                     this.set('block', '#'+this.get('id'));
621                     var b = this.get('block'),
622                         t = b.one('.title'),
623                         a = null;
624                     if (t && (a = t.one('.block_action'))) {
625                         var hide = Y.Node.create('<img class="block-hider-hide" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
626                         hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
627                         var show = Y.Node.create('<img class="block-hider-show" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
628                         show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
629                         a.insert(show, 0).insert(hide, 0);
630                     }
631                 },
632                 updateState : function(e, hide) {
633                     M.util.set_user_preference(this.get('preference'), hide);
634                     if (hide) {
635                         this.get('block').addClass('hidden');
636                     } else {
637                         this.get('block').removeClass('hidden');
638                     }
639                 }
640             };
641             Y.extend(blockhider, Y.Base, blockhider.prototype, {
642                 NAME : 'blockhider',
643                 ATTRS : {
644                     id : {},
645                     preference : {},
646                     iconVisible : {
647                         value : M.util.image_url('t/switch_minus', 'moodle')
648                     },
649                     iconHidden : {
650                         value : M.util.image_url('t/switch_plus', 'moodle')
651                     },
652                     block : {
653                         setter : function(node) {
654                             return Y.one(node);
655                         }
656                     }
657                 }
658             });
659             return blockhider;
660         })();
661         new M.util.block_hider(config);
662     });
666  * Returns a string registered in advance for usage in JavaScript
668  * If you do not pass the third parameter, the function will just return
669  * the corresponding value from the M.str object. If the third parameter is
670  * provided, the function performs {$a} placeholder substitution in the
671  * same way as PHP get_string() in Moodle does.
673  * @param {String} identifier string identifier
674  * @param {String} component the component providing the string
675  * @param {Object|String} a optional variable to populate placeholder with
676  */
677 M.util.get_string = function(identifier, component, a) {
678     var stringvalue;
680     if (M.cfg.developerdebug) {
681         // creating new instance if YUI is not optimal but it seems to be better way then
682         // require the instance via the function API - note that it is used in rare cases
683         // for debugging only anyway
684         var Y = new YUI({ debug : true });
685     }
687     if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
688         stringvalue = '[[' + identifier + ',' + component + ']]';
689         if (M.cfg.developerdebug) {
690             Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
691         }
692         return stringvalue;
693     }
695     stringvalue = M.str[component][identifier];
697     if (typeof a == 'undefined') {
698         // no placeholder substitution requested
699         return stringvalue;
700     }
702     if (typeof a == 'number' || typeof a == 'string') {
703         // replace all occurrences of {$a} with the placeholder value
704         stringvalue = stringvalue.replace(/\{\$a\}/g, a);
705         return stringvalue;
706     }
708     if (typeof a == 'object') {
709         // replace {$a->key} placeholders
710         for (var key in a) {
711             if (typeof a[key] != 'number' && typeof a[key] != 'string') {
712                 if (M.cfg.developerdebug) {
713                     Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
714                 }
715                 continue;
716             }
717             var search = '{$a->' + key + '}';
718             search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
719             search = new RegExp(search, 'g');
720             stringvalue = stringvalue.replace(search, a[key]);
721         }
722         return stringvalue;
723     }
725     if (M.cfg.developerdebug) {
726         Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
727     }
728     return stringvalue;
731 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
733 function checkall() {
734     var inputs = document.getElementsByTagName('input');
735     for (var i = 0; i < inputs.length; i++) {
736         if (inputs[i].type == 'checkbox') {
737             inputs[i].checked = true;
738         }
739     }
742 function checknone() {
743     var inputs = document.getElementsByTagName('input');
744     for (var i = 0; i < inputs.length; i++) {
745         if (inputs[i].type == 'checkbox') {
746             inputs[i].checked = false;
747         }
748     }
752  * Either check, or uncheck, all checkboxes inside the element with id is
753  * @param id the id of the container
754  * @param checked the new state, either '' or 'checked'.
755  */
756 function select_all_in_element_with_id(id, checked) {
757     var container = document.getElementById(id);
758     if (!container) {
759         return;
760     }
761     var inputs = container.getElementsByTagName('input');
762     for (var i = 0; i < inputs.length; ++i) {
763         if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
764             inputs[i].checked = checked;
765         }
766     }
769 function select_all_in(elTagName, elClass, elId) {
770     var inputs = document.getElementsByTagName('input');
771     inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
772     for(var i = 0; i < inputs.length; ++i) {
773         if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
774             inputs[i].checked = 'checked';
775         }
776     }
779 function deselect_all_in(elTagName, elClass, elId) {
780     var inputs = document.getElementsByTagName('INPUT');
781     inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
782     for(var i = 0; i < inputs.length; ++i) {
783         if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
784             inputs[i].checked = '';
785         }
786     }
789 function confirm_if(expr, message) {
790     if(!expr) {
791         return true;
792     }
793     return confirm(message);
798     findParentNode (start, elementName, elementClass, elementID)
800     Travels up the DOM hierarchy to find a parent element with the
801     specified tag name, class, and id. All conditions must be met,
802     but any can be ommitted. Returns the BODY element if no match
803     found.
805 function findParentNode(el, elName, elClass, elId) {
806     while (el.nodeName.toUpperCase() != 'BODY') {
807         if ((!elName || el.nodeName.toUpperCase() == elName) &&
808             (!elClass || el.className.indexOf(elClass) != -1) &&
809             (!elId || el.id == elId)) {
810             break;
811         }
812         el = el.parentNode;
813     }
814     return el;
817     findChildNode (start, elementName, elementClass, elementID)
819     Travels down the DOM hierarchy to find all child elements with the
820     specified tag name, class, and id. All conditions must be met,
821     but any can be ommitted.
822     Doesn't examine children of matches.
824 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
825     var children = new Array();
826     for (var i = 0; i < start.childNodes.length; i++) {
827         var classfound = false;
828         var child = start.childNodes[i];
829         if((child.nodeType == 1) &&//element node type
830                   (elementClass && (typeof(child.className)=='string'))) {
831             var childClasses = child.className.split(/\s+/);
832             for (var childClassIndex in childClasses) {
833                 if (childClasses[childClassIndex]==elementClass) {
834                     classfound = true;
835                     break;
836                 }
837             }
838         }
839         if(child.nodeType == 1) { //element node type
840             if  ( (!tagName || child.nodeName == tagName) &&
841                 (!elementClass || classfound)&&
842                 (!elementID || child.id == elementID) &&
843                 (!elementName || child.name == elementName))
844             {
845                 children = children.concat(child);
846             } else {
847                 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
848             }
849         }
850     }
851     return children;
854 function unmaskPassword(id) {
855   var pw = document.getElementById(id);
856   var chb = document.getElementById(id+'unmask');
858   try {
859     // first try IE way - it can not set name attribute later
860     if (chb.checked) {
861       var newpw = document.createElement('<input type="text" name="'+pw.name+'">');
862     } else {
863       var newpw = document.createElement('<input type="password" name="'+pw.name+'">');
864     }
865     newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
866   } catch (e) {
867     var newpw = document.createElement('input');
868     newpw.setAttribute('name', pw.name);
869     if (chb.checked) {
870       newpw.setAttribute('type', 'text');
871     } else {
872       newpw.setAttribute('type', 'password');
873     }
874     newpw.setAttribute('class', pw.getAttribute('class'));
875   }
876   newpw.id = pw.id;
877   newpw.size = pw.size;
878   newpw.onblur = pw.onblur;
879   newpw.onchange = pw.onchange;
880   newpw.value = pw.value;
881   pw.parentNode.replaceChild(newpw, pw);
884 function filterByParent(elCollection, parentFinder) {
885     var filteredCollection = [];
886     for (var i = 0; i < elCollection.length; ++i) {
887         var findParent = parentFinder(elCollection[i]);
888         if (findParent.nodeName.toUpperCase != 'BODY') {
889             filteredCollection.push(elCollection[i]);
890         }
891     }
892     return filteredCollection;
896     All this is here just so that IE gets to handle oversized blocks
897     in a visually pleasing manner. It does a browser detect. So sue me.
900 function fix_column_widths() {
901     var agt = navigator.userAgent.toLowerCase();
902     if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
903         fix_column_width('left-column');
904         fix_column_width('right-column');
905     }
908 function fix_column_width(colName) {
909     if(column = document.getElementById(colName)) {
910         if(!column.offsetWidth) {
911             setTimeout("fix_column_width('" + colName + "')", 20);
912             return;
913         }
915         var width = 0;
916         var nodes = column.childNodes;
918         for(i = 0; i < nodes.length; ++i) {
919             if(nodes[i].className.indexOf("block") != -1 ) {
920                 if(width < nodes[i].offsetWidth) {
921                     width = nodes[i].offsetWidth;
922                 }
923             }
924         }
926         for(i = 0; i < nodes.length; ++i) {
927             if(nodes[i].className.indexOf("block") != -1 ) {
928                 nodes[i].style.width = width + 'px';
929             }
930         }
931     }
936    Insert myValue at current cursor position
937  */
938 function insertAtCursor(myField, myValue) {
939     // IE support
940     if (document.selection) {
941         myField.focus();
942         sel = document.selection.createRange();
943         sel.text = myValue;
944     }
945     // Mozilla/Netscape support
946     else if (myField.selectionStart || myField.selectionStart == '0') {
947         var startPos = myField.selectionStart;
948         var endPos = myField.selectionEnd;
949         myField.value = myField.value.substring(0, startPos)
950             + myValue + myField.value.substring(endPos, myField.value.length);
951     } else {
952         myField.value += myValue;
953     }
958         Call instead of setting window.onload directly or setting body onload=.
959         Adds your function to a chain of functions rather than overwriting anything
960         that exists.
962 function addonload(fn) {
963     var oldhandler=window.onload;
964     window.onload=function() {
965         if(oldhandler) oldhandler();
966             fn();
967     }
970  * Replacement for getElementsByClassName in browsers that aren't cool enough
972  * Relying on the built-in getElementsByClassName is far, far faster than
973  * using YUI.
975  * Note: the third argument used to be an object with odd behaviour. It now
976  * acts like the 'name' in the HTML5 spec, though the old behaviour is still
977  * mimicked if you pass an object.
979  * @param {Node} oElm The top-level node for searching. To search a whole
980  *                    document, use `document`.
981  * @param {String} strTagName filter by tag names
982  * @param {String} name same as HTML5 spec
983  */
984 function getElementsByClassName(oElm, strTagName, name) {
985     // for backwards compatibility
986     if(typeof name == "object") {
987         var names = new Array();
988         for(var i=0; i<name.length; i++) names.push(names[i]);
989         name = names.join('');
990     }
991     // use native implementation if possible
992     if (oElm.getElementsByClassName && Array.filter) {
993         if (strTagName == '*') {
994             return oElm.getElementsByClassName(name);
995         } else {
996             return Array.filter(oElm.getElementsByClassName(name), function(el) {
997                 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
998             });
999         }
1000     }
1001     // native implementation unavailable, fall back to slow method
1002     var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1003     var arrReturnElements = new Array();
1004     var arrRegExpClassNames = new Array();
1005     var names = name.split(' ');
1006     for(var i=0; i<names.length; i++) {
1007         arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1008     }
1009     var oElement;
1010     var bMatchesAll;
1011     for(var j=0; j<arrElements.length; j++) {
1012         oElement = arrElements[j];
1013         bMatchesAll = true;
1014         for(var k=0; k<arrRegExpClassNames.length; k++) {
1015             if(!arrRegExpClassNames[k].test(oElement.className)) {
1016                 bMatchesAll = false;
1017                 break;
1018             }
1019         }
1020         if(bMatchesAll) {
1021             arrReturnElements.push(oElement);
1022         }
1023     }
1024     return (arrReturnElements)
1027 function openpopup(event, args) {
1029     if (event) {
1030         if (event.preventDefault) {
1031             event.preventDefault();
1032         } else {
1033             event.returnValue = false;
1034         }
1035     }
1037     var fullurl = args.url;
1038     if (!args.url.match(/https?:\/\//)) {
1039         fullurl = M.cfg.wwwroot + args.url;
1040     }
1041     var windowobj = window.open(fullurl,args.name,args.options);
1042     if (!windowobj) {
1043         return true;
1044     }
1045     if (args.fullscreen) {
1046         windowobj.moveTo(0,0);
1047         windowobj.resizeTo(screen.availWidth,screen.availHeight);
1048     }
1049     windowobj.focus();
1051     return false;
1054 /** Close the current browser window. */
1055 function close_window(e) {
1056     if (e.preventDefault) {
1057         e.preventDefault();
1058     } else {
1059         e.returnValue = false;
1060     }
1061     window.close();
1065  * Used in a couple of modules to hide navigation areas when using AJAX
1066  */
1068 function show_item(itemid) {
1069     var item = document.getElementById(itemid);
1070     if (item) {
1071         item.style.display = "";
1072     }
1075 function destroy_item(itemid) {
1076     var item = document.getElementById(itemid);
1077     if (item) {
1078         item.parentNode.removeChild(item);
1079     }
1082  * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1083  * @param controlid the control id.
1084  */
1085 function focuscontrol(controlid) {
1086     var control = document.getElementById(controlid);
1087     if (control) {
1088         control.focus();
1089     }
1093  * Transfers keyboard focus to an HTML element based on the old style style of focus
1094  * This function should be removed as soon as it is no longer used
1095  */
1096 function old_onload_focus(formid, controlname) {
1097     if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1098         document.forms[formid].elements[controlname].focus();
1099     }
1102 function build_querystring(obj) {
1103     return convert_object_to_string(obj, '&');
1106 function build_windowoptionsstring(obj) {
1107     return convert_object_to_string(obj, ',');
1110 function convert_object_to_string(obj, separator) {
1111     if (typeof obj !== 'object') {
1112         return null;
1113     }
1114     var list = [];
1115     for(var k in obj) {
1116         k = encodeURIComponent(k);
1117         var value = obj[k];
1118         if(obj[k] instanceof Array) {
1119             for(var i in value) {
1120                 list.push(k+'[]='+encodeURIComponent(value[i]));
1121             }
1122         } else {
1123             list.push(k+'='+encodeURIComponent(value));
1124         }
1125     }
1126     return list.join(separator);
1129 function stripHTML(str) {
1130     var re = /<\S[^><]*>/g;
1131     var ret = str.replace(re, "");
1132     return ret;
1135 Number.prototype.fixed=function(n){
1136     with(Math)
1137         return round(Number(this)*pow(10,n))/pow(10,n);
1139 function update_progress_bar (id, width, pt, msg, es){
1140     var percent = pt;
1141     var status = document.getElementById("status_"+id);
1142     var percent_indicator = document.getElementById("pt_"+id);
1143     var progress_bar = document.getElementById("progress_"+id);
1144     var time_es = document.getElementById("time_"+id);
1145     status.innerHTML = msg;
1146     percent_indicator.innerHTML = percent.fixed(2) + '%';
1147     if(percent == 100) {
1148         progress_bar.style.background = "green";
1149         time_es.style.display = "none";
1150     } else {
1151         progress_bar.style.background = "#FFCC66";
1152         if (es == '?'){
1153             time_es.innerHTML = "";
1154         }else {
1155             time_es.innerHTML = es.fixed(2)+" sec";
1156             time_es.style.display
1157                 = "block";
1158         }
1159     }
1160     progress_bar.style.width = width + "px";
1164 function frame_breakout(e, properties) {
1165     this.setAttribute('target', properties.framename);
1169 // ===== Deprecated core Javascript functions for Moodle ====
1170 //       DO NOT USE!!!!!!!
1171 // Do not put this stuff in separate file because it only adds extra load on servers!
1174  * Used in a couple of modules to hide navigation areas when using AJAX
1175  */
1176 function hide_item(itemid) {
1177     // use class='hiddenifjs' instead
1178     var item = document.getElementById(itemid);
1179     if (item) {
1180         item.style.display = "none";
1181     }
1184 M.util.help_icon = {
1185     Y : null,
1186     instance : null,
1187     add : function(Y, properties) {
1188         this.Y = Y;
1189         properties.node = Y.one('#'+properties.id);
1190         if (properties.node) {
1191             properties.node.on('click', this.display, this, properties);
1192         }
1193     },
1194     display : function(event, args) {
1195         event.preventDefault();
1196         if (M.util.help_icon.instance === null) {
1197             var Y = M.util.help_icon.Y;
1198             Y.use('overlay', 'io', 'event-mouseenter', 'node', 'event-key', function(Y) {
1199                 var help_content_overlay = {
1200                     helplink : null,
1201                     overlay : null,
1202                     init : function() {
1204                         var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img  src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
1205                         // Create an overlay from markup
1206                         this.overlay = new Y.Overlay({
1207                             headerContent: closebtn,
1208                             bodyContent: '',
1209                             id: 'helppopupbox',
1210                             width:'400px',
1211                             visible : false,
1212                             constrain : true
1213                         });
1214                         this.overlay.render(Y.one(document.body));
1216                         closebtn.on('click', this.overlay.hide, this.overlay);
1218                         var boundingBox = this.overlay.get("boundingBox");
1220                         //  Hide the menu if the user clicks outside of its content
1221                         boundingBox.get("ownerDocument").on("mousedown", function (event) {
1222                             var oTarget = event.target;
1223                             var menuButton = Y.one("#"+args.id);
1225                             if (!oTarget.compareTo(menuButton) &&
1226                                 !menuButton.contains(oTarget) &&
1227                                 !oTarget.compareTo(boundingBox) &&
1228                                 !boundingBox.contains(oTarget)) {
1229                                 this.overlay.hide();
1230                             }
1231                         }, this);
1233                         Y.on("key", this.close, closebtn , "down:13", this);
1234                         closebtn.on('click', this.close, this);
1235                     },
1237                     close : function(e) {
1238                         e.preventDefault();
1239                         this.helplink.focus();
1240                         this.overlay.hide();
1241                     },
1243                     display : function(event, args) {
1244                         this.helplink = args.node;
1245                         this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1246                         this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
1248                         var fullurl = args.url;
1249                         if (!args.url.match(/https?:\/\//)) {
1250                             fullurl = M.cfg.wwwroot + args.url;
1251                         }
1253                         var ajaxurl = fullurl + '&ajax=1';
1255                         var cfg = {
1256                             method: 'get',
1257                             context : this,
1258                             on: {
1259                                 success: function(id, o, node) {
1260                                     this.display_callback(o.responseText);
1261                                 },
1262                                 failure: function(id, o, node) {
1263                                     var debuginfo = o.statusText;
1264                                     if (M.cfg.developerdebug) {
1265                                         o.statusText += ' (' + ajaxurl + ')';
1266                                     }
1267                                     this.display_callback('bodyContent',debuginfo);
1268                                 }
1269                             }
1270                         };
1272                         Y.io(ajaxurl, cfg);
1273                         this.overlay.show();
1275                         Y.one('#closehelpbox').focus();
1276                     },
1278                     display_callback : function(content) {
1279                         this.overlay.set('bodyContent', content);
1280                     },
1282                     hideContent : function() {
1283                         help = this;
1284                         help.overlay.hide();
1285                     }
1286                 };
1287                 help_content_overlay.init();
1288                 M.util.help_icon.instance = help_content_overlay;
1289                 M.util.help_icon.instance.display(event, args);
1290             });
1291         } else {
1292             M.util.help_icon.instance.display(event, args);
1293         }
1294     },
1295     init : function(Y) {
1296         this.Y = Y;
1297     }
1301  * Custom menu namespace
1302  */
1303 M.core_custom_menu = {
1304     /**
1305      * This method is used to initialise a custom menu given the id that belongs
1306      * to the custom menu's root node.
1307      *
1308      * @param {YUI} Y
1309      * @param {string} nodeid
1310      */
1311     init : function(Y, nodeid) {
1312         var node = Y.one('#'+nodeid);
1313         if (node) {
1314             Y.use('node-menunav', function(Y) {
1315                 // Get the node
1316                 // Remove the javascript-disabled class.... obviously javascript is enabled.
1317                 node.removeClass('javascript-disabled');
1318                 // Initialise the menunav plugin
1319                 node.plug(Y.Plugin.NodeMenuNav);
1320             });
1321         }
1322     }
1326  * Used to store form manipulation methods and enhancments
1327  */
1328 M.form = M.form || {};
1331  * Converts a nbsp indented select box into a multi drop down custom control much
1332  * like the custom menu. It also selectable categories on or off.
1334  * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1336  * @param {YUI} Y
1337  * @param {string} id
1338  * @param {Array} options
1339  */
1340 M.form.init_smartselect = function(Y, id, options) {
1341     if (!id.match(/^id_/)) {
1342         id = 'id_'+id;
1343     }
1344     var select = Y.one('select#'+id);
1345     if (!select) {
1346         return false;
1347     }
1348     Y.use('event-delegate',function(){
1349         var smartselect = {
1350             id : id,
1351             structure : [],
1352             options : [],
1353             submenucount : 0,
1354             currentvalue : null,
1355             currenttext : null,
1356             shownevent : null,
1357             cfg : {
1358                 selectablecategories : true,
1359                 mode : null
1360             },
1361             nodes : {
1362                 select : null,
1363                 loading : null,
1364                 menu : null
1365             },
1366             init : function(Y, id, args, nodes) {
1367                 if (typeof(args)=='object') {
1368                     for (var i in this.cfg) {
1369                         if (args[i] || args[i]===false) {
1370                             this.cfg[i] = args[i];
1371                         }
1372                     }
1373                 }
1375                 // Display a loading message first up
1376                 this.nodes.select = nodes.select;
1378                 this.currentvalue = this.nodes.select.get('selectedIndex');
1379                 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1381                 var options = Array();
1382                 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1383                 this.nodes.select.all('option').each(function(option, index) {
1384                     var rawtext = option.get('innerHTML');
1385                     var text = rawtext.replace(/^(&nbsp;)*/, '');
1386                     if (rawtext === text) {
1387                         text = rawtext.replace(/^(\s)*/, '');
1388                         var depth = (rawtext.length - text.length ) + 1;
1389                     } else {
1390                         var depth = ((rawtext.length - text.length )/12)+1;
1391                     }
1392                     option.set('innerHTML', text);
1393                     options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1394                 }, this);
1396                 this.structure = [];
1397                 var structcount = 0;
1398                 for (var i in options) {
1399                     var o = options[i];
1400                     if (o.depth == 0) {
1401                         this.structure.push(o);
1402                         structcount++;
1403                     } else {
1404                         var d = o.depth;
1405                         var current = this.structure[structcount-1];
1406                         for (var j = 0; j < o.depth-1;j++) {
1407                             if (current && current.children) {
1408                                 current = current.children[current.children.length-1];
1409                             }
1410                         }
1411                         if (current && current.children) {
1412                             current.children.push(o);
1413                         }
1414                     }
1415                 }
1417                 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1418                 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1419                 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1420                 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1422                 if (this.cfg.mode == null) {
1423                     var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1424                     if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1425                         this.cfg.mode = 'compact';
1426                     } else {
1427                         this.cfg.mode = 'spanning';
1428                     }
1429                 }
1431                 if (this.cfg.mode == 'compact') {
1432                     this.nodes.menu.addClass('compactmenu');
1433                 } else {
1434                     this.nodes.menu.addClass('spanningmenu');
1435                     this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1436                 }
1438                 Y.one(document.body).append(this.nodes.menu);
1439                 var pos = this.nodes.select.getXY();
1440                 pos[0] += 1;
1441                 this.nodes.menu.setXY(pos);
1442                 this.nodes.menu.on('click', this.handle_click, this);
1444                 Y.one(window).on('resize', function(){
1445                      var pos = this.nodes.select.getXY();
1446                     pos[0] += 1;
1447                     this.nodes.menu.setXY(pos);
1448                  }, this);
1449             },
1450             generate_menu_content : function() {
1451                 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1452                 content += this.generate_submenu_content(this.structure[0], true);
1453                 content += '</ul></div>';
1454                 return content;
1455             },
1456             generate_submenu_content : function(item, rootelement) {
1457                 this.submenucount++;
1458                 var content = '';
1459                 if (item.children.length > 0) {
1460                     if (rootelement) {
1461                         content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1462                         content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1463                         content += '<div class="smartselect_menu_content">';
1464                     } else {
1465                         content += '<li class="smartselect_submenuitem">';
1466                         var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1467                         content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1468                         content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1469                         content += '<div class="smartselect_submenu_content">';
1470                     }
1471                     content += '<ul>';
1472                     for (var i in item.children) {
1473                         content += this.generate_submenu_content(item.children[i],false);
1474                     }
1475                     content += '</ul>';
1476                     content += '</div>';
1477                     content += '</div>';
1478                     if (rootelement) {
1479                     } else {
1480                         content += '</li>';
1481                     }
1482                 } else {
1483                     content += '<li class="smartselect_menuitem">';
1484                     content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1485                     content += '</li>';
1486                 }
1487                 return content;
1488             },
1489             select : function(e) {
1490                 var t = e.target;
1491                 e.halt();
1492                 this.currenttext = t.get('innerHTML');
1493                 this.currentvalue = t.getAttribute('value');
1494                 this.nodes.select.set('selectedIndex', this.currentvalue);
1495                 this.hide_menu();
1496             },
1497             handle_click : function(e) {
1498                 var target = e.target;
1499                 if (target.hasClass('smartselect_mask')) {
1500                     this.show_menu(e);
1501                 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1502                     this.select(e);
1503                 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1504                     this.show_sub_menu(e);
1505                 }
1506             },
1507             show_menu : function(e) {
1508                 e.halt();
1509                 var menu = e.target.ancestor().one('.smartselect_menu');
1510                 menu.addClass('visible');
1511                 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1512             },
1513             show_sub_menu : function(e) {
1514                 e.halt();
1515                 var target = e.target;
1516                 if (!target.hasClass('smartselect_submenuitem')) {
1517                     target = target.ancestor('.smartselect_submenuitem');
1518                 }
1519                 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1520                     target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1521                     return;
1522                 }
1523                 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1524                 target.one('.smartselect_submenu').addClass('visible');
1525             },
1526             hide_menu : function() {
1527                 this.nodes.menu.all('.visible').removeClass('visible');
1528                 if (this.shownevent) {
1529                     this.shownevent.detach();
1530                 }
1531             }
1532         };
1533         smartselect.init(Y, id, options, {select:select});
1534     });
1537 M.util.init_flvflowplayer = function (id, playerpath, fileurl) {
1538     $f(id, playerpath, {
1539         plugins: {
1540                 controls: {
1541                        autoHide: true
1542                 }
1543         },
1544         clip: {
1545             url: fileurl,
1546             autoPlay: false,
1547             autoBuffering: true
1548         }
1549     });
1552 M.util.init_mp3flowplayer = function (id, playerpath, audioplayerpath, fileurl, color) {
1554     $f(id, playerpath, {
1556             plugins: {
1557                     controls: {
1558                             fullscreen: false,
1559                             height: 25,
1560                             autoHide: false,
1561                             background: '#'+color['bgColour'],
1562                             buttonColor: '#'+color['btnColour'],
1563                             sliderColor: '#'+color['handleColour'],
1564                             volumeSliderColor: '#'+color['handleColour'],
1565                             volumeColor: '#'+color['trackColour'],
1566                             durationColor: '#'+color['fontColour'],
1567                             buttonOverColor: '#'+color['iconOverColour'],
1568                             progressColor: '#'+color['handleColour']
1569                     },
1570                     audio: {url: audioplayerpath}
1571             },
1572             clip: {url: fileurl,
1573                     provider: "audio",
1574                     autoPlay: false
1575             }
1576     });
1579 M.util.init_mp3flowplayerplugin = function (id, playerpath, audioplayerpath, fileurl, color) {
1580     $f(id, playerpath, {
1581             plugins: {
1582                     controls: {
1583                             all: false,
1584                             play: true,
1585                             pause: true,
1586                             scrubber: true,
1587                             autoHide: false,
1588                             height: 15,
1589                             background: '#'+color['bgColour'],
1590                             buttonColor: '#'+color['btnColour'],
1591                             sliderColor: '#'+color['handleColour'],
1592                             volumeSliderColor: '#'+color['handleColour'],
1593                             progressColor: '#'+color['handleColour'],
1594                             volumeColor: '#'+color['trackColour'],
1595                             buttonOverColor: '#'+color['iconOverColour']
1596                     },
1597                     audio: {url: audioplayerpath}
1598             },
1599             clip: {url: fileurl,
1600                     provider: "audio",
1601                     autoPlay: false
1602             }
1603     });