1 // Miscellaneous core Javascript functions for Moodle
2 // Global M object is initilised in inline javascript
5 * Add module to list of available modules that can be loaded from YUI.
6 * @param {Array} modules
8 M.yui.add_module = function(modules) {
9 for (var modname in modules) {
10 YUI_config.modules[modname] = modules[modname];
14 * The gallery version to use when loading YUI modules from the gallery.
15 * Will be changed every time when using local galleries.
17 M.yui.galleryversion = '2010.04.21-21-51';
20 * Various utility functions
22 M.util = M.util || {};
25 * Language strings - initialised from page footer.
30 * Returns url for images.
31 * @param {String} imagename
32 * @param {String} component
35 M.util.image_url = function(imagename, component) {
37 if (!component || component == '' || component == 'moodle' || component == 'core') {
41 var url = M.cfg.wwwroot + '/theme/image.php';
42 if (M.cfg.themerev > 0 && M.cfg.slasharguments == 1) {
43 if (!M.cfg.svgicons) {
46 url += '/' + M.cfg.theme + '/' + component + '/' + M.cfg.themerev + '/' + imagename;
48 url += '?theme=' + M.cfg.theme + '&component=' + component + '&rev=' + M.cfg.themerev + '&image=' + imagename;
49 if (!M.cfg.svgicons) {
57 M.util.in_array = function(item, array){
58 for( var i = 0; i<array.length; i++){
67 * Init a collapsible region, see print_collapsible_region in weblib.php
68 * @param {YUI} Y YUI3 instance with all libraries loaded
69 * @param {String} id the HTML id for the div.
70 * @param {String} userpref the user preference that records the state of this box. false if none.
71 * @param {String} strtooltip
73 M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
74 Y.use('anim', function(Y) {
75 new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
80 * Object to handle a collapsible region : instantiate and forget styled object
84 * @param {YUI} Y YUI3 instance with all libraries loaded
85 * @param {String} id The HTML id for the div.
86 * @param {String} userpref The user preference that records the state of this box. false if none.
87 * @param {String} strtooltip
89 M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
90 // Record the pref name
91 this.userpref = userpref;
93 // Find the divs in the document.
94 this.div = Y.one('#'+id);
96 // Get the caption for the collapsible region
97 var caption = this.div.one('#'+id + '_caption');
100 var a = Y.Node.create('<a href="#"></a>');
101 a.setAttribute('title', strtooltip);
103 // Get all the nodes from caption, remove them and append them to <a>
104 while (caption.hasChildNodes()) {
105 child = caption.get('firstChild');
111 // Get the height of the div at this point before we shrink it if required
112 var height = this.div.get('offsetHeight');
113 var collapsedimage = 't/collapsed'; // ltr mode
114 if (right_to_left()) {
115 collapsedimage = 't/collapsed_rtl';
117 collapsedimage = 't/collapsed';
119 if (this.div.hasClass('collapsed')) {
120 // Add the correct image and record the YUI node created in the process
121 this.icon = Y.Node.create('<img src="'+M.util.image_url(collapsedimage, 'moodle')+'" alt="" />');
122 // Shrink the div as it is collapsed by default
123 this.div.setStyle('height', caption.get('offsetHeight')+'px');
125 // Add the correct image and record the YUI node created in the process
126 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
130 // Create the animation.
131 var animation = new Y.Anim({
134 easing: Y.Easing.easeBoth,
135 to: {height:caption.get('offsetHeight')},
136 from: {height:height}
139 // Handler for the animation finishing.
140 animation.on('end', function() {
141 this.div.toggleClass('collapsed');
142 var collapsedimage = 't/collapsed'; // ltr mode
143 if (right_to_left()) {
144 collapsedimage = 't/collapsed_rtl';
146 collapsedimage = 't/collapsed';
148 if (this.div.hasClass('collapsed')) {
149 this.icon.set('src', M.util.image_url(collapsedimage, 'moodle'));
151 this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
155 // Hook up the event handler.
156 a.on('click', function(e, animation) {
158 // Animate to the appropriate size.
159 if (animation.get('running')) {
162 animation.set('reverse', this.div.hasClass('collapsed'));
163 // Update the user preference.
165 M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
172 * The user preference that stores the state of this box.
176 M.util.CollapsibleRegion.prototype.userpref = null;
179 * The key divs that make up this
183 M.util.CollapsibleRegion.prototype.div = null;
186 * The key divs that make up this
190 M.util.CollapsibleRegion.prototype.icon = null;
193 * Makes a best effort to connect back to Moodle to update a user preference,
194 * however, there is no mechanism for finding out if the update succeeded.
196 * Before you can use this function in your JavsScript, you must have called
197 * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
198 * the udpate is allowed, and how to safely clean and submitted values.
200 * @param String name the name of the setting to udpate.
201 * @param String the value to set it to.
203 M.util.set_user_preference = function(name, value) {
204 YUI().use('io', function(Y) {
205 var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
206 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
208 // If we are a developer, ensure that failures are reported.
213 if (M.cfg.developerdebug) {
214 cfg.on.failure = function(id, o, args) {
215 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
225 * Prints a confirmation dialog in the style of DOM.confirm().
226 * @param object event A YUI DOM event or null if launched manually
227 * @param string message The message to show in the dialog
228 * @param string url The URL to forward to if YES is clicked. Disabled if fn is given
229 * @param function fn A JS function to run if YES is clicked.
231 M.util.show_confirm_dialog = function(e, args) {
232 var target = e.target;
233 if (e.preventDefault) {
237 YUI().use('yui2-container', 'yui2-event', function(Y) {
238 var simpledialog = new Y.YUI2.widget.SimpleDialog('confirmdialog',
247 simpledialog.setHeader(M.str.admin.confirmation);
248 simpledialog.setBody(args.message);
249 simpledialog.cfg.setProperty('icon', Y.YUI2.widget.SimpleDialog.ICON_WARN);
251 var handle_cancel = function() {
255 var handle_yes = function() {
259 // args comes from PHP, so callback will be a string, needs to be evaluated by JS
261 if (Y.Lang.isFunction(args.callback)) {
262 callback = args.callback;
264 callback = eval('('+args.callback+')');
267 if (Y.Lang.isObject(args.scope)) {
273 if (args.callbackargs) {
274 callback.apply(sc, args.callbackargs);
281 var targetancestor = null,
284 if (target.test('a')) {
285 window.location = target.get('href');
287 } else if ((targetancestor = target.ancestor('a')) !== null) {
288 window.location = targetancestor.get('href');
290 } else if (target.test('input')) {
291 targetform = target.ancestor(function(node) { return node.get('tagName').toLowerCase() == 'form'; });
292 // We cannot use target.ancestor('form') on the previous line
293 // because of http://yuilibrary.com/projects/yui3/ticket/2531561
297 if (target.get('name') && target.get('value')) {
298 targetform.append('<input type="hidden" name="' + target.get('name') +
299 '" value="' + target.get('value') + '">');
303 } else if (target.get('tagName').toLowerCase() == 'form') {
304 // We cannot use target.test('form') on the previous line because of
305 // http://yuilibrary.com/projects/yui3/ticket/2531561
308 } else if (M.cfg.developerdebug) {
309 alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A, INPUT, or FORM");
313 if (!args.cancellabel) {
314 args.cancellabel = M.str.moodle.cancel;
316 if (!args.continuelabel) {
317 args.continuelabel = M.str.moodle.yes;
321 {text: args.cancellabel, handler: handle_cancel, isDefault: true},
322 {text: args.continuelabel, handler: handle_yes}
325 simpledialog.cfg.queueProperty('buttons', buttons);
327 simpledialog.render(document.body);
332 /** Useful for full embedding of various stuff */
333 M.util.init_maximised_embed = function(Y, id) {
334 var obj = Y.one('#'+id);
339 var get_htmlelement_size = function(el, prop) {
340 if (Y.Lang.isString(el)) {
341 el = Y.one('#' + el);
343 var val = el.getStyle(prop);
345 val = el.getComputedStyle(prop);
347 return parseInt(val);
350 var resize_object = function() {
351 obj.setStyle('width', '0px');
352 obj.setStyle('height', '0px');
353 var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
355 if (newwidth > 500) {
356 obj.setStyle('width', newwidth + 'px');
358 obj.setStyle('width', '500px');
361 var headerheight = get_htmlelement_size('page-header', 'height');
362 var footerheight = get_htmlelement_size('page-footer', 'height');
363 var newheight = parseInt(Y.one('body').get('winHeight')) - footerheight - headerheight - 100;
364 if (newheight < 400) {
367 obj.setStyle('height', newheight+'px');
371 // fix layout if window resized too
372 window.onresize = function() {
378 * Attach handler to single_select
380 M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
381 Y.use('event-key', function() {
382 var select = Y.one('#'+selectid);
384 // Try to get the form by id
385 var form = Y.one('#'+formid) || (function(){
386 // Hmmm the form's id may have been overriden by an internal input
387 // with the name id which will KILL IE.
388 // We need to manually iterate at this point because if the case
389 // above is true YUI's ancestor method will also kill IE!
391 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
392 form = form.ancestor();
396 // Make sure we have the form
399 // Create a function to handle our change event
400 var processchange = function(e, paramobject) {
401 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
402 // chrome doesn't pick up on a click when selecting an element in a select menu, so we use
403 // the on change event to fire this function. This just checks to see if a button was
404 // first pressed before redirecting to the appropriate page.
405 if (Y.UA.os == 'windows' && Y.UA.chrome){
406 if (buttonflag == 1) {
419 var changedown = function(e, paramobject) {
420 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
421 if(e.keyCode == 13) {
427 var paramobject = new Object();
428 paramobject.lastindex = select.get('selectedIndex');
429 paramobject.eventchangeorblur = select.on('click', processchange, form, paramobject);
430 // Bad hack to circumvent problems with different browsers on different systems.
431 if (Y.UA.os == 'macintosh') {
433 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
435 paramobject.eventkeypress = Y.on('key', processchange, select, 'press:13', form, paramobject);
437 if(Y.UA.os == 'windows' && Y.UA.chrome) {
438 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
440 paramobject.eventkeypress = Y.on('keydown', changedown, select, '', form, paramobject);
448 * Attach handler to url_select
449 * Deprecated from 2.4 onwards.
450 * Please use @see init_select_autosubmit() for redirecting to a url (above).
451 * This function has accessability issues and also does not use the formid passed through as a parameter.
453 M.util.init_url_select = function(Y, formid, selectid, nothing) {
454 YUI().use('node', function(Y) {
455 Y.on('change', function() {
456 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
457 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
465 * Breaks out all links to the top frame - used in frametop page layout.
467 M.util.init_frametop = function(Y) {
468 Y.all('a').each(function(node) {
469 node.set('target', '_top');
471 Y.all('form').each(function(node) {
472 node.set('target', '_top');
477 * Finds all nodes that match the given CSS selector and attaches events to them
478 * so that they toggle a given classname when clicked.
481 * @param {string} id An id containing elements to target
482 * @param {string} cssselector A selector to use to find targets
483 * @param {string} toggleclassname A classname to toggle
485 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
487 if (togglecssselector == '') {
488 togglecssselector = cssselector;
491 var node = Y.one('#'+id);
492 node.all(cssselector).each(function(n){
493 n.on('click', function(e){
495 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
496 if (this.test(togglecssselector)) {
497 this.toggleClass(toggleclassname);
499 this.ancestor(togglecssselector).toggleClass(toggleclassname);
504 // Attach this click event to the node rather than all selectors... will be much better
506 node.on('click', function(e){
507 if (e.target.hasClass('addtoall')) {
508 this.all(togglecssselector).addClass(toggleclassname);
509 } else if (e.target.hasClass('removefromall')) {
510 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
516 * Initialises a colour picker
518 * Designed to be used with admin_setting_configcolourpicker although could be used
519 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
520 * above or below the input (must have the same parent) and then call this with the
523 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
524 * contrib/blocks. For better docs refer to that.
528 * @param {object} previewconf
530 M.util.init_colour_picker = function(Y, id, previewconf) {
532 * We need node and event-mouseenter
534 Y.use('node', 'event-mouseenter', function(){
536 * The colour picker object
545 eventMouseEnter : null,
546 eventMouseLeave : null,
547 eventMouseMove : null,
552 * Initalises the colour picker by putting everything together and wiring the events
555 this.input = Y.one('#'+id);
556 this.box = this.input.ancestor().one('.admin_colourpicker');
557 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
558 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
559 this.preview = Y.Node.create('<div class="previewcolour"></div>');
560 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
561 this.current = Y.Node.create('<div class="currentcolour"></div>');
562 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
563 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
565 if (typeof(previewconf) === 'object' && previewconf !== null) {
566 Y.one('#'+id+'_preview').on('click', function(e){
567 if (Y.Lang.isString(previewconf.selector)) {
568 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
570 for (var i in previewconf.selector) {
571 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
577 this.eventClick = this.image.on('click', this.pickColour, this);
578 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
581 * Starts to follow the mouse once it enter the image
583 startFollow : function(e) {
584 this.eventMouseEnter.detach();
585 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
586 this.eventMouseMove = this.image.on('mousemove', function(e){
587 this.preview.setStyle('backgroundColor', this.determineColour(e));
591 * Stops following the mouse
593 endFollow : function(e) {
594 this.eventMouseMove.detach();
595 this.eventMouseLeave.detach();
596 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
599 * Picks the colour the was clicked on
601 pickColour : function(e) {
602 var colour = this.determineColour(e);
603 this.input.set('value', colour);
604 this.current.setStyle('backgroundColor', colour);
607 * Calculates the colour fromthe given co-ordinates
609 determineColour : function(e) {
610 var eventx = Math.floor(e.pageX-e.target.getX());
611 var eventy = Math.floor(e.pageY-e.target.getY());
613 var imagewidth = this.width;
614 var imageheight = this.height;
615 var factor = this.factor;
616 var colour = [255,0,0];
627 var matrixcount = matrices.length;
628 var limit = Math.round(imagewidth/matrixcount);
629 var heightbreak = Math.round(imageheight/2);
631 for (var x = 0; x < imagewidth; x++) {
632 var divisor = Math.floor(x / limit);
633 var matrix = matrices[divisor];
635 colour[0] += matrix[0]*factor;
636 colour[1] += matrix[1]*factor;
637 colour[2] += matrix[2]*factor;
644 var pixel = [colour[0], colour[1], colour[2]];
645 if (eventy < heightbreak) {
646 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
647 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
648 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
649 } else if (eventy > heightbreak) {
650 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
651 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
652 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
655 return this.convert_rgb_to_hex(pixel);
658 * Converts an RGB value to Hex
660 convert_rgb_to_hex : function(rgb) {
662 var hexchars = "0123456789ABCDEF";
663 for (var i=0; i<3; i++) {
664 var number = Math.abs(rgb[i]);
665 if (number == 0 || isNaN(number)) {
668 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
675 * Initialise the colour picker :) Hoorah
681 M.util.init_block_hider = function(Y, config) {
682 Y.use('base', 'node', function(Y) {
683 M.util.block_hider = M.util.block_hider || (function(){
684 var blockhider = function() {
685 blockhider.superclass.constructor.apply(this, arguments);
687 blockhider.prototype = {
688 initializer : function(config) {
689 this.set('block', '#'+this.get('id'));
690 var b = this.get('block'),
693 if (t && (a = t.one('.block_action'))) {
694 var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
695 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
696 hide.on('keypress', this.updateStateKey, this, true);
697 var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
698 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
699 show.on('keypress', this.updateStateKey, this, false);
700 a.insert(show, 0).insert(hide, 0);
703 updateState : function(e, hide) {
704 M.util.set_user_preference(this.get('preference'), hide);
706 this.get('block').addClass('hidden');
708 this.get('block').removeClass('hidden');
711 updateStateKey : function(e, hide) {
712 if (e.keyCode == 13) { //allow hide/show via enter key
713 this.updateState(this, hide);
717 Y.extend(blockhider, Y.Base, blockhider.prototype, {
723 value : M.util.image_url('t/switch_minus', 'moodle')
726 value : M.util.image_url('t/switch_plus', 'moodle')
729 setter : function(node) {
737 new M.util.block_hider(config);
742 * Returns a string registered in advance for usage in JavaScript
744 * If you do not pass the third parameter, the function will just return
745 * the corresponding value from the M.str object. If the third parameter is
746 * provided, the function performs {$a} placeholder substitution in the
747 * same way as PHP get_string() in Moodle does.
749 * @param {String} identifier string identifier
750 * @param {String} component the component providing the string
751 * @param {Object|String} a optional variable to populate placeholder with
753 M.util.get_string = function(identifier, component, a) {
756 if (M.cfg.developerdebug) {
757 // creating new instance if YUI is not optimal but it seems to be better way then
758 // require the instance via the function API - note that it is used in rare cases
759 // for debugging only anyway
760 // To ensure we don't kill browser performance if hundreds of get_string requests
761 // are made we cache the instance we generate within the M.util namespace.
762 // We don't publicly define the variable so that it doesn't get abused.
763 if (typeof M.util.get_string_yui_instance === 'undefined') {
764 M.util.get_string_yui_instance = new YUI({ debug : true });
766 var Y = M.util.get_string_yui_instance;
769 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
770 stringvalue = '[[' + identifier + ',' + component + ']]';
771 if (M.cfg.developerdebug) {
772 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
777 stringvalue = M.str[component][identifier];
779 if (typeof a == 'undefined') {
780 // no placeholder substitution requested
784 if (typeof a == 'number' || typeof a == 'string') {
785 // replace all occurrences of {$a} with the placeholder value
786 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
790 if (typeof a == 'object') {
791 // replace {$a->key} placeholders
793 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
794 if (M.cfg.developerdebug) {
795 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
799 var search = '{$a->' + key + '}';
800 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
801 search = new RegExp(search, 'g');
802 stringvalue = stringvalue.replace(search, a[key]);
807 if (M.cfg.developerdebug) {
808 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
814 * Set focus on username or password field of the login form
816 M.util.focus_login_form = function(Y) {
817 var username = Y.one('#username');
818 var password = Y.one('#password');
820 if (username == null || password == null) {
821 // something is wrong here
825 var curElement = document.activeElement
826 if (curElement == 'undefined') {
827 // legacy browser - skip refocus protection
828 } else if (curElement.tagName == 'INPUT') {
829 // user was probably faster to focus something, do not mess with focus
833 if (username.get('value') == '') {
841 * Adds lightbox hidden element that covers the whole node.
844 * @param {Node} the node lightbox should be added to
845 * @retun {Node} created lightbox node
847 M.util.add_lightbox = function(Y, node) {
848 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
850 // Check if lightbox is already there
851 if (node.one('.lightbox')) {
852 return node.one('.lightbox');
855 node.setStyle('position', 'relative');
856 var waiticon = Y.Node.create('<img />')
858 'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
861 'position' : 'relative',
865 var lightbox = Y.Node.create('<div></div>')
868 'position' : 'absolute',
873 'backgroundColor' : 'white',
874 'textAlign' : 'center'
876 .setAttribute('class', 'lightbox')
879 lightbox.appendChild(waiticon);
880 node.append(lightbox);
885 * Appends a hidden spinner element to the specified node.
888 * @param {Node} the node the spinner should be added to
889 * @return {Node} created spinner node
891 M.util.add_spinner = function(Y, node) {
892 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
894 // Check if spinner is already there
895 if (node.one('.spinner')) {
896 return node.one('.spinner');
899 var spinner = Y.Node.create('<img />')
900 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
902 .addClass('iconsmall')
905 node.append(spinner);
909 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
911 function checkall() {
912 var inputs = document.getElementsByTagName('input');
913 for (var i = 0; i < inputs.length; i++) {
914 if (inputs[i].type == 'checkbox') {
915 if (inputs[i].disabled || inputs[i].readOnly) {
918 inputs[i].checked = true;
923 function checknone() {
924 var inputs = document.getElementsByTagName('input');
925 for (var i = 0; i < inputs.length; i++) {
926 if (inputs[i].type == 'checkbox') {
927 if (inputs[i].disabled || inputs[i].readOnly) {
930 inputs[i].checked = false;
936 * Either check, or uncheck, all checkboxes inside the element with id is
937 * @param id the id of the container
938 * @param checked the new state, either '' or 'checked'.
940 function select_all_in_element_with_id(id, checked) {
941 var container = document.getElementById(id);
945 var inputs = container.getElementsByTagName('input');
946 for (var i = 0; i < inputs.length; ++i) {
947 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
948 inputs[i].checked = checked;
953 function select_all_in(elTagName, elClass, elId) {
954 var inputs = document.getElementsByTagName('input');
955 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
956 for(var i = 0; i < inputs.length; ++i) {
957 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
958 inputs[i].checked = 'checked';
963 function deselect_all_in(elTagName, elClass, elId) {
964 var inputs = document.getElementsByTagName('INPUT');
965 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
966 for(var i = 0; i < inputs.length; ++i) {
967 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
968 inputs[i].checked = '';
973 function confirm_if(expr, message) {
977 return confirm(message);
982 findParentNode (start, elementName, elementClass, elementID)
984 Travels up the DOM hierarchy to find a parent element with the
985 specified tag name, class, and id. All conditions must be met,
986 but any can be ommitted. Returns the BODY element if no match
989 function findParentNode(el, elName, elClass, elId) {
990 while (el.nodeName.toUpperCase() != 'BODY') {
991 if ((!elName || el.nodeName.toUpperCase() == elName) &&
992 (!elClass || el.className.indexOf(elClass) != -1) &&
993 (!elId || el.id == elId)) {
1001 findChildNode (start, elementName, elementClass, elementID)
1003 Travels down the DOM hierarchy to find all child elements with the
1004 specified tag name, class, and id. All conditions must be met,
1005 but any can be ommitted.
1006 Doesn't examine children of matches.
1008 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
1009 var children = new Array();
1010 for (var i = 0; i < start.childNodes.length; i++) {
1011 var classfound = false;
1012 var child = start.childNodes[i];
1013 if((child.nodeType == 1) &&//element node type
1014 (elementClass && (typeof(child.className)=='string'))) {
1015 var childClasses = child.className.split(/\s+/);
1016 for (var childClassIndex in childClasses) {
1017 if (childClasses[childClassIndex]==elementClass) {
1023 if(child.nodeType == 1) { //element node type
1024 if ( (!tagName || child.nodeName == tagName) &&
1025 (!elementClass || classfound)&&
1026 (!elementID || child.id == elementID) &&
1027 (!elementName || child.name == elementName))
1029 children = children.concat(child);
1031 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
1038 function unmaskPassword(id) {
1039 var pw = document.getElementById(id);
1040 var chb = document.getElementById(id+'unmask');
1043 // first try IE way - it can not set name attribute later
1045 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
1047 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
1049 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
1051 var newpw = document.createElement('input');
1052 newpw.setAttribute('autocomplete', 'off');
1053 newpw.setAttribute('name', pw.name);
1055 newpw.setAttribute('type', 'text');
1057 newpw.setAttribute('type', 'password');
1059 newpw.setAttribute('class', pw.getAttribute('class'));
1062 newpw.size = pw.size;
1063 newpw.onblur = pw.onblur;
1064 newpw.onchange = pw.onchange;
1065 newpw.value = pw.value;
1066 pw.parentNode.replaceChild(newpw, pw);
1069 function filterByParent(elCollection, parentFinder) {
1070 var filteredCollection = [];
1071 for (var i = 0; i < elCollection.length; ++i) {
1072 var findParent = parentFinder(elCollection[i]);
1073 if (findParent.nodeName.toUpperCase() != 'BODY') {
1074 filteredCollection.push(elCollection[i]);
1077 return filteredCollection;
1081 All this is here just so that IE gets to handle oversized blocks
1082 in a visually pleasing manner. It does a browser detect. So sue me.
1085 function fix_column_widths() {
1086 var agt = navigator.userAgent.toLowerCase();
1087 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1088 fix_column_width('left-column');
1089 fix_column_width('right-column');
1093 function fix_column_width(colName) {
1094 if(column = document.getElementById(colName)) {
1095 if(!column.offsetWidth) {
1096 setTimeout("fix_column_width('" + colName + "')", 20);
1101 var nodes = column.childNodes;
1103 for(i = 0; i < nodes.length; ++i) {
1104 if(nodes[i].className.indexOf("block") != -1 ) {
1105 if(width < nodes[i].offsetWidth) {
1106 width = nodes[i].offsetWidth;
1111 for(i = 0; i < nodes.length; ++i) {
1112 if(nodes[i].className.indexOf("block") != -1 ) {
1113 nodes[i].style.width = width + 'px';
1121 Insert myValue at current cursor position
1123 function insertAtCursor(myField, myValue) {
1125 if (document.selection) {
1127 sel = document.selection.createRange();
1130 // Mozilla/Netscape support
1131 else if (myField.selectionStart || myField.selectionStart == '0') {
1132 var startPos = myField.selectionStart;
1133 var endPos = myField.selectionEnd;
1134 myField.value = myField.value.substring(0, startPos)
1135 + myValue + myField.value.substring(endPos, myField.value.length);
1137 myField.value += myValue;
1143 Call instead of setting window.onload directly or setting body onload=.
1144 Adds your function to a chain of functions rather than overwriting anything
1147 function addonload(fn) {
1148 var oldhandler=window.onload;
1149 window.onload=function() {
1150 if(oldhandler) oldhandler();
1155 * Replacement for getElementsByClassName in browsers that aren't cool enough
1157 * Relying on the built-in getElementsByClassName is far, far faster than
1160 * Note: the third argument used to be an object with odd behaviour. It now
1161 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1162 * mimicked if you pass an object.
1164 * @param {Node} oElm The top-level node for searching. To search a whole
1165 * document, use `document`.
1166 * @param {String} strTagName filter by tag names
1167 * @param {String} name same as HTML5 spec
1169 function getElementsByClassName(oElm, strTagName, name) {
1170 // for backwards compatibility
1171 if(typeof name == "object") {
1172 var names = new Array();
1173 for(var i=0; i<name.length; i++) names.push(names[i]);
1174 name = names.join('');
1176 // use native implementation if possible
1177 if (oElm.getElementsByClassName && Array.filter) {
1178 if (strTagName == '*') {
1179 return oElm.getElementsByClassName(name);
1181 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1182 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1186 // native implementation unavailable, fall back to slow method
1187 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1188 var arrReturnElements = new Array();
1189 var arrRegExpClassNames = new Array();
1190 var names = name.split(' ');
1191 for(var i=0; i<names.length; i++) {
1192 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1196 for(var j=0; j<arrElements.length; j++) {
1197 oElement = arrElements[j];
1199 for(var k=0; k<arrRegExpClassNames.length; k++) {
1200 if(!arrRegExpClassNames[k].test(oElement.className)) {
1201 bMatchesAll = false;
1206 arrReturnElements.push(oElement);
1209 return (arrReturnElements)
1213 * Return whether we are in right to left mode or not.
1217 function right_to_left() {
1218 var body = Y.one('body');
1220 if (body && body.hasClass('dir-rtl')) {
1226 function openpopup(event, args) {
1229 if (event.preventDefault) {
1230 event.preventDefault();
1232 event.returnValue = false;
1236 // Make sure the name argument is set and valid.
1237 var nameregex = /[^a-z0-9_]/i;
1238 if (typeof args.name !== 'string') {
1239 args.name = '_blank';
1240 } else if (args.name.match(nameregex)) {
1241 // Cleans window name because IE does not support funky ones.
1242 args.name = args.name.replace(nameregex, '_');
1243 if (M.cfg.developerdebug) {
1244 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup()');
1248 var fullurl = args.url;
1249 if (!args.url.match(/https?:\/\//)) {
1250 fullurl = M.cfg.wwwroot + args.url;
1252 if (args.fullscreen) {
1253 args.options = args.options.
1254 replace(/top=\d+/, 'top=0').
1255 replace(/left=\d+/, 'left=0').
1256 replace(/width=\d+/, 'width=' + screen.availWidth).
1257 replace(/height=\d+/, 'height=' + screen.availHeight);
1259 var windowobj = window.open(fullurl,args.name,args.options);
1264 if (args.fullscreen) {
1265 // In some browser / OS combinations (E.g. Chrome on Windows), the
1266 // window initially opens slighly too big. The width and heigh options
1267 // seem to control the area inside the browser window, so what with
1268 // scroll-bars, etc. the actual window is bigger than the screen.
1269 // Therefore, we need to fix things up after the window is open.
1270 var hackcount = 100;
1271 var get_size_exactly_right = function() {
1272 windowobj.moveTo(0, 0);
1273 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1275 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1276 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1277 // about 50ms) after the window is open, then it actually behaves
1278 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1279 // check that the resize actually worked, and if not, repeatedly try
1280 // again after a short delay until it works (but with a limit of
1281 // hackcount repeats.
1282 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1284 setTimeout(get_size_exactly_right, 10);
1287 setTimeout(get_size_exactly_right, 0);
1294 /** Close the current browser window. */
1295 function close_window(e) {
1296 if (e.preventDefault) {
1299 e.returnValue = false;
1305 * Used in a couple of modules to hide navigation areas when using AJAX
1308 function show_item(itemid) {
1309 var item = document.getElementById(itemid);
1311 item.style.display = "";
1315 function destroy_item(itemid) {
1316 var item = document.getElementById(itemid);
1318 item.parentNode.removeChild(item);
1322 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1323 * @param controlid the control id.
1325 function focuscontrol(controlid) {
1326 var control = document.getElementById(controlid);
1333 * Transfers keyboard focus to an HTML element based on the old style style of focus
1334 * This function should be removed as soon as it is no longer used
1336 function old_onload_focus(formid, controlname) {
1337 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1338 document.forms[formid].elements[controlname].focus();
1342 function build_querystring(obj) {
1343 return convert_object_to_string(obj, '&');
1346 function build_windowoptionsstring(obj) {
1347 return convert_object_to_string(obj, ',');
1350 function convert_object_to_string(obj, separator) {
1351 if (typeof obj !== 'object') {
1356 k = encodeURIComponent(k);
1358 if(obj[k] instanceof Array) {
1359 for(var i in value) {
1360 list.push(k+'[]='+encodeURIComponent(value[i]));
1363 list.push(k+'='+encodeURIComponent(value));
1366 return list.join(separator);
1369 function stripHTML(str) {
1370 var re = /<\S[^><]*>/g;
1371 var ret = str.replace(re, "");
1375 Number.prototype.fixed=function(n){
1377 return round(Number(this)*pow(10,n))/pow(10,n);
1379 function update_progress_bar (id, width, pt, msg, es){
1381 var status = document.getElementById("status_"+id);
1382 var percent_indicator = document.getElementById("pt_"+id);
1383 var progress_bar = document.getElementById("progress_"+id);
1384 var time_es = document.getElementById("time_"+id);
1385 status.innerHTML = msg;
1386 percent_indicator.innerHTML = percent.fixed(2) + '%';
1387 if(percent == 100) {
1388 progress_bar.style.background = "green";
1389 time_es.style.display = "none";
1391 progress_bar.style.background = "#FFCC66";
1393 time_es.innerHTML = "";
1395 time_es.innerHTML = es.fixed(2)+" sec";
1396 time_es.style.display
1400 progress_bar.style.width = width + "px";
1405 // ===== Deprecated core Javascript functions for Moodle ====
1406 // DO NOT USE!!!!!!!
1407 // Do not put this stuff in separate file because it only adds extra load on servers!
1410 * Used in a couple of modules to hide navigation areas when using AJAX
1412 function hide_item(itemid) {
1413 // use class='hiddenifjs' instead
1414 var item = document.getElementById(itemid);
1416 item.style.display = "none";
1420 M.util.help_popups = {
1421 setup : function(Y) {
1422 Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this);
1424 open_popup : function(e) {
1425 // Prevent the default page action
1428 // Grab the anchor that was clicked
1429 var anchor = e.target.ancestor('a', true);
1432 'url' : anchor.getAttribute('href'),
1450 args.options = options.join(',');
1456 M.util.help_icon = {
1459 add : function(Y, properties) {
1461 properties.node = Y.one('#'+properties.id);
1462 if (properties.node) {
1463 properties.node.on('click', this.display, this, properties);
1466 display : function(event, args) {
1467 event.preventDefault();
1468 if (M.util.help_icon.instance === null) {
1469 var Y = M.util.help_icon.Y;
1470 Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', 'escape', function(Y) {
1471 var help_content_overlay = {
1476 var strclose = Y.Escape.html(M.str.form.close);
1477 var footerbtn = Y.Node.create('<button class="closebtn">'+strclose+'</button>');
1478 // Create an overlay from markup
1479 this.overlay = new Y.Overlay({
1480 footerContent: footerbtn,
1487 this.overlay.render(Y.one(document.body));
1489 footerbtn.on('click', this.overlay.hide, this.overlay);
1491 var boundingBox = this.overlay.get("boundingBox");
1493 // Hide the menu if the user clicks outside of its content
1494 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1495 var oTarget = event.target;
1496 var menuButton = Y.one("#"+args.id);
1498 if (!oTarget.compareTo(menuButton) &&
1499 !menuButton.contains(oTarget) &&
1500 !oTarget.compareTo(boundingBox) &&
1501 !boundingBox.contains(oTarget)) {
1502 this.overlay.hide();
1507 close : function(e) {
1509 this.helplink.focus();
1510 this.overlay.hide();
1513 display : function(event, args) {
1514 if (Y.one('html').get('dir') == 'rtl') {
1515 var overlayPosition = [Y.WidgetPositionAlign.TR, Y.WidgetPositionAlign.LC];
1517 var overlayPosition = [Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC];
1520 this.helplink = args.node;
1522 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1523 this.overlay.set("align", {node:args.node, points: overlayPosition});
1525 var fullurl = args.url;
1526 if (!args.url.match(/https?:\/\//)) {
1527 fullurl = M.cfg.wwwroot + args.url;
1530 var ajaxurl = fullurl + '&ajax=1';
1536 success: function(id, o, node) {
1537 this.display_callback(o.responseText);
1539 failure: function(id, o, node) {
1540 var debuginfo = o.statusText;
1541 if (M.cfg.developerdebug) {
1542 o.statusText += ' (' + ajaxurl + ')';
1544 this.display_callback('bodyContent',debuginfo);
1550 this.overlay.show();
1553 display_callback : function(content) {
1554 content = '<div role="alert">' + content + '</div>';
1555 this.overlay.set('bodyContent', content);
1558 hideContent : function() {
1560 help.overlay.hide();
1563 help_content_overlay.init();
1564 M.util.help_icon.instance = help_content_overlay;
1565 M.util.help_icon.instance.display(event, args);
1568 M.util.help_icon.instance.display(event, args);
1571 init : function(Y) {
1577 * Custom menu namespace
1579 M.core_custom_menu = {
1581 * This method is used to initialise a custom menu given the id that belongs
1582 * to the custom menu's root node.
1585 * @param {string} nodeid
1587 init : function(Y, nodeid) {
1588 var node = Y.one('#'+nodeid);
1590 Y.use('node-menunav', function(Y) {
1592 // Remove the javascript-disabled class.... obviously javascript is enabled.
1593 node.removeClass('javascript-disabled');
1594 // Initialise the menunav plugin
1595 node.plug(Y.Plugin.NodeMenuNav);
1602 * Used to store form manipulation methods and enhancments
1604 M.form = M.form || {};
1607 * Converts a nbsp indented select box into a multi drop down custom control much
1608 * like the custom menu. It also selectable categories on or off.
1610 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1613 * @param {string} id
1614 * @param {Array} options
1616 M.form.init_smartselect = function(Y, id, options) {
1617 if (!id.match(/^id_/)) {
1620 var select = Y.one('select#'+id);
1624 Y.use('event-delegate',function(){
1630 currentvalue : null,
1634 selectablecategories : true,
1642 init : function(Y, id, args, nodes) {
1643 if (typeof(args)=='object') {
1644 for (var i in this.cfg) {
1645 if (args[i] || args[i]===false) {
1646 this.cfg[i] = args[i];
1651 // Display a loading message first up
1652 this.nodes.select = nodes.select;
1654 this.currentvalue = this.nodes.select.get('selectedIndex');
1655 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1657 var options = Array();
1658 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1659 this.nodes.select.all('option').each(function(option, index) {
1660 var rawtext = option.get('innerHTML');
1661 var text = rawtext.replace(/^( )*/, '');
1662 if (rawtext === text) {
1663 text = rawtext.replace(/^(\s)*/, '');
1664 var depth = (rawtext.length - text.length ) + 1;
1666 var depth = ((rawtext.length - text.length )/12)+1;
1668 option.set('innerHTML', text);
1669 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1672 this.structure = [];
1673 var structcount = 0;
1674 for (var i in options) {
1677 this.structure.push(o);
1681 var current = this.structure[structcount-1];
1682 for (var j = 0; j < o.depth-1;j++) {
1683 if (current && current.children) {
1684 current = current.children[current.children.length-1];
1687 if (current && current.children) {
1688 current.children.push(o);
1693 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1694 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1695 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1696 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1698 if (this.cfg.mode == null) {
1699 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1700 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1701 this.cfg.mode = 'compact';
1703 this.cfg.mode = 'spanning';
1707 if (this.cfg.mode == 'compact') {
1708 this.nodes.menu.addClass('compactmenu');
1710 this.nodes.menu.addClass('spanningmenu');
1711 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1714 Y.one(document.body).append(this.nodes.menu);
1715 var pos = this.nodes.select.getXY();
1717 this.nodes.menu.setXY(pos);
1718 this.nodes.menu.on('click', this.handle_click, this);
1720 Y.one(window).on('resize', function(){
1721 var pos = this.nodes.select.getXY();
1723 this.nodes.menu.setXY(pos);
1726 generate_menu_content : function() {
1727 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1728 content += this.generate_submenu_content(this.structure[0], true);
1729 content += '</ul></div>';
1732 generate_submenu_content : function(item, rootelement) {
1733 this.submenucount++;
1735 if (item.children.length > 0) {
1737 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'"> </div>';
1738 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1739 content += '<div class="smartselect_menu_content">';
1741 content += '<li class="smartselect_submenuitem">';
1742 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1743 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1744 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1745 content += '<div class="smartselect_submenu_content">';
1748 for (var i in item.children) {
1749 content += this.generate_submenu_content(item.children[i],false);
1752 content += '</div>';
1753 content += '</div>';
1759 content += '<li class="smartselect_menuitem">';
1760 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1765 select : function(e) {
1768 this.currenttext = t.get('innerHTML');
1769 this.currentvalue = t.getAttribute('value');
1770 this.nodes.select.set('selectedIndex', this.currentvalue);
1773 handle_click : function(e) {
1774 var target = e.target;
1775 if (target.hasClass('smartselect_mask')) {
1777 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1779 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1780 this.show_sub_menu(e);
1783 show_menu : function(e) {
1785 var menu = e.target.ancestor().one('.smartselect_menu');
1786 menu.addClass('visible');
1787 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1789 show_sub_menu : function(e) {
1791 var target = e.target;
1792 if (!target.hasClass('smartselect_submenuitem')) {
1793 target = target.ancestor('.smartselect_submenuitem');
1795 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1796 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1799 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1800 target.one('.smartselect_submenu').addClass('visible');
1802 hide_menu : function() {
1803 this.nodes.menu.all('.visible').removeClass('visible');
1804 if (this.shownevent) {
1805 this.shownevent.detach();
1809 smartselect.init(Y, id, options, {select:select});
1813 /** List of flv players to be loaded */
1814 M.util.video_players = [];
1815 /** List of mp3 players to be loaded */
1816 M.util.audio_players = [];
1820 * @param id element id
1821 * @param fileurl media url
1824 * @param autosize true means detect size from media
1826 M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1827 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
1836 M.util.add_audio_player = function (id, fileurl, small) {
1837 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
1841 * Initialise all audio and video player, must be called from page footer.
1843 M.util.load_flowplayer = function() {
1844 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1847 if (typeof(flowplayer) == 'undefined') {
1850 var embed_function = function() {
1851 if (loaded || typeof(flowplayer) == 'undefined') {
1859 /* TODO: add CSS color overrides for the flv flow player */
1861 for(var i=0; i<M.util.video_players.length; i++) {
1862 var video = M.util.video_players[i];
1863 if (video.width > 0 && video.height > 0) {
1864 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.14.swf', width: video.width, height: video.height};
1866 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.14.swf';
1868 flowplayer(video.id, src, {
1869 plugins: {controls: controls},
1871 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1872 onMetaData: function(clip) {
1873 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1874 clip.mvideo.resized = true;
1875 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1876 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1877 // bad luck, we have to guess - we may not get metadata at all
1878 var width = clip.width;
1879 var height = clip.height;
1881 var width = clip.metaData.width;
1882 var height = clip.metaData.height;
1884 var minwidth = 300; // controls are messed up in smaller objects
1885 if (width < minwidth) {
1886 height = (height * minwidth) / width;
1890 var object = this._api();
1891 object.width = width;
1892 object.height = height;
1898 if (M.util.audio_players.length == 0) {
1911 backgroundGradient: [0.5,0,0.3]
1915 for (var j=0; j < document.styleSheets.length; j++) {
1917 // To avoid javascript security violation accessing cross domain stylesheets
1918 var allrules = false;
1920 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1921 allrules = document.styleSheets[j].rules;
1922 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1923 allrules = document.styleSheets[j].cssRules;
1932 // On cross domain style sheets Chrome V8 allows access to rules but returns null
1937 for(var i=0; i<allrules.length; i++) {
1939 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1940 if (typeof(allrules[i].cssText) != 'undefined') {
1941 rule = allrules[i].cssText;
1942 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1943 rule = allrules[i].style.cssText;
1945 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1946 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1947 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1948 controls[colprop] = rule;
1955 for(i=0; i<M.util.audio_players.length; i++) {
1956 var audio = M.util.audio_players[i];
1958 controls.controlall = false;
1959 controls.height = 15;
1960 controls.time = false;
1962 controls.controlall = true;
1963 controls.height = 25;
1964 controls.time = true;
1966 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.14.swf', {
1967 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.10.swf'}},
1968 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1973 if (M.cfg.jsrev == -1) {
1974 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.11.js';
1976 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.11.min.js&rev=' + M.cfg.jsrev;
1978 var fileref = document.createElement('script');
1979 fileref.setAttribute('type','text/javascript');
1980 fileref.setAttribute('src', jsurl);
1981 fileref.onload = embed_function;
1982 fileref.onreadystatechange = embed_function;
1983 document.getElementsByTagName('head')[0].appendChild(fileref);