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 laoded from YUI.
6 * @param {Array} modules
8 M.yui.add_module = function(modules) {
9 for (var modname in modules) {
10 M.yui.loader.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 if (M.cfg.themerev > 0 && M.cfg.slasharguments == 1) {
42 var url = M.cfg.wwwroot + '/theme/image.php/' + M.cfg.theme + '/' + component + '/' + M.cfg.themerev + '/' + imagename;
44 var url = M.cfg.wwwroot + '/theme/image.php?theme=' + M.cfg.theme + '&component=' + component + '&rev=' + M.cfg.themerev + '&image=' + imagename;
50 M.util.in_array = function(item, array){
51 for( var i = 0; i<array.length; i++){
60 * Init a collapsible region, see print_collapsible_region in weblib.php
61 * @param {YUI} Y YUI3 instance with all libraries loaded
62 * @param {String} id the HTML id for the div.
63 * @param {String} userpref the user preference that records the state of this box. false if none.
64 * @param {String} strtooltip
66 M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
67 Y.use('anim', function(Y) {
68 new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
73 * Object to handle a collapsible region : instantiate and forget styled object
77 * @param {YUI} Y YUI3 instance with all libraries loaded
78 * @param {String} id The HTML id for the div.
79 * @param {String} userpref The user preference that records the state of this box. false if none.
80 * @param {String} strtooltip
82 M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
83 // Record the pref name
84 this.userpref = userpref;
86 // Find the divs in the document.
87 this.div = Y.one('#'+id);
89 // Get the caption for the collapsible region
90 var caption = this.div.one('#'+id + '_caption');
93 var a = Y.Node.create('<a href="#"></a>');
94 a.setAttribute('title', strtooltip);
96 // Get all the nodes from caption, remove them and append them to <a>
97 while (caption.hasChildNodes()) {
98 child = caption.get('firstChild');
104 // Get the height of the div at this point before we shrink it if required
105 var height = this.div.get('offsetHeight');
106 if (this.div.hasClass('collapsed')) {
107 // Add the correct image and record the YUI node created in the process
108 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/collapsed', 'moodle')+'" alt="" />');
109 // Shrink the div as it is collapsed by default
110 this.div.setStyle('height', caption.get('offsetHeight')+'px');
112 // Add the correct image and record the YUI node created in the process
113 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
117 // Create the animation.
118 var animation = new Y.Anim({
121 easing: Y.Easing.easeBoth,
122 to: {height:caption.get('offsetHeight')},
123 from: {height:height}
126 // Handler for the animation finishing.
127 animation.on('end', function() {
128 this.div.toggleClass('collapsed');
129 if (this.div.hasClass('collapsed')) {
130 this.icon.set('src', M.util.image_url('t/collapsed', 'moodle'));
132 this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
136 // Hook up the event handler.
137 a.on('click', function(e, animation) {
139 // Animate to the appropriate size.
140 if (animation.get('running')) {
143 animation.set('reverse', this.div.hasClass('collapsed'));
144 // Update the user preference.
146 M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
153 * The user preference that stores the state of this box.
157 M.util.CollapsibleRegion.prototype.userpref = null;
160 * The key divs that make up this
164 M.util.CollapsibleRegion.prototype.div = null;
167 * The key divs that make up this
171 M.util.CollapsibleRegion.prototype.icon = null;
174 * Makes a best effort to connect back to Moodle to update a user preference,
175 * however, there is no mechanism for finding out if the update succeeded.
177 * Before you can use this function in your JavsScript, you must have called
178 * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
179 * the udpate is allowed, and how to safely clean and submitted values.
181 * @param String name the name of the setting to udpate.
182 * @param String the value to set it to.
184 M.util.set_user_preference = function(name, value) {
185 YUI(M.yui.loader).use('io', function(Y) {
186 var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
187 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
189 // If we are a developer, ensure that failures are reported.
194 if (M.cfg.developerdebug) {
195 cfg.on.failure = function(id, o, args) {
196 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
206 * Prints a confirmation dialog in the style of DOM.confirm().
207 * @param object event A YUI DOM event or null if launched manually
208 * @param string message The message to show in the dialog
209 * @param string url The URL to forward to if YES is clicked. Disabled if fn is given
210 * @param function fn A JS function to run if YES is clicked.
212 M.util.show_confirm_dialog = function(e, args) {
213 var target = e.target;
214 if (e.preventDefault) {
218 YUI(M.yui.loader).use('yui2-container', 'yui2-event', function(Y) {
219 var simpledialog = new YAHOO.widget.SimpleDialog('confirmdialog',
228 simpledialog.setHeader(M.str.admin.confirmation);
229 simpledialog.setBody(args.message);
230 simpledialog.cfg.setProperty('icon', YAHOO.widget.SimpleDialog.ICON_WARN);
232 var handle_cancel = function() {
236 var handle_yes = function() {
240 // args comes from PHP, so callback will be a string, needs to be evaluated by JS
242 if (Y.Lang.isFunction(args.callback)) {
243 callback = args.callback;
245 callback = eval('('+args.callback+')');
248 if (Y.Lang.isObject(args.scope)) {
254 if (args.callbackargs) {
255 callback.apply(sc, args.callbackargs);
262 var targetancestor = null,
265 if (target.test('a')) {
266 window.location = target.get('href');
268 } else if ((targetancestor = target.ancestor('a')) !== null) {
269 window.location = targetancestor.get('href');
271 } else if (target.test('input')) {
272 targetform = target.ancestor(function(node) { return node.get('tagName').toLowerCase() == 'form'; });
273 // We cannot use target.ancestor('form') on the previous line
274 // because of http://yuilibrary.com/projects/yui3/ticket/2531561
278 if (target.get('name') && target.get('value')) {
279 targetform.append('<input type="hidden" name="' + target.get('name') +
280 '" value="' + target.get('value') + '">');
284 } else if (target.get('tagName').toLowerCase() == 'form') {
285 // We cannot use target.test('form') on the previous line because of
286 // http://yuilibrary.com/projects/yui3/ticket/2531561
289 } else if (M.cfg.developerdebug) {
290 alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A, INPUT, or FORM");
294 if (!args.cancellabel) {
295 args.cancellabel = M.str.moodle.cancel;
297 if (!args.continuelabel) {
298 args.continuelabel = M.str.moodle.yes;
302 {text: args.cancellabel, handler: handle_cancel, isDefault: true},
303 {text: args.continuelabel, handler: handle_yes}
306 simpledialog.cfg.queueProperty('buttons', buttons);
308 simpledialog.render(document.body);
313 /** Useful for full embedding of various stuff */
314 M.util.init_maximised_embed = function(Y, id) {
315 var obj = Y.one('#'+id);
320 var get_htmlelement_size = function(el, prop) {
321 if (Y.Lang.isString(el)) {
322 el = Y.one('#' + el);
324 var val = el.getStyle(prop);
326 val = el.getComputedStyle(prop);
328 return parseInt(val);
331 var resize_object = function() {
332 obj.setStyle('width', '0px');
333 obj.setStyle('height', '0px');
334 var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
336 if (newwidth > 500) {
337 obj.setStyle('width', newwidth + 'px');
339 obj.setStyle('width', '500px');
342 var headerheight = get_htmlelement_size('page-header', 'height');
343 var footerheight = get_htmlelement_size('page-footer', 'height');
344 var newheight = parseInt(YAHOO.util.Dom.getViewportHeight()) - footerheight - headerheight - 100;
345 if (newheight < 400) {
348 obj.setStyle('height', newheight+'px');
352 // fix layout if window resized too
353 window.onresize = function() {
359 * Attach handler to single_select
361 M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
362 Y.use('event-key', function() {
363 var select = Y.one('#'+selectid);
365 // Try to get the form by id
366 var form = Y.one('#'+formid) || (function(){
367 // Hmmm the form's id may have been overriden by an internal input
368 // with the name id which will KILL IE.
369 // We need to manually iterate at this point because if the case
370 // above is true YUI's ancestor method will also kill IE!
372 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
373 form = form.ancestor();
377 // Make sure we have the form
379 // Create a function to handle our change event
380 var processchange = function(e, paramobject) {
381 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
382 //prevent event bubbling and detach handlers to prevent multiple submissions caused by double clicking
384 paramobject.eventkeypress.detach();
385 paramobject.eventblur.detach();
386 paramobject.eventchangeorblur.detach();
391 // Attach the change event to the keypress, blur, and click actions.
392 // We don't use the change event because IE fires it on every arrow up/down
393 // event.... usability
394 var paramobject = new Object();
395 paramobject.lastindex = select.get('selectedIndex');
396 paramobject.eventkeypress = Y.on('key', processchange, select, 'press:13', form, paramobject);
397 paramobject.eventblur = select.on('blur', processchange, form, paramobject);
398 //little hack for chrome that need onChange event instead of onClick - see MDL-23224
400 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
402 paramobject.eventchangeorblur = select.on('click', processchange, form, paramobject);
410 * Attach handler to url_select
412 M.util.init_url_select = function(Y, formid, selectid, nothing) {
413 YUI(M.yui.loader).use('node', function(Y) {
414 Y.on('change', function() {
415 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
416 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
424 * Breaks out all links to the top frame - used in frametop page layout.
426 M.util.init_frametop = function(Y) {
427 Y.all('a').each(function(node) {
428 node.set('target', '_top');
430 Y.all('form').each(function(node) {
431 node.set('target', '_top');
436 * Finds all nodes that match the given CSS selector and attaches events to them
437 * so that they toggle a given classname when clicked.
440 * @param {string} id An id containing elements to target
441 * @param {string} cssselector A selector to use to find targets
442 * @param {string} toggleclassname A classname to toggle
444 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
446 if (togglecssselector == '') {
447 togglecssselector = cssselector;
450 var node = Y.one('#'+id);
451 node.all(cssselector).each(function(n){
452 n.on('click', function(e){
454 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
455 if (this.test(togglecssselector)) {
456 this.toggleClass(toggleclassname);
458 this.ancestor(togglecssselector).toggleClass(toggleclassname);
463 // Attach this click event to the node rather than all selectors... will be much better
465 node.on('click', function(e){
466 if (e.target.hasClass('addtoall')) {
467 this.all(togglecssselector).addClass(toggleclassname);
468 } else if (e.target.hasClass('removefromall')) {
469 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
475 * Initialises a colour picker
477 * Designed to be used with admin_setting_configcolourpicker although could be used
478 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
479 * above or below the input (must have the same parent) and then call this with the
482 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
483 * contrib/blocks. For better docs refer to that.
487 * @param {object} previewconf
489 M.util.init_colour_picker = function(Y, id, previewconf) {
491 * We need node and event-mouseenter
493 Y.use('node', 'event-mouseenter', function(){
495 * The colour picker object
504 eventMouseEnter : null,
505 eventMouseLeave : null,
506 eventMouseMove : null,
511 * Initalises the colour picker by putting everything together and wiring the events
514 this.input = Y.one('#'+id);
515 this.box = this.input.ancestor().one('.admin_colourpicker');
516 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
517 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
518 this.preview = Y.Node.create('<div class="previewcolour"></div>');
519 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
520 this.current = Y.Node.create('<div class="currentcolour"></div>');
521 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
522 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
524 if (typeof(previewconf) === 'object' && previewconf !== null) {
525 Y.one('#'+id+'_preview').on('click', function(e){
526 if (Y.Lang.isString(previewconf.selector)) {
527 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
529 for (var i in previewconf.selector) {
530 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
536 this.eventClick = this.image.on('click', this.pickColour, this);
537 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
540 * Starts to follow the mouse once it enter the image
542 startFollow : function(e) {
543 this.eventMouseEnter.detach();
544 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
545 this.eventMouseMove = this.image.on('mousemove', function(e){
546 this.preview.setStyle('backgroundColor', this.determineColour(e));
550 * Stops following the mouse
552 endFollow : function(e) {
553 this.eventMouseMove.detach();
554 this.eventMouseLeave.detach();
555 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
558 * Picks the colour the was clicked on
560 pickColour : function(e) {
561 var colour = this.determineColour(e);
562 this.input.set('value', colour);
563 this.current.setStyle('backgroundColor', colour);
566 * Calculates the colour fromthe given co-ordinates
568 determineColour : function(e) {
569 var eventx = Math.floor(e.pageX-e.target.getX());
570 var eventy = Math.floor(e.pageY-e.target.getY());
572 var imagewidth = this.width;
573 var imageheight = this.height;
574 var factor = this.factor;
575 var colour = [255,0,0];
586 var matrixcount = matrices.length;
587 var limit = Math.round(imagewidth/matrixcount);
588 var heightbreak = Math.round(imageheight/2);
590 for (var x = 0; x < imagewidth; x++) {
591 var divisor = Math.floor(x / limit);
592 var matrix = matrices[divisor];
594 colour[0] += matrix[0]*factor;
595 colour[1] += matrix[1]*factor;
596 colour[2] += matrix[2]*factor;
603 var pixel = [colour[0], colour[1], colour[2]];
604 if (eventy < heightbreak) {
605 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
606 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
607 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
608 } else if (eventy > heightbreak) {
609 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
610 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
611 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
614 return this.convert_rgb_to_hex(pixel);
617 * Converts an RGB value to Hex
619 convert_rgb_to_hex : function(rgb) {
621 var hexchars = "0123456789ABCDEF";
622 for (var i=0; i<3; i++) {
623 var number = Math.abs(rgb[i]);
624 if (number == 0 || isNaN(number)) {
627 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
634 * Initialise the colour picker :) Hoorah
640 M.util.init_block_hider = function(Y, config) {
641 Y.use('base', 'node', function(Y) {
642 M.util.block_hider = M.util.block_hider || (function(){
643 var blockhider = function() {
644 blockhider.superclass.constructor.apply(this, arguments);
646 blockhider.prototype = {
647 initializer : function(config) {
648 this.set('block', '#'+this.get('id'));
649 var b = this.get('block'),
652 if (t && (a = t.one('.block_action'))) {
653 var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
654 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
655 hide.on('keypress', this.updateStateKey, this, true);
656 var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
657 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
658 show.on('keypress', this.updateStateKey, this, false);
659 a.insert(show, 0).insert(hide, 0);
662 updateState : function(e, hide) {
663 M.util.set_user_preference(this.get('preference'), hide);
665 this.get('block').addClass('hidden');
667 this.get('block').removeClass('hidden');
670 updateStateKey : function(e, hide) {
671 if (e.keyCode == 13) { //allow hide/show via enter key
672 this.updateState(this, hide);
676 Y.extend(blockhider, Y.Base, blockhider.prototype, {
682 value : M.util.image_url('t/switch_minus', 'moodle')
685 value : M.util.image_url('t/switch_plus', 'moodle')
688 setter : function(node) {
696 new M.util.block_hider(config);
701 * Returns a string registered in advance for usage in JavaScript
703 * If you do not pass the third parameter, the function will just return
704 * the corresponding value from the M.str object. If the third parameter is
705 * provided, the function performs {$a} placeholder substitution in the
706 * same way as PHP get_string() in Moodle does.
708 * @param {String} identifier string identifier
709 * @param {String} component the component providing the string
710 * @param {Object|String} a optional variable to populate placeholder with
712 M.util.get_string = function(identifier, component, a) {
715 if (M.cfg.developerdebug) {
716 // creating new instance if YUI is not optimal but it seems to be better way then
717 // require the instance via the function API - note that it is used in rare cases
718 // for debugging only anyway
719 // To ensure we don't kill browser performance if hundreds of get_string requests
720 // are made we cache the instance we generate within the M.util namespace.
721 // We don't publicly define the variable so that it doesn't get abused.
722 if (typeof M.util.get_string_yui_instance === 'undefined') {
723 M.util.get_string_yui_instance = new YUI({ debug : true });
725 var Y = M.util.get_string_yui_instance;
728 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
729 stringvalue = '[[' + identifier + ',' + component + ']]';
730 if (M.cfg.developerdebug) {
731 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
736 stringvalue = M.str[component][identifier];
738 if (typeof a == 'undefined') {
739 // no placeholder substitution requested
743 if (typeof a == 'number' || typeof a == 'string') {
744 // replace all occurrences of {$a} with the placeholder value
745 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
749 if (typeof a == 'object') {
750 // replace {$a->key} placeholders
752 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
753 if (M.cfg.developerdebug) {
754 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
758 var search = '{$a->' + key + '}';
759 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
760 search = new RegExp(search, 'g');
761 stringvalue = stringvalue.replace(search, a[key]);
766 if (M.cfg.developerdebug) {
767 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
773 * Set focus on username or password field of the login form
775 M.util.focus_login_form = function(Y) {
776 var username = Y.one('#username');
777 var password = Y.one('#password');
779 if (username == null || password == null) {
780 // something is wrong here
784 var curElement = document.activeElement
785 if (curElement == 'undefined') {
786 // legacy browser - skip refocus protection
787 } else if (curElement.tagName == 'INPUT') {
788 // user was probably faster to focus something, do not mess with focus
792 if (username.get('value') == '') {
800 * Adds lightbox hidden element that covers the whole node.
803 * @param {Node} the node lightbox should be added to
804 * @retun {Node} created lightbox node
806 M.util.add_lightbox = function(Y, node) {
807 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
809 // Check if lightbox is already there
810 if (node.one('.lightbox')) {
811 return node.one('.lightbox');
814 node.setStyle('position', 'relative');
815 var waiticon = Y.Node.create('<img />')
817 'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
820 'position' : 'relative',
824 var lightbox = Y.Node.create('<div></div>')
827 'position' : 'absolute',
832 'backgroundColor' : 'white',
833 'text-align' : 'center'
835 .setAttribute('class', 'lightbox')
838 lightbox.appendChild(waiticon);
839 node.append(lightbox);
844 * Appends a hidden spinner element to the specified node.
847 * @param {Node} the node the spinner should be added to
848 * @return {Node} created spinner node
850 M.util.add_spinner = function(Y, node) {
851 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
853 // Check if spinner is already there
854 if (node.one('.spinner')) {
855 return node.one('.spinner');
858 var spinner = Y.Node.create('<img />')
859 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
861 .addClass('iconsmall')
864 node.append(spinner);
868 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
870 function checkall() {
871 var inputs = document.getElementsByTagName('input');
872 for (var i = 0; i < inputs.length; i++) {
873 if (inputs[i].type == 'checkbox') {
874 if (inputs[i].disabled || inputs[i].readOnly) {
877 inputs[i].checked = true;
882 function checknone() {
883 var inputs = document.getElementsByTagName('input');
884 for (var i = 0; i < inputs.length; i++) {
885 if (inputs[i].type == 'checkbox') {
886 if (inputs[i].disabled || inputs[i].readOnly) {
889 inputs[i].checked = false;
895 * Either check, or uncheck, all checkboxes inside the element with id is
896 * @param id the id of the container
897 * @param checked the new state, either '' or 'checked'.
899 function select_all_in_element_with_id(id, checked) {
900 var container = document.getElementById(id);
904 var inputs = container.getElementsByTagName('input');
905 for (var i = 0; i < inputs.length; ++i) {
906 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
907 inputs[i].checked = checked;
912 function select_all_in(elTagName, elClass, elId) {
913 var inputs = document.getElementsByTagName('input');
914 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
915 for(var i = 0; i < inputs.length; ++i) {
916 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
917 inputs[i].checked = 'checked';
922 function deselect_all_in(elTagName, elClass, elId) {
923 var inputs = document.getElementsByTagName('INPUT');
924 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
925 for(var i = 0; i < inputs.length; ++i) {
926 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
927 inputs[i].checked = '';
932 function confirm_if(expr, message) {
936 return confirm(message);
941 findParentNode (start, elementName, elementClass, elementID)
943 Travels up the DOM hierarchy to find a parent element with the
944 specified tag name, class, and id. All conditions must be met,
945 but any can be ommitted. Returns the BODY element if no match
948 function findParentNode(el, elName, elClass, elId) {
949 while (el.nodeName.toUpperCase() != 'BODY') {
950 if ((!elName || el.nodeName.toUpperCase() == elName) &&
951 (!elClass || el.className.indexOf(elClass) != -1) &&
952 (!elId || el.id == elId)) {
960 findChildNode (start, elementName, elementClass, elementID)
962 Travels down the DOM hierarchy to find all child elements with the
963 specified tag name, class, and id. All conditions must be met,
964 but any can be ommitted.
965 Doesn't examine children of matches.
967 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
968 var children = new Array();
969 for (var i = 0; i < start.childNodes.length; i++) {
970 var classfound = false;
971 var child = start.childNodes[i];
972 if((child.nodeType == 1) &&//element node type
973 (elementClass && (typeof(child.className)=='string'))) {
974 var childClasses = child.className.split(/\s+/);
975 for (var childClassIndex in childClasses) {
976 if (childClasses[childClassIndex]==elementClass) {
982 if(child.nodeType == 1) { //element node type
983 if ( (!tagName || child.nodeName == tagName) &&
984 (!elementClass || classfound)&&
985 (!elementID || child.id == elementID) &&
986 (!elementName || child.name == elementName))
988 children = children.concat(child);
990 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
997 function unmaskPassword(id) {
998 var pw = document.getElementById(id);
999 var chb = document.getElementById(id+'unmask');
1002 // first try IE way - it can not set name attribute later
1004 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
1006 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
1008 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
1010 var newpw = document.createElement('input');
1011 newpw.setAttribute('autocomplete', 'off');
1012 newpw.setAttribute('name', pw.name);
1014 newpw.setAttribute('type', 'text');
1016 newpw.setAttribute('type', 'password');
1018 newpw.setAttribute('class', pw.getAttribute('class'));
1021 newpw.size = pw.size;
1022 newpw.onblur = pw.onblur;
1023 newpw.onchange = pw.onchange;
1024 newpw.value = pw.value;
1025 pw.parentNode.replaceChild(newpw, pw);
1028 function filterByParent(elCollection, parentFinder) {
1029 var filteredCollection = [];
1030 for (var i = 0; i < elCollection.length; ++i) {
1031 var findParent = parentFinder(elCollection[i]);
1032 if (findParent.nodeName.toUpperCase() != 'BODY') {
1033 filteredCollection.push(elCollection[i]);
1036 return filteredCollection;
1040 All this is here just so that IE gets to handle oversized blocks
1041 in a visually pleasing manner. It does a browser detect. So sue me.
1044 function fix_column_widths() {
1045 var agt = navigator.userAgent.toLowerCase();
1046 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1047 fix_column_width('left-column');
1048 fix_column_width('right-column');
1052 function fix_column_width(colName) {
1053 if(column = document.getElementById(colName)) {
1054 if(!column.offsetWidth) {
1055 setTimeout("fix_column_width('" + colName + "')", 20);
1060 var nodes = column.childNodes;
1062 for(i = 0; i < nodes.length; ++i) {
1063 if(nodes[i].className.indexOf("block") != -1 ) {
1064 if(width < nodes[i].offsetWidth) {
1065 width = nodes[i].offsetWidth;
1070 for(i = 0; i < nodes.length; ++i) {
1071 if(nodes[i].className.indexOf("block") != -1 ) {
1072 nodes[i].style.width = width + 'px';
1080 Insert myValue at current cursor position
1082 function insertAtCursor(myField, myValue) {
1084 if (document.selection) {
1086 sel = document.selection.createRange();
1089 // Mozilla/Netscape support
1090 else if (myField.selectionStart || myField.selectionStart == '0') {
1091 var startPos = myField.selectionStart;
1092 var endPos = myField.selectionEnd;
1093 myField.value = myField.value.substring(0, startPos)
1094 + myValue + myField.value.substring(endPos, myField.value.length);
1096 myField.value += myValue;
1102 Call instead of setting window.onload directly or setting body onload=.
1103 Adds your function to a chain of functions rather than overwriting anything
1106 function addonload(fn) {
1107 var oldhandler=window.onload;
1108 window.onload=function() {
1109 if(oldhandler) oldhandler();
1114 * Replacement for getElementsByClassName in browsers that aren't cool enough
1116 * Relying on the built-in getElementsByClassName is far, far faster than
1119 * Note: the third argument used to be an object with odd behaviour. It now
1120 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1121 * mimicked if you pass an object.
1123 * @param {Node} oElm The top-level node for searching. To search a whole
1124 * document, use `document`.
1125 * @param {String} strTagName filter by tag names
1126 * @param {String} name same as HTML5 spec
1128 function getElementsByClassName(oElm, strTagName, name) {
1129 // for backwards compatibility
1130 if(typeof name == "object") {
1131 var names = new Array();
1132 for(var i=0; i<name.length; i++) names.push(names[i]);
1133 name = names.join('');
1135 // use native implementation if possible
1136 if (oElm.getElementsByClassName && Array.filter) {
1137 if (strTagName == '*') {
1138 return oElm.getElementsByClassName(name);
1140 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1141 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1145 // native implementation unavailable, fall back to slow method
1146 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1147 var arrReturnElements = new Array();
1148 var arrRegExpClassNames = new Array();
1149 var names = name.split(' ');
1150 for(var i=0; i<names.length; i++) {
1151 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1155 for(var j=0; j<arrElements.length; j++) {
1156 oElement = arrElements[j];
1158 for(var k=0; k<arrRegExpClassNames.length; k++) {
1159 if(!arrRegExpClassNames[k].test(oElement.className)) {
1160 bMatchesAll = false;
1165 arrReturnElements.push(oElement);
1168 return (arrReturnElements)
1171 function openpopup(event, args) {
1174 if (event.preventDefault) {
1175 event.preventDefault();
1177 event.returnValue = false;
1181 var fullurl = args.url;
1182 if (!args.url.match(/https?:\/\//)) {
1183 fullurl = M.cfg.wwwroot + args.url;
1185 if (args.fullscreen) {
1186 args.options = args.options.
1187 replace(/top=\d+/, 'top=0').
1188 replace(/left=\d+/, 'left=0').
1189 replace(/width=\d+/, 'width=' + screen.availWidth).
1190 replace(/height=\d+/, 'height=' + screen.availHeight);
1192 var windowobj = window.open(fullurl,args.name,args.options);
1197 if (args.fullscreen) {
1198 // In some browser / OS combinations (E.g. Chrome on Windows), the
1199 // window initially opens slighly too big. The width and heigh options
1200 // seem to control the area inside the browser window, so what with
1201 // scroll-bars, etc. the actual window is bigger than the screen.
1202 // Therefore, we need to fix things up after the window is open.
1203 var hackcount = 100;
1204 var get_size_exactly_right = function() {
1205 windowobj.moveTo(0, 0);
1206 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1208 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1209 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1210 // about 50ms) after the window is open, then it actually behaves
1211 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1212 // check that the resize actually worked, and if not, repeatedly try
1213 // again after a short delay until it works (but with a limit of
1214 // hackcount repeats.
1215 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1217 setTimeout(get_size_exactly_right, 10);
1220 setTimeout(get_size_exactly_right, 0);
1227 /** Close the current browser window. */
1228 function close_window(e) {
1229 if (e.preventDefault) {
1232 e.returnValue = false;
1238 * Used in a couple of modules to hide navigation areas when using AJAX
1241 function show_item(itemid) {
1242 var item = document.getElementById(itemid);
1244 item.style.display = "";
1248 function destroy_item(itemid) {
1249 var item = document.getElementById(itemid);
1251 item.parentNode.removeChild(item);
1255 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1256 * @param controlid the control id.
1258 function focuscontrol(controlid) {
1259 var control = document.getElementById(controlid);
1266 * Transfers keyboard focus to an HTML element based on the old style style of focus
1267 * This function should be removed as soon as it is no longer used
1269 function old_onload_focus(formid, controlname) {
1270 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1271 document.forms[formid].elements[controlname].focus();
1275 function build_querystring(obj) {
1276 return convert_object_to_string(obj, '&');
1279 function build_windowoptionsstring(obj) {
1280 return convert_object_to_string(obj, ',');
1283 function convert_object_to_string(obj, separator) {
1284 if (typeof obj !== 'object') {
1289 k = encodeURIComponent(k);
1291 if(obj[k] instanceof Array) {
1292 for(var i in value) {
1293 list.push(k+'[]='+encodeURIComponent(value[i]));
1296 list.push(k+'='+encodeURIComponent(value));
1299 return list.join(separator);
1302 function stripHTML(str) {
1303 var re = /<\S[^><]*>/g;
1304 var ret = str.replace(re, "");
1308 Number.prototype.fixed=function(n){
1310 return round(Number(this)*pow(10,n))/pow(10,n);
1312 function update_progress_bar (id, width, pt, msg, es){
1314 var status = document.getElementById("status_"+id);
1315 var percent_indicator = document.getElementById("pt_"+id);
1316 var progress_bar = document.getElementById("progress_"+id);
1317 var time_es = document.getElementById("time_"+id);
1318 status.innerHTML = msg;
1319 percent_indicator.innerHTML = percent.fixed(2) + '%';
1320 if(percent == 100) {
1321 progress_bar.style.background = "green";
1322 time_es.style.display = "none";
1324 progress_bar.style.background = "#FFCC66";
1326 time_es.innerHTML = "";
1328 time_es.innerHTML = es.fixed(2)+" sec";
1329 time_es.style.display
1333 progress_bar.style.width = width + "px";
1338 // ===== Deprecated core Javascript functions for Moodle ====
1339 // DO NOT USE!!!!!!!
1340 // Do not put this stuff in separate file because it only adds extra load on servers!
1343 * Used in a couple of modules to hide navigation areas when using AJAX
1345 function hide_item(itemid) {
1346 // use class='hiddenifjs' instead
1347 var item = document.getElementById(itemid);
1349 item.style.display = "none";
1353 M.util.help_icon = {
1356 add : function(Y, properties) {
1358 properties.node = Y.one('#'+properties.id);
1359 if (properties.node) {
1360 properties.node.on('click', this.display, this, properties);
1363 display : function(event, args) {
1364 event.preventDefault();
1365 if (M.util.help_icon.instance === null) {
1366 var Y = M.util.help_icon.Y;
1367 Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', function(Y) {
1368 var help_content_overlay = {
1373 var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
1374 // Create an overlay from markup
1375 this.overlay = new Y.Overlay({
1376 headerContent: closebtn,
1383 this.overlay.render(Y.one(document.body));
1385 closebtn.on('click', this.overlay.hide, this.overlay);
1387 var boundingBox = this.overlay.get("boundingBox");
1389 // Hide the menu if the user clicks outside of its content
1390 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1391 var oTarget = event.target;
1392 var menuButton = Y.one("#"+args.id);
1394 if (!oTarget.compareTo(menuButton) &&
1395 !menuButton.contains(oTarget) &&
1396 !oTarget.compareTo(boundingBox) &&
1397 !boundingBox.contains(oTarget)) {
1398 this.overlay.hide();
1402 Y.on("key", this.close, closebtn , "down:13", this);
1403 closebtn.on('click', this.close, this);
1406 close : function(e) {
1408 this.helplink.focus();
1409 this.overlay.hide();
1412 display : function(event, args) {
1413 this.helplink = args.node;
1414 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1415 this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
1417 var fullurl = args.url;
1418 if (!args.url.match(/https?:\/\//)) {
1419 fullurl = M.cfg.wwwroot + args.url;
1422 var ajaxurl = fullurl + '&ajax=1';
1428 success: function(id, o, node) {
1429 this.display_callback(o.responseText);
1431 failure: function(id, o, node) {
1432 var debuginfo = o.statusText;
1433 if (M.cfg.developerdebug) {
1434 o.statusText += ' (' + ajaxurl + ')';
1436 this.display_callback('bodyContent',debuginfo);
1442 this.overlay.show();
1444 Y.one('#closehelpbox').focus();
1447 display_callback : function(content) {
1448 this.overlay.set('bodyContent', content);
1451 hideContent : function() {
1453 help.overlay.hide();
1456 help_content_overlay.init();
1457 M.util.help_icon.instance = help_content_overlay;
1458 M.util.help_icon.instance.display(event, args);
1461 M.util.help_icon.instance.display(event, args);
1464 init : function(Y) {
1470 * Custom menu namespace
1472 M.core_custom_menu = {
1474 * This method is used to initialise a custom menu given the id that belongs
1475 * to the custom menu's root node.
1478 * @param {string} nodeid
1480 init : function(Y, nodeid) {
1481 var node = Y.one('#'+nodeid);
1483 Y.use('node-menunav', function(Y) {
1485 // Remove the javascript-disabled class.... obviously javascript is enabled.
1486 node.removeClass('javascript-disabled');
1487 // Initialise the menunav plugin
1488 node.plug(Y.Plugin.NodeMenuNav);
1495 * Used to store form manipulation methods and enhancments
1497 M.form = M.form || {};
1500 * Converts a nbsp indented select box into a multi drop down custom control much
1501 * like the custom menu. It also selectable categories on or off.
1503 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1506 * @param {string} id
1507 * @param {Array} options
1509 M.form.init_smartselect = function(Y, id, options) {
1510 if (!id.match(/^id_/)) {
1513 var select = Y.one('select#'+id);
1517 Y.use('event-delegate',function(){
1523 currentvalue : null,
1527 selectablecategories : true,
1535 init : function(Y, id, args, nodes) {
1536 if (typeof(args)=='object') {
1537 for (var i in this.cfg) {
1538 if (args[i] || args[i]===false) {
1539 this.cfg[i] = args[i];
1544 // Display a loading message first up
1545 this.nodes.select = nodes.select;
1547 this.currentvalue = this.nodes.select.get('selectedIndex');
1548 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1550 var options = Array();
1551 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1552 this.nodes.select.all('option').each(function(option, index) {
1553 var rawtext = option.get('innerHTML');
1554 var text = rawtext.replace(/^( )*/, '');
1555 if (rawtext === text) {
1556 text = rawtext.replace(/^(\s)*/, '');
1557 var depth = (rawtext.length - text.length ) + 1;
1559 var depth = ((rawtext.length - text.length )/12)+1;
1561 option.set('innerHTML', text);
1562 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1565 this.structure = [];
1566 var structcount = 0;
1567 for (var i in options) {
1570 this.structure.push(o);
1574 var current = this.structure[structcount-1];
1575 for (var j = 0; j < o.depth-1;j++) {
1576 if (current && current.children) {
1577 current = current.children[current.children.length-1];
1580 if (current && current.children) {
1581 current.children.push(o);
1586 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1587 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1588 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1589 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1591 if (this.cfg.mode == null) {
1592 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1593 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1594 this.cfg.mode = 'compact';
1596 this.cfg.mode = 'spanning';
1600 if (this.cfg.mode == 'compact') {
1601 this.nodes.menu.addClass('compactmenu');
1603 this.nodes.menu.addClass('spanningmenu');
1604 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1607 Y.one(document.body).append(this.nodes.menu);
1608 var pos = this.nodes.select.getXY();
1610 this.nodes.menu.setXY(pos);
1611 this.nodes.menu.on('click', this.handle_click, this);
1613 Y.one(window).on('resize', function(){
1614 var pos = this.nodes.select.getXY();
1616 this.nodes.menu.setXY(pos);
1619 generate_menu_content : function() {
1620 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1621 content += this.generate_submenu_content(this.structure[0], true);
1622 content += '</ul></div>';
1625 generate_submenu_content : function(item, rootelement) {
1626 this.submenucount++;
1628 if (item.children.length > 0) {
1630 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'"> </div>';
1631 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1632 content += '<div class="smartselect_menu_content">';
1634 content += '<li class="smartselect_submenuitem">';
1635 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1636 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1637 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1638 content += '<div class="smartselect_submenu_content">';
1641 for (var i in item.children) {
1642 content += this.generate_submenu_content(item.children[i],false);
1645 content += '</div>';
1646 content += '</div>';
1652 content += '<li class="smartselect_menuitem">';
1653 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1658 select : function(e) {
1661 this.currenttext = t.get('innerHTML');
1662 this.currentvalue = t.getAttribute('value');
1663 this.nodes.select.set('selectedIndex', this.currentvalue);
1666 handle_click : function(e) {
1667 var target = e.target;
1668 if (target.hasClass('smartselect_mask')) {
1670 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1672 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1673 this.show_sub_menu(e);
1676 show_menu : function(e) {
1678 var menu = e.target.ancestor().one('.smartselect_menu');
1679 menu.addClass('visible');
1680 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1682 show_sub_menu : function(e) {
1684 var target = e.target;
1685 if (!target.hasClass('smartselect_submenuitem')) {
1686 target = target.ancestor('.smartselect_submenuitem');
1688 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1689 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1692 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1693 target.one('.smartselect_submenu').addClass('visible');
1695 hide_menu : function() {
1696 this.nodes.menu.all('.visible').removeClass('visible');
1697 if (this.shownevent) {
1698 this.shownevent.detach();
1702 smartselect.init(Y, id, options, {select:select});
1706 /** List of flv players to be loaded */
1707 M.util.video_players = [];
1708 /** List of mp3 players to be loaded */
1709 M.util.audio_players = [];
1713 * @param id element id
1714 * @param fileurl media url
1717 * @param autosize true means detect size from media
1719 M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1720 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
1729 M.util.add_audio_player = function (id, fileurl, small) {
1730 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
1734 * Initialise all audio and video player, must be called from page footer.
1736 M.util.load_flowplayer = function() {
1737 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1740 if (typeof(flowplayer) == 'undefined') {
1743 var embed_function = function() {
1744 if (loaded || typeof(flowplayer) == 'undefined') {
1752 /* TODO: add CSS color overrides for the flv flow player */
1754 for(var i=0; i<M.util.video_players.length; i++) {
1755 var video = M.util.video_players[i];
1756 if (video.width > 0 && video.height > 0) {
1757 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.9.swf', width: video.width, height: video.height};
1759 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.9.swf';
1761 flowplayer(video.id, src, {
1762 plugins: {controls: controls},
1764 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1765 onMetaData: function(clip) {
1766 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1767 clip.mvideo.resized = true;
1768 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1769 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1770 // bad luck, we have to guess - we may not get metadata at all
1771 var width = clip.width;
1772 var height = clip.height;
1774 var width = clip.metaData.width;
1775 var height = clip.metaData.height;
1777 var minwidth = 300; // controls are messed up in smaller objects
1778 if (width < minwidth) {
1779 height = (height * minwidth) / width;
1783 var object = this._api();
1784 object.width = width;
1785 object.height = height;
1791 if (M.util.audio_players.length == 0) {
1804 backgroundGradient: [0.5,0,0.3]
1808 for (var j=0; j < document.styleSheets.length; j++) {
1810 // To avoid javascript security violation accessing cross domain stylesheets
1811 var allrules = false;
1813 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1814 allrules = document.styleSheets[j].rules;
1815 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1816 allrules = document.styleSheets[j].cssRules;
1825 // On cross domain style sheets Chrome V8 allows access to rules but returns null
1830 for(var i=0; i<allrules.length; i++) {
1832 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1833 if (typeof(allrules[i].cssText) != 'undefined') {
1834 rule = allrules[i].cssText;
1835 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1836 rule = allrules[i].style.cssText;
1838 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1839 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1840 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1841 controls[colprop] = rule;
1848 for(i=0; i<M.util.audio_players.length; i++) {
1849 var audio = M.util.audio_players[i];
1851 controls.controlall = false;
1852 controls.height = 15;
1853 controls.time = false;
1855 controls.controlall = true;
1856 controls.height = 25;
1857 controls.time = true;
1859 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.9.swf', {
1860 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.8.swf'}},
1861 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1866 if (M.cfg.jsrev == -10) {
1867 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.8.min.js';
1869 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.8.min.js&rev=' + M.cfg.jsrev;
1871 var fileref = document.createElement('script');
1872 fileref.setAttribute('type','text/javascript');
1873 fileref.setAttribute('src', jsurl);
1874 fileref.onload = embed_function;
1875 fileref.onreadystatechange = embed_function;
1876 document.getElementsByTagName('head')[0].appendChild(fileref);