1 // Miscellaneous core Javascript functions for Moodle
2 // Global M object is initilised in inline javascript
5 * Add module to list of available modules that can be loaded from YUI.
6 * @param {Array} modules
8 M.yui.add_module = function(modules) {
9 for (var modname in modules) {
10 YUI_config.modules[modname] = modules[modname];
14 * The gallery version to use when loading YUI modules from the gallery.
15 * Will be changed every time when using local galleries.
17 M.yui.galleryversion = '2010.04.21-21-51';
20 * Various utility functions
22 M.util = M.util || {};
25 * Language strings - initialised from page footer.
30 * Returns url for images.
31 * @param {String} imagename
32 * @param {String} component
35 M.util.image_url = function(imagename, component) {
37 if (!component || component == '' || component == 'moodle' || component == 'core') {
41 var url = M.cfg.wwwroot + '/theme/image.php';
42 if (M.cfg.themerev > 0 && M.cfg.slasharguments == 1) {
43 if (!M.cfg.svgicons) {
46 url += '/' + M.cfg.theme + '/' + component + '/' + M.cfg.themerev + '/' + imagename;
48 url += '?theme=' + M.cfg.theme + '&component=' + component + '&rev=' + M.cfg.themerev + '&image=' + imagename;
49 if (!M.cfg.svgicons) {
57 M.util.in_array = function(item, array){
58 for( var i = 0; i<array.length; i++){
67 * Init a collapsible region, see print_collapsible_region in weblib.php
68 * @param {YUI} Y YUI3 instance with all libraries loaded
69 * @param {String} id the HTML id for the div.
70 * @param {String} userpref the user preference that records the state of this box. false if none.
71 * @param {String} strtooltip
73 M.util.init_collapsible_region = function(Y, id, userpref, strtooltip) {
74 Y.use('anim', function(Y) {
75 new M.util.CollapsibleRegion(Y, id, userpref, strtooltip);
80 * Object to handle a collapsible region : instantiate and forget styled object
84 * @param {YUI} Y YUI3 instance with all libraries loaded
85 * @param {String} id The HTML id for the div.
86 * @param {String} userpref The user preference that records the state of this box. false if none.
87 * @param {String} strtooltip
89 M.util.CollapsibleRegion = function(Y, id, userpref, strtooltip) {
90 // Record the pref name
91 this.userpref = userpref;
93 // Find the divs in the document.
94 this.div = Y.one('#'+id);
96 // Get the caption for the collapsible region
97 var caption = this.div.one('#'+id + '_caption');
100 var a = Y.Node.create('<a href="#"></a>');
101 a.setAttribute('title', strtooltip);
103 // Get all the nodes from caption, remove them and append them to <a>
104 while (caption.hasChildNodes()) {
105 child = caption.get('firstChild');
111 // Get the height of the div at this point before we shrink it if required
112 var height = this.div.get('offsetHeight');
113 var collapsedimage = 't/collapsed'; // ltr mode
114 if (right_to_left()) {
115 collapsedimage = 't/collapsed_rtl';
117 collapsedimage = 't/collapsed';
119 if (this.div.hasClass('collapsed')) {
120 // Add the correct image and record the YUI node created in the process
121 this.icon = Y.Node.create('<img src="'+M.util.image_url(collapsedimage, 'moodle')+'" alt="" />');
122 // Shrink the div as it is collapsed by default
123 this.div.setStyle('height', caption.get('offsetHeight')+'px');
125 // Add the correct image and record the YUI node created in the process
126 this.icon = Y.Node.create('<img src="'+M.util.image_url('t/expanded', 'moodle')+'" alt="" />');
130 // Create the animation.
131 var animation = new Y.Anim({
134 easing: Y.Easing.easeBoth,
135 to: {height:caption.get('offsetHeight')},
136 from: {height:height}
139 // Handler for the animation finishing.
140 animation.on('end', function() {
141 this.div.toggleClass('collapsed');
142 var collapsedimage = 't/collapsed'; // ltr mode
143 if (right_to_left()) {
144 collapsedimage = 't/collapsed_rtl';
146 collapsedimage = 't/collapsed';
148 if (this.div.hasClass('collapsed')) {
149 this.icon.set('src', M.util.image_url(collapsedimage, 'moodle'));
151 this.icon.set('src', M.util.image_url('t/expanded', 'moodle'));
155 // Hook up the event handler.
156 a.on('click', function(e, animation) {
158 // Animate to the appropriate size.
159 if (animation.get('running')) {
162 animation.set('reverse', this.div.hasClass('collapsed'));
163 // Update the user preference.
165 M.util.set_user_preference(this.userpref, !this.div.hasClass('collapsed'));
172 * The user preference that stores the state of this box.
176 M.util.CollapsibleRegion.prototype.userpref = null;
179 * The key divs that make up this
183 M.util.CollapsibleRegion.prototype.div = null;
186 * The key divs that make up this
190 M.util.CollapsibleRegion.prototype.icon = null;
193 * Makes a best effort to connect back to Moodle to update a user preference,
194 * however, there is no mechanism for finding out if the update succeeded.
196 * Before you can use this function in your JavsScript, you must have called
197 * user_preference_allow_ajax_update from moodlelib.php to tell Moodle that
198 * the udpate is allowed, and how to safely clean and submitted values.
200 * @param String name the name of the setting to udpate.
201 * @param String the value to set it to.
203 M.util.set_user_preference = function(name, value) {
204 YUI().use('io', function(Y) {
205 var url = M.cfg.wwwroot + '/lib/ajax/setuserpref.php?sesskey=' +
206 M.cfg.sesskey + '&pref=' + encodeURI(name) + '&value=' + encodeURI(value);
208 // If we are a developer, ensure that failures are reported.
213 if (M.cfg.developerdebug) {
214 cfg.on.failure = function(id, o, args) {
215 alert("Error updating user preference '" + name + "' using ajax. Clicking this link will repeat the Ajax call that failed so you can see the error: ");
225 * Prints a confirmation dialog in the style of DOM.confirm().
227 * @method show_confirm_dialog
228 * @param {EventFacade} e
229 * @param {Object} args
230 * @param {String} args.message The question to ask the user
231 * @param {Function} [args.callback] A callback to apply on confirmation.
232 * @param {Object} [args.scope] The scope to use when calling the callback.
233 * @param {Object} [args.callbackargs] Any arguments to pass to the callback.
234 * @param {String} [args.cancellabel] The label to use on the cancel button.
235 * @param {String} [args.continuelabel] The label to use on the continue button.
237 M.util.show_confirm_dialog = function(e, args) {
238 var target = e.target;
239 if (e.preventDefault) {
243 YUI().use('moodle-core-notification-confirm', function(Y) {
244 var confirmationDialogue = new M.core.confirm({
250 title: M.util.get_string('confirmation', 'admin'),
251 noLabel: M.util.get_string('cancel', 'moodle'),
252 question: args.message
255 // The dialogue was submitted with a positive value indication.
256 confirmationDialogue.on('complete-yes', function(e) {
257 // Handle any callbacks.
259 if (!Y.Lang.isFunction(args.callback)) {
260 Y.log('Callbacks to show_confirm_dialog must now be functions. Please update your code to pass in a function instead.',
261 'warn', 'M.util.show_confirm_dialog');
265 var scope = e.target;
266 if (Y.Lang.isObject(args.scope)) {
270 var callbackargs = args.callbackargs || [];
271 args.callback.apply(scope, callbackargs);
275 var targetancestor = null,
278 if (target.test('a')) {
279 window.location = target.get('href');
281 } else if ((targetancestor = target.ancestor('a')) !== null) {
282 window.location = targetancestor.get('href');
284 } else if (target.test('input')) {
285 targetform = target.ancestor('form', true);
289 if (target.get('name') && target.get('value')) {
290 targetform.append('<input type="hidden" name="' + target.get('name') +
291 '" value="' + target.get('value') + '">');
295 } else if (target.test('form')) {
299 Y.log("Element of type " + target.get('tagName') +
300 " is not supported by the M.util.show_confirm_dialog function. Use A, INPUT, or FORM",
301 'warn', 'javascript-static');
305 if (args.cancellabel) {
306 confirmationDialogue.set('noLabel', args.cancellabel);
309 if (args.continuelabel) {
310 confirmationDialogue.set('yesLabel', args.continuelabel);
313 confirmationDialogue.render()
318 /** Useful for full embedding of various stuff */
319 M.util.init_maximised_embed = function(Y, id) {
320 var obj = Y.one('#'+id);
325 var get_htmlelement_size = function(el, prop) {
326 if (Y.Lang.isString(el)) {
327 el = Y.one('#' + el);
329 // Ensure element exists.
331 var val = el.getStyle(prop);
333 val = el.getComputedStyle(prop);
335 return parseInt(val);
341 var resize_object = function() {
342 obj.setStyle('width', '0px');
343 obj.setStyle('height', '0px');
344 var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
346 if (newwidth > 500) {
347 obj.setStyle('width', newwidth + 'px');
349 obj.setStyle('width', '500px');
352 var headerheight = get_htmlelement_size('page-header', 'height');
353 var footerheight = get_htmlelement_size('page-footer', 'height');
354 var newheight = parseInt(Y.one('body').get('winHeight')) - footerheight - headerheight - 100;
355 if (newheight < 400) {
358 obj.setStyle('height', newheight+'px');
362 // fix layout if window resized too
363 window.onresize = function() {
369 * Breaks out all links to the top frame - used in frametop page layout.
371 M.util.init_frametop = function(Y) {
372 Y.all('a').each(function(node) {
373 node.set('target', '_top');
375 Y.all('form').each(function(node) {
376 node.set('target', '_top');
381 * Finds all nodes that match the given CSS selector and attaches events to them
382 * so that they toggle a given classname when clicked.
385 * @param {string} id An id containing elements to target
386 * @param {string} cssselector A selector to use to find targets
387 * @param {string} toggleclassname A classname to toggle
389 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
391 if (togglecssselector == '') {
392 togglecssselector = cssselector;
395 var node = Y.one('#'+id);
396 node.all(cssselector).each(function(n){
397 n.on('click', function(e){
399 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
400 if (this.test(togglecssselector)) {
401 this.toggleClass(toggleclassname);
403 this.ancestor(togglecssselector).toggleClass(toggleclassname);
408 // Attach this click event to the node rather than all selectors... will be much better
410 node.on('click', function(e){
411 if (e.target.hasClass('addtoall')) {
412 this.all(togglecssselector).addClass(toggleclassname);
413 } else if (e.target.hasClass('removefromall')) {
414 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
420 * Initialises a colour picker
422 * Designed to be used with admin_setting_configcolourpicker although could be used
423 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
424 * above or below the input (must have the same parent) and then call this with the
427 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
428 * contrib/blocks. For better docs refer to that.
432 * @param {object} previewconf
434 M.util.init_colour_picker = function(Y, id, previewconf) {
436 * We need node and event-mouseenter
438 Y.use('node', 'event-mouseenter', function(){
440 * The colour picker object
449 eventMouseEnter : null,
450 eventMouseLeave : null,
451 eventMouseMove : null,
456 * Initalises the colour picker by putting everything together and wiring the events
459 this.input = Y.one('#'+id);
460 this.box = this.input.ancestor().one('.admin_colourpicker');
461 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
462 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
463 this.preview = Y.Node.create('<div class="previewcolour"></div>');
464 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
465 this.current = Y.Node.create('<div class="currentcolour"></div>');
466 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
467 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
469 if (typeof(previewconf) === 'object' && previewconf !== null) {
470 Y.one('#'+id+'_preview').on('click', function(e){
471 if (Y.Lang.isString(previewconf.selector)) {
472 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
474 for (var i in previewconf.selector) {
475 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
481 this.eventClick = this.image.on('click', this.pickColour, this);
482 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
485 * Starts to follow the mouse once it enter the image
487 startFollow : function(e) {
488 this.eventMouseEnter.detach();
489 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
490 this.eventMouseMove = this.image.on('mousemove', function(e){
491 this.preview.setStyle('backgroundColor', this.determineColour(e));
495 * Stops following the mouse
497 endFollow : function(e) {
498 this.eventMouseMove.detach();
499 this.eventMouseLeave.detach();
500 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
503 * Picks the colour the was clicked on
505 pickColour : function(e) {
506 var colour = this.determineColour(e);
507 this.input.set('value', colour);
508 this.current.setStyle('backgroundColor', colour);
511 * Calculates the colour fromthe given co-ordinates
513 determineColour : function(e) {
514 var eventx = Math.floor(e.pageX-e.target.getX());
515 var eventy = Math.floor(e.pageY-e.target.getY());
517 var imagewidth = this.width;
518 var imageheight = this.height;
519 var factor = this.factor;
520 var colour = [255,0,0];
531 var matrixcount = matrices.length;
532 var limit = Math.round(imagewidth/matrixcount);
533 var heightbreak = Math.round(imageheight/2);
535 for (var x = 0; x < imagewidth; x++) {
536 var divisor = Math.floor(x / limit);
537 var matrix = matrices[divisor];
539 colour[0] += matrix[0]*factor;
540 colour[1] += matrix[1]*factor;
541 colour[2] += matrix[2]*factor;
548 var pixel = [colour[0], colour[1], colour[2]];
549 if (eventy < heightbreak) {
550 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
551 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
552 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
553 } else if (eventy > heightbreak) {
554 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
555 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
556 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
559 return this.convert_rgb_to_hex(pixel);
562 * Converts an RGB value to Hex
564 convert_rgb_to_hex : function(rgb) {
566 var hexchars = "0123456789ABCDEF";
567 for (var i=0; i<3; i++) {
568 var number = Math.abs(rgb[i]);
569 if (number == 0 || isNaN(number)) {
572 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
579 * Initialise the colour picker :) Hoorah
585 M.util.init_block_hider = function(Y, config) {
586 Y.use('base', 'node', function(Y) {
587 M.util.block_hider = M.util.block_hider || (function(){
588 var blockhider = function() {
589 blockhider.superclass.constructor.apply(this, arguments);
591 blockhider.prototype = {
592 initializer : function(config) {
593 this.set('block', '#'+this.get('id'));
594 var b = this.get('block'),
597 if (t && (a = t.one('.block_action'))) {
598 var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
599 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
600 hide.on('keypress', this.updateStateKey, this, true);
601 var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
602 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
603 show.on('keypress', this.updateStateKey, this, false);
604 a.insert(show, 0).insert(hide, 0);
607 updateState : function(e, hide) {
608 M.util.set_user_preference(this.get('preference'), hide);
610 this.get('block').addClass('hidden');
612 this.get('block').removeClass('hidden');
615 updateStateKey : function(e, hide) {
616 if (e.keyCode == 13) { //allow hide/show via enter key
617 this.updateState(this, hide);
621 Y.extend(blockhider, Y.Base, blockhider.prototype, {
627 value : M.util.image_url('t/switch_minus', 'moodle')
630 value : M.util.image_url('t/switch_plus', 'moodle')
633 setter : function(node) {
641 new M.util.block_hider(config);
646 * @var pending_js - The keys are the list of all pending js actions.
649 M.util.pending_js = [];
650 M.util.complete_js = [];
653 * Register any long running javascript code with a unique identifier.
654 * Should be followed with a call to js_complete with a matching
655 * idenfitier when the code is complete. May also be called with no arguments
656 * to test if there is any js calls pending. This is relied on by behat so that
657 * it can wait for all pending updates before interacting with a page.
658 * @param String uniqid - optional, if provided,
659 * registers this identifier until js_complete is called.
660 * @return boolean - True if there is any pending js.
662 M.util.js_pending = function(uniqid) {
663 if (uniqid !== false) {
664 M.util.pending_js.push(uniqid);
667 return M.util.pending_js.length;
671 M.util.js_pending('init');
674 * Register listeners for Y.io start/end so we can wait for them in behat.
676 YUI.add('moodle-core-io', function(Y) {
677 Y.on('io:start', function(id) {
678 M.util.js_pending('io:' + id);
680 Y.on('io:end', function(id) {
681 M.util.js_complete('io:' + id);
691 * Unregister any long running javascript code by unique identifier.
692 * This function should form a matching pair with js_pending
694 * @param String uniqid - required, unregisters this identifier
695 * @return boolean - True if there is any pending js.
697 M.util.js_complete = function(uniqid) {
698 // Use the Y.Array.indexOf instead of the native because some older browsers do not support
699 // the native function. Y.Array polyfills the native function if it does not exist.
700 var index = Y.Array.indexOf(M.util.pending_js, uniqid);
702 M.util.complete_js.push(M.util.pending_js.splice(index, 1));
705 return M.util.pending_js.length;
709 * Returns a string registered in advance for usage in JavaScript
711 * If you do not pass the third parameter, the function will just return
712 * the corresponding value from the M.str object. If the third parameter is
713 * provided, the function performs {$a} placeholder substitution in the
714 * same way as PHP get_string() in Moodle does.
716 * @param {String} identifier string identifier
717 * @param {String} component the component providing the string
718 * @param {Object|String} a optional variable to populate placeholder with
720 M.util.get_string = function(identifier, component, a) {
723 if (M.cfg.developerdebug) {
724 // creating new instance if YUI is not optimal but it seems to be better way then
725 // require the instance via the function API - note that it is used in rare cases
726 // for debugging only anyway
727 // To ensure we don't kill browser performance if hundreds of get_string requests
728 // are made we cache the instance we generate within the M.util namespace.
729 // We don't publicly define the variable so that it doesn't get abused.
730 if (typeof M.util.get_string_yui_instance === 'undefined') {
731 M.util.get_string_yui_instance = new YUI({ debug : true });
733 var Y = M.util.get_string_yui_instance;
736 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
737 stringvalue = '[[' + identifier + ',' + component + ']]';
738 if (M.cfg.developerdebug) {
739 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
744 stringvalue = M.str[component][identifier];
746 if (typeof a == 'undefined') {
747 // no placeholder substitution requested
751 if (typeof a == 'number' || typeof a == 'string') {
752 // replace all occurrences of {$a} with the placeholder value
753 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
757 if (typeof a == 'object') {
758 // replace {$a->key} placeholders
760 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
761 if (M.cfg.developerdebug) {
762 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
766 var search = '{$a->' + key + '}';
767 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
768 search = new RegExp(search, 'g');
769 stringvalue = stringvalue.replace(search, a[key]);
774 if (M.cfg.developerdebug) {
775 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
781 * Set focus on username or password field of the login form
783 M.util.focus_login_form = function(Y) {
784 var username = Y.one('#username');
785 var password = Y.one('#password');
787 if (username == null || password == null) {
788 // something is wrong here
792 var curElement = document.activeElement
793 if (curElement == 'undefined') {
794 // legacy browser - skip refocus protection
795 } else if (curElement.tagName == 'INPUT') {
796 // user was probably faster to focus something, do not mess with focus
800 if (username.get('value') == '') {
808 * Set focus on login error message
810 M.util.focus_login_error = function(Y) {
811 var errorlog = Y.one('#loginerrormessage');
818 * Adds lightbox hidden element that covers the whole node.
821 * @param {Node} the node lightbox should be added to
822 * @retun {Node} created lightbox node
824 M.util.add_lightbox = function(Y, node) {
825 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
827 // Check if lightbox is already there
828 if (node.one('.lightbox')) {
829 return node.one('.lightbox');
832 node.setStyle('position', 'relative');
833 var waiticon = Y.Node.create('<img />')
835 'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
838 'position' : 'relative',
842 var lightbox = Y.Node.create('<div></div>')
845 'position' : 'absolute',
850 'backgroundColor' : 'white',
851 'textAlign' : 'center'
853 .setAttribute('class', 'lightbox')
856 lightbox.appendChild(waiticon);
857 node.append(lightbox);
862 * Appends a hidden spinner element to the specified node.
865 * @param {Node} the node the spinner should be added to
866 * @return {Node} created spinner node
868 M.util.add_spinner = function(Y, node) {
869 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
871 // Check if spinner is already there
872 if (node.one('.spinner')) {
873 return node.one('.spinner');
876 var spinner = Y.Node.create('<img />')
877 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
879 .addClass('iconsmall')
882 node.append(spinner);
886 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
888 function checkall() {
889 var inputs = document.getElementsByTagName('input');
890 for (var i = 0; i < inputs.length; i++) {
891 if (inputs[i].type == 'checkbox') {
892 if (inputs[i].disabled || inputs[i].readOnly) {
895 inputs[i].checked = true;
900 function checknone() {
901 var inputs = document.getElementsByTagName('input');
902 for (var i = 0; i < inputs.length; i++) {
903 if (inputs[i].type == 'checkbox') {
904 if (inputs[i].disabled || inputs[i].readOnly) {
907 inputs[i].checked = false;
913 * Either check, or uncheck, all checkboxes inside the element with id is
914 * @param id the id of the container
915 * @param checked the new state, either '' or 'checked'.
917 function select_all_in_element_with_id(id, checked) {
918 var container = document.getElementById(id);
922 var inputs = container.getElementsByTagName('input');
923 for (var i = 0; i < inputs.length; ++i) {
924 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
925 inputs[i].checked = checked;
930 function select_all_in(elTagName, elClass, elId) {
931 var inputs = document.getElementsByTagName('input');
932 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
933 for(var i = 0; i < inputs.length; ++i) {
934 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
935 inputs[i].checked = 'checked';
940 function deselect_all_in(elTagName, elClass, elId) {
941 var inputs = document.getElementsByTagName('INPUT');
942 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
943 for(var i = 0; i < inputs.length; ++i) {
944 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
945 inputs[i].checked = '';
950 function confirm_if(expr, message) {
954 return confirm(message);
959 findParentNode (start, elementName, elementClass, elementID)
961 Travels up the DOM hierarchy to find a parent element with the
962 specified tag name, class, and id. All conditions must be met,
963 but any can be ommitted. Returns the BODY element if no match
966 function findParentNode(el, elName, elClass, elId) {
967 while (el.nodeName.toUpperCase() != 'BODY') {
968 if ((!elName || el.nodeName.toUpperCase() == elName) &&
969 (!elClass || el.className.indexOf(elClass) != -1) &&
970 (!elId || el.id == elId)) {
978 findChildNode (start, elementName, elementClass, elementID)
980 Travels down the DOM hierarchy to find all child elements with the
981 specified tag name, class, and id. All conditions must be met,
982 but any can be ommitted.
983 Doesn't examine children of matches.
985 @deprecated since Moodle 2.7 - please do not use this function any more.
986 @todo MDL-43242 This will be deleted in Moodle 2.9.
989 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
990 Y.log("findChildNodes() is deprecated. Please use Y.all instead.",
991 "warn", "javascript-static.js");
992 var children = new Array();
993 for (var i = 0; i < start.childNodes.length; i++) {
994 var classfound = false;
995 var child = start.childNodes[i];
996 if((child.nodeType == 1) &&//element node type
997 (elementClass && (typeof(child.className)=='string'))) {
998 var childClasses = child.className.split(/\s+/);
999 for (var childClassIndex in childClasses) {
1000 if (childClasses[childClassIndex]==elementClass) {
1006 if(child.nodeType == 1) { //element node type
1007 if ( (!tagName || child.nodeName == tagName) &&
1008 (!elementClass || classfound)&&
1009 (!elementID || child.id == elementID) &&
1010 (!elementName || child.name == elementName))
1012 children = children.concat(child);
1014 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
1021 function unmaskPassword(id) {
1022 var pw = document.getElementById(id);
1023 var chb = document.getElementById(id+'unmask');
1025 // MDL-30438 - The capability to changing the value of input type is not supported by IE8 or lower.
1026 // Replacing existing child with a new one, removed all yui properties for the node. Therefore, this
1027 // functionality won't work in IE8 or lower.
1028 // This is a temporary fixed to allow other browsers to function properly.
1029 if (Y.UA.ie == 0 || Y.UA.ie >= 9) {
1033 pw.type = "password";
1035 } else { //IE Browser version 8 or lower
1037 // first try IE way - it can not set name attribute later
1039 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
1041 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
1043 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
1045 var newpw = document.createElement('input');
1046 newpw.setAttribute('autocomplete', 'off');
1047 newpw.setAttribute('name', pw.name);
1049 newpw.setAttribute('type', 'text');
1051 newpw.setAttribute('type', 'password');
1053 newpw.setAttribute('class', pw.getAttribute('class'));
1056 newpw.size = pw.size;
1057 newpw.onblur = pw.onblur;
1058 newpw.onchange = pw.onchange;
1059 newpw.value = pw.value;
1060 pw.parentNode.replaceChild(newpw, pw);
1064 function filterByParent(elCollection, parentFinder) {
1065 var filteredCollection = [];
1066 for (var i = 0; i < elCollection.length; ++i) {
1067 var findParent = parentFinder(elCollection[i]);
1068 if (findParent.nodeName.toUpperCase() != 'BODY') {
1069 filteredCollection.push(elCollection[i]);
1072 return filteredCollection;
1076 All this is here just so that IE gets to handle oversized blocks
1077 in a visually pleasing manner. It does a browser detect. So sue me.
1080 function fix_column_widths() {
1081 var agt = navigator.userAgent.toLowerCase();
1082 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1083 fix_column_width('left-column');
1084 fix_column_width('right-column');
1088 function fix_column_width(colName) {
1089 if(column = document.getElementById(colName)) {
1090 if(!column.offsetWidth) {
1091 setTimeout("fix_column_width('" + colName + "')", 20);
1096 var nodes = column.childNodes;
1098 for(i = 0; i < nodes.length; ++i) {
1099 if(nodes[i].className.indexOf("block") != -1 ) {
1100 if(width < nodes[i].offsetWidth) {
1101 width = nodes[i].offsetWidth;
1106 for(i = 0; i < nodes.length; ++i) {
1107 if(nodes[i].className.indexOf("block") != -1 ) {
1108 nodes[i].style.width = width + 'px';
1116 Insert myValue at current cursor position
1118 function insertAtCursor(myField, myValue) {
1120 if (document.selection) {
1122 sel = document.selection.createRange();
1125 // Mozilla/Netscape support
1126 else if (myField.selectionStart || myField.selectionStart == '0') {
1127 var startPos = myField.selectionStart;
1128 var endPos = myField.selectionEnd;
1129 myField.value = myField.value.substring(0, startPos)
1130 + myValue + myField.value.substring(endPos, myField.value.length);
1132 myField.value += myValue;
1138 Call instead of setting window.onload directly or setting body onload=.
1139 Adds your function to a chain of functions rather than overwriting anything
1141 @deprecated Since Moodle 2.7. This will be removed in Moodle 2.9.
1143 function addonload(fn) {
1144 Y.log('addonload has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1145 'warn', 'javascript-static.js');
1146 var oldhandler=window.onload;
1147 window.onload=function() {
1148 if(oldhandler) oldhandler();
1153 * Replacement for getElementsByClassName in browsers that aren't cool enough
1155 * Relying on the built-in getElementsByClassName is far, far faster than
1158 * Note: the third argument used to be an object with odd behaviour. It now
1159 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1160 * mimicked if you pass an object.
1162 * @param {Node} oElm The top-level node for searching. To search a whole
1163 * document, use `document`.
1164 * @param {String} strTagName filter by tag names
1165 * @param {String} name same as HTML5 spec
1166 * @deprecated Since Moodle 2.7. This will be removed in Moodle 2.9.
1168 function getElementsByClassName(oElm, strTagName, name) {
1169 Y.log('getElementsByClassName has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1170 'warn', 'javascript-static.js');
1171 // for backwards compatibility
1172 if(typeof name == "object") {
1173 var names = new Array();
1174 for(var i=0; i<name.length; i++) names.push(names[i]);
1175 name = names.join('');
1177 // use native implementation if possible
1178 if (oElm.getElementsByClassName && Array.filter) {
1179 if (strTagName == '*') {
1180 return oElm.getElementsByClassName(name);
1182 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1183 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1187 // native implementation unavailable, fall back to slow method
1188 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1189 var arrReturnElements = new Array();
1190 var arrRegExpClassNames = new Array();
1191 var names = name.split(' ');
1192 for(var i=0; i<names.length; i++) {
1193 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1197 for(var j=0; j<arrElements.length; j++) {
1198 oElement = arrElements[j];
1200 for(var k=0; k<arrRegExpClassNames.length; k++) {
1201 if(!arrRegExpClassNames[k].test(oElement.className)) {
1202 bMatchesAll = false;
1207 arrReturnElements.push(oElement);
1210 return (arrReturnElements)
1214 * Increment a file name.
1216 * @param string file name.
1217 * @param boolean ignoreextension do not extract the extension prior to appending the
1218 * suffix. Useful when incrementing folder names.
1219 * @return string the incremented file name.
1221 function increment_filename(filename, ignoreextension) {
1223 var basename = filename;
1225 // Split the file name into the basename + extension.
1226 if (!ignoreextension) {
1227 var dotpos = filename.lastIndexOf('.');
1228 if (dotpos !== -1) {
1229 basename = filename.substr(0, dotpos);
1230 extension = filename.substr(dotpos, filename.length);
1234 // Look to see if the name already has (NN) at the end of it.
1236 var hasnumber = basename.match(/^(.*) \((\d+)\)$/);
1237 if (hasnumber !== null) {
1238 // Note the current number & remove it from the basename.
1239 number = parseInt(hasnumber[2], 10);
1240 basename = hasnumber[1];
1244 var newname = basename + ' (' + number + ')' + extension;
1249 * Return whether we are in right to left mode or not.
1253 function right_to_left() {
1254 var body = Y.one('body');
1256 if (body && body.hasClass('dir-rtl')) {
1262 function openpopup(event, args) {
1265 if (event.preventDefault) {
1266 event.preventDefault();
1268 event.returnValue = false;
1272 // Make sure the name argument is set and valid.
1273 var nameregex = /[^a-z0-9_]/i;
1274 if (typeof args.name !== 'string') {
1275 args.name = '_blank';
1276 } else if (args.name.match(nameregex)) {
1277 // Cleans window name because IE does not support funky ones.
1278 if (M.cfg.developerdebug) {
1279 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup(): ' + args.name);
1281 args.name = args.name.replace(nameregex, '_');
1284 var fullurl = args.url;
1285 if (!args.url.match(/https?:\/\//)) {
1286 fullurl = M.cfg.wwwroot + args.url;
1288 if (args.fullscreen) {
1289 args.options = args.options.
1290 replace(/top=\d+/, 'top=0').
1291 replace(/left=\d+/, 'left=0').
1292 replace(/width=\d+/, 'width=' + screen.availWidth).
1293 replace(/height=\d+/, 'height=' + screen.availHeight);
1295 var windowobj = window.open(fullurl,args.name,args.options);
1300 if (args.fullscreen) {
1301 // In some browser / OS combinations (E.g. Chrome on Windows), the
1302 // window initially opens slighly too big. The width and heigh options
1303 // seem to control the area inside the browser window, so what with
1304 // scroll-bars, etc. the actual window is bigger than the screen.
1305 // Therefore, we need to fix things up after the window is open.
1306 var hackcount = 100;
1307 var get_size_exactly_right = function() {
1308 windowobj.moveTo(0, 0);
1309 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1311 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1312 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1313 // about 50ms) after the window is open, then it actually behaves
1314 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1315 // check that the resize actually worked, and if not, repeatedly try
1316 // again after a short delay until it works (but with a limit of
1317 // hackcount repeats.
1318 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1320 setTimeout(get_size_exactly_right, 10);
1323 setTimeout(get_size_exactly_right, 0);
1330 /** Close the current browser window. */
1331 function close_window(e) {
1332 if (e.preventDefault) {
1335 e.returnValue = false;
1341 * Used in a couple of modules to hide navigation areas when using AJAX
1342 * @deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
1344 function show_item(itemid) {
1345 Y.log('show_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1346 'warn', 'javascript-static.js');
1347 var item = Y.one('#' + itemid);
1353 // Deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
1354 function destroy_item(itemid) {
1355 Y.log('destroy_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1356 'warn', 'javascript-static.js');
1357 var item = Y.one('#' + itemid);
1363 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1364 * @param controlid the control id.
1366 function focuscontrol(controlid) {
1367 var control = document.getElementById(controlid);
1374 * Transfers keyboard focus to an HTML element based on the old style style of focus
1375 * This function should be removed as soon as it is no longer used
1377 function old_onload_focus(formid, controlname) {
1378 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1379 document.forms[formid].elements[controlname].focus();
1383 function build_querystring(obj) {
1384 return convert_object_to_string(obj, '&');
1387 function build_windowoptionsstring(obj) {
1388 return convert_object_to_string(obj, ',');
1391 function convert_object_to_string(obj, separator) {
1392 if (typeof obj !== 'object') {
1397 k = encodeURIComponent(k);
1399 if(obj[k] instanceof Array) {
1400 for(var i in value) {
1401 list.push(k+'[]='+encodeURIComponent(value[i]));
1404 list.push(k+'='+encodeURIComponent(value));
1407 return list.join(separator);
1410 function stripHTML(str) {
1411 var re = /<\S[^><]*>/g;
1412 var ret = str.replace(re, "");
1416 Number.prototype.fixed=function(n){
1418 return round(Number(this)*pow(10,n))/pow(10,n);
1420 function update_progress_bar (id, width, pt, msg, es){
1422 var status = document.getElementById("status_"+id);
1423 var percent_indicator = document.getElementById("pt_"+id);
1424 var progress_bar = document.getElementById("progress_"+id);
1425 var time_es = document.getElementById("time_"+id);
1426 status.innerHTML = msg;
1427 percent_indicator.innerHTML = percent.fixed(2) + '%';
1428 if(percent == 100) {
1429 progress_bar.style.background = "green";
1430 time_es.style.display = "none";
1432 progress_bar.style.background = "#FFCC66";
1434 time_es.innerHTML = "";
1436 time_es.innerHTML = es.fixed(2)+" sec";
1437 time_es.style.display
1441 progress_bar.style.width = width + "px";
1446 // ===== Deprecated core Javascript functions for Moodle ====
1447 // DO NOT USE!!!!!!!
1448 // Do not put this stuff in separate file because it only adds extra load on servers!
1451 * Used in a couple of modules to hide navigation areas when using AJAX
1452 * @deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
1454 function hide_item(itemid) {
1455 Y.log('hide_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1456 'warn', 'javascript-static.js');
1457 var item = Y.one('#' + itemid);
1463 M.util.help_popups = {
1464 setup : function(Y) {
1465 Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this);
1467 open_popup : function(e) {
1468 // Prevent the default page action
1471 // Grab the anchor that was clicked
1472 var anchor = e.target.ancestor('a', true);
1475 'url' : anchor.getAttribute('href'),
1493 args.options = options.join(',');
1500 * Custom menu namespace
1502 M.core_custom_menu = {
1504 * This method is used to initialise a custom menu given the id that belongs
1505 * to the custom menu's root node.
1508 * @param {string} nodeid
1510 init : function(Y, nodeid) {
1511 var node = Y.one('#'+nodeid);
1513 Y.use('node-menunav', function(Y) {
1515 // Remove the javascript-disabled class.... obviously javascript is enabled.
1516 node.removeClass('javascript-disabled');
1517 // Initialise the menunav plugin
1518 node.plug(Y.Plugin.NodeMenuNav);
1525 * Used to store form manipulation methods and enhancments
1527 M.form = M.form || {};
1530 * Converts a nbsp indented select box into a multi drop down custom control much
1531 * like the custom menu. It also selectable categories on or off.
1533 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1536 * @param {string} id
1537 * @param {Array} options
1539 M.form.init_smartselect = function(Y, id, options) {
1540 if (!id.match(/^id_/)) {
1543 var select = Y.one('select#'+id);
1547 Y.use('event-delegate',function(){
1553 currentvalue : null,
1557 selectablecategories : true,
1565 init : function(Y, id, args, nodes) {
1566 if (typeof(args)=='object') {
1567 for (var i in this.cfg) {
1568 if (args[i] || args[i]===false) {
1569 this.cfg[i] = args[i];
1574 // Display a loading message first up
1575 this.nodes.select = nodes.select;
1577 this.currentvalue = this.nodes.select.get('selectedIndex');
1578 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1580 var options = Array();
1581 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1582 this.nodes.select.all('option').each(function(option, index) {
1583 var rawtext = option.get('innerHTML');
1584 var text = rawtext.replace(/^( )*/, '');
1585 if (rawtext === text) {
1586 text = rawtext.replace(/^(\s)*/, '');
1587 var depth = (rawtext.length - text.length ) + 1;
1589 var depth = ((rawtext.length - text.length )/12)+1;
1591 option.set('innerHTML', text);
1592 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1595 this.structure = [];
1596 var structcount = 0;
1597 for (var i in options) {
1600 this.structure.push(o);
1604 var current = this.structure[structcount-1];
1605 for (var j = 0; j < o.depth-1;j++) {
1606 if (current && current.children) {
1607 current = current.children[current.children.length-1];
1610 if (current && current.children) {
1611 current.children.push(o);
1616 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1617 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1618 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1619 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1621 if (this.cfg.mode == null) {
1622 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1623 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1624 this.cfg.mode = 'compact';
1626 this.cfg.mode = 'spanning';
1630 if (this.cfg.mode == 'compact') {
1631 this.nodes.menu.addClass('compactmenu');
1633 this.nodes.menu.addClass('spanningmenu');
1634 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1637 Y.one(document.body).append(this.nodes.menu);
1638 var pos = this.nodes.select.getXY();
1640 this.nodes.menu.setXY(pos);
1641 this.nodes.menu.on('click', this.handle_click, this);
1643 Y.one(window).on('resize', function(){
1644 var pos = this.nodes.select.getXY();
1646 this.nodes.menu.setXY(pos);
1649 generate_menu_content : function() {
1650 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1651 content += this.generate_submenu_content(this.structure[0], true);
1652 content += '</ul></div>';
1655 generate_submenu_content : function(item, rootelement) {
1656 this.submenucount++;
1658 if (item.children.length > 0) {
1660 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'"> </div>';
1661 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1662 content += '<div class="smartselect_menu_content">';
1664 content += '<li class="smartselect_submenuitem">';
1665 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1666 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1667 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1668 content += '<div class="smartselect_submenu_content">';
1671 for (var i in item.children) {
1672 content += this.generate_submenu_content(item.children[i],false);
1675 content += '</div>';
1676 content += '</div>';
1682 content += '<li class="smartselect_menuitem">';
1683 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1688 select : function(e) {
1691 this.currenttext = t.get('innerHTML');
1692 this.currentvalue = t.getAttribute('value');
1693 this.nodes.select.set('selectedIndex', this.currentvalue);
1696 handle_click : function(e) {
1697 var target = e.target;
1698 if (target.hasClass('smartselect_mask')) {
1700 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1702 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1703 this.show_sub_menu(e);
1706 show_menu : function(e) {
1708 var menu = e.target.ancestor().one('.smartselect_menu');
1709 menu.addClass('visible');
1710 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1712 show_sub_menu : function(e) {
1714 var target = e.target;
1715 if (!target.hasClass('smartselect_submenuitem')) {
1716 target = target.ancestor('.smartselect_submenuitem');
1718 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1719 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1722 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1723 target.one('.smartselect_submenu').addClass('visible');
1725 hide_menu : function() {
1726 this.nodes.menu.all('.visible').removeClass('visible');
1727 if (this.shownevent) {
1728 this.shownevent.detach();
1732 smartselect.init(Y, id, options, {select:select});
1736 /** List of flv players to be loaded */
1737 M.util.video_players = [];
1738 /** List of mp3 players to be loaded */
1739 M.util.audio_players = [];
1743 * @param id element id
1744 * @param fileurl media url
1747 * @param autosize true means detect size from media
1749 M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1750 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
1759 M.util.add_audio_player = function (id, fileurl, small) {
1760 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
1764 * Initialise all audio and video player, must be called from page footer.
1766 M.util.load_flowplayer = function() {
1767 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1770 if (typeof(flowplayer) == 'undefined') {
1773 var embed_function = function() {
1774 if (loaded || typeof(flowplayer) == 'undefined') {
1782 /* TODO: add CSS color overrides for the flv flow player */
1784 for(var i=0; i<M.util.video_players.length; i++) {
1785 var video = M.util.video_players[i];
1786 if (video.width > 0 && video.height > 0) {
1787 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', width: video.width, height: video.height};
1789 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf';
1791 flowplayer(video.id, src, {
1792 plugins: {controls: controls},
1794 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1795 onMetaData: function(clip) {
1796 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1797 clip.mvideo.resized = true;
1798 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1799 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1800 // bad luck, we have to guess - we may not get metadata at all
1801 var width = clip.width;
1802 var height = clip.height;
1804 var width = clip.metaData.width;
1805 var height = clip.metaData.height;
1807 var minwidth = 300; // controls are messed up in smaller objects
1808 if (width < minwidth) {
1809 height = (height * minwidth) / width;
1813 var object = this._api();
1814 object.width = width;
1815 object.height = height;
1821 if (M.util.audio_players.length == 0) {
1834 backgroundGradient: [0.5,0,0.3]
1838 for (var j=0; j < document.styleSheets.length; j++) {
1840 // To avoid javascript security violation accessing cross domain stylesheets
1841 var allrules = false;
1843 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1844 allrules = document.styleSheets[j].rules;
1845 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1846 allrules = document.styleSheets[j].cssRules;
1855 // On cross domain style sheets Chrome V8 allows access to rules but returns null
1860 for(var i=0; i<allrules.length; i++) {
1862 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1863 if (typeof(allrules[i].cssText) != 'undefined') {
1864 rule = allrules[i].cssText;
1865 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1866 rule = allrules[i].style.cssText;
1868 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1869 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1870 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1871 controls[colprop] = rule;
1878 for(i=0; i<M.util.audio_players.length; i++) {
1879 var audio = M.util.audio_players[i];
1881 controls.controlall = false;
1882 controls.height = 15;
1883 controls.time = false;
1885 controls.controlall = true;
1886 controls.height = 25;
1887 controls.time = true;
1889 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', {
1890 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.11.swf'}},
1891 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1896 if (M.cfg.jsrev == -1) {
1897 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.13.js';
1899 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.13.min.js&rev=' + M.cfg.jsrev;
1901 var fileref = document.createElement('script');
1902 fileref.setAttribute('type','text/javascript');
1903 fileref.setAttribute('src', jsurl);
1904 fileref.onload = embed_function;
1905 fileref.onreadystatechange = embed_function;
1906 document.getElementsByTagName('head')[0].appendChild(fileref);