d79230d16eb495ba5cdbd189eca103010d7bedc2
[moodle.git] / lib / javascript-static.js
1 // Miscellaneous core Javascript functions for Moodle
2 // Global M object is initilised in inline javascript
4 /**
5  * Add module to list of available modules that can be laoded from YUI.
6  * @param {Array} modules
7  */
8 M.yui.add_module = function(modules) {
9     for (var modname in modules) {
10         M.yui.loader.modules[modname] = modules[modname];
11     }
12 };
13 /**
14  * The gallery version to use when loading YUI modules from the gallery.
15  * Will be changed every time when using local galleries.
16  */
17 M.yui.galleryversion = '2010.04.21-21-51';
19 /**
20  * Various utility functions
21  */
22 M.util = M.util || {};
24 /**
25  * Language strings - initialised from page footer.
26  */
27 M.str = M.str || {};
29 /**
30  * Returns url for images.
31  * @param {String} imagename
32  * @param {String} component
33  * @return {String}
34  */
35 M.util.image_url = function(imagename, component) {
36     var url = M.cfg.wwwroot + '/theme/image.php?theme=' + M.cfg.theme + '&image=' + imagename;
38     if (M.cfg.themerev > 0) {
39         url = url + '&rev=' + M.cfg.themerev;
40     }
42     if (component && component != '' && component != 'moodle' && component != 'core') {
43         url = url + '&component=' + component;
44     }
46     return url;
47 };
49 M.util.create_UFO_object = function (eid, FO) {
50     UFO.create(FO, eid);
51 };
53 M.util.in_array = function(item, array){
54     for( var i = 0; i<array.length; i++){
55         if(item==array[i]){
56             return true;
57         }
58     }
59     return false;
60 };
62 /**
63  * Init a collapsible region, see print_collapsible_region in weblib.php
64  * @param {YUI} Y YUI3 instance with all libraries loaded
65  * @param {String} id the HTML id for the div.
66  * @param {String} userpref the user preference that records the state of this box. false if none.
67  * @param {String} strtooltip
68  */
69 M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
70     Y.use('anim', function(Y) {
71         new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
72     });
73 };
75 /**
76  * Object to handle a collapsible region : instantiate and forget styled object
77  *
78  * @class
79  * @constructor
80  * @param {YUI} Y YUI3 instance with all libraries loaded
81  * @param {String} id The HTML id for the div.
82  * @param {String} userpref The user preference that records the state of this box. false if none.
83  * @param {String} strtooltip
84  */
85 M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
86     // Record the pref name
87     this.userpref = userpref;
89     // Find the divs in the document.
90     this.div = Y.one('#'+id);
92     // Get the caption for the collapsible region
93     var caption = this.div.one('#'+id + '_caption');
94     caption.setAttribute('title', strtooltip);
96     // Create a link
97     var a = Y.Node.create('<a href="#"></a>');
98     // Create a local scoped lamba function to move nodes to a new link
99     var movenode = function(node){
100         node.remove();
101         a.append(node);
102     };
103     // Apply the lamba function on each of the captions child nodes
104     caption.get('children').each(movenode, this);
105     caption.append(a);
107     // Get the height of the div at this point before we shrink it if required
108     var height = this.div.get('offsetHeight');
109     if (this.div.hasClass('collapsed')) {
110         // Add the correct image and record the YUI node created in the process
111         this.icon = Y.Node.create('<img src="'+M.util.image_url('t/collapsed', 'moodle')+'" alt="" />');
112         // Shrink the div as it is collapsed by default
113         this.div.setStyle('height', caption.get('offsetHeight')+'px');
114     } else {
115         // Add the correct image and record the YUI node created in the process
116         this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
117     }
118     a.append(this.icon);
120     // Create the animation.
121     var animation = new Y.Anim({
122         node: this.div,
123         duration: 0.3,
124         easing: Y.Easing.easeBoth,
125         to: {height:caption.get('offsetHeight')},
126         from: {height:height}
127     });
129     // Handler for the animation finishing.
130     animation.on('end', function() {
131         this.div.toggleClass('collapsed');
132         if (this.div.hasClass('collapsed')) {
133             this.icon.set('src', M.util.image_url('t/collapsed', 'moodle'));
134         } else {
135             this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
136         }
137     }, this);
139     // Hook up the event handler.
140     a.on('click', function(e, animation) {
141         e.preventDefault();
142         // Animate to the appropriate size.
143         if (animation.get('running')) {
144             animation.stop();
145         }
146         animation.set('reverse', this.div.hasClass('collapsed'));
147         // Update the user preference.
148         if (this.userpref) {
149             M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
150         }
151         animation.run();
152     }, this, animation);
153 };
155 /**
156  * The user preference that stores the state of this box.
157  * @property userpref
158  * @type String
159  */
160 M.util.CollapsibleRegion.prototype.userpref = null;
162 /**
163  * The key divs that make up this
164  * @property div
165  * @type Y.Node
166  */
167 M.util.CollapsibleRegion.prototype.div = null;
169 /**
170  * The key divs that make up this
171  * @property icon
172  * @type Y.Node
173  */
174 M.util.CollapsibleRegion.prototype.icon = null;
176 /**
177  * Makes a best effort to connect back to Moodle to update a user preference,
178  * however, there is no mechanism for finding out if the update succeeded.
179  *
180  * Before you can use this function in your JavsScript, you must have called
181  * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
182  * the udpate is allowed, and how to safely clean and submitted values.
183  *
184  * @param String name the name of the setting to udpate.
185  * @param String the value to set it to.
186  */
187 M.util.set_user_preference = function(name, value) {
188     YUI(M.yui.loader).use('io', function(Y) {
189         var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
190                 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
192         // If we are a developer, ensure that failures are reported.
193         var cfg = {
194                 method: 'get',
195                 on: {}
196             };
197         if (M.cfg.developerdebug) {
198             cfg.on.failure = function(id, o, args) {
199                 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
200             }
201         }
203         // Make the request.
204         Y.io(url, cfg);
205     });
206 };
208 /**
209  * Prints a confirmation dialog in the style of DOM.confirm().
210  * @param object event A YUI DOM event or null if launched manually
211  * @param string message The message to show in the dialog
212  * @param string url The URL to forward to if YES is clicked. Disabled if fn is given
213  * @param function fn A JS function to run if YES is clicked.
214  */
215 M.util.show_confirm_dialog = function(e, args) {
216     var target = e.target;
217     if (e.preventDefault) {
218         e.preventDefault();
219     }
221     YUI(M.yui.loader).use('yui2-container', 'yui2-event', function(Y) {
222         var simpledialog = new YAHOO.widget.SimpleDialog('confirmdialog',
223             {width: '300px',
224               fixedcenter: true,
225               modal: true,
226               visible: false,
227               draggable: false
228             }
229         );
231         simpledialog.setHeader(M.str.admin.confirmation);
232         simpledialog.setBody(args.message);
233         simpledialog.cfg.setProperty('icon', YAHOO.widget.SimpleDialog.ICON_WARN);
235         var handle_cancel = function() {
236             simpledialog.hide();
237         };
239         var handle_yes = function() {
240             simpledialog.hide();
242             if (args.callback) {
243                 // args comes from PHP, so callback will be a string, needs to be evaluated by JS
244                 var callback = null;
245                 if (Y.Lang.isFunction(args.callback)) {
246                     callback = args.callback;
247                 } else {
248                     callback = eval('('+args.callback+')');
249                 }
251                 if (Y.Lang.isObject(args.scope)) {
252                     var sc = args.scope;
253                 } else {
254                     var sc = e.target;
255                 }
257                 if (args.callbackargs) {
258                     callback.apply(sc, args.callbackargs);
259                 } else {
260                     callback.apply(sc);
261                 }
262                 return;
263             }
265             if (target.get('tagName').toLowerCase() == 'a') {
266                 window.location = target.get('href');
267             } else if (target.get('tagName').toLowerCase() == 'input') {
268                 var parentelement = target.get('parentNode');
269                 while (parentelement.get('tagName').toLowerCase() != 'form' && parentelement.get('tagName').toLowerCase() != 'body') {
270                     parentelement = parentelement.get('parentNode');
271                 }
272                 if (parentelement.get('tagName').toLowerCase() == 'form') {
273                     parentelement.submit();
274                 }
275             } else if (M.cfg.developerdebug) {
276                 alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A or INPUT");
277             }
278         };
280         var buttons = [ {text: M.str.moodle.cancel, handler: handle_cancel, isDefault: true},
281                         {text: M.str.moodle.yes, handler: handle_yes} ];
283         simpledialog.cfg.queueProperty('buttons', buttons);
285         simpledialog.render(document.body);
286         simpledialog.show();
287     });
288 };
290 /** Useful for full embedding of various stuff */
291 M.util.init_maximised_embed = function(Y, id) {
292     var obj = Y.one('#'+id);
293     if (!obj) {
294         return;
295     }
298     var get_htmlelement_size = function(el, prop) {
299         if (Y.Lang.isString(el)) {
300             el = Y.one('#' + el);
301         }
302         var val = el.getStyle(prop);
303         if (val == 'auto') {
304             val = el.getComputedStyle(prop);
305         }
306         return parseInt(val);
307     };
309     var resize_object = function() {
310         obj.setStyle('width', '0px');
311         obj.setStyle('height', '0px');
312         var newwidth = get_htmlelement_size('maincontent', 'width') - 15;
314         if (newwidth > 600) {
315             obj.setStyle('width', newwidth  + 'px');
316         } else {
317             obj.setStyle('width', '600px');
318         }
320         var headerheight = get_htmlelement_size('page-header', 'height');
321         var footerheight = get_htmlelement_size('page-footer', 'height');
322         var newheight = parseInt(YAHOO.util.Dom.getViewportHeight()) - footerheight - headerheight - 20;
323         if (newheight < 400) {
324             newheight = 400;
325         }
326         obj.setStyle('height', newheight+'px');
327     };
329     resize_object();
330     // fix layout if window resized too
331     window.onresize = function() {
332         resize_object();
333     };
334 };
336 /**
337  * Attach handler to single_select
338  */
339 M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
340     Y.use('event-key', function() {
341         var select = Y.one('#'+selectid);
342         if (select) {
343             // Try to get the form by id
344             var form = Y.one('#'+formid) || (function(){
345                 // Hmmm the form's id may have been overriden by an internal input
346                 // with the name id which will KILL IE.
347                 // We need to manually iterate at this point because if the case
348                 // above is true YUI's ancestor method will also kill IE!
349                 var form = select;
350                 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
351                     form = form.ancestor();
352                 }
353                 return form;
354             })();
355             // Make sure we have the form
356             if (form) {
357                 // Create a function to handle our change event
358                 var processchange = function(e, lastindex) {
359                     if ((nothing===false || select.get('value') != nothing) && lastindex != select.get('selectedIndex')) {
360                         this.submit();
361                     }
362                 };
363                 // Attach the change event to the keypress, blur, and click actions.
364                 // We don't use the change event because IE fires it on every arrow up/down
365                 // event.... usability
366                 Y.on('key', processchange, select, 'press:13', form, select.get('selectedIndex'));
367                 select.on('blur', processchange, form, select.get('selectedIndex'));
368                 select.on('click', processchange, form, select.get('selectedIndex'));
369             }
370         }
371     });
372 };
374 /**
375  * Attach handler to url_select
376  */
377 M.util.init_url_select = function(Y, formid, selectid, nothing) {
378     YUI(M.yui.loader).use('node', function(Y) {
379         Y.on('change', function() {
380             if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
381                 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
382             }
383         },
384         '#'+selectid);
385     });
386 };
388 /**
389  * Breaks out all links to the top frame - used in frametop page layout.
390  */
391 M.util.init_frametop = function(Y) {
392     Y.all('a').each(function(node) {
393         node.set('target', '_top');
394     });
395     Y.all('form').each(function(node) {
396         node.set('target', '_top');
397     });
398 };
400 /**
401  * Finds all nodes that match the given CSS selector and attaches events to them
402  * so that they toggle a given classname when clicked.
403  *
404  * @param {YUI} Y
405  * @param {string} id An id containing elements to target
406  * @param {string} cssselector A selector to use to find targets
407  * @param {string} toggleclassname A classname to toggle
408  */
409 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
411     if (togglecssselector == '') {
412         togglecssselector = cssselector;
413     }
415     var node = Y.one('#'+id);
416     node.all(cssselector).each(function(n){
417         n.on('click', function(e){
418             e.stopPropagation();
419             if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
420                 if (this.test(togglecssselector)) {
421                     this.toggleClass(toggleclassname);
422                 } else {
423                     this.ancestor(togglecssselector).toggleClass(toggleclassname);
424             }
425             }
426         }, n);
427     });
428     // Attach this click event to the node rather than all selectors... will be much better
429     // for performance
430     node.on('click', function(e){
431         if (e.target.hasClass('addtoall')) {
432             this.all(togglecssselector).addClass(toggleclassname);
433         } else if (e.target.hasClass('removefromall')) {
434             this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
435         }
436     }, node);
437 };
439 /**
440  * Initialises a colour picker
441  *
442  * Designed to be used with admin_setting_configcolourpicker although could be used
443  * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
444  * above or below the input (must have the same parent) and then call this with the
445  * id.
446  *
447  * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
448  * contrib/blocks. For better docs refer to that.
449  *
450  * @param {YUI} Y
451  * @param {int} id
452  * @param {object} previewconf
453  */
454 M.util.init_colour_picker = function(Y, id, previewconf) {
455     /**
456      * We need node and event-mouseenter
457      */
458     Y.use('node', 'event-mouseenter', function(){
459         /**
460          * The colour picker object
461          */
462         var colourpicker = {
463             box : null,
464             input : null,
465             image : null,
466             preview : null,
467             current : null,
468             eventClick : null,
469             eventMouseEnter : null,
470             eventMouseLeave : null,
471             eventMouseMove : null,
472             width : 300,
473             height :  100,
474             factor : 5,
475             /**
476              * Initalises the colour picker by putting everything together and wiring the events
477              */
478             init : function() {
479                 this.input = Y.one('#'+id);
480                 this.box = this.input.ancestor().one('.admin_colourpicker');
481                 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
482                 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
483                 this.preview = Y.Node.create('<div class="previewcolour"></div>');
484                 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
485                 this.current = Y.Node.create('<div class="currentcolour"></div>');
486                 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
487                 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
489                 if (typeof(previewconf) === 'object' && previewconf !== null) {
490                     Y.one('#'+id+'_preview').on('click', function(e){
491                         if (Y.Lang.isString(previewconf.selector)) {
492                             Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
493                         } else {
494                             for (var i in previewconf.selector) {
495                                 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
496                             }
497                         }
498                     }, this);
499                 }
501                 this.eventClick = this.image.on('click', this.pickColour, this);
502                 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
503             },
504             /**
505              * Starts to follow the mouse once it enter the image
506              */
507             startFollow : function(e) {
508                 this.eventMouseEnter.detach();
509                 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
510                 this.eventMouseMove = this.image.on('mousemove', function(e){
511                     this.preview.setStyle('backgroundColor', this.determineColour(e));
512                 }, this);
513             },
514             /**
515              * Stops following the mouse
516              */
517             endFollow : function(e) {
518                 this.eventMouseMove.detach();
519                 this.eventMouseLeave.detach();
520                 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
521             },
522             /**
523              * Picks the colour the was clicked on
524              */
525             pickColour : function(e) {
526                 var colour = this.determineColour(e);
527                 this.input.set('value', colour);
528                 this.current.setStyle('backgroundColor', colour);
529             },
530             /**
531              * Calculates the colour fromthe given co-ordinates
532              */
533             determineColour : function(e) {
534                 var eventx = Math.floor(e.pageX-e.target.getX());
535                 var eventy = Math.floor(e.pageY-e.target.getY());
537                 var imagewidth = this.width;
538                 var imageheight = this.height;
539                 var factor = this.factor;
540                 var colour = [255,0,0];
542                 var matrices = [
543                     [  0,  1,  0],
544                     [ -1,  0,  0],
545                     [  0,  0,  1],
546                     [  0, -1,  0],
547                     [  1,  0,  0],
548                     [  0,  0, -1]
549                 ];
551                 var matrixcount = matrices.length;
552                 var limit = Math.round(imagewidth/matrixcount);
553                 var heightbreak = Math.round(imageheight/2);
555                 for (var x = 0; x < imagewidth; x++) {
556                     var divisor = Math.floor(x / limit);
557                     var matrix = matrices[divisor];
559                     colour[0] += matrix[0]*factor;
560                     colour[1] += matrix[1]*factor;
561                     colour[2] += matrix[2]*factor;
563                     if (eventx==x) {
564                         break;
565                     }
566                 }
568                 var pixel = [colour[0], colour[1], colour[2]];
569                 if (eventy < heightbreak) {
570                     pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
571                     pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
572                     pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
573                 } else if (eventy > heightbreak) {
574                     pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
575                     pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
576                     pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
577                 }
579                 return this.convert_rgb_to_hex(pixel);
580             },
581             /**
582              * Converts an RGB value to Hex
583              */
584             convert_rgb_to_hex : function(rgb) {
585                 var hex = '#';
586                 var hexchars = "0123456789ABCDEF";
587                 for (var i=0; i<3; i++) {
588                     var number = Math.abs(rgb[i]);
589                     if (number == 0 || isNaN(number)) {
590                         hex += '00';
591                     } else {
592                         hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
593                     }
594                 }
595                 return hex;
596             }
597         };
598         /**
599          * Initialise the colour picker :) Hoorah
600          */
601         colourpicker.init();
602     });
603 };
605 M.util.init_block_hider = function(Y, config) {
606     Y.use('base', 'node', function(Y) {
607         M.util.block_hider = M.util.block_hider || (function(){
608             var blockhider = function() {
609                 blockhider.superclass.constructor.apply(this, arguments);
610             };
611             blockhider.prototype = {
612                 initializer : function(config) {
613                     this.set('block', '#'+this.get('id'));
614                     var b = this.get('block'),
615                         t = b.one('.title'),
616                         a = null;
617                     if (t && (a = t.one('.block_action'))) {
618                         var hide = Y.Node.create('<img class="block-hider-hide" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
619                         hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
620                         var show = Y.Node.create('<img class="block-hider-show" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
621                         show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
622                         a.insert(show, 0).insert(hide, 0);
623                     }
624                 },
625                 updateState : function(e, hide) {
626                     M.util.set_user_preference(this.get('preference'), hide);
627                     if (hide) {
628                         this.get('block').addClass('hidden');
629                     } else {
630                         this.get('block').removeClass('hidden');
631                     }
632                 }
633             };
634             Y.extend(blockhider, Y.Base, blockhider.prototype, {
635                 NAME : 'blockhider',
636                 ATTRS : {
637                     id : {},
638                     preference : {},
639                     iconVisible : {
640                         value : M.util.image_url('t/switch_minus', 'moodle')
641                     },
642                     iconHidden : {
643                         value : M.util.image_url('t/switch_plus', 'moodle')
644                     },
645                     block : {
646                         setter : function(node) {
647                             return Y.one(node);
648                         }
649                     }
650                 }
651             });
652             return blockhider;
653         })();
654         new M.util.block_hider(config);
655     });
656 };
658 /**
659  * Returns a string registered in advance for usage in JavaScript
660  *
661  * If you do not pass the third parameter, the function will just return
662  * the corresponding value from the M.str object. If the third parameter is
663  * provided, the function performs {$a} placeholder substitution in the
664  * same way as PHP get_string() in Moodle does.
665  *
666  * @param {String} identifier string identifier
667  * @param {String} component the component providing the string
668  * @param {Object|String} a optional variable to populate placeholder with
669  */
670 M.util.get_string = function(identifier, component, a) {
671     var stringvalue;
673     if (M.cfg.developerdebug) {
674         // creating new instance if YUI is not optimal but it seems to be better way then
675         // require the instance via the function API - note that it is used in rare cases
676         // for debugging only anyway
677         var Y = new YUI({ debug : true });
678     }
680     if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
681         stringvalue = '[[' + identifier + ',' + component + ']]';
682         if (M.cfg.developerdebug) {
683             Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
684         }
685         return stringvalue;
686     }
688     stringvalue = M.str[component][identifier];
690     if (typeof a == 'undefined') {
691         // no placeholder substitution requested
692         return stringvalue;
693     }
695     if (typeof a == 'number' || typeof a == 'string') {
696         // replace all occurrences of {$a} with the placeholder value
697         stringvalue = stringvalue.replace(/\{\$a\}/g, a);
698         return stringvalue;
699     }
701     if (typeof a == 'object') {
702         // replace {$a->key} placeholders
703         for (var key in a) {
704             if (typeof a[key] != 'number' && typeof a[key] != 'string') {
705                 if (M.cfg.developerdebug) {
706                     Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
707                 }
708                 continue;
709             }
710             var search = '{$a->' + key + '}';
711             search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
712             search = new RegExp(search, 'g');
713             stringvalue = stringvalue.replace(search, a[key]);
714         }
715         return stringvalue;
716     }
718     if (M.cfg.developerdebug) {
719         Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
720     }
721     return stringvalue;
722 };
724 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
726 function checkall() {
727     var inputs = document.getElementsByTagName('input');
728     for (var i = 0; i < inputs.length; i++) {
729         if (inputs[i].type == 'checkbox') {
730             inputs[i].checked = true;
731         }
732     }
735 function checknone() {
736     var inputs = document.getElementsByTagName('input');
737     for (var i = 0; i < inputs.length; i++) {
738         if (inputs[i].type == 'checkbox') {
739             inputs[i].checked = false;
740         }
741     }
744 /**
745  * Either check, or uncheck, all checkboxes inside the element with id is
746  * @param id the id of the container
747  * @param checked the new state, either '' or 'checked'.
748  */
749 function select_all_in_element_with_id(id, checked) {
750     var container = document.getElementById(id);
751     if (!container) {
752         return;
753     }
754     var inputs = container.getElementsByTagName('input');
755     for (var i = 0; i < inputs.length; ++i) {
756         if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
757             inputs[i].checked = checked;
758         }
759     }
762 function select_all_in(elTagName, elClass, elId) {
763     var inputs = document.getElementsByTagName('input');
764     inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
765     for(var i = 0; i < inputs.length; ++i) {
766         if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
767             inputs[i].checked = 'checked';
768         }
769     }
772 function deselect_all_in(elTagName, elClass, elId) {
773     var inputs = document.getElementsByTagName('INPUT');
774     inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
775     for(var i = 0; i < inputs.length; ++i) {
776         if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
777             inputs[i].checked = '';
778         }
779     }
782 function confirm_if(expr, message) {
783     if(!expr) {
784         return true;
785     }
786     return confirm(message);
790 /*
791     findParentNode (start, elementName, elementClass, elementID)
793     Travels up the DOM hierarchy to find a parent element with the
794     specified tag name, class, and id. All conditions must be met,
795     but any can be ommitted. Returns the BODY element if no match
796     found.
797 */
798 function findParentNode(el, elName, elClass, elId) {
799     while (el.nodeName.toUpperCase() != 'BODY') {
800         if ((!elName || el.nodeName.toUpperCase() == elName) &&
801             (!elClass || el.className.indexOf(elClass) != -1) &&
802             (!elId || el.id == elId)) {
803             break;
804         }
805         el = el.parentNode;
806     }
807     return el;
809 /*
810     findChildNode (start, elementName, elementClass, elementID)
812     Travels down the DOM hierarchy to find all child elements with the
813     specified tag name, class, and id. All conditions must be met,
814     but any can be ommitted.
815     Doesn't examine children of matches.
816 */
817 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
818     var children = new Array();
819     for (var i = 0; i < start.childNodes.length; i++) {
820         var classfound = false;
821         var child = start.childNodes[i];
822         if((child.nodeType == 1) &&//element node type
823                   (elementClass && (typeof(child.className)=='string'))) {
824             var childClasses = child.className.split(/\s+/);
825             for (var childClassIndex in childClasses) {
826                 if (childClasses[childClassIndex]==elementClass) {
827                     classfound = true;
828                     break;
829                 }
830             }
831         }
832         if(child.nodeType == 1) { //element node type
833             if  ( (!tagName || child.nodeName == tagName) &&
834                 (!elementClass || classfound)&&
835                 (!elementID || child.id == elementID) &&
836                 (!elementName || child.name == elementName))
837             {
838                 children = children.concat(child);
839             } else {
840                 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
841             }
842         }
843     }
844     return children;
847 function unmaskPassword(id) {
848   var pw = document.getElementById(id);
849   var chb = document.getElementById(id+'unmask');
851   try {
852     // first try IE way - it can not set name attribute later
853     if (chb.checked) {
854       var newpw = document.createElement('<input type="text" name="'+pw.name+'">');
855     } else {
856       var newpw = document.createElement('<input type="password" name="'+pw.name+'">');
857     }
858     newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
859   } catch (e) {
860     var newpw = document.createElement('input');
861     newpw.setAttribute('name', pw.name);
862     if (chb.checked) {
863       newpw.setAttribute('type', 'text');
864     } else {
865       newpw.setAttribute('type', 'password');
866     }
867     newpw.setAttribute('class', pw.getAttribute('class'));
868   }
869   newpw.id = pw.id;
870   newpw.size = pw.size;
871   newpw.onblur = pw.onblur;
872   newpw.onchange = pw.onchange;
873   newpw.value = pw.value;
874   pw.parentNode.replaceChild(newpw, pw);
877 function filterByParent(elCollection, parentFinder) {
878     var filteredCollection = [];
879     for (var i = 0; i < elCollection.length; ++i) {
880         var findParent = parentFinder(elCollection[i]);
881         if (findParent.nodeName.toUpperCase != 'BODY') {
882             filteredCollection.push(elCollection[i]);
883         }
884     }
885     return filteredCollection;
888 /*
889     All this is here just so that IE gets to handle oversized blocks
890     in a visually pleasing manner. It does a browser detect. So sue me.
891 */
893 function fix_column_widths() {
894     var agt = navigator.userAgent.toLowerCase();
895     if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
896         fix_column_width('left-column');
897         fix_column_width('right-column');
898     }
901 function fix_column_width(colName) {
902     if(column = document.getElementById(colName)) {
903         if(!column.offsetWidth) {
904             setTimeout("fix_column_width('" + colName + "')", 20);
905             return;
906         }
908         var width = 0;
909         var nodes = column.childNodes;
911         for(i = 0; i < nodes.length; ++i) {
912             if(nodes[i].className.indexOf("block") != -1 ) {
913                 if(width < nodes[i].offsetWidth) {
914                     width = nodes[i].offsetWidth;
915                 }
916             }
917         }
919         for(i = 0; i < nodes.length; ++i) {
920             if(nodes[i].className.indexOf("block") != -1 ) {
921                 nodes[i].style.width = width + 'px';
922             }
923         }
924     }
928 /*
929    Insert myValue at current cursor position
930  */
931 function insertAtCursor(myField, myValue) {
932     // IE support
933     if (document.selection) {
934         myField.focus();
935         sel = document.selection.createRange();
936         sel.text = myValue;
937     }
938     // Mozilla/Netscape support
939     else if (myField.selectionStart || myField.selectionStart == '0') {
940         var startPos = myField.selectionStart;
941         var endPos = myField.selectionEnd;
942         myField.value = myField.value.substring(0, startPos)
943             + myValue + myField.value.substring(endPos, myField.value.length);
944     } else {
945         myField.value += myValue;
946     }
950 /*
951         Call instead of setting window.onload directly or setting body onload=.
952         Adds your function to a chain of functions rather than overwriting anything
953         that exists.
954 */
955 function addonload(fn) {
956     var oldhandler=window.onload;
957     window.onload=function() {
958         if(oldhandler) oldhandler();
959             fn();
960     }
962 /**
963  * Replacement for getElementsByClassName in browsers that aren't cool enough
964  *
965  * Relying on the built-in getElementsByClassName is far, far faster than
966  * using YUI.
967  *
968  * Note: the third argument used to be an object with odd behaviour. It now
969  * acts like the 'name' in the HTML5 spec, though the old behaviour is still
970  * mimicked if you pass an object.
971  *
972  * @param {Node} oElm The top-level node for searching. To search a whole
973  *                    document, use `document`.
974  * @param {String} strTagName filter by tag names
975  * @param {String} name same as HTML5 spec
976  */
977 function getElementsByClassName(oElm, strTagName, name) {
978     // for backwards compatibility
979     if(typeof name == "object") {
980         var names = new Array();
981         for(var i=0; i<name.length; i++) names.push(names[i]);
982         name = names.join('');
983     }
984     // use native implementation if possible
985     if (oElm.getElementsByClassName && Array.filter) {
986         if (strTagName == '*') {
987             return oElm.getElementsByClassName(name);
988         } else {
989             return Array.filter(oElm.getElementsByClassName(name), function(el) {
990                 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
991             });
992         }
993     }
994     // native implementation unavailable, fall back to slow method
995     var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
996     var arrReturnElements = new Array();
997     var arrRegExpClassNames = new Array();
998     var names = name.split(' ');
999     for(var i=0; i<names.length; i++) {
1000         arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1001     }
1002     var oElement;
1003     var bMatchesAll;
1004     for(var j=0; j<arrElements.length; j++) {
1005         oElement = arrElements[j];
1006         bMatchesAll = true;
1007         for(var k=0; k<arrRegExpClassNames.length; k++) {
1008             if(!arrRegExpClassNames[k].test(oElement.className)) {
1009                 bMatchesAll = false;
1010                 break;
1011             }
1012         }
1013         if(bMatchesAll) {
1014             arrReturnElements.push(oElement);
1015         }
1016     }
1017     return (arrReturnElements)
1020 function openpopup(event, args) {
1022     if (event) {
1023         if (event.preventDefault) {
1024             event.preventDefault();
1025         } else {
1026             event.returnValue = false;
1027         }
1028     }
1030     var fullurl = args.url;
1031     if (!args.url.match(/https?:\/\//)) {
1032         fullurl = M.cfg.wwwroot + args.url;
1033     }
1034     var windowobj = window.open(fullurl,args.name,args.options);
1035     if (!windowobj) {
1036         return true;
1037     }
1038     if (args.fullscreen) {
1039         windowobj.moveTo(0,0);
1040         windowobj.resizeTo(screen.availWidth,screen.availHeight);
1041     }
1042     windowobj.focus();
1044     return false;
1047 /** Close the current browser window. */
1048 function close_window(e) {
1049     if (e.preventDefault) {
1050         e.preventDefault();
1051     } else {
1052         e.returnValue = false;
1053     }
1054     self.close();
1057 /**
1058  * Close the current browser window, forcing the window/tab that opened this
1059  * popup to reload itself. */
1060 function close_window_reloading_opener() {
1061     if (window.opener) {
1062         window.opener.location.reload(1);
1063         close_window();
1064         // Intentionally, only try to close the window if there is some evidence we are in a popup.
1065     }
1068 /**
1069  * Used in a couple of modules to hide navigation areas when using AJAX
1070  */
1072 function show_item(itemid) {
1073     var item = document.getElementById(itemid);
1074     if (item) {
1075         item.style.display = "";
1076     }
1079 function destroy_item(itemid) {
1080     var item = document.getElementById(itemid);
1081     if (item) {
1082         item.parentNode.removeChild(item);
1083     }
1085 /**
1086  * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1087  * @param controlid the control id.
1088  */
1089 function focuscontrol(controlid) {
1090     var control = document.getElementById(controlid);
1091     if (control) {
1092         control.focus();
1093     }
1096 /**
1097  * Transfers keyboard focus to an HTML element based on the old style style of focus
1098  * This function should be removed as soon as it is no longer used
1099  */
1100 function old_onload_focus(formid, controlname) {
1101     if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1102         document.forms[formid].elements[controlname].focus();
1103     }
1106 function build_querystring(obj) {
1107     return convert_object_to_string(obj, '&');
1110 function build_windowoptionsstring(obj) {
1111     return convert_object_to_string(obj, ',');
1114 function convert_object_to_string(obj, separator) {
1115     if (typeof obj !== 'object') {
1116         return null;
1117     }
1118     var list = [];
1119     for(var k in obj) {
1120         k = encodeURIComponent(k);
1121         var value = obj[k];
1122         if(obj[k] instanceof Array) {
1123             for(var i in value) {
1124                 list.push(k+'[]='+encodeURIComponent(value[i]));
1125             }
1126         } else {
1127             list.push(k+'='+encodeURIComponent(value));
1128         }
1129     }
1130     return list.join(separator);
1133 function stripHTML(str) {
1134     var re = /<\S[^><]*>/g;
1135     var ret = str.replace(re, "");
1136     return ret;
1139 Number.prototype.fixed=function(n){
1140     with(Math)
1141         return round(Number(this)*pow(10,n))/pow(10,n);
1142 };
1143 function update_progress_bar (id, width, pt, msg, es){
1144     var percent = pt;
1145     var status = document.getElementById("status_"+id);
1146     var percent_indicator = document.getElementById("pt_"+id);
1147     var progress_bar = document.getElementById("progress_"+id);
1148     var time_es = document.getElementById("time_"+id);
1149     status.innerHTML = msg;
1150     percent_indicator.innerHTML = percent.fixed(2) + '%';
1151     if(percent == 100) {
1152         progress_bar.style.background = "green";
1153         time_es.style.display = "none";
1154     } else {
1155         progress_bar.style.background = "#FFCC66";
1156         if (es == '?'){
1157             time_es.innerHTML = "";
1158         }else {
1159             time_es.innerHTML = es.fixed(2)+" sec";
1160             time_es.style.display
1161                 = "block";
1162         }
1163     }
1164     progress_bar.style.width = width + "px";
1168 function frame_breakout(e, properties) {
1169     this.setAttribute('target', properties.framename);
1173 // ===== Deprecated core Javascript functions for Moodle ====
1174 //       DO NOT USE!!!!!!!
1175 // Do not put this stuff in separate file because it only adds extra load on servers!
1177 /**
1178  * Used in a couple of modules to hide navigation areas when using AJAX
1179  */
1180 function hide_item(itemid) {
1181     // use class='hiddenifjs' instead
1182     var item = document.getElementById(itemid);
1183     if (item) {
1184         item.style.display = "none";
1185     }
1188 M.util.help_icon = {
1189     Y : null,
1190     instance : null,
1191     add : function(Y, properties) {
1192         this.Y = Y;
1193         properties.node = Y.one('#'+properties.id);
1194         if (properties.node) {
1195             properties.node.on('click', this.display, this, properties);
1196         }
1197     },
1198     display : function(event, args) {
1199         event.preventDefault();
1200         if (M.util.help_icon.instance === null) {
1201             var Y = M.util.help_icon.Y;
1202             Y.use('overlay', 'io', 'event-mouseenter', 'node', 'event-key', function(Y) {
1203                 var help_content_overlay = {
1204                     helplink : null,
1205                     overlay : null,
1206                     init : function() {
1208                         var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img  src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
1209                         // Create an overlay from markup
1210                         this.overlay = new Y.Overlay({
1211                             headerContent: closebtn,
1212                             bodyContent: '',
1213                             id: 'helppopupbox',
1214                             width:'400px',
1215                             visible : false,
1216                             constrain : true
1217                         });
1218                         this.overlay.render(Y.one(document.body));
1220                         closebtn.on('click', this.overlay.hide, this.overlay);
1222                         var boundingBox = this.overlay.get("boundingBox");
1224                         //  Hide the menu if the user clicks outside of its content
1225                         boundingBox.get("ownerDocument").on("mousedown", function (event) {
1226                             var oTarget = event.target;
1227                             var menuButton = Y.one("#"+args.id);
1229                             if (!oTarget.compareTo(menuButton) &&
1230                                 !menuButton.contains(oTarget) &&
1231                                 !oTarget.compareTo(boundingBox) &&
1232                                 !boundingBox.contains(oTarget)) {
1233                                 this.overlay.hide();
1234                             }
1235                         }, this);
1237                         Y.on("key", this.close, closebtn , "down:13", this);
1238                         closebtn.on('click', this.close, this);
1239                     },
1241                     close : function(e) {
1242                         e.preventDefault();
1243                         this.helplink.focus();
1244                         this.overlay.hide();
1245                     },
1247                     display : function(event, args) {
1248                         this.helplink = args.node;
1249                         this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1250                         this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
1252                         var fullurl = args.url;
1253                         if (!args.url.match(/https?:\/\//)) {
1254                             fullurl = M.cfg.wwwroot + args.url;
1255                         }
1257                         var ajaxurl = fullurl + '&ajax=1';
1259                         var cfg = {
1260                             method: 'get',
1261                             context : this,
1262                             on: {
1263                                 success: function(id, o, node) {
1264                                     this.display_callback(o.responseText);
1265                                 },
1266                                 failure: function(id, o, node) {
1267                                     var debuginfo = o.statusText;
1268                                     if (M.cfg.developerdebug) {
1269                                         o.statusText += ' (' + ajaxurl + ')';
1270                                     }
1271                                     this.display_callback('bodyContent',debuginfo);
1272                                 }
1273                             }
1274                         };
1276                         Y.io(ajaxurl, cfg);
1277                         this.overlay.show();
1279                         Y.one('#closehelpbox').focus();
1280                     },
1282                     display_callback : function(content) {
1283                         this.overlay.set('bodyContent', content);
1284                     },
1286                     hideContent : function() {
1287                         help = this;
1288                         help.overlay.hide();
1289                     }
1290                 };
1291                 help_content_overlay.init();
1292                 M.util.help_icon.instance = help_content_overlay;
1293                 M.util.help_icon.instance.display(event, args);
1294             });
1295         } else {
1296             M.util.help_icon.instance.display(event, args);
1297         }
1298     },
1299     init : function(Y) {
1300         this.Y = Y;
1301     }
1302 };
1304 /**
1305  * Custom menu namespace
1306  */
1307 M.core_custom_menu = {
1308     /**
1309      * This method is used to initialise a custom menu given the id that belongs
1310      * to the custom menu's root node.
1311      *
1312      * @param {YUI} Y
1313      * @param {string} nodeid
1314      */
1315     init : function(Y, nodeid) {
1316         var node = Y.one('#'+nodeid);
1317         if (node) {
1318             Y.use('node-menunav', function(Y) {
1319                 // Get the node
1320                 // Remove the javascript-disabled class.... obviously javascript is enabled.
1321                 node.removeClass('javascript-disabled');
1322                 // Initialise the menunav plugin
1323                 node.plug(Y.Plugin.NodeMenuNav);
1324             });
1325         }
1326     }
1327 };
1329 /**
1330  * Used to store form manipulation methods and enhancments
1331  */
1332 M.form = M.form || {};
1334 /**
1335  * Converts a nbsp indented select box into a multi drop down custom control much
1336  * like the custom menu. It also selectable categories on or off.
1337  *
1338  * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1339  *
1340  * @param {YUI} Y
1341  * @param {string} id
1342  * @param {Array} options
1343  */
1344 M.form.init_smartselect = function(Y, id, options) {
1345     if (!id.match(/^id_/)) {
1346         id = 'id_'+id;
1347     }
1348     var select = Y.one('select#'+id);
1349     if (!select) {
1350         return false;
1351     }
1352     Y.use('event-delegate',function(){
1353         var smartselect = {
1354             id : id,
1355             structure : [],
1356             options : [],
1357             submenucount : 0,
1358             currentvalue : null,
1359             currenttext : null,
1360             shownevent : null,
1361             cfg : {
1362                 selectablecategories : true,
1363                 mode : null
1364             },
1365             nodes : {
1366                 select : null,
1367                 loading : null,
1368                 menu : null
1369             },
1370             init : function(Y, id, args, nodes) {
1371                 if (typeof(args)=='object') {
1372                     for (var i in this.cfg) {
1373                         if (args[i] || args[i]===false) {
1374                             this.cfg[i] = args[i];
1375                         }
1376                     }
1377                 }
1379                 // Display a loading message first up
1380                 this.nodes.select = nodes.select;
1382                 this.currentvalue = this.nodes.select.get('selectedIndex');
1383                 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1385                 var options = Array();
1386                 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1387                 this.nodes.select.all('option').each(function(option, index) {
1388                     var rawtext = option.get('innerHTML');
1389                     var text = rawtext.replace(/^(&nbsp;)*/, '');
1390                     if (rawtext === text) {
1391                         text = rawtext.replace(/^(\s)*/, '');
1392                         var depth = (rawtext.length - text.length ) + 1;
1393                     } else {
1394                         var depth = ((rawtext.length - text.length )/12)+1;
1395                     }
1396                     option.set('innerHTML', text);
1397                     options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1398                 }, this);
1400                 this.structure = [];
1401                 var structcount = 0;
1402                 for (var i in options) {
1403                     var o = options[i];
1404                     if (o.depth == 0) {
1405                         this.structure.push(o);
1406                         structcount++;
1407                     } else {
1408                         var d = o.depth;
1409                         var current = this.structure[structcount-1];
1410                         for (var j = 0; j < o.depth-1;j++) {
1411                             if (current && current.children) {
1412                                 current = current.children[current.children.length-1];
1413                             }
1414                         }
1415                         if (current && current.children) {
1416                             current.children.push(o);
1417                         }
1418                     }
1419                 }
1421                 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1422                 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1423                 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1424                 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1426                 if (this.cfg.mode == null) {
1427                     var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1428                     if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1429                         this.cfg.mode = 'compact';
1430                     } else {
1431                         this.cfg.mode = 'spanning';
1432                     }
1433                 }
1435                 if (this.cfg.mode == 'compact') {
1436                     this.nodes.menu.addClass('compactmenu');
1437                 } else {
1438                     this.nodes.menu.addClass('spanningmenu');
1439                     this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1440                 }
1442                 Y.one(document.body).append(this.nodes.menu);
1443                 var pos = this.nodes.select.getXY();
1444                 pos[0] += 1;
1445                 this.nodes.menu.setXY(pos);
1446                 this.nodes.menu.on('click', this.handle_click, this);
1448                 Y.one(window).on('resize', function(){
1449                      var pos = this.nodes.select.getXY();
1450                     pos[0] += 1;
1451                     this.nodes.menu.setXY(pos);
1452                  }, this);
1453             },
1454             generate_menu_content : function() {
1455                 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1456                 content += this.generate_submenu_content(this.structure[0], true);
1457                 content += '</ul></div>';
1458                 return content;
1459             },
1460             generate_submenu_content : function(item, rootelement) {
1461                 this.submenucount++;
1462                 var content = '';
1463                 if (item.children.length > 0) {
1464                     if (rootelement) {
1465                         content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'">&nbsp;</div>';
1466                         content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1467                         content += '<div class="smartselect_menu_content">';
1468                     } else {
1469                         content += '<li class="smartselect_submenuitem">';
1470                         var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1471                         content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1472                         content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1473                         content += '<div class="smartselect_submenu_content">';
1474                     }
1475                     content += '<ul>';
1476                     for (var i in item.children) {
1477                         content += this.generate_submenu_content(item.children[i],false);
1478                     }
1479                     content += '</ul>';
1480                     content += '</div>';
1481                     content += '</div>';
1482                     if (rootelement) {
1483                     } else {
1484                         content += '</li>';
1485                     }
1486                 } else {
1487                     content += '<li class="smartselect_menuitem">';
1488                     content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1489                     content += '</li>';
1490                 }
1491                 return content;
1492             },
1493             select : function(e) {
1494                 var t = e.target;
1495                 e.halt();
1496                 this.currenttext = t.get('innerHTML');
1497                 this.currentvalue = t.getAttribute('value');
1498                 this.nodes.select.set('selectedIndex', this.currentvalue);
1499                 this.hide_menu();
1500             },
1501             handle_click : function(e) {
1502                 var target = e.target;
1503                 if (target.hasClass('smartselect_mask')) {
1504                     this.show_menu(e);
1505                 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1506                     this.select(e);
1507                 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1508                     this.show_sub_menu(e);
1509                 }
1510             },
1511             show_menu : function(e) {
1512                 e.halt();
1513                 var menu = e.target.ancestor().one('.smartselect_menu');
1514                 menu.addClass('visible');
1515                 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1516             },
1517             show_sub_menu : function(e) {
1518                 e.halt();
1519                 var target = e.target;
1520                 if (!target.hasClass('smartselect_submenuitem')) {
1521                     target = target.ancestor('.smartselect_submenuitem');
1522                 }
1523                 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1524                     target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1525                     return;
1526                 }
1527                 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1528                 target.one('.smartselect_submenu').addClass('visible');
1529             },
1530             hide_menu : function() {
1531                 this.nodes.menu.all('.visible').removeClass('visible');
1532                 if (this.shownevent) {
1533                     this.shownevent.detach();
1534                 }
1535             }
1536         };
1537         smartselect.init(Y, id, options, {select:select});
1538     });
1539 };
1541 M.util.init_flvflowplayer = function (id, playerpath, fileurl) {
1542     $f(id, playerpath, {
1543         plugins: {
1544                 controls: {
1545                        autoHide: true
1546                 }
1547         },
1548         clip: {
1549             url: fileurl,
1550             autoPlay: false,
1551             autoBuffering: true
1552         }
1553     });
1554 };
1556 M.util.init_mp3flowplayer = function (id, playerpath, audioplayerpath, fileurl, color) {
1558     $f(id, playerpath, {
1560             plugins: {
1561                     controls: {
1562                             fullscreen: false,
1563                             height: 25,
1564                             autoHide: false,
1565                             background: '#'+color['bgColour'],
1566                             buttonColor: '#'+color['btnColour'],
1567                             sliderColor: '#'+color['handleColour'],
1568                             volumeSliderColor: '#'+color['handleColour'],
1569                             volumeColor: '#'+color['trackColour'],
1570                             durationColor: '#'+color['fontColour'],
1571                             buttonOverColor: '#'+color['iconOverColour'],
1572                             progressColor: '#'+color['handleColour']
1573                     },
1574                     audio: {url: audioplayerpath}
1575             },
1576             clip: {url: fileurl,
1577                     provider: "audio",
1578                     autoPlay: false
1579             }
1580     });
1581 };
1583 M.util.init_mp3flowplayerplugin = function (id, playerpath, audioplayerpath, fileurl, color) {
1584     $f(id, playerpath, {
1585             plugins: {
1586                     controls: {
1587                             all: false,
1588                             play: true,
1589                             pause: true,
1590                             scrubber: true,
1591                             autoHide: false,
1592                             height: 15,
1593                             background: '#'+color['bgColour'],
1594                             buttonColor: '#'+color['btnColour'],
1595                             sliderColor: '#'+color['handleColour'],
1596                             volumeSliderColor: '#'+color['handleColour'],
1597                             progressColor: '#'+color['handleColour'],
1598                             volumeColor: '#'+color['trackColour'],
1599                             buttonOverColor: '#'+color['iconOverColour']
1600                     },
1601                     audio: {url: audioplayerpath}
1602             },
1603             clip: {url: fileurl,
1604                     provider: "audio",
1605                     autoPlay: false
1606             }
1607     });
1608 };