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);
345 var resize_object = function() {
346 obj.setStyle('width', '0px');
347 obj.setStyle('height', '0px');
348 var newwidth = get_htmlelement_size('maincontent', 'width') - 35;
350 if (newwidth > 500) {
351 obj.setStyle('width', newwidth + 'px');
353 obj.setStyle('width', '500px');
356 var headerheight = get_htmlelement_size('page-header', 'height');
357 var footerheight = get_htmlelement_size('page-footer', 'height');
358 var newheight = parseInt(Y.one('body').get('docHeight')) - footerheight - headerheight - 100;
359 if (newheight < 400) {
362 obj.setStyle('height', newheight+'px');
366 // fix layout if window resized too
367 window.onresize = function() {
373 * Breaks out all links to the top frame - used in frametop page layout.
375 M.util.init_frametop = function(Y) {
376 Y.all('a').each(function(node) {
377 node.set('target', '_top');
379 Y.all('form').each(function(node) {
380 node.set('target', '_top');
385 * Finds all nodes that match the given CSS selector and attaches events to them
386 * so that they toggle a given classname when clicked.
389 * @param {string} id An id containing elements to target
390 * @param {string} cssselector A selector to use to find targets
391 * @param {string} toggleclassname A classname to toggle
393 M.util.init_toggle_class_on_click = function(Y, id, cssselector, toggleclassname, togglecssselector) {
395 if (togglecssselector == '') {
396 togglecssselector = cssselector;
399 var node = Y.one('#'+id);
400 node.all(cssselector).each(function(n){
401 n.on('click', function(e){
403 if (e.target.test(cssselector) && !e.target.test('a') && !e.target.test('img')) {
404 if (this.test(togglecssselector)) {
405 this.toggleClass(toggleclassname);
407 this.ancestor(togglecssselector).toggleClass(toggleclassname);
412 // Attach this click event to the node rather than all selectors... will be much better
414 node.on('click', function(e){
415 if (e.target.hasClass('addtoall')) {
416 this.all(togglecssselector).addClass(toggleclassname);
417 } else if (e.target.hasClass('removefromall')) {
418 this.all(togglecssselector+'.'+toggleclassname).removeClass(toggleclassname);
424 * Initialises a colour picker
426 * Designed to be used with admin_setting_configcolourpicker although could be used
427 * anywhere, just give a text input an id and insert a div with the class admin_colourpicker
428 * above or below the input (must have the same parent) and then call this with the
431 * This code was mostly taken from my [Sam Hemelryk] css theme tool available in
432 * contrib/blocks. For better docs refer to that.
436 * @param {object} previewconf
438 M.util.init_colour_picker = function(Y, id, previewconf) {
440 * We need node and event-mouseenter
442 Y.use('node', 'event-mouseenter', function(){
444 * The colour picker object
453 eventMouseEnter : null,
454 eventMouseLeave : null,
455 eventMouseMove : null,
460 * Initalises the colour picker by putting everything together and wiring the events
463 this.input = Y.one('#'+id);
464 this.box = this.input.ancestor().one('.admin_colourpicker');
465 this.image = Y.Node.create('<img alt="" class="colourdialogue" />');
466 this.image.setAttribute('src', M.util.image_url('i/colourpicker', 'moodle'));
467 this.preview = Y.Node.create('<div class="previewcolour"></div>');
468 this.preview.setStyle('width', this.height/2).setStyle('height', this.height/2).setStyle('backgroundColor', this.input.get('value'));
469 this.current = Y.Node.create('<div class="currentcolour"></div>');
470 this.current.setStyle('width', this.height/2).setStyle('height', this.height/2 -1).setStyle('backgroundColor', this.input.get('value'));
471 this.box.setContent('').append(this.image).append(this.preview).append(this.current);
473 if (typeof(previewconf) === 'object' && previewconf !== null) {
474 Y.one('#'+id+'_preview').on('click', function(e){
475 if (Y.Lang.isString(previewconf.selector)) {
476 Y.all(previewconf.selector).setStyle(previewconf.style, this.input.get('value'));
478 for (var i in previewconf.selector) {
479 Y.all(previewconf.selector[i]).setStyle(previewconf.style, this.input.get('value'));
485 this.eventClick = this.image.on('click', this.pickColour, this);
486 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
489 * Starts to follow the mouse once it enter the image
491 startFollow : function(e) {
492 this.eventMouseEnter.detach();
493 this.eventMouseLeave = Y.on('mouseleave', this.endFollow, this.image, this);
494 this.eventMouseMove = this.image.on('mousemove', function(e){
495 this.preview.setStyle('backgroundColor', this.determineColour(e));
499 * Stops following the mouse
501 endFollow : function(e) {
502 this.eventMouseMove.detach();
503 this.eventMouseLeave.detach();
504 this.eventMouseEnter = Y.on('mouseenter', this.startFollow, this.image, this);
507 * Picks the colour the was clicked on
509 pickColour : function(e) {
510 var colour = this.determineColour(e);
511 this.input.set('value', colour);
512 this.current.setStyle('backgroundColor', colour);
515 * Calculates the colour fromthe given co-ordinates
517 determineColour : function(e) {
518 var eventx = Math.floor(e.pageX-e.target.getX());
519 var eventy = Math.floor(e.pageY-e.target.getY());
521 var imagewidth = this.width;
522 var imageheight = this.height;
523 var factor = this.factor;
524 var colour = [255,0,0];
535 var matrixcount = matrices.length;
536 var limit = Math.round(imagewidth/matrixcount);
537 var heightbreak = Math.round(imageheight/2);
539 for (var x = 0; x < imagewidth; x++) {
540 var divisor = Math.floor(x / limit);
541 var matrix = matrices[divisor];
543 colour[0] += matrix[0]*factor;
544 colour[1] += matrix[1]*factor;
545 colour[2] += matrix[2]*factor;
552 var pixel = [colour[0], colour[1], colour[2]];
553 if (eventy < heightbreak) {
554 pixel[0] += Math.floor(((255-pixel[0])/heightbreak) * (heightbreak - eventy));
555 pixel[1] += Math.floor(((255-pixel[1])/heightbreak) * (heightbreak - eventy));
556 pixel[2] += Math.floor(((255-pixel[2])/heightbreak) * (heightbreak - eventy));
557 } else if (eventy > heightbreak) {
558 pixel[0] = Math.floor((imageheight-eventy)*(pixel[0]/heightbreak));
559 pixel[1] = Math.floor((imageheight-eventy)*(pixel[1]/heightbreak));
560 pixel[2] = Math.floor((imageheight-eventy)*(pixel[2]/heightbreak));
563 return this.convert_rgb_to_hex(pixel);
566 * Converts an RGB value to Hex
568 convert_rgb_to_hex : function(rgb) {
570 var hexchars = "0123456789ABCDEF";
571 for (var i=0; i<3; i++) {
572 var number = Math.abs(rgb[i]);
573 if (number == 0 || isNaN(number)) {
576 hex += hexchars.charAt((number-number%16)/16)+hexchars.charAt(number%16);
583 * Initialise the colour picker :) Hoorah
589 M.util.init_block_hider = function(Y, config) {
590 Y.use('base', 'node', function(Y) {
591 M.util.block_hider = M.util.block_hider || (function(){
592 var blockhider = function() {
593 blockhider.superclass.constructor.apply(this, arguments);
595 blockhider.prototype = {
596 initializer : function(config) {
597 this.set('block', '#'+this.get('id'));
598 var b = this.get('block'),
601 if (t && (a = t.one('.block_action'))) {
602 var hide = Y.Node.create('<img class="block-hider-hide" tabindex="0" alt="'+config.tooltipVisible+'" title="'+config.tooltipVisible+'" />');
603 hide.setAttribute('src', this.get('iconVisible')).on('click', this.updateState, this, true);
604 hide.on('keypress', this.updateStateKey, this, true);
605 var show = Y.Node.create('<img class="block-hider-show" tabindex="0" alt="'+config.tooltipHidden+'" title="'+config.tooltipHidden+'" />');
606 show.setAttribute('src', this.get('iconHidden')).on('click', this.updateState, this, false);
607 show.on('keypress', this.updateStateKey, this, false);
608 a.insert(show, 0).insert(hide, 0);
611 updateState : function(e, hide) {
612 M.util.set_user_preference(this.get('preference'), hide);
614 this.get('block').addClass('hidden');
616 this.get('block').removeClass('hidden');
619 updateStateKey : function(e, hide) {
620 if (e.keyCode == 13) { //allow hide/show via enter key
621 this.updateState(this, hide);
625 Y.extend(blockhider, Y.Base, blockhider.prototype, {
631 value : M.util.image_url('t/switch_minus', 'moodle')
634 value : M.util.image_url('t/switch_plus', 'moodle')
637 setter : function(node) {
645 new M.util.block_hider(config);
650 * @var pending_js - The keys are the list of all pending js actions.
653 M.util.pending_js = [];
654 M.util.complete_js = [];
657 * Register any long running javascript code with a unique identifier.
658 * Should be followed with a call to js_complete with a matching
659 * idenfitier when the code is complete. May also be called with no arguments
660 * to test if there is any js calls pending. This is relied on by behat so that
661 * it can wait for all pending updates before interacting with a page.
662 * @param String uniqid - optional, if provided,
663 * registers this identifier until js_complete is called.
664 * @return boolean - True if there is any pending js.
666 M.util.js_pending = function(uniqid) {
667 if (uniqid !== false) {
668 M.util.pending_js.push(uniqid);
671 return M.util.pending_js.length;
675 M.util.js_pending('init');
678 * Register listeners for Y.io start/end so we can wait for them in behat.
680 YUI.add('moodle-core-io', function(Y) {
681 Y.on('io:start', function(id) {
682 M.util.js_pending('io:' + id);
684 Y.on('io:end', function(id) {
685 M.util.js_complete('io:' + id);
695 * Unregister any long running javascript code by unique identifier.
696 * This function should form a matching pair with js_pending
698 * @param String uniqid - required, unregisters this identifier
699 * @return boolean - True if there is any pending js.
701 M.util.js_complete = function(uniqid) {
702 // Use the Y.Array.indexOf instead of the native because some older browsers do not support
703 // the native function. Y.Array polyfills the native function if it does not exist.
704 var index = Y.Array.indexOf(M.util.pending_js, uniqid);
706 M.util.complete_js.push(M.util.pending_js.splice(index, 1));
709 return M.util.pending_js.length;
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 * Set focus on login error message
814 M.util.focus_login_error = function(Y) {
815 var errorlog = Y.one('#loginerrormessage');
822 * Adds lightbox hidden element that covers the whole node.
825 * @param {Node} the node lightbox should be added to
826 * @retun {Node} created lightbox node
828 M.util.add_lightbox = function(Y, node) {
829 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
831 // Check if lightbox is already there
832 if (node.one('.lightbox')) {
833 return node.one('.lightbox');
836 node.setStyle('position', 'relative');
837 var waiticon = Y.Node.create('<img />')
839 'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
842 'position' : 'relative',
846 var lightbox = Y.Node.create('<div></div>')
849 'position' : 'absolute',
854 'backgroundColor' : 'white',
855 'textAlign' : 'center'
857 .setAttribute('class', 'lightbox')
860 lightbox.appendChild(waiticon);
861 node.append(lightbox);
866 * Appends a hidden spinner element to the specified node.
869 * @param {Node} the node the spinner should be added to
870 * @return {Node} created spinner node
872 M.util.add_spinner = function(Y, node) {
873 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
875 // Check if spinner is already there
876 if (node.one('.spinner')) {
877 return node.one('.spinner');
880 var spinner = Y.Node.create('<img />')
881 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
883 .addClass('iconsmall')
886 node.append(spinner);
890 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
892 function checkall() {
893 var inputs = document.getElementsByTagName('input');
894 for (var i = 0; i < inputs.length; i++) {
895 if (inputs[i].type == 'checkbox') {
896 if (inputs[i].disabled || inputs[i].readOnly) {
899 inputs[i].checked = true;
904 function checknone() {
905 var inputs = document.getElementsByTagName('input');
906 for (var i = 0; i < inputs.length; i++) {
907 if (inputs[i].type == 'checkbox') {
908 if (inputs[i].disabled || inputs[i].readOnly) {
911 inputs[i].checked = false;
917 * Either check, or uncheck, all checkboxes inside the element with id is
918 * @param id the id of the container
919 * @param checked the new state, either '' or 'checked'.
921 function select_all_in_element_with_id(id, checked) {
922 var container = document.getElementById(id);
926 var inputs = container.getElementsByTagName('input');
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 select_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 = 'checked';
944 function deselect_all_in(elTagName, elClass, elId) {
945 var inputs = document.getElementsByTagName('INPUT');
946 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
947 for(var i = 0; i < inputs.length; ++i) {
948 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
949 inputs[i].checked = '';
954 function confirm_if(expr, message) {
958 return confirm(message);
963 findParentNode (start, elementName, elementClass, elementID)
965 Travels up the DOM hierarchy to find a parent element with the
966 specified tag name, class, and id. All conditions must be met,
967 but any can be ommitted. Returns the BODY element if no match
970 function findParentNode(el, elName, elClass, elId) {
971 while (el.nodeName.toUpperCase() != 'BODY') {
972 if ((!elName || el.nodeName.toUpperCase() == elName) &&
973 (!elClass || el.className.indexOf(elClass) != -1) &&
974 (!elId || el.id == elId)) {
982 findChildNode (start, elementName, elementClass, elementID)
984 Travels down the DOM hierarchy to find all child elements with the
985 specified tag name, class, and id. All conditions must be met,
986 but any can be ommitted.
987 Doesn't examine children of matches.
989 @deprecated since Moodle 2.7 - please do not use this function any more.
990 @todo MDL-43242 This will be deleted in Moodle 2.9.
993 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
994 Y.log("findChildNodes() is deprecated. Please use Y.all instead.",
995 "warn", "javascript-static.js");
996 var children = new Array();
997 for (var i = 0; i < start.childNodes.length; i++) {
998 var classfound = false;
999 var child = start.childNodes[i];
1000 if((child.nodeType == 1) &&//element node type
1001 (elementClass && (typeof(child.className)=='string'))) {
1002 var childClasses = child.className.split(/\s+/);
1003 for (var childClassIndex in childClasses) {
1004 if (childClasses[childClassIndex]==elementClass) {
1010 if(child.nodeType == 1) { //element node type
1011 if ( (!tagName || child.nodeName == tagName) &&
1012 (!elementClass || classfound)&&
1013 (!elementID || child.id == elementID) &&
1014 (!elementName || child.name == elementName))
1016 children = children.concat(child);
1018 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
1025 function unmaskPassword(id) {
1026 var pw = document.getElementById(id);
1027 var chb = document.getElementById(id+'unmask');
1029 // MDL-30438 - The capability to changing the value of input type is not supported by IE8 or lower.
1030 // Replacing existing child with a new one, removed all yui properties for the node. Therefore, this
1031 // functionality won't work in IE8 or lower.
1032 // This is a temporary fixed to allow other browsers to function properly.
1033 if (Y.UA.ie == 0 || Y.UA.ie >= 9) {
1037 pw.type = "password";
1039 } else { //IE Browser version 8 or lower
1041 // first try IE way - it can not set name attribute later
1043 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
1045 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
1047 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
1049 var newpw = document.createElement('input');
1050 newpw.setAttribute('autocomplete', 'off');
1051 newpw.setAttribute('name', pw.name);
1053 newpw.setAttribute('type', 'text');
1055 newpw.setAttribute('type', 'password');
1057 newpw.setAttribute('class', pw.getAttribute('class'));
1060 newpw.size = pw.size;
1061 newpw.onblur = pw.onblur;
1062 newpw.onchange = pw.onchange;
1063 newpw.value = pw.value;
1064 pw.parentNode.replaceChild(newpw, pw);
1068 function filterByParent(elCollection, parentFinder) {
1069 var filteredCollection = [];
1070 for (var i = 0; i < elCollection.length; ++i) {
1071 var findParent = parentFinder(elCollection[i]);
1072 if (findParent.nodeName.toUpperCase() != 'BODY') {
1073 filteredCollection.push(elCollection[i]);
1076 return filteredCollection;
1080 All this is here just so that IE gets to handle oversized blocks
1081 in a visually pleasing manner. It does a browser detect. So sue me.
1084 function fix_column_widths() {
1085 var agt = navigator.userAgent.toLowerCase();
1086 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1087 fix_column_width('left-column');
1088 fix_column_width('right-column');
1092 function fix_column_width(colName) {
1093 if(column = document.getElementById(colName)) {
1094 if(!column.offsetWidth) {
1095 setTimeout("fix_column_width('" + colName + "')", 20);
1100 var nodes = column.childNodes;
1102 for(i = 0; i < nodes.length; ++i) {
1103 if(nodes[i].className.indexOf("block") != -1 ) {
1104 if(width < nodes[i].offsetWidth) {
1105 width = nodes[i].offsetWidth;
1110 for(i = 0; i < nodes.length; ++i) {
1111 if(nodes[i].className.indexOf("block") != -1 ) {
1112 nodes[i].style.width = width + 'px';
1120 Insert myValue at current cursor position
1122 function insertAtCursor(myField, myValue) {
1124 if (document.selection) {
1126 sel = document.selection.createRange();
1129 // Mozilla/Netscape support
1130 else if (myField.selectionStart || myField.selectionStart == '0') {
1131 var startPos = myField.selectionStart;
1132 var endPos = myField.selectionEnd;
1133 myField.value = myField.value.substring(0, startPos)
1134 + myValue + myField.value.substring(endPos, myField.value.length);
1136 myField.value += myValue;
1142 Call instead of setting window.onload directly or setting body onload=.
1143 Adds your function to a chain of functions rather than overwriting anything
1145 @deprecated Since Moodle 2.7. This will be removed in Moodle 2.9.
1147 function addonload(fn) {
1148 Y.log('addonload has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1149 'warn', 'javascript-static.js');
1150 var oldhandler=window.onload;
1151 window.onload=function() {
1152 if(oldhandler) oldhandler();
1157 * Replacement for getElementsByClassName in browsers that aren't cool enough
1159 * Relying on the built-in getElementsByClassName is far, far faster than
1162 * Note: the third argument used to be an object with odd behaviour. It now
1163 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1164 * mimicked if you pass an object.
1166 * @param {Node} oElm The top-level node for searching. To search a whole
1167 * document, use `document`.
1168 * @param {String} strTagName filter by tag names
1169 * @param {String} name same as HTML5 spec
1170 * @deprecated Since Moodle 2.7. This will be removed in Moodle 2.9.
1172 function getElementsByClassName(oElm, strTagName, name) {
1173 Y.log('getElementsByClassName has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1174 'warn', 'javascript-static.js');
1175 // for backwards compatibility
1176 if(typeof name == "object") {
1177 var names = new Array();
1178 for(var i=0; i<name.length; i++) names.push(names[i]);
1179 name = names.join('');
1181 // use native implementation if possible
1182 if (oElm.getElementsByClassName && Array.filter) {
1183 if (strTagName == '*') {
1184 return oElm.getElementsByClassName(name);
1186 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1187 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1191 // native implementation unavailable, fall back to slow method
1192 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1193 var arrReturnElements = new Array();
1194 var arrRegExpClassNames = new Array();
1195 var names = name.split(' ');
1196 for(var i=0; i<names.length; i++) {
1197 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1201 for(var j=0; j<arrElements.length; j++) {
1202 oElement = arrElements[j];
1204 for(var k=0; k<arrRegExpClassNames.length; k++) {
1205 if(!arrRegExpClassNames[k].test(oElement.className)) {
1206 bMatchesAll = false;
1211 arrReturnElements.push(oElement);
1214 return (arrReturnElements)
1218 * Increment a file name.
1220 * @param string file name.
1221 * @param boolean ignoreextension do not extract the extension prior to appending the
1222 * suffix. Useful when incrementing folder names.
1223 * @return string the incremented file name.
1225 function increment_filename(filename, ignoreextension) {
1227 var basename = filename;
1229 // Split the file name into the basename + extension.
1230 if (!ignoreextension) {
1231 var dotpos = filename.lastIndexOf('.');
1232 if (dotpos !== -1) {
1233 basename = filename.substr(0, dotpos);
1234 extension = filename.substr(dotpos, filename.length);
1238 // Look to see if the name already has (NN) at the end of it.
1240 var hasnumber = basename.match(/^(.*) \((\d+)\)$/);
1241 if (hasnumber !== null) {
1242 // Note the current number & remove it from the basename.
1243 number = parseInt(hasnumber[2], 10);
1244 basename = hasnumber[1];
1248 var newname = basename + ' (' + number + ')' + extension;
1253 * Return whether we are in right to left mode or not.
1257 function right_to_left() {
1258 var body = Y.one('body');
1260 if (body && body.hasClass('dir-rtl')) {
1266 function openpopup(event, args) {
1269 if (event.preventDefault) {
1270 event.preventDefault();
1272 event.returnValue = false;
1276 // Make sure the name argument is set and valid.
1277 var nameregex = /[^a-z0-9_]/i;
1278 if (typeof args.name !== 'string') {
1279 args.name = '_blank';
1280 } else if (args.name.match(nameregex)) {
1281 // Cleans window name because IE does not support funky ones.
1282 if (M.cfg.developerdebug) {
1283 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup(): ' + args.name);
1285 args.name = args.name.replace(nameregex, '_');
1288 var fullurl = args.url;
1289 if (!args.url.match(/https?:\/\//)) {
1290 fullurl = M.cfg.wwwroot + args.url;
1292 if (args.fullscreen) {
1293 args.options = args.options.
1294 replace(/top=\d+/, 'top=0').
1295 replace(/left=\d+/, 'left=0').
1296 replace(/width=\d+/, 'width=' + screen.availWidth).
1297 replace(/height=\d+/, 'height=' + screen.availHeight);
1299 var windowobj = window.open(fullurl,args.name,args.options);
1304 if (args.fullscreen) {
1305 // In some browser / OS combinations (E.g. Chrome on Windows), the
1306 // window initially opens slighly too big. The width and heigh options
1307 // seem to control the area inside the browser window, so what with
1308 // scroll-bars, etc. the actual window is bigger than the screen.
1309 // Therefore, we need to fix things up after the window is open.
1310 var hackcount = 100;
1311 var get_size_exactly_right = function() {
1312 windowobj.moveTo(0, 0);
1313 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1315 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1316 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1317 // about 50ms) after the window is open, then it actually behaves
1318 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1319 // check that the resize actually worked, and if not, repeatedly try
1320 // again after a short delay until it works (but with a limit of
1321 // hackcount repeats.
1322 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1324 setTimeout(get_size_exactly_right, 10);
1327 setTimeout(get_size_exactly_right, 0);
1334 /** Close the current browser window. */
1335 function close_window(e) {
1336 if (e.preventDefault) {
1339 e.returnValue = false;
1345 * Used in a couple of modules to hide navigation areas when using AJAX
1346 * @deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
1348 function show_item(itemid) {
1349 Y.log('show_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1350 'warn', 'javascript-static.js');
1351 var item = Y.one('#' + itemid);
1357 // Deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
1358 function destroy_item(itemid) {
1359 Y.log('destroy_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1360 'warn', 'javascript-static.js');
1361 var item = Y.one('#' + itemid);
1367 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1368 * @param controlid the control id.
1370 function focuscontrol(controlid) {
1371 var control = document.getElementById(controlid);
1378 * Transfers keyboard focus to an HTML element based on the old style style of focus
1379 * This function should be removed as soon as it is no longer used
1381 function old_onload_focus(formid, controlname) {
1382 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1383 document.forms[formid].elements[controlname].focus();
1387 function build_querystring(obj) {
1388 return convert_object_to_string(obj, '&');
1391 function build_windowoptionsstring(obj) {
1392 return convert_object_to_string(obj, ',');
1395 function convert_object_to_string(obj, separator) {
1396 if (typeof obj !== 'object') {
1401 k = encodeURIComponent(k);
1403 if(obj[k] instanceof Array) {
1404 for(var i in value) {
1405 list.push(k+'[]='+encodeURIComponent(value[i]));
1408 list.push(k+'='+encodeURIComponent(value));
1411 return list.join(separator);
1414 function stripHTML(str) {
1415 var re = /<\S[^><]*>/g;
1416 var ret = str.replace(re, "");
1420 Number.prototype.fixed=function(n){
1422 return round(Number(this)*pow(10,n))/pow(10,n);
1424 function update_progress_bar (id, width, pt, msg, es){
1426 var status = document.getElementById("status_"+id);
1427 var percent_indicator = document.getElementById("pt_"+id);
1428 var progress_bar = document.getElementById("progress_"+id);
1429 var time_es = document.getElementById("time_"+id);
1430 status.innerHTML = msg;
1431 percent_indicator.innerHTML = percent.fixed(2) + '%';
1432 if(percent == 100) {
1433 progress_bar.style.background = "green";
1434 time_es.style.display = "none";
1436 progress_bar.style.background = "#FFCC66";
1438 time_es.innerHTML = "";
1440 time_es.innerHTML = es.fixed(2)+" sec";
1441 time_es.style.display
1445 progress_bar.style.width = width + "px";
1450 // ===== Deprecated core Javascript functions for Moodle ====
1451 // DO NOT USE!!!!!!!
1452 // Do not put this stuff in separate file because it only adds extra load on servers!
1455 * Used in a couple of modules to hide navigation areas when using AJAX
1456 * @deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
1458 function hide_item(itemid) {
1459 Y.log('hide_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1460 'warn', 'javascript-static.js');
1461 var item = Y.one('#' + itemid);
1467 M.util.help_popups = {
1468 setup : function(Y) {
1469 Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this);
1471 open_popup : function(e) {
1472 // Prevent the default page action
1475 // Grab the anchor that was clicked
1476 var anchor = e.target.ancestor('a', true);
1479 'url' : anchor.getAttribute('href'),
1497 args.options = options.join(',');
1504 * Custom menu namespace
1506 M.core_custom_menu = {
1508 * This method is used to initialise a custom menu given the id that belongs
1509 * to the custom menu's root node.
1512 * @param {string} nodeid
1514 init : function(Y, nodeid) {
1515 var node = Y.one('#'+nodeid);
1517 Y.use('node-menunav', function(Y) {
1519 // Remove the javascript-disabled class.... obviously javascript is enabled.
1520 node.removeClass('javascript-disabled');
1521 // Initialise the menunav plugin
1522 node.plug(Y.Plugin.NodeMenuNav);
1529 * Used to store form manipulation methods and enhancments
1531 M.form = M.form || {};
1534 * Converts a nbsp indented select box into a multi drop down custom control much
1535 * like the custom menu. It also selectable categories on or off.
1537 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1540 * @param {string} id
1541 * @param {Array} options
1543 M.form.init_smartselect = function(Y, id, options) {
1544 if (!id.match(/^id_/)) {
1547 var select = Y.one('select#'+id);
1551 Y.use('event-delegate',function(){
1557 currentvalue : null,
1561 selectablecategories : true,
1569 init : function(Y, id, args, nodes) {
1570 if (typeof(args)=='object') {
1571 for (var i in this.cfg) {
1572 if (args[i] || args[i]===false) {
1573 this.cfg[i] = args[i];
1578 // Display a loading message first up
1579 this.nodes.select = nodes.select;
1581 this.currentvalue = this.nodes.select.get('selectedIndex');
1582 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1584 var options = Array();
1585 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1586 this.nodes.select.all('option').each(function(option, index) {
1587 var rawtext = option.get('innerHTML');
1588 var text = rawtext.replace(/^( )*/, '');
1589 if (rawtext === text) {
1590 text = rawtext.replace(/^(\s)*/, '');
1591 var depth = (rawtext.length - text.length ) + 1;
1593 var depth = ((rawtext.length - text.length )/12)+1;
1595 option.set('innerHTML', text);
1596 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1599 this.structure = [];
1600 var structcount = 0;
1601 for (var i in options) {
1604 this.structure.push(o);
1608 var current = this.structure[structcount-1];
1609 for (var j = 0; j < o.depth-1;j++) {
1610 if (current && current.children) {
1611 current = current.children[current.children.length-1];
1614 if (current && current.children) {
1615 current.children.push(o);
1620 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1621 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1622 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1623 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1625 if (this.cfg.mode == null) {
1626 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1627 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1628 this.cfg.mode = 'compact';
1630 this.cfg.mode = 'spanning';
1634 if (this.cfg.mode == 'compact') {
1635 this.nodes.menu.addClass('compactmenu');
1637 this.nodes.menu.addClass('spanningmenu');
1638 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1641 Y.one(document.body).append(this.nodes.menu);
1642 var pos = this.nodes.select.getXY();
1644 this.nodes.menu.setXY(pos);
1645 this.nodes.menu.on('click', this.handle_click, this);
1647 Y.one(window).on('resize', function(){
1648 var pos = this.nodes.select.getXY();
1650 this.nodes.menu.setXY(pos);
1653 generate_menu_content : function() {
1654 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1655 content += this.generate_submenu_content(this.structure[0], true);
1656 content += '</ul></div>';
1659 generate_submenu_content : function(item, rootelement) {
1660 this.submenucount++;
1662 if (item.children.length > 0) {
1664 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'"> </div>';
1665 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1666 content += '<div class="smartselect_menu_content">';
1668 content += '<li class="smartselect_submenuitem">';
1669 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1670 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1671 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1672 content += '<div class="smartselect_submenu_content">';
1675 for (var i in item.children) {
1676 content += this.generate_submenu_content(item.children[i],false);
1679 content += '</div>';
1680 content += '</div>';
1686 content += '<li class="smartselect_menuitem">';
1687 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1692 select : function(e) {
1695 this.currenttext = t.get('innerHTML');
1696 this.currentvalue = t.getAttribute('value');
1697 this.nodes.select.set('selectedIndex', this.currentvalue);
1700 handle_click : function(e) {
1701 var target = e.target;
1702 if (target.hasClass('smartselect_mask')) {
1704 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1706 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1707 this.show_sub_menu(e);
1710 show_menu : function(e) {
1712 var menu = e.target.ancestor().one('.smartselect_menu');
1713 menu.addClass('visible');
1714 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1716 show_sub_menu : function(e) {
1718 var target = e.target;
1719 if (!target.hasClass('smartselect_submenuitem')) {
1720 target = target.ancestor('.smartselect_submenuitem');
1722 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1723 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1726 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1727 target.one('.smartselect_submenu').addClass('visible');
1729 hide_menu : function() {
1730 this.nodes.menu.all('.visible').removeClass('visible');
1731 if (this.shownevent) {
1732 this.shownevent.detach();
1736 smartselect.init(Y, id, options, {select:select});
1740 /** List of flv players to be loaded */
1741 M.util.video_players = [];
1742 /** List of mp3 players to be loaded */
1743 M.util.audio_players = [];
1747 * @param id element id
1748 * @param fileurl media url
1751 * @param autosize true means detect size from media
1753 M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1754 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
1763 M.util.add_audio_player = function (id, fileurl, small) {
1764 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
1768 * Initialise all audio and video player, must be called from page footer.
1770 M.util.load_flowplayer = function() {
1771 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1774 if (typeof(flowplayer) == 'undefined') {
1777 var embed_function = function() {
1778 if (loaded || typeof(flowplayer) == 'undefined') {
1786 /* TODO: add CSS color overrides for the flv flow player */
1788 for(var i=0; i<M.util.video_players.length; i++) {
1789 var video = M.util.video_players[i];
1790 if (video.width > 0 && video.height > 0) {
1791 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', width: video.width, height: video.height};
1793 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf';
1795 flowplayer(video.id, src, {
1796 plugins: {controls: controls},
1798 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1799 onMetaData: function(clip) {
1800 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1801 clip.mvideo.resized = true;
1802 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1803 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1804 // bad luck, we have to guess - we may not get metadata at all
1805 var width = clip.width;
1806 var height = clip.height;
1808 var width = clip.metaData.width;
1809 var height = clip.metaData.height;
1811 var minwidth = 300; // controls are messed up in smaller objects
1812 if (width < minwidth) {
1813 height = (height * minwidth) / width;
1817 var object = this._api();
1818 object.width = width;
1819 object.height = height;
1825 if (M.util.audio_players.length == 0) {
1838 backgroundGradient: [0.5,0,0.3]
1842 for (var j=0; j < document.styleSheets.length; j++) {
1844 // To avoid javascript security violation accessing cross domain stylesheets
1845 var allrules = false;
1847 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1848 allrules = document.styleSheets[j].rules;
1849 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1850 allrules = document.styleSheets[j].cssRules;
1859 // On cross domain style sheets Chrome V8 allows access to rules but returns null
1864 for(var i=0; i<allrules.length; i++) {
1866 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1867 if (typeof(allrules[i].cssText) != 'undefined') {
1868 rule = allrules[i].cssText;
1869 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1870 rule = allrules[i].style.cssText;
1872 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1873 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1874 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1875 controls[colprop] = rule;
1882 for(i=0; i<M.util.audio_players.length; i++) {
1883 var audio = M.util.audio_players[i];
1885 controls.controlall = false;
1886 controls.height = 15;
1887 controls.time = false;
1889 controls.controlall = true;
1890 controls.height = 25;
1891 controls.time = true;
1893 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', {
1894 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.11.swf'}},
1895 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1900 if (M.cfg.jsrev == -1) {
1901 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.13.js';
1903 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.13.min.js&rev=' + M.cfg.jsrev;
1905 var fileref = document.createElement('script');
1906 fileref.setAttribute('type','text/javascript');
1907 fileref.setAttribute('src', jsurl);
1908 fileref.onload = embed_function;
1909 fileref.onreadystatechange = embed_function;
1910 document.getElementsByTagName('head')[0].appendChild(fileref);