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'),
603 if (t && (a = t.one('.block_action'))) {
604 hide = Y.Node.create('<img />')
605 .addClass('block-hider-hide')
607 alt: config.tooltipVisible,
608 src: this.get('iconVisible'),
610 'title': config.tooltipVisible
612 hide.on('keypress', this.updateStateKey, this, true);
613 hide.on('click', this.updateState, this, true);
615 show = Y.Node.create('<img />')
616 .addClass('block-hider-show')
618 alt: config.tooltipHidden,
619 src: this.get('iconHidden'),
621 'title': config.tooltipHidden
623 show.on('keypress', this.updateStateKey, this, false);
624 show.on('click', this.updateState, this, false);
626 a.insert(show, 0).insert(hide, 0);
629 updateState : function(e, hide) {
630 M.util.set_user_preference(this.get('preference'), hide);
632 this.get('block').addClass('hidden');
634 this.get('block').removeClass('hidden');
637 updateStateKey : function(e, hide) {
638 if (e.keyCode == 13) { //allow hide/show via enter key
639 this.updateState(this, hide);
643 Y.extend(blockhider, Y.Base, blockhider.prototype, {
649 value : M.util.image_url('t/switch_minus', 'moodle')
652 value : M.util.image_url('t/switch_plus', 'moodle')
655 setter : function(node) {
663 new M.util.block_hider(config);
668 * @var pending_js - The keys are the list of all pending js actions.
671 M.util.pending_js = [];
672 M.util.complete_js = [];
675 * Register any long running javascript code with a unique identifier.
676 * Should be followed with a call to js_complete with a matching
677 * idenfitier when the code is complete. May also be called with no arguments
678 * to test if there is any js calls pending. This is relied on by behat so that
679 * it can wait for all pending updates before interacting with a page.
680 * @param String uniqid - optional, if provided,
681 * registers this identifier until js_complete is called.
682 * @return boolean - True if there is any pending js.
684 M.util.js_pending = function(uniqid) {
685 if (uniqid !== false) {
686 M.util.pending_js.push(uniqid);
689 return M.util.pending_js.length;
693 M.util.js_pending('init');
696 * Register listeners for Y.io start/end so we can wait for them in behat.
698 YUI.add('moodle-core-io', function(Y) {
699 Y.on('io:start', function(id) {
700 M.util.js_pending('io:' + id);
702 Y.on('io:end', function(id) {
703 M.util.js_complete('io:' + id);
713 * Unregister any long running javascript code by unique identifier.
714 * This function should form a matching pair with js_pending
716 * @param String uniqid - required, unregisters this identifier
717 * @return boolean - True if there is any pending js.
719 M.util.js_complete = function(uniqid) {
720 // Use the Y.Array.indexOf instead of the native because some older browsers do not support
721 // the native function. Y.Array polyfills the native function if it does not exist.
722 var index = Y.Array.indexOf(M.util.pending_js, uniqid);
724 M.util.complete_js.push(M.util.pending_js.splice(index, 1));
727 return M.util.pending_js.length;
731 * Returns a string registered in advance for usage in JavaScript
733 * If you do not pass the third parameter, the function will just return
734 * the corresponding value from the M.str object. If the third parameter is
735 * provided, the function performs {$a} placeholder substitution in the
736 * same way as PHP get_string() in Moodle does.
738 * @param {String} identifier string identifier
739 * @param {String} component the component providing the string
740 * @param {Object|String} a optional variable to populate placeholder with
742 M.util.get_string = function(identifier, component, a) {
745 if (M.cfg.developerdebug) {
746 // creating new instance if YUI is not optimal but it seems to be better way then
747 // require the instance via the function API - note that it is used in rare cases
748 // for debugging only anyway
749 // To ensure we don't kill browser performance if hundreds of get_string requests
750 // are made we cache the instance we generate within the M.util namespace.
751 // We don't publicly define the variable so that it doesn't get abused.
752 if (typeof M.util.get_string_yui_instance === 'undefined') {
753 M.util.get_string_yui_instance = new YUI({ debug : true });
755 var Y = M.util.get_string_yui_instance;
758 if (!M.str.hasOwnProperty(component) || !M.str[component].hasOwnProperty(identifier)) {
759 stringvalue = '[[' + identifier + ',' + component + ']]';
760 if (M.cfg.developerdebug) {
761 Y.log('undefined string ' + stringvalue, 'warn', 'M.util.get_string');
766 stringvalue = M.str[component][identifier];
768 if (typeof a == 'undefined') {
769 // no placeholder substitution requested
773 if (typeof a == 'number' || typeof a == 'string') {
774 // replace all occurrences of {$a} with the placeholder value
775 stringvalue = stringvalue.replace(/\{\$a\}/g, a);
779 if (typeof a == 'object') {
780 // replace {$a->key} placeholders
782 if (typeof a[key] != 'number' && typeof a[key] != 'string') {
783 if (M.cfg.developerdebug) {
784 Y.log('invalid value type for $a->' + key, 'warn', 'M.util.get_string');
788 var search = '{$a->' + key + '}';
789 search = search.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
790 search = new RegExp(search, 'g');
791 stringvalue = stringvalue.replace(search, a[key]);
796 if (M.cfg.developerdebug) {
797 Y.log('incorrect placeholder type', 'warn', 'M.util.get_string');
803 * Set focus on username or password field of the login form
805 M.util.focus_login_form = function(Y) {
806 var username = Y.one('#username');
807 var password = Y.one('#password');
809 if (username == null || password == null) {
810 // something is wrong here
814 var curElement = document.activeElement
815 if (curElement == 'undefined') {
816 // legacy browser - skip refocus protection
817 } else if (curElement.tagName == 'INPUT') {
818 // user was probably faster to focus something, do not mess with focus
822 if (username.get('value') == '') {
830 * Set focus on login error message
832 M.util.focus_login_error = function(Y) {
833 var errorlog = Y.one('#loginerrormessage');
840 * Adds lightbox hidden element that covers the whole node.
843 * @param {Node} the node lightbox should be added to
844 * @retun {Node} created lightbox node
846 M.util.add_lightbox = function(Y, node) {
847 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
849 // Check if lightbox is already there
850 if (node.one('.lightbox')) {
851 return node.one('.lightbox');
854 node.setStyle('position', 'relative');
855 var waiticon = Y.Node.create('<img />')
857 'src' : M.util.image_url(WAITICON.pix, WAITICON.component)
860 'position' : 'relative',
864 var lightbox = Y.Node.create('<div></div>')
867 'position' : 'absolute',
872 'backgroundColor' : 'white',
873 'textAlign' : 'center'
875 .setAttribute('class', 'lightbox')
878 lightbox.appendChild(waiticon);
879 node.append(lightbox);
884 * Appends a hidden spinner element to the specified node.
887 * @param {Node} the node the spinner should be added to
888 * @return {Node} created spinner node
890 M.util.add_spinner = function(Y, node) {
891 var WAITICON = {'pix':"i/loading_small",'component':'moodle'};
893 // Check if spinner is already there
894 if (node.one('.spinner')) {
895 return node.one('.spinner');
898 var spinner = Y.Node.create('<img />')
899 .setAttribute('src', M.util.image_url(WAITICON.pix, WAITICON.component))
901 .addClass('iconsmall')
904 node.append(spinner);
908 //=== old legacy JS code, hopefully to be replaced soon by M.xx.yy and YUI3 code ===
910 function checkall() {
911 var inputs = document.getElementsByTagName('input');
912 for (var i = 0; i < inputs.length; i++) {
913 if (inputs[i].type == 'checkbox') {
914 if (inputs[i].disabled || inputs[i].readOnly) {
917 inputs[i].checked = true;
922 function checknone() {
923 var inputs = document.getElementsByTagName('input');
924 for (var i = 0; i < inputs.length; i++) {
925 if (inputs[i].type == 'checkbox') {
926 if (inputs[i].disabled || inputs[i].readOnly) {
929 inputs[i].checked = false;
935 * Either check, or uncheck, all checkboxes inside the element with id is
936 * @param id the id of the container
937 * @param checked the new state, either '' or 'checked'.
939 function select_all_in_element_with_id(id, checked) {
940 var container = document.getElementById(id);
944 var inputs = container.getElementsByTagName('input');
945 for (var i = 0; i < inputs.length; ++i) {
946 if (inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
947 inputs[i].checked = checked;
952 function select_all_in(elTagName, elClass, elId) {
953 var inputs = document.getElementsByTagName('input');
954 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
955 for(var i = 0; i < inputs.length; ++i) {
956 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
957 inputs[i].checked = 'checked';
962 function deselect_all_in(elTagName, elClass, elId) {
963 var inputs = document.getElementsByTagName('INPUT');
964 inputs = filterByParent(inputs, function(el) {return findParentNode(el, elTagName, elClass, elId);});
965 for(var i = 0; i < inputs.length; ++i) {
966 if(inputs[i].type == 'checkbox' || inputs[i].type == 'radio') {
967 inputs[i].checked = '';
972 function confirm_if(expr, message) {
976 return confirm(message);
981 findParentNode (start, elementName, elementClass, elementID)
983 Travels up the DOM hierarchy to find a parent element with the
984 specified tag name, class, and id. All conditions must be met,
985 but any can be ommitted. Returns the BODY element if no match
988 function findParentNode(el, elName, elClass, elId) {
989 while (el.nodeName.toUpperCase() != 'BODY') {
990 if ((!elName || el.nodeName.toUpperCase() == elName) &&
991 (!elClass || el.className.indexOf(elClass) != -1) &&
992 (!elId || el.id == elId)) {
1000 findChildNode (start, elementName, elementClass, elementID)
1002 Travels down the DOM hierarchy to find all child elements with the
1003 specified tag name, class, and id. All conditions must be met,
1004 but any can be ommitted.
1005 Doesn't examine children of matches.
1007 @deprecated since Moodle 2.7 - please do not use this function any more.
1008 @todo MDL-43242 This will be deleted in Moodle 2.9.
1011 function findChildNodes(start, tagName, elementClass, elementID, elementName) {
1012 Y.log("findChildNodes() is deprecated. Please use Y.all instead.",
1013 "warn", "javascript-static.js");
1014 var children = new Array();
1015 for (var i = 0; i < start.childNodes.length; i++) {
1016 var classfound = false;
1017 var child = start.childNodes[i];
1018 if((child.nodeType == 1) &&//element node type
1019 (elementClass && (typeof(child.className)=='string'))) {
1020 var childClasses = child.className.split(/\s+/);
1021 for (var childClassIndex in childClasses) {
1022 if (childClasses[childClassIndex]==elementClass) {
1028 if(child.nodeType == 1) { //element node type
1029 if ( (!tagName || child.nodeName == tagName) &&
1030 (!elementClass || classfound)&&
1031 (!elementID || child.id == elementID) &&
1032 (!elementName || child.name == elementName))
1034 children = children.concat(child);
1036 children = children.concat(findChildNodes(child, tagName, elementClass, elementID, elementName));
1043 function unmaskPassword(id) {
1044 var pw = document.getElementById(id);
1045 var chb = document.getElementById(id+'unmask');
1047 // MDL-30438 - The capability to changing the value of input type is not supported by IE8 or lower.
1048 // Replacing existing child with a new one, removed all yui properties for the node. Therefore, this
1049 // functionality won't work in IE8 or lower.
1050 // This is a temporary fixed to allow other browsers to function properly.
1051 if (Y.UA.ie == 0 || Y.UA.ie >= 9) {
1055 pw.type = "password";
1057 } else { //IE Browser version 8 or lower
1059 // first try IE way - it can not set name attribute later
1061 var newpw = document.createElement('<input type="text" autocomplete="off" name="'+pw.name+'">');
1063 var newpw = document.createElement('<input type="password" autocomplete="off" name="'+pw.name+'">');
1065 newpw.attributes['class'].nodeValue = pw.attributes['class'].nodeValue;
1067 var newpw = document.createElement('input');
1068 newpw.setAttribute('autocomplete', 'off');
1069 newpw.setAttribute('name', pw.name);
1071 newpw.setAttribute('type', 'text');
1073 newpw.setAttribute('type', 'password');
1075 newpw.setAttribute('class', pw.getAttribute('class'));
1078 newpw.size = pw.size;
1079 newpw.onblur = pw.onblur;
1080 newpw.onchange = pw.onchange;
1081 newpw.value = pw.value;
1082 pw.parentNode.replaceChild(newpw, pw);
1086 function filterByParent(elCollection, parentFinder) {
1087 var filteredCollection = [];
1088 for (var i = 0; i < elCollection.length; ++i) {
1089 var findParent = parentFinder(elCollection[i]);
1090 if (findParent.nodeName.toUpperCase() != 'BODY') {
1091 filteredCollection.push(elCollection[i]);
1094 return filteredCollection;
1098 All this is here just so that IE gets to handle oversized blocks
1099 in a visually pleasing manner. It does a browser detect. So sue me.
1102 function fix_column_widths() {
1103 var agt = navigator.userAgent.toLowerCase();
1104 if ((agt.indexOf("msie") != -1) && (agt.indexOf("opera") == -1)) {
1105 fix_column_width('left-column');
1106 fix_column_width('right-column');
1110 function fix_column_width(colName) {
1111 if(column = document.getElementById(colName)) {
1112 if(!column.offsetWidth) {
1113 setTimeout("fix_column_width('" + colName + "')", 20);
1118 var nodes = column.childNodes;
1120 for(i = 0; i < nodes.length; ++i) {
1121 if(nodes[i].className.indexOf("block") != -1 ) {
1122 if(width < nodes[i].offsetWidth) {
1123 width = nodes[i].offsetWidth;
1128 for(i = 0; i < nodes.length; ++i) {
1129 if(nodes[i].className.indexOf("block") != -1 ) {
1130 nodes[i].style.width = width + 'px';
1138 Insert myValue at current cursor position
1140 function insertAtCursor(myField, myValue) {
1142 if (document.selection) {
1144 sel = document.selection.createRange();
1147 // Mozilla/Netscape support
1148 else if (myField.selectionStart || myField.selectionStart == '0') {
1149 var startPos = myField.selectionStart;
1150 var endPos = myField.selectionEnd;
1151 myField.value = myField.value.substring(0, startPos)
1152 + myValue + myField.value.substring(endPos, myField.value.length);
1154 myField.value += myValue;
1160 Call instead of setting window.onload directly or setting body onload=.
1161 Adds your function to a chain of functions rather than overwriting anything
1163 @deprecated Since Moodle 2.7. This will be removed in Moodle 2.9.
1165 function addonload(fn) {
1166 Y.log('addonload has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1167 'warn', 'javascript-static.js');
1168 var oldhandler=window.onload;
1169 window.onload=function() {
1170 if(oldhandler) oldhandler();
1175 * Replacement for getElementsByClassName in browsers that aren't cool enough
1177 * Relying on the built-in getElementsByClassName is far, far faster than
1180 * Note: the third argument used to be an object with odd behaviour. It now
1181 * acts like the 'name' in the HTML5 spec, though the old behaviour is still
1182 * mimicked if you pass an object.
1184 * @param {Node} oElm The top-level node for searching. To search a whole
1185 * document, use `document`.
1186 * @param {String} strTagName filter by tag names
1187 * @param {String} name same as HTML5 spec
1188 * @deprecated Since Moodle 2.7. This will be removed in Moodle 2.9.
1190 function getElementsByClassName(oElm, strTagName, name) {
1191 Y.log('getElementsByClassName has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1192 'warn', 'javascript-static.js');
1193 // for backwards compatibility
1194 if(typeof name == "object") {
1195 var names = new Array();
1196 for(var i=0; i<name.length; i++) names.push(names[i]);
1197 name = names.join('');
1199 // use native implementation if possible
1200 if (oElm.getElementsByClassName && Array.filter) {
1201 if (strTagName == '*') {
1202 return oElm.getElementsByClassName(name);
1204 return Array.filter(oElm.getElementsByClassName(name), function(el) {
1205 return el.nodeName.toLowerCase() == strTagName.toLowerCase();
1209 // native implementation unavailable, fall back to slow method
1210 var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
1211 var arrReturnElements = new Array();
1212 var arrRegExpClassNames = new Array();
1213 var names = name.split(' ');
1214 for(var i=0; i<names.length; i++) {
1215 arrRegExpClassNames.push(new RegExp("(^|\\s)" + names[i].replace(/\-/g, "\\-") + "(\\s|$)"));
1219 for(var j=0; j<arrElements.length; j++) {
1220 oElement = arrElements[j];
1222 for(var k=0; k<arrRegExpClassNames.length; k++) {
1223 if(!arrRegExpClassNames[k].test(oElement.className)) {
1224 bMatchesAll = false;
1229 arrReturnElements.push(oElement);
1232 return (arrReturnElements)
1236 * Increment a file name.
1238 * @param string file name.
1239 * @param boolean ignoreextension do not extract the extension prior to appending the
1240 * suffix. Useful when incrementing folder names.
1241 * @return string the incremented file name.
1243 function increment_filename(filename, ignoreextension) {
1245 var basename = filename;
1247 // Split the file name into the basename + extension.
1248 if (!ignoreextension) {
1249 var dotpos = filename.lastIndexOf('.');
1250 if (dotpos !== -1) {
1251 basename = filename.substr(0, dotpos);
1252 extension = filename.substr(dotpos, filename.length);
1256 // Look to see if the name already has (NN) at the end of it.
1258 var hasnumber = basename.match(/^(.*) \((\d+)\)$/);
1259 if (hasnumber !== null) {
1260 // Note the current number & remove it from the basename.
1261 number = parseInt(hasnumber[2], 10);
1262 basename = hasnumber[1];
1266 var newname = basename + ' (' + number + ')' + extension;
1271 * Return whether we are in right to left mode or not.
1275 function right_to_left() {
1276 var body = Y.one('body');
1278 if (body && body.hasClass('dir-rtl')) {
1284 function openpopup(event, args) {
1287 if (event.preventDefault) {
1288 event.preventDefault();
1290 event.returnValue = false;
1294 // Make sure the name argument is set and valid.
1295 var nameregex = /[^a-z0-9_]/i;
1296 if (typeof args.name !== 'string') {
1297 args.name = '_blank';
1298 } else if (args.name.match(nameregex)) {
1299 // Cleans window name because IE does not support funky ones.
1300 if (M.cfg.developerdebug) {
1301 alert('DEVELOPER NOTICE: Invalid \'name\' passed to openpopup(): ' + args.name);
1303 args.name = args.name.replace(nameregex, '_');
1306 var fullurl = args.url;
1307 if (!args.url.match(/https?:\/\//)) {
1308 fullurl = M.cfg.wwwroot + args.url;
1310 if (args.fullscreen) {
1311 args.options = args.options.
1312 replace(/top=\d+/, 'top=0').
1313 replace(/left=\d+/, 'left=0').
1314 replace(/width=\d+/, 'width=' + screen.availWidth).
1315 replace(/height=\d+/, 'height=' + screen.availHeight);
1317 var windowobj = window.open(fullurl,args.name,args.options);
1322 if (args.fullscreen) {
1323 // In some browser / OS combinations (E.g. Chrome on Windows), the
1324 // window initially opens slighly too big. The width and heigh options
1325 // seem to control the area inside the browser window, so what with
1326 // scroll-bars, etc. the actual window is bigger than the screen.
1327 // Therefore, we need to fix things up after the window is open.
1328 var hackcount = 100;
1329 var get_size_exactly_right = function() {
1330 windowobj.moveTo(0, 0);
1331 windowobj.resizeTo(screen.availWidth, screen.availHeight);
1333 // Unfortunately, it seems that in Chrome on Ubuntu, if you call
1334 // something like windowobj.resizeTo(1280, 1024) too soon (up to
1335 // about 50ms) after the window is open, then it actually behaves
1336 // as if you called windowobj.resizeTo(0, 0). Therefore, we need to
1337 // check that the resize actually worked, and if not, repeatedly try
1338 // again after a short delay until it works (but with a limit of
1339 // hackcount repeats.
1340 if (hackcount > 0 && (windowobj.innerHeight < 10 || windowobj.innerWidth < 10)) {
1342 setTimeout(get_size_exactly_right, 10);
1345 setTimeout(get_size_exactly_right, 0);
1352 /** Close the current browser window. */
1353 function close_window(e) {
1354 if (e.preventDefault) {
1357 e.returnValue = false;
1363 * Used in a couple of modules to hide navigation areas when using AJAX
1364 * @deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
1366 function show_item(itemid) {
1367 Y.log('show_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1368 'warn', 'javascript-static.js');
1369 var item = Y.one('#' + itemid);
1375 // Deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
1376 function destroy_item(itemid) {
1377 Y.log('destroy_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1378 'warn', 'javascript-static.js');
1379 var item = Y.one('#' + itemid);
1385 * Tranfer keyboard focus to the HTML element with the given id, if it exists.
1386 * @param controlid the control id.
1388 function focuscontrol(controlid) {
1389 var control = document.getElementById(controlid);
1396 * Transfers keyboard focus to an HTML element based on the old style style of focus
1397 * This function should be removed as soon as it is no longer used
1399 function old_onload_focus(formid, controlname) {
1400 if (document.forms[formid] && document.forms[formid].elements && document.forms[formid].elements[controlname]) {
1401 document.forms[formid].elements[controlname].focus();
1405 function build_querystring(obj) {
1406 return convert_object_to_string(obj, '&');
1409 function build_windowoptionsstring(obj) {
1410 return convert_object_to_string(obj, ',');
1413 function convert_object_to_string(obj, separator) {
1414 if (typeof obj !== 'object') {
1419 k = encodeURIComponent(k);
1421 if(obj[k] instanceof Array) {
1422 for(var i in value) {
1423 list.push(k+'[]='+encodeURIComponent(value[i]));
1426 list.push(k+'='+encodeURIComponent(value));
1429 return list.join(separator);
1432 function stripHTML(str) {
1433 var re = /<\S[^><]*>/g;
1434 var ret = str.replace(re, "");
1438 function updateProgressBar(id, percent, msg, estimate) {
1439 var progressIndicator = Y.one('#' + id);
1440 if (!progressIndicator) {
1444 var progressBar = progressIndicator.one('.bar'),
1445 statusIndicator = progressIndicator.one('h2'),
1446 estimateIndicator = progressIndicator.one('p');
1448 statusIndicator.set('innerHTML', Y.Escape.html(msg));
1449 progressBar.set('innerHTML', Y.Escape.html('' + percent + '%'));
1450 if (percent === 100) {
1451 progressIndicator.addClass('progress-success');
1452 estimateIndicator.set('innerHTML', null);
1455 estimateIndicator.set('innerHTML', Y.Escape.html(estimate));
1457 estimateIndicator.set('innerHTML', null);
1459 progressIndicator.removeClass('progress-success');
1461 progressBar.setAttribute('aria-valuenow', percent);
1462 progressBar.setStyle('width', percent + '%');
1465 // ===== Deprecated core Javascript functions for Moodle ====
1466 // DO NOT USE!!!!!!!
1467 // Do not put this stuff in separate file because it only adds extra load on servers!
1470 * Used in a couple of modules to hide navigation areas when using AJAX
1471 * @deprecated since Moodle 2.7. This function will be removed in Moodle 2.9.
1473 function hide_item(itemid) {
1474 Y.log('hide_item has been deprecated since Moodle 2.7 and will be removed in Moodle 2.9',
1475 'warn', 'javascript-static.js');
1476 var item = Y.one('#' + itemid);
1482 M.util.help_popups = {
1483 setup : function(Y) {
1484 Y.one('body').delegate('click', this.open_popup, 'a.helplinkpopup', this);
1486 open_popup : function(e) {
1487 // Prevent the default page action
1490 // Grab the anchor that was clicked
1491 var anchor = e.target.ancestor('a', true);
1494 'url' : anchor.getAttribute('href'),
1512 args.options = options.join(',');
1519 * Custom menu namespace
1521 M.core_custom_menu = {
1523 * This method is used to initialise a custom menu given the id that belongs
1524 * to the custom menu's root node.
1527 * @param {string} nodeid
1529 init : function(Y, nodeid) {
1530 var node = Y.one('#'+nodeid);
1532 Y.use('node-menunav', function(Y) {
1534 // Remove the javascript-disabled class.... obviously javascript is enabled.
1535 node.removeClass('javascript-disabled');
1536 // Initialise the menunav plugin
1537 node.plug(Y.Plugin.NodeMenuNav);
1544 * Used to store form manipulation methods and enhancments
1546 M.form = M.form || {};
1549 * Converts a nbsp indented select box into a multi drop down custom control much
1550 * like the custom menu. It also selectable categories on or off.
1552 * $form->init_javascript_enhancement('elementname','smartselect', array('selectablecategories'=>true|false, 'mode'=>'compact'|'spanning'));
1555 * @param {string} id
1556 * @param {Array} options
1558 M.form.init_smartselect = function(Y, id, options) {
1559 if (!id.match(/^id_/)) {
1562 var select = Y.one('select#'+id);
1566 Y.use('event-delegate',function(){
1572 currentvalue : null,
1576 selectablecategories : true,
1584 init : function(Y, id, args, nodes) {
1585 if (typeof(args)=='object') {
1586 for (var i in this.cfg) {
1587 if (args[i] || args[i]===false) {
1588 this.cfg[i] = args[i];
1593 // Display a loading message first up
1594 this.nodes.select = nodes.select;
1596 this.currentvalue = this.nodes.select.get('selectedIndex');
1597 this.currenttext = this.nodes.select.all('option').item(this.currentvalue).get('innerHTML');
1599 var options = Array();
1600 options[''] = {text:this.currenttext,value:'',depth:0,children:[]};
1601 this.nodes.select.all('option').each(function(option, index) {
1602 var rawtext = option.get('innerHTML');
1603 var text = rawtext.replace(/^( )*/, '');
1604 if (rawtext === text) {
1605 text = rawtext.replace(/^(\s)*/, '');
1606 var depth = (rawtext.length - text.length ) + 1;
1608 var depth = ((rawtext.length - text.length )/12)+1;
1610 option.set('innerHTML', text);
1611 options['i'+index] = {text:text,depth:depth,index:index,children:[]};
1614 this.structure = [];
1615 var structcount = 0;
1616 for (var i in options) {
1619 this.structure.push(o);
1623 var current = this.structure[structcount-1];
1624 for (var j = 0; j < o.depth-1;j++) {
1625 if (current && current.children) {
1626 current = current.children[current.children.length-1];
1629 if (current && current.children) {
1630 current.children.push(o);
1635 this.nodes.menu = Y.Node.create(this.generate_menu_content());
1636 this.nodes.menu.one('.smartselect_mask').setStyle('opacity', 0.01);
1637 this.nodes.menu.one('.smartselect_mask').setStyle('width', (this.nodes.select.get('offsetWidth')+5)+'px');
1638 this.nodes.menu.one('.smartselect_mask').setStyle('height', (this.nodes.select.get('offsetHeight'))+'px');
1640 if (this.cfg.mode == null) {
1641 var formwidth = this.nodes.select.ancestor('form').get('offsetWidth');
1642 if (formwidth < 400 || this.nodes.menu.get('offsetWidth') < formwidth*2) {
1643 this.cfg.mode = 'compact';
1645 this.cfg.mode = 'spanning';
1649 if (this.cfg.mode == 'compact') {
1650 this.nodes.menu.addClass('compactmenu');
1652 this.nodes.menu.addClass('spanningmenu');
1653 this.nodes.menu.delegate('mouseover', this.show_sub_menu, '.smartselect_submenuitem', this);
1656 Y.one(document.body).append(this.nodes.menu);
1657 var pos = this.nodes.select.getXY();
1659 this.nodes.menu.setXY(pos);
1660 this.nodes.menu.on('click', this.handle_click, this);
1662 Y.one(window).on('resize', function(){
1663 var pos = this.nodes.select.getXY();
1665 this.nodes.menu.setXY(pos);
1668 generate_menu_content : function() {
1669 var content = '<div id="'+this.id+'_smart_select" class="smartselect">';
1670 content += this.generate_submenu_content(this.structure[0], true);
1671 content += '</ul></div>';
1674 generate_submenu_content : function(item, rootelement) {
1675 this.submenucount++;
1677 if (item.children.length > 0) {
1679 content += '<div class="smartselect_mask" href="#ss_submenu'+this.submenucount+'"> </div>';
1680 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_menu">';
1681 content += '<div class="smartselect_menu_content">';
1683 content += '<li class="smartselect_submenuitem">';
1684 var categoryclass = (this.cfg.selectablecategories)?'selectable':'notselectable';
1685 content += '<a class="smartselect_menuitem_label '+categoryclass+'" href="#ss_submenu'+this.submenucount+'" value="'+item.index+'">'+item.text+'</a>';
1686 content += '<div id="ss_submenu'+this.submenucount+'" class="smartselect_submenu">';
1687 content += '<div class="smartselect_submenu_content">';
1690 for (var i in item.children) {
1691 content += this.generate_submenu_content(item.children[i],false);
1694 content += '</div>';
1695 content += '</div>';
1701 content += '<li class="smartselect_menuitem">';
1702 content += '<a class="smartselect_menuitem_content selectable" href="#" value="'+item.index+'">'+item.text+'</a>';
1707 select : function(e) {
1710 this.currenttext = t.get('innerHTML');
1711 this.currentvalue = t.getAttribute('value');
1712 this.nodes.select.set('selectedIndex', this.currentvalue);
1715 handle_click : function(e) {
1716 var target = e.target;
1717 if (target.hasClass('smartselect_mask')) {
1719 } else if (target.hasClass('selectable') || target.hasClass('smartselect_menuitem')) {
1721 } else if (target.hasClass('smartselect_menuitem_label') || target.hasClass('smartselect_submenuitem')) {
1722 this.show_sub_menu(e);
1725 show_menu : function(e) {
1727 var menu = e.target.ancestor().one('.smartselect_menu');
1728 menu.addClass('visible');
1729 this.shownevent = Y.one(document.body).on('click', this.hide_menu, this);
1731 show_sub_menu : function(e) {
1733 var target = e.target;
1734 if (!target.hasClass('smartselect_submenuitem')) {
1735 target = target.ancestor('.smartselect_submenuitem');
1737 if (this.cfg.mode == 'compact' && target.one('.smartselect_submenu').hasClass('visible')) {
1738 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1741 target.ancestor('ul').all('.smartselect_submenu.visible').removeClass('visible');
1742 target.one('.smartselect_submenu').addClass('visible');
1744 hide_menu : function() {
1745 this.nodes.menu.all('.visible').removeClass('visible');
1746 if (this.shownevent) {
1747 this.shownevent.detach();
1751 smartselect.init(Y, id, options, {select:select});
1755 /** List of flv players to be loaded */
1756 M.util.video_players = [];
1757 /** List of mp3 players to be loaded */
1758 M.util.audio_players = [];
1762 * @param id element id
1763 * @param fileurl media url
1766 * @param autosize true means detect size from media
1768 M.util.add_video_player = function (id, fileurl, width, height, autosize) {
1769 M.util.video_players.push({id: id, fileurl: fileurl, width: width, height: height, autosize: autosize, resized: false});
1778 M.util.add_audio_player = function (id, fileurl, small) {
1779 M.util.audio_players.push({id: id, fileurl: fileurl, small: small});
1783 * Initialise all audio and video player, must be called from page footer.
1785 M.util.load_flowplayer = function() {
1786 if (M.util.video_players.length == 0 && M.util.audio_players.length == 0) {
1789 if (typeof(flowplayer) == 'undefined') {
1792 var embed_function = function() {
1793 if (loaded || typeof(flowplayer) == 'undefined') {
1801 /* TODO: add CSS color overrides for the flv flow player */
1803 for(var i=0; i<M.util.video_players.length; i++) {
1804 var video = M.util.video_players[i];
1805 if (video.width > 0 && video.height > 0) {
1806 var src = {src: M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', width: video.width, height: video.height};
1808 var src = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf';
1810 flowplayer(video.id, src, {
1811 plugins: {controls: controls},
1813 url: video.fileurl, autoPlay: false, autoBuffering: true, scaling: 'fit', mvideo: video,
1814 onMetaData: function(clip) {
1815 if (clip.mvideo.autosize && !clip.mvideo.resized) {
1816 clip.mvideo.resized = true;
1817 //alert("metadata!!! "+clip.width+' '+clip.height+' '+JSON.stringify(clip.metaData));
1818 if (typeof(clip.metaData.width) == 'undefined' || typeof(clip.metaData.height) == 'undefined') {
1819 // bad luck, we have to guess - we may not get metadata at all
1820 var width = clip.width;
1821 var height = clip.height;
1823 var width = clip.metaData.width;
1824 var height = clip.metaData.height;
1826 var minwidth = 300; // controls are messed up in smaller objects
1827 if (width < minwidth) {
1828 height = (height * minwidth) / width;
1832 var object = this._api();
1833 object.width = width;
1834 object.height = height;
1840 if (M.util.audio_players.length == 0) {
1853 backgroundGradient: [0.5,0,0.3]
1857 for (var j=0; j < document.styleSheets.length; j++) {
1859 // To avoid javascript security violation accessing cross domain stylesheets
1860 var allrules = false;
1862 if (typeof (document.styleSheets[j].rules) != 'undefined') {
1863 allrules = document.styleSheets[j].rules;
1864 } else if (typeof (document.styleSheets[j].cssRules) != 'undefined') {
1865 allrules = document.styleSheets[j].cssRules;
1874 // On cross domain style sheets Chrome V8 allows access to rules but returns null
1879 for(var i=0; i<allrules.length; i++) {
1881 if (/^\.mp3flowplayer_.*Color$/.test(allrules[i].selectorText)) {
1882 if (typeof(allrules[i].cssText) != 'undefined') {
1883 rule = allrules[i].cssText;
1884 } else if (typeof(allrules[i].style.cssText) != 'undefined') {
1885 rule = allrules[i].style.cssText;
1887 if (rule != '' && /.*color\s*:\s*([^;]+).*/gi.test(rule)) {
1888 rule = rule.replace(/.*color\s*:\s*([^;]+).*/gi, '$1');
1889 var colprop = allrules[i].selectorText.replace(/^\.mp3flowplayer_/, '');
1890 controls[colprop] = rule;
1897 for(i=0; i<M.util.audio_players.length; i++) {
1898 var audio = M.util.audio_players[i];
1900 controls.controlall = false;
1901 controls.height = 15;
1902 controls.time = false;
1904 controls.controlall = true;
1905 controls.height = 25;
1906 controls.time = true;
1908 flowplayer(audio.id, M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.18.swf', {
1909 plugins: {controls: controls, audio: {url: M.cfg.wwwroot + '/lib/flowplayer/flowplayer.audio-3.2.11.swf'}},
1910 clip: {url: audio.fileurl, provider: "audio", autoPlay: false}
1915 if (M.cfg.jsrev == -1) {
1916 var jsurl = M.cfg.wwwroot + '/lib/flowplayer/flowplayer-3.2.13.js';
1918 var jsurl = M.cfg.wwwroot + '/lib/javascript.php?jsfile=/lib/flowplayer/flowplayer-3.2.13.min.js&rev=' + M.cfg.jsrev;
1920 var fileref = document.createElement('script');
1921 fileref.setAttribute('type','text/javascript');
1922 fileref.setAttribute('src', jsurl);
1923 fileref.onload = embed_function;
1924 fileref.onreadystatechange = embed_function;
1925 document.getElementsByTagName('head')[0].appendChild(fileref);