javascript lib MDL-23224 dropdown onclick event is not trigger in Chrome
[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                 //little hack for chrome that need onChange event instead of onClick - see MDL-23224
369                 if (Y.UA.webkit) {
370                     select.on('change', processchange, form, select.get('selectedIndex'));
371                 } else {
372                     select.on('click', processchange, form, select.get('selectedIndex'));
373                 }
374             }
375         }
376     });
377 };
379 /**
380  * Attach handler to url_select
381  */
382 M.util.init_url_select = function(Y, formid, selectid, nothing) {
383     YUI(M.yui.loader).use('node', function(Y) {
384         Y.on('change', function() {
385             if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
386                 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
387             }
388         },
389         '#'+selectid);
390     });
391 };
393 /**
394  * Breaks out all links to the top frame - used in frametop page layout.
395  */
396 M.util.init_frametop = function(Y) {
397     Y.all('a').each(function(node) {
398         node.set('target', '_top');
399     });
400     Y.all('form').each(function(node) {
401         node.set('target', '_top');
402     });
403 };
405 /**
406  * Finds all nodes that match the given CSS selector and attaches events to them
407  * so that they toggle a given classname when clicked.
408  *
409  * @param {YUI} Y
410  * @param {string} id An id containing elements to target
411  * @param {string} cssselector A selector to use to find targets
412  * @param {string} toggleclassname A classname to toggle
413  */
414 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
416     if (togglecssselector == '') {
417         togglecssselector = cssselector;
418     }
420     var node = Y.one('#'+id);
421     node.all(cssselector).each(function(n){
422         n.on('click', function(e){
423             e.stopPropagation();
424             if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
425                 if (this.test(togglecssselector)) {
426                     this.toggleClass(toggleclassname);
427                 } else {
428                     this.ancestor(togglecssselector).toggleClass(toggleclassname);
429             }
430             }
431         }, n);
432     });
433     // Attach this click event to the node rather than all selectors... will be much better
434     // for performance
435     node.on('click', function(e){
436         if (e.target.hasClass('addtoall')) {
437             this.all(togglecssselector).addClass(toggleclassname);
438         } else if (e.target.hasClass('removefromall')) {
439             this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
440         }
441     }, node);
442 };
444 /**
445  * Initialises a colour picker
446  *
447  * Designed to be used with admin_setting_configcolourpicker although could be used
448  * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
449  * above or below the input (must have the same parent) and then call this with the
450  * id.
451  *
452  * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
453  * contrib/blocks. For better docs refer to that.
454  *
455  * @param {YUI} Y
456  * @param {int} id
457  * @param {object} previewconf
458  */
459 M.util.init_colour_picker = function(Y, id, previewconf) {
460     /**
461      * We need node and event-mouseenter
462      */
463     Y.use('node', 'event-mouseenter', function(){
464         /**
465          * The colour picker object
466          */
467         var colourpicker = {
468             box : null,
469             input : null,
470             image : null,
471             preview : null,
472             current : null,
473             eventClick : null,
474             eventMouseEnter : null,
475             eventMouseLeave : null,
476             eventMouseMove : null,
477             width : 300,
478             height :  100,
479             factor : 5,
480             /**
481              * Initalises the colour picker by putting everything together and wiring the events
482              */
483             init : function() {
484                 this.input = Y.one('#'+id);
485                 this.box = this.input.ancestor().one('.admin_colourpicker');
486                 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
487                 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
488                 this.preview = Y.Node.create('<div class="previewcolour"></div>');
489                 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
490                 this.current = Y.Node.create('<div class="currentcolour"></div>');
491                 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
492                 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
494                 if (typeof(previewconf) === 'object' && previewconf !== null) {
495                     Y.one('#'+id+'_preview').on('click', function(e){
496                         if (Y.Lang.isString(previewconf.selector)) {
497                             Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
498                         } else {
499                             for (var i in previewconf.selector) {
500                                 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
501                             }
502                         }
503                     }, this);
504                 }
506                 this.eventClick = this.image.on('click', this.pickColour, this);
507                 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
508             },
509             /**
510              * Starts to follow the mouse once it enter the image
511              */
512             startFollow : function(e) {
513                 this.eventMouseEnter.detach();
514                 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
515                 this.eventMouseMove = this.image.on('mousemove', function(e){
516                     this.preview.setStyle('backgroundColor', this.determineColour(e));
517                 }, this);
518             },
519             /**
520              * Stops following the mouse
521              */
522             endFollow : function(e) {
523                 this.eventMouseMove.detach();
524                 this.eventMouseLeave.detach();
525                 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
526             },
527             /**
528              * Picks the colour the was clicked on
529              */
530             pickColour : function(e) {
531                 var colour = this.determineColour(e);
532                 this.input.set('value', colour);
533                 this.current.setStyle('backgroundColor', colour);
534             },
535             /**
536              * Calculates the colour fromthe given co-ordinates
537              */
538             determineColour : function(e) {
539                 var eventx = Math.floor(e.pageX-e.target.getX());
540                 var eventy = Math.floor(e.pageY-e.target.getY());
542                 var imagewidth = this.width;
543                 var imageheight = this.height;
544                 var factor = this.factor;
545                 var colour = [255,0,0];
547                 var matrices = [
548                     [  0,  1,  0],
549                     [ -1,  0,  0],
550                     [  0,  0,  1],
551                     [  0, -1,  0],
552                     [  1,  0,  0],
553                     [  0,  0, -1]
554                 ];
556                 var matrixcount = matrices.length;
557                 var limit = Math.round(imagewidth/matrixcount);
558                 var heightbreak = Math.round(imageheight/2);
560                 for (var x = 0; x < imagewidth; x++) {
561                     var divisor = Math.floor(x / limit);
562                     var matrix = matrices[divisor];
564                     colour[0] += matrix[0]*factor;
565                     colour[1] += matrix[1]*factor;
566                     colour[2] += matrix[2]*factor;
568                     if (eventx==x) {
569                         break;
570                     }
571                 }
573                 var pixel = [colour[0], colour[1], colour[2]];
574                 if (eventy < heightbreak) {
575                     pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
576                     pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
577                     pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
578                 } else if (eventy > heightbreak) {
579                     pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
580                     pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
581                     pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
582                 }
584                 return this.convert_rgb_to_hex(pixel);
585             },
586             /**
587              * Converts an RGB value to Hex
588              */
589             convert_rgb_to_hex : function(rgb) {
590                 var hex = '#';
591                 var hexchars = "0123456789ABCDEF";
592                 for (var i=0; i<3; i++) {
593                     var number = Math.abs(rgb[i]);
594                     if (number == 0 || isNaN(number)) {
595                         hex += '00';
596                     } else {
597                         hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
598                     }
599                 }
600                 return hex;
601             }
602         };
603         /**
604          * Initialise the colour picker :) Hoorah
605          */
606         colourpicker.init();
607     });
608 };
610 M.util.init_block_hider = function(Y, config) {
611     Y.use('base', 'node', function(Y) {
612         M.util.block_hider = M.util.block_hider || (function(){
613             var blockhider = function() {
614                 blockhider.superclass.constructor.apply(this, arguments);
615             };
616             blockhider.prototype = {
617                 initializer : function(config) {
618                     this.set('block', '#'+this.get('id'));
619                     var b = this.get('block'),
620                         t = b.one('.title'),
621                         a = null;
622                     if (t && (a = t.one('.block_action'))) {
623                         var hide = Y.Node.create('<img class="block-hider-hide" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
624                         hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
625                         var show = Y.Node.create('<img class="block-hider-show" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
626                         show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
627                         a.insert(show, 0).insert(hide, 0);
628                     }
629                 },
630                 updateState : function(e, hide) {
631                     M.util.set_user_preference(this.get('preference'), hide);
632                     if (hide) {
633                         this.get('block').addClass('hidden');
634                     } else {
635                         this.get('block').removeClass('hidden');
636                     }
637                 }
638             };
639             Y.extend(blockhider, Y.Base, blockhider.prototype, {
640                 NAME : 'blockhider',
641                 ATTRS : {
642                     id : {},
643                     preference : {},
644                     iconVisible : {
645                         value : M.util.image_url('t/switch_minus', 'moodle')
646                     },
647                     iconHidden : {
648                         value : M.util.image_url('t/switch_plus', 'moodle')
649                     },
650                     block : {
651                         setter : function(node) {
652                             return Y.one(node);
653                         }
654                     }
655                 }
656             });
657             return blockhider;
658         })();
659         new M.util.block_hider(config);
660     });
661 };
663 /**
664  * Returns a string registered in advance for usage in JavaScript
665  *
666  * If you do not pass the third parameter, the function will just return
667  * the corresponding value from the M.str object. If the third parameter is
668  * provided, the function performs {$a} placeholder substitution in the
669  * same way as PHP get_string() in Moodle does.
670  *
671  * @param {String} identifier string identifier
672  * @param {String} component the component providing the string
673  * @param {Object|String} a optional variable to populate placeholder with
674  */
675 M.util.get_string = function(identifier, component, a) {
676     var stringvalue;
678     if (M.cfg.developerdebug) {
679         // creating new instance if YUI is not optimal but it seems to be better way then
680         // require the instance via the function API - note that it is used in rare cases
681         // for debugging only anyway
682         var Y = new YUI({ debug : true });
683     }
685     if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
686         stringvalue = '[[' + identifier + ',' + component + ']]';
687         if (M.cfg.developerdebug) {
688             Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
689         }
690         return stringvalue;
691     }
693     stringvalue = M.str[component][identifier];
695     if (typeof a == 'undefined') {
696         // no placeholder substitution requested
697         return stringvalue;
698     }
700     if (typeof a == 'number' || typeof a == 'string') {
701         // replace all occurrences of {$a} with the placeholder value
702         stringvalue = stringvalue.replace(/\{\$a\}/g, a);
703         return stringvalue;
704     }
706     if (typeof a == 'object') {
707         // replace {$a->key} placeholders
708         for (var key in a) {
709             if (typeof a[key] != 'number' && typeof a[key] != 'string') {
710                 if (M.cfg.developerdebug) {
711                     Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
712                 }
713                 continue;
714             }
715             var search = '{$a->' + key + '}';
716             search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
717             search = new RegExp(search, 'g');
718             stringvalue = stringvalue.replace(search, a[key]);
719         }
720         return stringvalue;
721     }
723     if (M.cfg.developerdebug) {
724         Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
725     }
726     return stringvalue;
727 };
729 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
731 function checkall() {
732     var inputs = document.getElementsByTagName('input');
733     for (var i = 0; i < inputs.length; i++) {
734         if (inputs[i].type == 'checkbox') {
735             inputs[i].checked = true;
736         }
737     }
740 function checknone() {
741     var inputs = document.getElementsByTagName('input');
742     for (var i = 0; i < inputs.length; i++) {
743         if (inputs[i].type == 'checkbox') {
744             inputs[i].checked = false;
745         }
746     }
749 /**
750  * Either check, or uncheck, all checkboxes inside the element with id is
751  * @param id the id of the container
752  * @param checked the new state, either '' or 'checked'.
753  */
754 function select_all_in_element_with_id(id, checked) {
755     var container = document.getElementById(id);
756     if (!container) {
757         return;
758     }
759     var inputs = container.getElementsByTagName('input');
760     for (var i = 0; i < inputs.length; ++i) {
761         if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
762             inputs[i].checked = checked;
763         }
764     }
767 function select_all_in(elTagName, elClass, elId) {
768     var inputs = document.getElementsByTagName('input');
769     inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
770     for(var i = 0; i < inputs.length; ++i) {
771         if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
772             inputs[i].checked = 'checked';
773         }
774     }
777 function deselect_all_in(elTagName, elClass, elId) {
778     var inputs = document.getElementsByTagName('INPUT');
779     inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
780     for(var i = 0; i < inputs.length; ++i) {
781         if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
782             inputs[i].checked = '';
783         }
784     }
787 function confirm_if(expr, message) {
788     if(!expr) {
789         return true;
790     }
791     return confirm(message);
795 /*
796     findParentNode (start, elementName, elementClass, elementID)
798     Travels up the DOM hierarchy to find a parent element with the
799     specified tag name, class, and id. All conditions must be met,
800     but any can be ommitted. Returns the BODY element if no match
801     found.
802 */
803 function findParentNode(el, elName, elClass, elId) {
804     while (el.nodeName.toUpperCase() != 'BODY') {
805         if ((!elName || el.nodeName.toUpperCase() == elName) &&
806             (!elClass || el.className.indexOf(elClass) != -1) &&
807             (!elId || el.id == elId)) {
808             break;
809         }
810         el = el.parentNode;
811     }
812     return el;
814 /*
815     findChildNode (start, elementName, elementClass, elementID)
817     Travels down the DOM hierarchy to find all child elements with the
818     specified tag name, class, and id. All conditions must be met,
819     but any can be ommitted.
820     Doesn't examine children of matches.
821 */
822 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
823     var children = new Array();
824     for (var i = 0; i < start.childNodes.length; i++) {
825         var classfound = false;
826         var child = start.childNodes[i];
827         if((child.nodeType == 1) &&//element node type
828                   (elementClass && (typeof(child.className)=='string'))) {
829             var childClasses = child.className.split(/\s+/);
830             for (var childClassIndex in childClasses) {
831                 if (childClasses[childClassIndex]==elementClass) {
832                     classfound = true;
833                     break;
834                 }
835             }
836         }
837         if(child.nodeType == 1) { //element node type
838             if  ( (!tagName || child.nodeName == tagName) &&
839                 (!elementClass || classfound)&&
840                 (!elementID || child.id == elementID) &&
841                 (!elementName || child.name == elementName))
842             {
843                 children = children.concat(child);
844             } else {
845                 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
846             }
847         }
848     }
849     return children;
852 function unmaskPassword(id) {
853   var pw = document.getElementById(id);
854   var chb = document.getElementById(id+'unmask');
856   try {
857     // first try IE way - it can not set name attribute later
858     if (chb.checked) {
859       var newpw = document.createElement('<input type="text" name="'+pw.name+'">');
860     } else {
861       var newpw = document.createElement('<input type="password" name="'+pw.name+'">');
862     }
863     newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
864   } catch (e) {
865     var newpw = document.createElement('input');
866     newpw.setAttribute('name', pw.name);
867     if (chb.checked) {
868       newpw.setAttribute('type', 'text');
869     } else {
870       newpw.setAttribute('type', 'password');
871     }
872     newpw.setAttribute('class', pw.getAttribute('class'));
873   }
874   newpw.id = pw.id;
875   newpw.size = pw.size;
876   newpw.onblur = pw.onblur;
877   newpw.onchange = pw.onchange;
878   newpw.value = pw.value;
879   pw.parentNode.replaceChild(newpw, pw);
882 function filterByParent(elCollection, parentFinder) {
883     var filteredCollection = [];
884     for (var i = 0; i < elCollection.length; ++i) {
885         var findParent = parentFinder(elCollection[i]);
886         if (findParent.nodeName.toUpperCase != 'BODY') {
887             filteredCollection.push(elCollection[i]);
888         }
889     }
890     return filteredCollection;
893 /*
894     All this is here just so that IE gets to handle oversized blocks
895     in a visually pleasing manner. It does a browser detect. So sue me.
896 */
898 function fix_column_widths() {
899     var agt = navigator.userAgent.toLowerCase();
900     if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
901         fix_column_width('left-column');
902         fix_column_width('right-column');
903     }
906 function fix_column_width(colName) {
907     if(column = document.getElementById(colName)) {
908         if(!column.offsetWidth) {
909             setTimeout("fix_column_width('" + colName + "')", 20);
910             return;
911         }
913         var width = 0;
914         var nodes = column.childNodes;
916         for(i = 0; i < nodes.length; ++i) {
917             if(nodes[i].className.indexOf("block") != -1 ) {
918                 if(width < nodes[i].offsetWidth) {
919                     width = nodes[i].offsetWidth;
920                 }
921             }
922         }
924         for(i = 0; i < nodes.length; ++i) {
925             if(nodes[i].className.indexOf("block") != -1 ) {
926                 nodes[i].style.width = width + 'px';
927             }
928         }
929     }
933 /*
934    Insert myValue at current cursor position
935  */
936 function insertAtCursor(myField, myValue) {
937     // IE support
938     if (document.selection) {
939         myField.focus();
940         sel = document.selection.createRange();
941         sel.text = myValue;
942     }
943     // Mozilla/Netscape support
944     else if (myField.selectionStart || myField.selectionStart == '0') {
945         var startPos = myField.selectionStart;
946         var endPos = myField.selectionEnd;
947         myField.value = myField.value.substring(0, startPos)
948             + myValue + myField.value.substring(endPos, myField.value.length);
949     } else {
950         myField.value += myValue;
951     }
955 /*
956         Call instead of setting window.onload directly or setting body onload=.
957         Adds your function to a chain of functions rather than overwriting anything
958         that exists.
959 */
960 function addonload(fn) {
961     var oldhandler=window.onload;
962     window.onload=function() {
963         if(oldhandler) oldhandler();
964             fn();
965     }
967 /**
968  * Replacement for getElementsByClassName in browsers that aren't cool enough
969  *
970  * Relying on the built-in getElementsByClassName is far, far faster than
971  * using YUI.
972  *
973  * Note: the third argument used to be an object with odd behaviour. It now
974  * acts like the 'name' in the HTML5 spec, though the old behaviour is still
975  * mimicked if you pass an object.
976  *
977  * @param {Node} oElm The top-level node for searching. To search a whole
978  *                    document, use `document`.
979  * @param {String} strTagName filter by tag names
980  * @param {String} name same as HTML5 spec
981  */
982 function getElementsByClassName(oElm, strTagName, name) {
983     // for backwards compatibility
984     if(typeof name == "object") {
985         var names = new Array();
986         for(var i=0; i<name.length; i++) names.push(names[i]);
987         name = names.join('');
988     }
989     // use native implementation if possible
990     if (oElm.getElementsByClassName && Array.filter) {
991         if (strTagName == '*') {
992             return oElm.getElementsByClassName(name);
993         } else {
994             return Array.filter(oElm.getElementsByClassName(name), function(el) {
995                 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
996             });
997         }
998     }
999     // native implementation unavailable, fall back to slow method
1000     var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1001     var arrReturnElements = new Array();
1002     var arrRegExpClassNames = new Array();
1003     var names = name.split(' ');
1004     for(var i=0; i<names.length; i++) {
1005         arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1006     }
1007     var oElement;
1008     var bMatchesAll;
1009     for(var j=0; j<arrElements.length; j++) {
1010         oElement = arrElements[j];
1011         bMatchesAll = true;
1012         for(var k=0; k<arrRegExpClassNames.length; k++) {
1013             if(!arrRegExpClassNames[k].test(oElement.className)) {
1014                 bMatchesAll = false;
1015                 break;
1016             }
1017         }
1018         if(bMatchesAll) {
1019             arrReturnElements.push(oElement);
1020         }
1021     }
1022     return (arrReturnElements)
1025 function openpopup(event, args) {
1027     if (event) {
1028         if (event.preventDefault) {
1029             event.preventDefault();
1030         } else {
1031             event.returnValue = false;
1032         }
1033     }
1035     var fullurl = args.url;
1036     if (!args.url.match(/https?:\/\//)) {
1037         fullurl = M.cfg.wwwroot + args.url;
1038     }
1039     var windowobj = window.open(fullurl,args.name,args.options);
1040     if (!windowobj) {
1041         return true;
1042     }
1043     if (args.fullscreen) {
1044         windowobj.moveTo(0,0);
1045         windowobj.resizeTo(screen.availWidth,screen.availHeight);
1046     }
1047     windowobj.focus();
1049     return false;
1052 /** Close the current browser window. */
1053 function close_window(e) {
1054     if (e.preventDefault) {
1055         e.preventDefault();
1056     } else {
1057         e.returnValue = false;
1058     }
1059     self.close();
1062 /**
1063  * Close the current browser window, forcing the window/tab that opened this
1064  * popup to reload itself. */
1065 function close_window_reloading_opener() {
1066     if (window.opener) {
1067         window.opener.location.reload(1);
1068         close_window();
1069         // Intentionally, only try to close the window if there is some evidence we are in a popup.
1070     }
1073 /**
1074  * Used in a couple of modules to hide navigation areas when using AJAX
1075  */
1077 function show_item(itemid) {
1078     var item = document.getElementById(itemid);
1079     if (item) {
1080         item.style.display = "";
1081     }
1084 function destroy_item(itemid) {
1085     var item = document.getElementById(itemid);
1086     if (item) {
1087         item.parentNode.removeChild(item);
1088     }
1090 /**
1091  * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1092  * @param controlid the control id.
1093  */
1094 function focuscontrol(controlid) {
1095     var control = document.getElementById(controlid);
1096     if (control) {
1097         control.focus();
1098     }
1101 /**
1102  * Transfers keyboard focus to an HTML element based on the old style style of focus
1103  * This function should be removed as soon as it is no longer used
1104  */
1105 function old_onload_focus(formid, controlname) {
1106     if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1107         document.forms[formid].elements[controlname].focus();
1108     }
1111 function build_querystring(obj) {
1112     return convert_object_to_string(obj, '&');
1115 function build_windowoptionsstring(obj) {
1116     return convert_object_to_string(obj, ',');
1119 function convert_object_to_string(obj, separator) {
1120     if (typeof obj !== 'object') {
1121         return null;
1122     }
1123     var list = [];
1124     for(var k in obj) {
1125         k = encodeURIComponent(k);
1126         var value = obj[k];
1127         if(obj[k] instanceof Array) {
1128             for(var i in value) {
1129                 list.push(k+'[]='+encodeURIComponent(value[i]));
1130             }
1131         } else {
1132             list.push(k+'='+encodeURIComponent(value));
1133         }
1134     }
1135     return list.join(separator);
1138 function stripHTML(str) {
1139     var re = /<\S[^><]*>/g;
1140     var ret = str.replace(re, "");
1141     return ret;
1144 Number.prototype.fixed=function(n){
1145     with(Math)
1146         return round(Number(this)*pow(10,n))/pow(10,n);
1147 };
1148 function update_progress_bar (id, width, pt, msg, es){
1149     var percent = pt;
1150     var status = document.getElementById("status_"+id);
1151     var percent_indicator = document.getElementById("pt_"+id);
1152     var progress_bar = document.getElementById("progress_"+id);
1153     var time_es = document.getElementById("time_"+id);
1154     status.innerHTML = msg;
1155     percent_indicator.innerHTML = percent.fixed(2) + '%';
1156     if(percent == 100) {
1157         progress_bar.style.background = "green";
1158         time_es.style.display = "none";
1159     } else {
1160         progress_bar.style.background = "#FFCC66";
1161         if (es == '?'){
1162             time_es.innerHTML = "";
1163         }else {
1164             time_es.innerHTML = es.fixed(2)+" sec";
1165             time_es.style.display
1166                 = "block";
1167         }
1168     }
1169     progress_bar.style.width = width + "px";
1173 function frame_breakout(e, properties) {
1174     this.setAttribute('target', properties.framename);
1178 // ===== Deprecated core Javascript functions for Moodle ====
1179 //       DO NOT USE!!!!!!!
1180 // Do not put this stuff in separate file because it only adds extra load on servers!
1182 /**
1183  * Used in a couple of modules to hide navigation areas when using AJAX
1184  */
1185 function hide_item(itemid) {
1186     // use class='hiddenifjs' instead
1187     var item = document.getElementById(itemid);
1188     if (item) {
1189         item.style.display = "none";
1190     }
1193 M.util.help_icon = {
1194     Y : null,
1195     instance : null,
1196     add : function(Y, properties) {
1197         this.Y = Y;
1198         properties.node = Y.one('#'+properties.id);
1199         if (properties.node) {
1200             properties.node.on('click', this.display, this, properties);
1201         }
1202     },
1203     display : function(event, args) {
1204         event.preventDefault();
1205         if (M.util.help_icon.instance === null) {
1206             var Y = M.util.help_icon.Y;
1207             Y.use('overlay', 'io', 'event-mouseenter', 'node', 'event-key', function(Y) {
1208                 var help_content_overlay = {
1209                     helplink : null,
1210                     overlay : null,
1211                     init : function() {
1213                         var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img  src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
1214                         // Create an overlay from markup
1215                         this.overlay = new Y.Overlay({
1216                             headerContent: closebtn,
1217                             bodyContent: '',
1218                             id: 'helppopupbox',
1219                             width:'400px',
1220                             visible : false,
1221                             constrain : true
1222                         });
1223                         this.overlay.render(Y.one(document.body));
1225                         closebtn.on('click', this.overlay.hide, this.overlay);
1227                         var boundingBox = this.overlay.get("boundingBox");
1229                         //  Hide the menu if the user clicks outside of its content
1230                         boundingBox.get("ownerDocument").on("mousedown", function (event) {
1231                             var oTarget = event.target;
1232                             var menuButton = Y.one("#"+args.id);
1234                             if (!oTarget.compareTo(menuButton) &&
1235                                 !menuButton.contains(oTarget) &&
1236                                 !oTarget.compareTo(boundingBox) &&
1237                                 !boundingBox.contains(oTarget)) {
1238                                 this.overlay.hide();
1239                             }
1240                         }, this);
1242                         Y.on("key", this.close, closebtn , "down:13", this);
1243                         closebtn.on('click', this.close, this);
1244                     },
1246                     close : function(e) {
1247                         e.preventDefault();
1248                         this.helplink.focus();
1249                         this.overlay.hide();
1250                     },
1252                     display : function(event, args) {
1253                         this.helplink = args.node;
1254                         this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1255                         this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
1257                         var fullurl = args.url;
1258                         if (!args.url.match(/https?:\/\//)) {
1259                             fullurl = M.cfg.wwwroot + args.url;
1260                         }
1262                         var ajaxurl = fullurl + '&ajax=1';
1264                         var cfg = {
1265                             method: 'get',
1266                             context : this,
1267                             on: {
1268                                 success: function(id, o, node) {
1269                                     this.display_callback(o.responseText);
1270                                 },
1271                                 failure: function(id, o, node) {
1272                                     var debuginfo = o.statusText;
1273                                     if (M.cfg.developerdebug) {
1274                                         o.statusText += ' (' + ajaxurl + ')';
1275                                     }
1276                                     this.display_callback('bodyContent',debuginfo);
1277                                 }
1278                             }
1279                         };
1281                         Y.io(ajaxurl, cfg);
1282                         this.overlay.show();
1284                         Y.one('#closehelpbox').focus();
1285                     },
1287                     display_callback : function(content) {
1288                         this.overlay.set('bodyContent', content);
1289                     },
1291                     hideContent : function() {
1292                         help = this;
1293                         help.overlay.hide();
1294                     }
1295                 };
1296                 help_content_overlay.init();
1297                 M.util.help_icon.instance = help_content_overlay;
1298                 M.util.help_icon.instance.display(event, args);
1299             });
1300         } else {
1301             M.util.help_icon.instance.display(event, args);
1302         }
1303     },
1304     init : function(Y) {
1305         this.Y = Y;
1306     }
1307 };
1309 /**
1310  * Custom menu namespace
1311  */
1312 M.core_custom_menu = {
1313     /**
1314      * This method is used to initialise a custom menu given the id that belongs
1315      * to the custom menu's root node.
1316      *
1317      * @param {YUI} Y
1318      * @param {string} nodeid
1319      */
1320     init : function(Y, nodeid) {
1321         var node = Y.one('#'+nodeid);
1322         if (node) {
1323             Y.use('node-menunav', function(Y) {
1324                 // Get the node
1325                 // Remove the javascript-disabled class.... obviously javascript is enabled.
1326                 node.removeClass('javascript-disabled');
1327                 // Initialise the menunav plugin
1328                 node.plug(Y.Plugin.NodeMenuNav);
1329             });
1330         }
1331     }
1332 };
1334 /**
1335  * Used to store form manipulation methods and enhancments
1336  */
1337 M.form = M.form || {};
1339 /**
1340  * Converts a nbsp indented select box into a multi drop down custom control much
1341  * like the custom menu. It also selectable categories on or off.
1342  *
1343  * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1344  *
1345  * @param {YUI} Y
1346  * @param {string} id
1347  * @param {Array} options
1348  */
1349 M.form.init_smartselect = function(Y, id, options) {
1350     if (!id.match(/^id_/)) {
1351         id = 'id_'+id;
1352     }
1353     var select = Y.one('select#'+id);
1354     if (!select) {
1355         return false;
1356     }
1357     Y.use('event-delegate',function(){
1358         var smartselect = {
1359             id : id,
1360             structure : [],
1361             options : [],
1362             submenucount : 0,
1363             currentvalue : null,
1364             currenttext : null,
1365             shownevent : null,
1366             cfg : {
1367                 selectablecategories : true,
1368                 mode : null
1369             },
1370             nodes : {
1371                 select : null,
1372                 loading : null,
1373                 menu : null
1374             },
1375             init : function(Y, id, args, nodes) {
1376                 if (typeof(args)=='object') {
1377                     for (var i in this.cfg) {
1378                         if (args[i] || args[i]===false) {
1379                             this.cfg[i] = args[i];
1380                         }
1381                     }
1382                 }
1384                 // Display a loading message first up
1385                 this.nodes.select = nodes.select;
1387                 this.currentvalue = this.nodes.select.get('selectedIndex');
1388                 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1390                 var options = Array();
1391                 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1392                 this.nodes.select.all('option').each(function(option, index) {
1393                     var rawtext = option.get('innerHTML');
1394                     var text = rawtext.replace(/^(&nbsp;)*/, '');
1395                     if (rawtext === text) {
1396                         text = rawtext.replace(/^(\s)*/, '');
1397                         var depth = (rawtext.length - text.length ) + 1;
1398                     } else {
1399                         var depth = ((rawtext.length - text.length )/12)+1;
1400                     }
1401                     option.set('innerHTML', text);
1402                     options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1403                 }, this);
1405                 this.structure = [];
1406                 var structcount = 0;
1407                 for (var i in options) {
1408                     var o = options[i];
1409                     if (o.depth == 0) {
1410                         this.structure.push(o);
1411                         structcount++;
1412                     } else {
1413                         var d = o.depth;
1414                         var current = this.structure[structcount-1];
1415                         for (var j = 0; j < o.depth-1;j++) {
1416                             if (current && current.children) {
1417                                 current = current.children[current.children.length-1];
1418                             }
1419                         }
1420                         if (current && current.children) {
1421                             current.children.push(o);
1422                         }
1423                     }
1424                 }
1426                 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1427                 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1428                 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1429                 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1431                 if (this.cfg.mode == null) {
1432                     var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1433                     if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1434                         this.cfg.mode = 'compact';
1435                     } else {
1436                         this.cfg.mode = 'spanning';
1437                     }
1438                 }
1440                 if (this.cfg.mode == 'compact') {
1441                     this.nodes.menu.addClass('compactmenu');
1442                 } else {
1443                     this.nodes.menu.addClass('spanningmenu');
1444                     this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1445                 }
1447                 Y.one(document.body).append(this.nodes.menu);
1448                 var pos = this.nodes.select.getXY();
1449                 pos[0] += 1;
1450                 this.nodes.menu.setXY(pos);
1451                 this.nodes.menu.on('click', this.handle_click, this);
1453                 Y.one(window).on('resize', function(){
1454                      var pos = this.nodes.select.getXY();
1455                     pos[0] += 1;
1456                     this.nodes.menu.setXY(pos);
1457                  }, this);
1458             },
1459             generate_menu_content : function() {
1460                 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1461                 content += this.generate_submenu_content(this.structure[0], true);
1462                 content += '</ul></div>';
1463                 return content;
1464             },
1465             generate_submenu_content : function(item, rootelement) {
1466                 this.submenucount++;
1467                 var content = '';
1468                 if (item.children.length > 0) {
1469                     if (rootelement) {
1470                         content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1471                         content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1472                         content += '<div class="smartselect_menu_content">';
1473                     } else {
1474                         content += '<li class="smartselect_submenuitem">';
1475                         var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1476                         content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1477                         content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1478                         content += '<div class="smartselect_submenu_content">';
1479                     }
1480                     content += '<ul>';
1481                     for (var i in item.children) {
1482                         content += this.generate_submenu_content(item.children[i],false);
1483                     }
1484                     content += '</ul>';
1485                     content += '</div>';
1486                     content += '</div>';
1487                     if (rootelement) {
1488                     } else {
1489                         content += '</li>';
1490                     }
1491                 } else {
1492                     content += '<li class="smartselect_menuitem">';
1493                     content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1494                     content += '</li>';
1495                 }
1496                 return content;
1497             },
1498             select : function(e) {
1499                 var t = e.target;
1500                 e.halt();
1501                 this.currenttext = t.get('innerHTML');
1502                 this.currentvalue = t.getAttribute('value');
1503                 this.nodes.select.set('selectedIndex', this.currentvalue);
1504                 this.hide_menu();
1505             },
1506             handle_click : function(e) {
1507                 var target = e.target;
1508                 if (target.hasClass('smartselect_mask')) {
1509                     this.show_menu(e);
1510                 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1511                     this.select(e);
1512                 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1513                     this.show_sub_menu(e);
1514                 }
1515             },
1516             show_menu : function(e) {
1517                 e.halt();
1518                 var menu = e.target.ancestor().one('.smartselect_menu');
1519                 menu.addClass('visible');
1520                 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1521             },
1522             show_sub_menu : function(e) {
1523                 e.halt();
1524                 var target = e.target;
1525                 if (!target.hasClass('smartselect_submenuitem')) {
1526                     target = target.ancestor('.smartselect_submenuitem');
1527                 }
1528                 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1529                     target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1530                     return;
1531                 }
1532                 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1533                 target.one('.smartselect_submenu').addClass('visible');
1534             },
1535             hide_menu : function() {
1536                 this.nodes.menu.all('.visible').removeClass('visible');
1537                 if (this.shownevent) {
1538                     this.shownevent.detach();
1539                 }
1540             }
1541         };
1542         smartselect.init(Y, id, options, {select:select});
1543     });
1544 };
1546 M.util.init_flvflowplayer = function (id, playerpath, fileurl) {
1547     $f(id, playerpath, {
1548         plugins: {
1549                 controls: {
1550                        autoHide: true
1551                 }
1552         },
1553         clip: {
1554             url: fileurl,
1555             autoPlay: false,
1556             autoBuffering: true
1557         }
1558     });
1559 };
1561 M.util.init_mp3flowplayer = function (id, playerpath, audioplayerpath, fileurl, color) {
1563     $f(id, playerpath, {
1565             plugins: {
1566                     controls: {
1567                             fullscreen: false,
1568                             height: 25,
1569                             autoHide: false,
1570                             background: '#'+color['bgColour'],
1571                             buttonColor: '#'+color['btnColour'],
1572                             sliderColor: '#'+color['handleColour'],
1573                             volumeSliderColor: '#'+color['handleColour'],
1574                             volumeColor: '#'+color['trackColour'],
1575                             durationColor: '#'+color['fontColour'],
1576                             buttonOverColor: '#'+color['iconOverColour'],
1577                             progressColor: '#'+color['handleColour']
1578                     },
1579                     audio: {url: audioplayerpath}
1580             },
1581             clip: {url: fileurl,
1582                     provider: "audio",
1583                     autoPlay: false
1584             }
1585     });
1586 };
1588 M.util.init_mp3flowplayerplugin = function (id, playerpath, audioplayerpath, fileurl, color) {
1589     $f(id, playerpath, {
1590             plugins: {
1591                     controls: {
1592                             all: false,
1593                             play: true,
1594                             pause: true,
1595                             scrubber: true,
1596                             autoHide: false,
1597                             height: 15,
1598                             background: '#'+color['bgColour'],
1599                             buttonColor: '#'+color['btnColour'],
1600                             sliderColor: '#'+color['handleColour'],
1601                             volumeSliderColor: '#'+color['handleColour'],
1602                             progressColor: '#'+color['handleColour'],
1603                             volumeColor: '#'+color['trackColour'],
1604                             buttonOverColor: '#'+color['iconOverColour']
1605                     },
1606                     audio: {url: audioplayerpath}
1607             },
1608             clip: {url: fileurl,
1609                     provider: "audio",
1610                     autoPlay: false
1611             }
1612     });
1613 };