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 var collapsedimage = 't/collapsed'; // ltr mode
107 if ( Y.one(document.body).hasClass('dir-rtl') ) {
108 collapsedimage = 't/collapsed_rtl';
110 collapsedimage = 't/collapsed';
112 if (this.div.hasClass('collapsed')) {
113 // Add the correct image and record the YUI node created in the process
114 this.icon = Y.Node.create('<img src="'+M.util.image_url(collapsedimage, 'moodle')+'" alt="" />');
115 // Shrink the div as it is collapsed by default
116 this.div.setStyle('height', caption.get('offsetHeight')+'px');
118 // Add the correct image and record the YUI node created in the process
119 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
123 // Create the animation.
124 var animation = new Y.Anim({
127 easing: Y.Easing.easeBoth,
128 to: {height:caption.get('offsetHeight')},
129 from: {height:height}
132 // Handler for the animation finishing.
133 animation.on('end', function() {
134 this.div.toggleClass('collapsed');
135 var collapsedimage = 't/collapsed'; // ltr mode
136 if ( Y.one(document.body).hasClass('dir-rtl') ) {
137 collapsedimage = 't/collapsed_rtl';
139 collapsedimage = 't/collapsed';
141 if (this.div.hasClass('collapsed')) {
142 this.icon.set('src', M.util.image_url(collapsedimage, 'moodle'));
144 this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
148 // Hook up the event handler.
149 a.on('click', function(e, animation) {
151 // Animate to the appropriate size.
152 if (animation.get('running')) {
155 animation.set('reverse', this.div.hasClass('collapsed'));
156 // Update the user preference.
158 M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
165 * The user preference that stores the state of this box.
169 M.util.CollapsibleRegion.prototype.userpref = null;
172 * The key divs that make up this
176 M.util.CollapsibleRegion.prototype.div = null;
179 * The key divs that make up this
183 M.util.CollapsibleRegion.prototype.icon = null;
186 * Makes a best effort to connect back to Moodle to update a user preference,
187 * however, there is no mechanism for finding out if the update succeeded.
189 * Before you can use this function in your JavsScript, you must have called
190 * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
191 * the udpate is allowed, and how to safely clean and submitted values.
193 * @param String name the name of the setting to udpate.
194 * @param String the value to set it to.
196 M.util.set_user_preference = function(name, value) {
197 YUI(M.yui.loader).use('io', function(Y) {
198 var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
199 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
201 // If we are a developer, ensure that failures are reported.
206 if (M.cfg.developerdebug) {
207 cfg.on.failure = function(id, o, args) {
208 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
218 * Prints a confirmation dialog in the style of DOM.confirm().
219 * @param object event A YUI DOM event or null if launched manually
220 * @param string message The message to show in the dialog
221 * @param string url The URL to forward to if YES is clicked. Disabled if fn is given
222 * @param function fn A JS function to run if YES is clicked.
224 M.util.show_confirm_dialog = function(e, args) {
225 var target = e.target;
226 if (e.preventDefault) {
230 YUI(M.yui.loader).use('yui2-container', 'yui2-event', function(Y) {
231 var simpledialog = new YAHOO.widget.SimpleDialog('confirmdialog',
240 simpledialog.setHeader(M.str.admin.confirmation);
241 simpledialog.setBody(args.message);
242 simpledialog.cfg.setProperty('icon', YAHOO.widget.SimpleDialog.ICON_WARN);
244 var handle_cancel = function() {
248 var handle_yes = function() {
252 // args comes from PHP, so callback will be a string, needs to be evaluated by JS
254 if (Y.Lang.isFunction(args.callback)) {
255 callback = args.callback;
257 callback = eval('('+args.callback+')');
260 if (Y.Lang.isObject(args.scope)) {
266 if (args.callbackargs) {
267 callback.apply(sc, args.callbackargs);
274 var targetancestor = null,
277 if (target.test('a')) {
278 window.location = target.get('href');
280 } else if ((targetancestor = target.ancestor('a')) !== null) {
281 window.location = targetancestor.get('href');
283 } else if (target.test('input')) {
284 targetform = target.ancestor(function(node) { return node.get('tagName').toLowerCase() == 'form'; });
285 // We cannot use target.ancestor('form') on the previous line
286 // because of http://yuilibrary.com/projects/yui3/ticket/2531561
290 if (target.get('name') && target.get('value')) {
291 targetform.append('<input type="hidden" name="' + target.get('name') +
292 '" value="' + target.get('value') + '">');
296 } else if (target.get('tagName').toLowerCase() == 'form') {
297 // We cannot use target.test('form') on the previous line because of
298 // http://yuilibrary.com/projects/yui3/ticket/2531561
301 } else if (M.cfg.developerdebug) {
302 alert("Element of type " + target.get('tagName') + " is not supported by the M.util.show_confirm_dialog function. Use A, INPUT, or FORM");
306 if (!args.cancellabel) {
307 args.cancellabel = M.str.moodle.cancel;
309 if (!args.continuelabel) {
310 args.continuelabel = M.str.moodle.yes;
314 {text: args.cancellabel, handler: handle_cancel, isDefault: true},
315 {text: args.continuelabel, handler: handle_yes}
318 simpledialog.cfg.queueProperty('buttons', buttons);
320 simpledialog.render(document.body);
325 /** Useful for full embedding of various stuff */
326 M.util.init_maximised_embed = function(Y, id) {
327 var obj = Y.one('#'+id);
332 var get_htmlelement_size = function(el, prop) {
333 if (Y.Lang.isString(el)) {
334 el = Y.one('#' + el);
336 var val = el.getStyle(prop);
338 val = el.getComputedStyle(prop);
340 return parseInt(val);
343 var resize_object = function() {
344 obj.setStyle('width', '0px');
345 obj.setStyle('height', '0px');
346 var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
348 if (newwidth > 500) {
349 obj.setStyle('width', newwidth + 'px');
351 obj.setStyle('width', '500px');
354 var headerheight = get_htmlelement_size('page-header', 'height');
355 var footerheight = get_htmlelement_size('page-footer', 'height');
356 var newheight = parseInt(YAHOO.util.Dom.getViewportHeight()) - footerheight - headerheight - 100;
357 if (newheight < 400) {
360 obj.setStyle('height', newheight+'px');
364 // fix layout if window resized too
365 window.onresize = function() {
371 * Attach handler to single_select
373 M.util.init_select_autosubmit = function(Y, formid, selectid, nothing) {
374 Y.use('event-key', function() {
375 var select = Y.one('#'+selectid);
377 // Try to get the form by id
378 var form = Y.one('#'+formid) || (function(){
379 // Hmmm the form's id may have been overriden by an internal input
380 // with the name id which will KILL IE.
381 // We need to manually iterate at this point because if the case
382 // above is true YUI's ancestor method will also kill IE!
384 while (form && form.get('nodeName').toUpperCase() !== 'FORM') {
385 form = form.ancestor();
389 // Make sure we have the form
391 // Create a function to handle our change event
392 var processchange = function(e, paramobject) {
393 if ((nothing===false || select.get('value') != nothing) && paramobject.lastindex != select.get('selectedIndex')) {
394 //prevent event bubbling and detach handlers to prevent multiple submissions caused by double clicking
396 paramobject.eventkeypress.detach();
397 paramobject.eventblur.detach();
398 paramobject.eventchangeorblur.detach();
403 // Attach the change event to the keypress, blur, and click actions.
404 // We don't use the change event because IE fires it on every arrow up/down
405 // event.... usability
406 var paramobject = new Object();
407 paramobject.lastindex = select.get('selectedIndex');
408 paramobject.eventkeypress = Y.on('key', processchange, select, 'press:13', form, paramobject);
409 paramobject.eventblur = select.on('blur', processchange, form, paramobject);
410 //little hack for chrome that need onChange event instead of onClick - see MDL-23224
412 paramobject.eventchangeorblur = select.on('change', processchange, form, paramobject);
414 paramobject.eventchangeorblur = select.on('click', processchange, form, paramobject);
422 * Attach handler to url_select
424 M.util.init_url_select = function(Y, formid, selectid, nothing) {
425 YUI(M.yui.loader).use('node', function(Y) {
426 Y.on('change', function() {
427 if ((nothing == false && Y.Lang.isBoolean(nothing)) || Y.one('#'+selectid).get('value') != nothing) {
428 window.location = M.cfg.wwwroot+Y.one('#'+selectid).get('value');
436 * Breaks out all links to the top frame - used in frametop page layout.
438 M.util.init_frametop = function(Y) {
439 Y.all('a').each(function(node) {
440 node.set('target', '_top');
442 Y.all('form').each(function(node) {
443 node.set('target', '_top');
448 * Finds all nodes that match the given CSS selector and attaches events to them
449 * so that they toggle a given classname when clicked.
452 * @param {string} id An id containing elements to target
453 * @param {string} cssselector A selector to use to find targets
454 * @param {string} toggleclassname A classname to toggle
456 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
458 if (togglecssselector == '') {
459 togglecssselector = cssselector;
462 var node = Y.one('#'+id);
463 node.all(cssselector).each(function(n){
464 n.on('click', function(e){
466 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
467 if (this.test(togglecssselector)) {
468 this.toggleClass(toggleclassname);
470 this.ancestor(togglecssselector).toggleClass(toggleclassname);
475 // Attach this click event to the node rather than all selectors... will be much better
477 node.on('click', function(e){
478 if (e.target.hasClass('addtoall')) {
479 this.all(togglecssselector).addClass(toggleclassname);
480 } else if (e.target.hasClass('removefromall')) {
481 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
487 * Initialises a colour picker
489 * Designed to be used with admin_setting_configcolourpicker although could be used
490 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
491 * above or below the input (must have the same parent) and then call this with the
494 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
495 * contrib/blocks. For better docs refer to that.
499 * @param {object} previewconf
501 M.util.init_colour_picker = function(Y, id, previewconf) {
503 * We need node and event-mouseenter
505 Y.use('node', 'event-mouseenter', function(){
507 * The colour picker object
516 eventMouseEnter : null,
517 eventMouseLeave : null,
518 eventMouseMove : null,
523 * Initalises the colour picker by putting everything together and wiring the events
526 this.input = Y.one('#'+id);
527 this.box = this.input.ancestor().one('.admin_colourpicker');
528 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
529 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
530 this.preview = Y.Node.create('<div class="previewcolour"></div>');
531 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
532 this.current = Y.Node.create('<div class="currentcolour"></div>');
533 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
534 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
536 if (typeof(previewconf) === 'object' && previewconf !== null) {
537 Y.one('#'+id+'_preview').on('click', function(e){
538 if (Y.Lang.isString(previewconf.selector)) {
539 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
541 for (var i in previewconf.selector) {
542 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
548 this.eventClick = this.image.on('click', this.pickColour, this);
549 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
552 * Starts to follow the mouse once it enter the image
554 startFollow : function(e) {
555 this.eventMouseEnter.detach();
556 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
557 this.eventMouseMove = this.image.on('mousemove', function(e){
558 this.preview.setStyle('backgroundColor', this.determineColour(e));
562 * Stops following the mouse
564 endFollow : function(e) {
565 this.eventMouseMove.detach();
566 this.eventMouseLeave.detach();
567 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
570 * Picks the colour the was clicked on
572 pickColour : function(e) {
573 var colour = this.determineColour(e);
574 this.input.set('value', colour);
575 this.current.setStyle('backgroundColor', colour);
578 * Calculates the colour fromthe given co-ordinates
580 determineColour : function(e) {
581 var eventx = Math.floor(e.pageX-e.target.getX());
582 var eventy = Math.floor(e.pageY-e.target.getY());
584 var imagewidth = this.width;
585 var imageheight = this.height;
586 var factor = this.factor;
587 var colour = [255,0,0];
598 var matrixcount = matrices.length;
599 var limit = Math.round(imagewidth/matrixcount);
600 var heightbreak = Math.round(imageheight/2);
602 for (var x = 0; x < imagewidth; x++) {
603 var divisor = Math.floor(x / limit);
604 var matrix = matrices[divisor];
606 colour[0] += matrix[0]*factor;
607 colour[1] += matrix[1]*factor;
608 colour[2] += matrix[2]*factor;
615 var pixel = [colour[0], colour[1], colour[2]];
616 if (eventy < heightbreak) {
617 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
618 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
619 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
620 } else if (eventy > heightbreak) {
621 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
622 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
623 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
626 return this.convert_rgb_to_hex(pixel);
629 * Converts an RGB value to Hex
631 convert_rgb_to_hex : function(rgb) {
633 var hexchars = "0123456789ABCDEF";
634 for (var i=0; i<3; i++) {
635 var number = Math.abs(rgb[i]);
636 if (number == 0 || isNaN(number)) {
639 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
646 * Initialise the colour picker :) Hoorah
652 M.util.init_block_hider = function(Y, config) {
653 Y.use('base', 'node', function(Y) {
654 M.util.block_hider = M.util.block_hider || (function(){
655 var blockhider = function() {
656 blockhider.superclass.constructor.apply(this, arguments);
658 blockhider.prototype = {
659 initializer : function(config) {
660 this.set('block', '#'+this.get('id'));
661 var b = this.get('block'),
664 if (t && (a = t.one('.block_action'))) {
665 var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
666 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
667 hide.on('keypress', this.updateStateKey, this, true);
668 var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
669 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
670 show.on('keypress', this.updateStateKey, this, false);
671 a.insert(show, 0).insert(hide, 0);
674 updateState : function(e, hide) {
675 M.util.set_user_preference(this.get('preference'), hide);
677 this.get('block').addClass('hidden');
679 this.get('block').removeClass('hidden');
682 updateStateKey : function(e, hide) {
683 if (e.keyCode == 13) { //allow hide/show via enter key
684 this.updateState(this, hide);
688 Y.extend(blockhider, Y.Base, blockhider.prototype, {
694 value : M.util.image_url('t/switch_minus', 'moodle')
697 value : M.util.image_url('t/switch_plus', 'moodle')
700 setter : function(node) {
708 new M.util.block_hider(config);
713 * Returns a string registered in advance for usage in JavaScript
715 * If you do not pass the third parameter, the function will just return
716 * the corresponding value from the M.str object. If the third parameter is
717 * provided, the function performs {$a} placeholder substitution in the
718 * same way as PHP get_string() in Moodle does.
720 * @param {String} identifier string identifier
721 * @param {String} component the component providing the string
722 * @param {Object|String} a optional variable to populate placeholder with
724 M.util.get_string = function(identifier, component, a) {
727 if (M.cfg.developerdebug) {
728 // creating new instance if YUI is not optimal but it seems to be better way then
729 // require the instance via the function API - note that it is used in rare cases
730 // for debugging only anyway
731 // To ensure we don't kill browser performance if hundreds of get_string requests
732 // are made we cache the instance we generate within the M.util namespace.
733 // We don't publicly define the variable so that it doesn't get abused.
734 if (typeof M.util.get_string_yui_instance === 'undefined') {
735 M.util.get_string_yui_instance = new YUI({ debug : true });
737 var Y = M.util.get_string_yui_instance;
740 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
741 stringvalue = '[[' + identifier + ',' + component + ']]';
742 if (M.cfg.developerdebug) {
743 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
748 stringvalue = M.str[component][identifier];
750 if (typeof a == 'undefined') {
751 // no placeholder substitution requested
755 if (typeof a == 'number' || typeof a == 'string') {
756 // replace all occurrences of {$a} with the placeholder value
757 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
761 if (typeof a == 'object') {
762 // replace {$a->key} placeholders
764 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
765 if (M.cfg.developerdebug) {
766 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
770 var search = '{$a->' + key + '}';
771 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
772 search = new RegExp(search, 'g');
773 stringvalue = stringvalue.replace(search, a[key]);
778 if (M.cfg.developerdebug) {
779 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
785 * Set focus on username or password field of the login form
787 M.util.focus_login_form = function(Y) {
788 var username = Y.one('#username');
789 var password = Y.one('#password');
791 if (username == null || password == null) {
792 // something is wrong here
796 var curElement = document.activeElement
797 if (curElement == 'undefined') {
798 // legacy browser - skip refocus protection
799 } else if (curElement.tagName == 'INPUT') {
800 // user was probably faster to focus something, do not mess with focus
804 if (username.get('value') == '') {
812 * Adds lightbox hidden element that covers the whole node.
815 * @param {Node} the node lightbox should be added to
816 * @retun {Node} created lightbox node
818 M.util.add_lightbox = function(Y, node) {
819 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
821 // Check if lightbox is already there
822 if (node.one('.lightbox')) {
823 return node.one('.lightbox');
826 node.setStyle('position', 'relative');
827 var waiticon = Y.Node.create('<img />')
829 'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
832 'position' : 'relative',
836 var lightbox = Y.Node.create('<div></div>')
839 'position' : 'absolute',
844 'backgroundColor' : 'white',
845 'text-align' : 'center'
847 .setAttribute('class', 'lightbox')
850 lightbox.appendChild(waiticon);
851 node.append(lightbox);
856 * Appends a hidden spinner element to the specified node.
859 * @param {Node} the node the spinner should be added to
860 * @return {Node} created spinner node
862 M.util.add_spinner = function(Y, node) {
863 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
865 // Check if spinner is already there
866 if (node.one('.spinner')) {
867 return node.one('.spinner');
870 var spinner = Y.Node.create('<img />')
871 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
873 .addClass('iconsmall')
876 node.append(spinner);
880 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
882 function checkall() {
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 = true;
894 function checknone() {
895 var inputs = document.getElementsByTagName('input');
896 for (var i = 0; i < inputs.length; i++) {
897 if (inputs[i].type == 'checkbox') {
898 if (inputs[i].disabled || inputs[i].readOnly) {
901 inputs[i].checked = false;
907 * Either check, or uncheck, all checkboxes inside the element with id is
908 * @param id the id of the container
909 * @param checked the new state, either '' or 'checked'.
911 function select_all_in_element_with_id(id, checked) {
912 var container = document.getElementById(id);
916 var inputs = container.getElementsByTagName('input');
917 for (var i = 0; i < inputs.length; ++i) {
918 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
919 inputs[i].checked = checked;
924 function select_all_in(elTagName, elClass, elId) {
925 var inputs = document.getElementsByTagName('input');
926 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
927 for(var i = 0; i < inputs.length; ++i) {
928 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
929 inputs[i].checked = 'checked';
934 function deselect_all_in(elTagName, elClass, elId) {
935 var inputs = document.getElementsByTagName('INPUT');
936 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
937 for(var i = 0; i < inputs.length; ++i) {
938 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
939 inputs[i].checked = '';
944 function confirm_if(expr, message) {
948 return confirm(message);
953 findParentNode (start, elementName, elementClass, elementID)
955 Travels up the DOM hierarchy to find a parent element with the
956 specified tag name, class, and id. All conditions must be met,
957 but any can be ommitted. Returns the BODY element if no match
960 function findParentNode(el, elName, elClass, elId) {
961 while (el.nodeName.toUpperCase() != 'BODY') {
962 if ((!elName || el.nodeName.toUpperCase() == elName) &&
963 (!elClass || el.className.indexOf(elClass) != -1) &&
964 (!elId || el.id == elId)) {
972 findChildNode (start, elementName, elementClass, elementID)
974 Travels down the DOM hierarchy to find all child elements with the
975 specified tag name, class, and id. All conditions must be met,
976 but any can be ommitted.
977 Doesn't examine children of matches.
979 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
980 var children = new Array();
981 for (var i = 0; i < start.childNodes.length; i++) {
982 var classfound = false;
983 var child = start.childNodes[i];
984 if((child.nodeType == 1) &&//element node type
985 (elementClass && (typeof(child.className)=='string'))) {
986 var childClasses = child.className.split(/\s+/);
987 for (var childClassIndex in childClasses) {
988 if (childClasses[childClassIndex]==elementClass) {
994 if(child.nodeType == 1) { //element node type
995 if ( (!tagName || child.nodeName == tagName) &&
996 (!elementClass || classfound)&&
997 (!elementID || child.id == elementID) &&
998 (!elementName || child.name == elementName))
1000 children = children.concat(child);
1002 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
1009 function unmaskPassword(id) {
1010 var pw = document.getElementById(id);
1011 var chb = document.getElementById(id+'unmask');
1014 // first try IE way - it can not set name attribute later
1016 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
1018 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
1020 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
1022 var newpw = document.createElement('input');
1023 newpw.setAttribute('autocomplete', 'off');
1024 newpw.setAttribute('name', pw.name);
1026 newpw.setAttribute('type', 'text');
1028 newpw.setAttribute('type', 'password');
1030 newpw.setAttribute('class', pw.getAttribute('class'));
1033 newpw.size = pw.size;
1034 newpw.onblur = pw.onblur;
1035 newpw.onchange = pw.onchange;
1036 newpw.value = pw.value;
1037 pw.parentNode.replaceChild(newpw, pw);
1040 function filterByParent(elCollection, parentFinder) {
1041 var filteredCollection = [];
1042 for (var i = 0; i < elCollection.length; ++i) {
1043 var findParent = parentFinder(elCollection[i]);
1044 if (findParent.nodeName.toUpperCase() != 'BODY') {
1045 filteredCollection.push(elCollection[i]);
1048 return filteredCollection;
1052 All this is here just so that IE gets to handle oversized blocks
1053 in a visually pleasing manner. It does a browser detect. So sue me.
1056 function fix_column_widths() {
1057 var agt = navigator.userAgent.toLowerCase();
1058 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1059 fix_column_width('left-column');
1060 fix_column_width('right-column');
1064 function fix_column_width(colName) {
1065 if(column = document.getElementById(colName)) {
1066 if(!column.offsetWidth) {
1067 setTimeout("fix_column_width('" + colName + "')", 20);
1072 var nodes = column.childNodes;
1074 for(i = 0; i < nodes.length; ++i) {
1075 if(nodes[i].className.indexOf("block") != -1 ) {
1076 if(width < nodes[i].offsetWidth) {
1077 width = nodes[i].offsetWidth;
1082 for(i = 0; i < nodes.length; ++i) {
1083 if(nodes[i].className.indexOf("block") != -1 ) {
1084 nodes[i].style.width = width + 'px';
1092 Insert myValue at current cursor position
1094 function insertAtCursor(myField, myValue) {
1096 if (document.selection) {
1098 sel = document.selection.createRange();
1101 // Mozilla/Netscape support
1102 else if (myField.selectionStart || myField.selectionStart == '0') {
1103 var startPos = myField.selectionStart;
1104 var endPos = myField.selectionEnd;
1105 myField.value = myField.value.substring(0, startPos)
1106 + myValue + myField.value.substring(endPos, myField.value.length);
1108 myField.value += myValue;
1114 Call instead of setting window.onload directly or setting body onload=.
1115 Adds your function to a chain of functions rather than overwriting anything
1118 function addonload(fn) {
1119 var oldhandler=window.onload;
1120 window.onload=function() {
1121 if(oldhandler) oldhandler();
1126 * Replacement for getElementsByClassName in browsers that aren't cool enough
1128 * Relying on the built-in getElementsByClassName is far, far faster than
1131 * Note: the third argument used to be an object with odd behaviour. It now
1132 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1133 * mimicked if you pass an object.
1135 * @param {Node} oElm The top-level node for searching. To search a whole
1136 * document, use `document`.
1137 * @param {String} strTagName filter by tag names
1138 * @param {String} name same as HTML5 spec
1140 function getElementsByClassName(oElm, strTagName, name) {
1141 // for backwards compatibility
1142 if(typeof name == "object") {
1143 var names = new Array();
1144 for(var i=0; i<name.length; i++) names.push(names[i]);
1145 name = names.join('');
1147 // use native implementation if possible
1148 if (oElm.getElementsByClassName && Array.filter) {
1149 if (strTagName == '*') {
1150 return oElm.getElementsByClassName(name);
1152 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1153 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1157 // native implementation unavailable, fall back to slow method
1158 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1159 var arrReturnElements = new Array();
1160 var arrRegExpClassNames = new Array();
1161 var names = name.split(' ');
1162 for(var i=0; i<names.length; i++) {
1163 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1167 for(var j=0; j<arrElements.length; j++) {
1168 oElement = arrElements[j];
1170 for(var k=0; k<arrRegExpClassNames.length; k++) {
1171 if(!arrRegExpClassNames[k].test(oElement.className)) {
1172 bMatchesAll = false;
1177 arrReturnElements.push(oElement);
1180 return (arrReturnElements)
1183 function openpopup(event, args) {
1186 if (event.preventDefault) {
1187 event.preventDefault();
1189 event.returnValue = false;
1193 // Make sure the name argument is set and valid.
1194 var nameregex = /[^a-z0-9_]/i;
1195 if (typeof args.name !== 'string') {
1196 args.name = '_blank';
1197 } else if (args.name.match(nameregex)) {
1198 // Cleans window name because IE does not support funky ones.
1199 args.name = args.name.replace(nameregex, '_');
1200 if (M.cfg.developerdebug) {
1201 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup()');
1205 var fullurl = args.url;
1206 if (!args.url.match(/https?:\/\//)) {
1207 fullurl = M.cfg.wwwroot + args.url;
1209 if (args.fullscreen) {
1210 args.options = args.options.
1211 replace(/top=\d+/, 'top=0').
1212 replace(/left=\d+/, 'left=0').
1213 replace(/width=\d+/, 'width=' + screen.availWidth).
1214 replace(/height=\d+/, 'height=' + screen.availHeight);
1216 var windowobj = window.open(fullurl,args.name,args.options);
1221 if (args.fullscreen) {
1222 // In some browser / OS combinations (E.g. Chrome on Windows), the
1223 // window initially opens slighly too big. The width and heigh options
1224 // seem to control the area inside the browser window, so what with
1225 // scroll-bars, etc. the actual window is bigger than the screen.
1226 // Therefore, we need to fix things up after the window is open.
1227 var hackcount = 100;
1228 var get_size_exactly_right = function() {
1229 windowobj.moveTo(0, 0);
1230 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1232 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1233 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1234 // about 50ms) after the window is open, then it actually behaves
1235 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1236 // check that the resize actually worked, and if not, repeatedly try
1237 // again after a short delay until it works (but with a limit of
1238 // hackcount repeats.
1239 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1241 setTimeout(get_size_exactly_right, 10);
1244 setTimeout(get_size_exactly_right, 0);
1251 /** Close the current browser window. */
1252 function close_window(e) {
1253 if (e.preventDefault) {
1256 e.returnValue = false;
1262 * Used in a couple of modules to hide navigation areas when using AJAX
1265 function show_item(itemid) {
1266 var item = document.getElementById(itemid);
1268 item.style.display = "";
1272 function destroy_item(itemid) {
1273 var item = document.getElementById(itemid);
1275 item.parentNode.removeChild(item);
1279 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1280 * @param controlid the control id.
1282 function focuscontrol(controlid) {
1283 var control = document.getElementById(controlid);
1290 * Transfers keyboard focus to an HTML element based on the old style style of focus
1291 * This function should be removed as soon as it is no longer used
1293 function old_onload_focus(formid, controlname) {
1294 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1295 document.forms[formid].elements[controlname].focus();
1299 function build_querystring(obj) {
1300 return convert_object_to_string(obj, '&');
1303 function build_windowoptionsstring(obj) {
1304 return convert_object_to_string(obj, ',');
1307 function convert_object_to_string(obj, separator) {
1308 if (typeof obj !== 'object') {
1313 k = encodeURIComponent(k);
1315 if(obj[k] instanceof Array) {
1316 for(var i in value) {
1317 list.push(k+'[]='+encodeURIComponent(value[i]));
1320 list.push(k+'='+encodeURIComponent(value));
1323 return list.join(separator);
1326 function stripHTML(str) {
1327 var re = /<\S[^><]*>/g;
1328 var ret = str.replace(re, "");
1332 Number.prototype.fixed=function(n){
1334 return round(Number(this)*pow(10,n))/pow(10,n);
1336 function update_progress_bar (id, width, pt, msg, es){
1338 var status = document.getElementById("status_"+id);
1339 var percent_indicator = document.getElementById("pt_"+id);
1340 var progress_bar = document.getElementById("progress_"+id);
1341 var time_es = document.getElementById("time_"+id);
1342 status.innerHTML = msg;
1343 percent_indicator.innerHTML = percent.fixed(2) + '%';
1344 if(percent == 100) {
1345 progress_bar.style.background = "green";
1346 time_es.style.display = "none";
1348 progress_bar.style.background = "#FFCC66";
1350 time_es.innerHTML = "";
1352 time_es.innerHTML = es.fixed(2)+" sec";
1353 time_es.style.display
1357 progress_bar.style.width = width + "px";
1362 // ===== Deprecated core Javascript functions for Moodle ====
1363 // DO NOT USE!!!!!!!
1364 // Do not put this stuff in separate file because it only adds extra load on servers!
1367 * Used in a couple of modules to hide navigation areas when using AJAX
1369 function hide_item(itemid) {
1370 // use class='hiddenifjs' instead
1371 var item = document.getElementById(itemid);
1373 item.style.display = "none";
1377 M.util.help_icon = {
1380 add : function(Y, properties) {
1382 properties.node = Y.one('#'+properties.id);
1383 if (properties.node) {
1384 properties.node.on('click', this.display, this, properties);
1387 display : function(event, args) {
1388 event.preventDefault();
1389 if (M.util.help_icon.instance === null) {
1390 var Y = M.util.help_icon.Y;
1391 Y.use('overlay', 'io-base', 'event-mouseenter', 'node', 'event-key', function(Y) {
1392 var help_content_overlay = {
1397 var closebtn = Y.Node.create('<a id="closehelpbox" href="#"><img src="'+M.util.image_url('t/delete', 'moodle')+'" /></a>');
1398 // Create an overlay from markup
1399 this.overlay = new Y.Overlay({
1400 headerContent: closebtn,
1407 this.overlay.render(Y.one(document.body));
1409 closebtn.on('click', this.overlay.hide, this.overlay);
1411 var boundingBox = this.overlay.get("boundingBox");
1413 // Hide the menu if the user clicks outside of its content
1414 boundingBox.get("ownerDocument").on("mousedown", function (event) {
1415 var oTarget = event.target;
1416 var menuButton = Y.one("#"+args.id);
1418 if (!oTarget.compareTo(menuButton) &&
1419 !menuButton.contains(oTarget) &&
1420 !oTarget.compareTo(boundingBox) &&
1421 !boundingBox.contains(oTarget)) {
1422 this.overlay.hide();
1426 Y.on("key", this.close, closebtn , "down:13", this);
1427 closebtn.on('click', this.close, this);
1430 close : function(e) {
1432 this.helplink.focus();
1433 this.overlay.hide();
1436 display : function(event, args) {
1437 this.helplink = args.node;
1438 this.overlay.set('bodyContent', Y.Node.create('<img src="'+M.cfg.loadingicon+'" class="spinner" />'));
1439 this.overlay.set("align", {node:args.node, points:[Y.WidgetPositionAlign.TL, Y.WidgetPositionAlign.RC]});
1441 var fullurl = args.url;
1442 if (!args.url.match(/https?:\/\//)) {
1443 fullurl = M.cfg.wwwroot + args.url;
1446 var ajaxurl = fullurl + '&ajax=1';
1452 success: function(id, o, node) {
1453 this.display_callback(o.responseText);
1455 failure: function(id, o, node) {
1456 var debuginfo = o.statusText;
1457 if (M.cfg.developerdebug) {
1458 o.statusText += ' (' + ajaxurl + ')';
1460 this.display_callback('bodyContent',debuginfo);
1466 this.overlay.show();
1468 Y.one('#closehelpbox').focus();
1471 display_callback : function(content) {
1472 content = '<div role="alert">' + content + '</div>';
1473 this.overlay.set('bodyContent', content);
1476 hideContent : function() {
1478 help.overlay.hide();
1481 help_content_overlay.init();
1482 M.util.help_icon.instance = help_content_overlay;
1483 M.util.help_icon.instance.display(event, args);
1486 M.util.help_icon.instance.display(event, args);
1489 init : function(Y) {
1495 * Custom menu namespace
1497 M.core_custom_menu = {
1499 * This method is used to initialise a custom menu given the id that belongs
1500 * to the custom menu's root node.
1503 * @param {string} nodeid
1505 init : function(Y, nodeid) {
1506 var node = Y.one('#'+nodeid);
1508 Y.use('node-menunav', function(Y) {
1510 // Remove the javascript-disabled class.... obviously javascript is enabled.
1511 node.removeClass('javascript-disabled');
1512 // Initialise the menunav plugin
1513 node.plug(Y.Plugin.NodeMenuNav);
1520 * Used to store form manipulation methods and enhancments
1522 M.form = M.form || {};
1525 * Converts a nbsp indented select box into a multi drop down custom control much
1526 * like the custom menu. It also selectable categories on or off.
1528 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1531 * @param {string} id
1532 * @param {Array} options
1534 M.form.init_smartselect = function(Y, id, options) {
1535 if (!id.match(/^id_/)) {
1538 var select = Y.one('select#'+id);
1542 Y.use('event-delegate',function(){
1548 currentvalue : null,
1552 selectablecategories : true,
1560 init : function(Y, id, args, nodes) {
1561 if (typeof(args)=='object') {
1562 for (var i in this.cfg) {
1563 if (args[i] || args[i]===false) {
1564 this.cfg[i] = args[i];
1569 // Display a loading message first up
1570 this.nodes.select = nodes.select;
1572 this.currentvalue = this.nodes.select.get('selectedIndex');
1573 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1575 var options = Array();
1576 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1577 this.nodes.select.all('option').each(function(option, index) {
1578 var rawtext = option.get('innerHTML');
1579 var text = rawtext.replace(/^( )*/, '');
1580 if (rawtext === text) {
1581 text = rawtext.replace(/^(\s)*/, '');
1582 var depth = (rawtext.length - text.length ) + 1;
1584 var depth = ((rawtext.length - text.length )/12)+1;
1586 option.set('innerHTML', text);
1587 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1590 this.structure = [];
1591 var structcount = 0;
1592 for (var i in options) {
1595 this.structure.push(o);
1599 var current = this.structure[structcount-1];
1600 for (var j = 0; j < o.depth-1;j++) {
1601 if (current && current.children) {
1602 current = current.children[current.children.length-1];
1605 if (current && current.children) {
1606 current.children.push(o);
1611 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1612 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1613 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1614 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1616 if (this.cfg.mode == null) {
1617 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1618 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1619 this.cfg.mode = 'compact';
1621 this.cfg.mode = 'spanning';
1625 if (this.cfg.mode == 'compact') {
1626 this.nodes.menu.addClass('compactmenu');
1628 this.nodes.menu.addClass('spanningmenu');
1629 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1632 Y.one(document.body).append(this.nodes.menu);
1633 var pos = this.nodes.select.getXY();
1635 this.nodes.menu.setXY(pos);
1636 this.nodes.menu.on('click', this.handle_click, this);
1638 Y.one(window).on('resize', function(){
1639 var pos = this.nodes.select.getXY();
1641 this.nodes.menu.setXY(pos);
1644 generate_menu_content : function() {
1645 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1646 content += this.generate_submenu_content(this.structure[0], true);
1647 content += '</ul></div>';
1650 generate_submenu_content : function(item, rootelement) {
1651 this.submenucount++;
1653 if (item.children.length > 0) {
1655 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'"> </div>';
1656 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1657 content += '<div class="smartselect_menu_content">';
1659 content += '<li class="smartselect_submenuitem">';
1660 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1661 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1662 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1663 content += '<div class="smartselect_submenu_content">';
1666 for (var i in item.children) {
1667 content += this.generate_submenu_content(item.children[i],false);
1670 content += '</div>';
1671 content += '</div>';
1677 content += '<li class="smartselect_menuitem">';
1678 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1683 select : function(e) {
1686 this.currenttext = t.get('innerHTML');
1687 this.currentvalue = t.getAttribute('value');
1688 this.nodes.select.set('selectedIndex', this.currentvalue);
1691 handle_click : function(e) {
1692 var target = e.target;
1693 if (target.hasClass('smartselect_mask')) {
1695 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1697 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1698 this.show_sub_menu(e);
1701 show_menu : function(e) {
1703 var menu = e.target.ancestor().one('.smartselect_menu');
1704 menu.addClass('visible');
1705 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1707 show_sub_menu : function(e) {
1709 var target = e.target;
1710 if (!target.hasClass('smartselect_submenuitem')) {
1711 target = target.ancestor('.smartselect_submenuitem');
1713 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1714 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1717 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1718 target.one('.smartselect_submenu').addClass('visible');
1720 hide_menu : function() {
1721 this.nodes.menu.all('.visible').removeClass('visible');
1722 if (this.shownevent) {
1723 this.shownevent.detach();
1727 smartselect.init(Y, id, options, {select:select});
1731 /** List of flv players to be loaded */
1732 M.util.video_players = [];
1733 /** List of mp3 players to be loaded */
1734 M.util.audio_players = [];
1738 * @param id element id
1739 * @param fileurl media url
1742 * @param autosize true means detect size from media
1744 M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1745 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
1754 M.util.add_audio_player = function (id, fileurl, small) {
1755 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
1759 * Initialise all audio and video player, must be called from page footer.
1761 M.util.load_flowplayer = function() {
1762 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1765 if (typeof(flowplayer) == 'undefined') {
1768 var embed_function = function() {
1769 if (loaded || typeof(flowplayer) == 'undefined') {
1777 /* TODO: add CSS color overrides for the flv flow player */
1779 for(var i=0; i<M.util.video_players.length; i++) {
1780 var video = M.util.video_players[i];
1781 if (video.width > 0 && video.height > 0) {
1782 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.9.swf', width: video.width, height: video.height};
1784 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.9.swf';
1786 flowplayer(video.id, src, {
1787 plugins: {controls: controls},
1789 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1790 onMetaData: function(clip) {
1791 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1792 clip.mvideo.resized = true;
1793 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1794 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1795 // bad luck, we have to guess - we may not get metadata at all
1796 var width = clip.width;
1797 var height = clip.height;
1799 var width = clip.metaData.width;
1800 var height = clip.metaData.height;
1802 var minwidth = 300; // controls are messed up in smaller objects
1803 if (width < minwidth) {
1804 height = (height * minwidth) / width;
1808 var object = this._api();
1809 object.width = width;
1810 object.height = height;
1816 if (M.util.audio_players.length == 0) {
1829 backgroundGradient: [0.5,0,0.3]
1833 for (var j=0; j < document.styleSheets.length; j++) {
1835 // To avoid javascript security violation accessing cross domain stylesheets
1836 var allrules = false;
1838 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1839 allrules = document.styleSheets[j].rules;
1840 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1841 allrules = document.styleSheets[j].cssRules;
1850 // On cross domain style sheets Chrome V8 allows access to rules but returns null
1855 for(var i=0; i<allrules.length; i++) {
1857 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1858 if (typeof(allrules[i].cssText) != 'undefined') {
1859 rule = allrules[i].cssText;
1860 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1861 rule = allrules[i].style.cssText;
1863 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1864 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1865 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1866 controls[colprop] = rule;
1873 for(i=0; i<M.util.audio_players.length; i++) {
1874 var audio = M.util.audio_players[i];
1876 controls.controlall = false;
1877 controls.height = 15;
1878 controls.time = false;
1880 controls.controlall = true;
1881 controls.height = 25;
1882 controls.time = true;
1884 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.9.swf', {
1885 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.8.swf'}},
1886 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1891 if (M.cfg.jsrev == -10) {
1892 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.8.min.js';
1894 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.8.min.js&rev=' + M.cfg.jsrev;
1896 var fileref = document.createElement('script');
1897 fileref.setAttribute('type','text/javascript');
1898 fileref.setAttribute('src', jsurl);
1899 fileref.onload = embed_function;
1900 fileref.onreadystatechange = embed_function;
1901 document.getElementsByTagName('head')[0].appendChild(fileref);